/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package libcore.xml;
import java.io.StringReader;
import java.util.Arrays;
import junit.framework.TestCase;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
* Test doctype handling in pull parsers.
*/
public abstract class PullParserDtdTest extends TestCase {
private static final int READ_BUFFER_SIZE = 8192;
/**
* Android's Expat pull parser permits parameter entities to be declared,
* but it doesn't permit such entities to be used.
*/
public void testDeclaringParameterEntities() throws Exception {
String xml = ""
+ "]>";
XmlPullParser parser = newPullParser(xml);
while (parser.next() != XmlPullParser.END_DOCUMENT) {
}
}
public void testUsingParameterEntitiesInDtds() throws Exception {
assertParseFailure(""
+ " "
+ "]>");
}
public void testUsingParameterInDocuments() throws Exception {
assertParseFailure(""
+ "]>&a;");
}
public void testGeneralAndParameterEntityWithTheSameName() throws Exception {
String xml = ""
+ " "
+ "]>&a;";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals(XmlPullParser.TEXT, parser.next());
assertEquals("aaa", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testInternalEntities() throws Exception {
String xml = ""
+ "]>&a;";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals(XmlPullParser.TEXT, parser.next());
assertEquals("android", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testExternalDtdIsSilentlyIgnored() throws Exception {
String xml = "";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("foo", parser.getName());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals("foo", parser.getName());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testExternalAndInternalDtd() throws Exception {
String xml = ""
+ "]>&a;";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals(XmlPullParser.TEXT, parser.next());
assertEquals("android", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testInternalEntitiesAreParsed() throws Exception {
String xml = "" // & expands to '&', A expands to 'A'
+ "]>&a;";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals(XmlPullParser.TEXT, parser.next());
assertEquals("A", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testFoolishlyRecursiveInternalEntities() throws Exception {
String xml = "" // expand & to '&' only twice
+ "]>&a;";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals(XmlPullParser.TEXT, parser.next());
assertEquals("&", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
/**
* Test that the output of {@code &} is parsed, but {@code &} isn't.
* http://www.w3.org/TR/2008/REC-xml-20081126/#sec-entexpand
*/
public void testExpansionOfEntityAndCharacterReferences() throws Exception {
String xml = "An ampersand (&) may be escaped\n"
+ "numerically (&) or with a general entity\n"
+ "(&).
\" >"
+ "]>&example;";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("p", parser.getName());
assertEquals(XmlPullParser.TEXT, parser.next());
assertEquals("An ampersand (&) may be escaped\n"
+ "numerically (&) or with a general entity\n"
+ "(&).", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals("p", parser.getName());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testGeneralEntitiesAreStoredUnresolved() throws Exception {
String xml = ""
+ ""
+ "]>&a;";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals(XmlPullParser.TEXT, parser.next());
assertEquals("android", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testStructuredEntityAndNextToken() throws Exception {
String xml = "baz\">]>a&bb;c";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.DOCDECL, parser.nextToken());
assertEquals(XmlPullParser.START_TAG, parser.nextToken());
assertEquals("foo", parser.getName());
assertEquals(XmlPullParser.TEXT, parser.nextToken());
assertEquals("a", parser.getText());
assertEquals(XmlPullParser.ENTITY_REF, parser.nextToken());
assertEquals("bb", parser.getName());
assertEquals("", parser.getText());
assertEquals(XmlPullParser.START_TAG, parser.nextToken());
assertEquals("bar", parser.getName());
assertEquals(XmlPullParser.TEXT, parser.nextToken());
assertEquals("baz", parser.getText());
assertEquals(XmlPullParser.COMMENT, parser.nextToken());
assertEquals("quux", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.nextToken());
assertEquals("bar", parser.getName());
assertEquals(XmlPullParser.TEXT, parser.nextToken());
assertEquals("c", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
/**
* Android's Expat replaces external entities with the empty string.
*/
public void testUsingExternalEntities() throws Exception {
String xml = ""
+ "]>&a;";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
// &a; is dropped!
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
/**
* Android's ExpatPullParser replaces missing entities with the empty string
* when an external DTD is declared.
*/
public void testExternalDtdAndMissingEntity() throws Exception {
String xml = ""
+ "&a;";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testExternalIdIsCaseSensitive() throws Exception {
// The spec requires 'SYSTEM' in upper case
assertParseFailure(""
+ "]>");
}
/**
* Use a DTD to specify that {@code } only contains {@code } tags.
* Validating parsers react to this by dropping whitespace between the two
* tags.
*/
public void testDtdDoesNotInformIgnorableWhitespace() throws Exception {
String xml = "\n"
+ " \n"
+ "]>"
+ " \n \t ";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("foo", parser.getName());
assertEquals(XmlPullParser.TEXT, parser.next());
assertEquals(" \n ", parser.getText());
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("bar", parser.getName());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals("bar", parser.getName());
assertEquals(XmlPullParser.TEXT, parser.next());
assertEquals(" \t ", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testEmptyDoesNotInformIgnorableWhitespace() throws Exception {
String xml = "\n"
+ "]>"
+ " \n ";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("foo", parser.getName());
assertEquals(XmlPullParser.TEXT, parser.next());
assertEquals(" \n ", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
/**
* Test that the parser doesn't expand the entity attributes.
*/
public void testAttributeOfTypeEntity() throws Exception {
String xml = ""
+ " \n"
+ " "
+ "]>"
+ "";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("foo", parser.getName());
assertEquals("a", parser.getAttributeValue(null, "bar"));
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testTagStructureNotValidated() throws Exception {
String xml = "\n"
+ " \n"
+ " \n"
+ "]>"
+ "";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("foo", parser.getName());
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("bar", parser.getName());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("bar", parser.getName());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("baz", parser.getName());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testAttributeDefaultValues() throws Exception {
String xml = ""
+ "]>"
+ ""
+ ""
+ ""
+ "";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("bar", parser.getName());
assertEquals("c", parser.getAttributeValue(null, "baz"));
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("bar", parser.getName());
assertEquals("a", parser.getAttributeValue(null, "baz"));
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testAttributeDefaultValueEntitiesExpanded() throws Exception {
String xml = ""
+ " \n"
+ " "
+ "]>"
+ "";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("foo", parser.getName());
assertEquals("abc & def ghi jk", parser.getAttributeValue(null, "bar"));
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testAttributeDefaultValuesAndNamespaces() throws Exception {
String xml = ""
+ "]>"
+ "";
XmlPullParser parser = newPullParser(xml);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("foo", parser.getName());
// In Expat, namespaces don't apply to default attributes
int index = indexOfAttributeWithName(parser, "bar:a");
assertEquals("", parser.getAttributeNamespace(index));
assertEquals("bar:a", parser.getAttributeName(index));
assertEquals("android", parser.getAttributeValue(index));
assertEquals("CDATA", parser.getAttributeType(index));
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
private int indexOfAttributeWithName(XmlPullParser parser, String name) {
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals(name)) {
return i;
}
}
return -1;
}
public void testAttributeEntitiesExpandedEagerly() throws Exception {
assertParseFailure("\n"
+ " "
+ " "
+ "]>"
+ "");
}
public void testRequiredAttributesOmitted() throws Exception {
String xml = "\n"
+ " "
+ "]>"
+ "";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("foo", parser.getName());
assertEquals(null, parser.getAttributeValue(null, "bar"));
assertEquals(XmlPullParser.END_TAG, parser.next());
}
public void testFixedAttributesWithConflictingValues() throws Exception {
String xml = "\n"
+ " "
+ "]>"
+ "";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("foo", parser.getName());
assertEquals("a", parser.getAttributeValue(null, "bar"));
assertEquals(XmlPullParser.END_TAG, parser.next());
}
public void testParsingNotations() throws Exception {
String xml = " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ "]>"
+ "";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("foo", parser.getName());
assertEquals(XmlPullParser.END_TAG, parser.next());
}
public void testCommentsInDoctype() throws Exception {
String xml = ""
+ "]>android";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals(XmlPullParser.TEXT, parser.next());
assertEquals("android", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testDoctypeNameOnly() throws Exception {
String xml = "";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals("foo", parser.getName());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals("foo", parser.getName());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testVeryLongEntities() throws Exception {
String a = repeat('a', READ_BUFFER_SIZE + 1);
String b = repeat('b', READ_BUFFER_SIZE + 1);
String c = repeat('c', READ_BUFFER_SIZE + 1);
String xml = ""
+ " "
+ "]>"
+ "h &" + a + "; i";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals(XmlPullParser.TEXT, parser.next());
assertEquals("h d f " + c + " g e i", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testManuallyRegisteredEntitiesWithDoctypeParsing() throws Exception {
String xml = "&a;";
XmlPullParser parser = newPullParser(xml);
try {
parser.defineEntityReplacementText("a", "android");
fail();
} catch (UnsupportedOperationException expected) {
} catch (IllegalStateException expected) {
}
}
public void testDoctypeWithNextToken() throws Exception {
String xml = "]>a&bb;c";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.DOCDECL, parser.nextToken());
assertEquals(" foo []", parser.getText());
assertNull(parser.getName());
assertEquals(XmlPullParser.START_TAG, parser.nextToken());
assertEquals(XmlPullParser.TEXT, parser.next());
assertEquals("abar bazc", parser.getText());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testDoctypeSpansBuffers() throws Exception {
char[] doctypeChars = new char[READ_BUFFER_SIZE + 1];
Arrays.fill(doctypeChars, 'x');
String doctypeBody = " foo []";
String xml = "";
XmlPullParser parser = newPullParser(xml);
assertEquals(XmlPullParser.DOCDECL, parser.nextToken());
assertEquals(doctypeBody, parser.getText());
assertEquals(XmlPullParser.START_TAG, parser.next());
assertEquals(XmlPullParser.END_TAG, parser.next());
assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
}
public void testDoctypeInDocumentElement() throws Exception {
assertParseFailure("");
}
public void testDoctypeAfterDocumentElement() throws Exception {
assertParseFailure("");
}
private void assertParseFailure(String xml) throws Exception {
XmlPullParser parser = newPullParser();
parser.setInput(new StringReader(xml));
try {
while (parser.next() != XmlPullParser.END_DOCUMENT) {
}
fail();
} catch (XmlPullParserException expected) {
}
}
private String repeat(char c, int length) {
char[] chars = new char[length];
Arrays.fill(chars, c);
return new String(chars);
}
private XmlPullParser newPullParser(String xml) throws XmlPullParserException {
XmlPullParser result = newPullParser();
result.setInput(new StringReader(xml));
return result;
}
/**
* Creates a new pull parser.
*/
abstract XmlPullParser newPullParser() throws XmlPullParserException;
}