1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.xml;
18
19import java.io.StringReader;
20import java.util.Arrays;
21import junit.framework.TestCase;
22import org.xmlpull.v1.XmlPullParser;
23import org.xmlpull.v1.XmlPullParserException;
24
25/**
26 * Test doctype handling in pull parsers.
27 */
28public abstract class PullParserDtdTest extends TestCase {
29
30    private static final int READ_BUFFER_SIZE = 8192;
31
32    /**
33     * Android's Expat pull parser permits parameter entities to be declared,
34     * but it doesn't permit such entities to be used.
35     */
36    public void testDeclaringParameterEntities() throws Exception {
37        String xml = "<!DOCTYPE foo ["
38            + "  <!ENTITY % a \"android\">"
39            + "]><foo></foo>";
40        XmlPullParser parser = newPullParser(xml);
41        while (parser.next() != XmlPullParser.END_DOCUMENT) {
42        }
43    }
44
45    public void testUsingParameterEntitiesInDtds() throws Exception {
46        assertParseFailure("<!DOCTYPE foo ["
47            + "  <!ENTITY % a \"android\">"
48            + "  <!ENTITY b \"%a;\">"
49            + "]><foo></foo>");
50    }
51
52    public void testUsingParameterInDocuments() throws Exception {
53        assertParseFailure("<!DOCTYPE foo ["
54            + "  <!ENTITY % a \"android\">"
55            + "]><foo>&a;</foo>");
56    }
57
58    public void testGeneralAndParameterEntityWithTheSameName() throws Exception {
59        String xml = "<!DOCTYPE foo ["
60                + "  <!ENTITY a \"aaa\">"
61                + "  <!ENTITY % a \"bbb\">"
62                + "]><foo>&a;</foo>";
63        XmlPullParser parser = newPullParser(xml);
64        assertEquals(XmlPullParser.START_TAG, parser.next());
65        assertEquals(XmlPullParser.TEXT, parser.next());
66        assertEquals("aaa", parser.getText());
67        assertEquals(XmlPullParser.END_TAG, parser.next());
68        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
69    }
70
71    public void testInternalEntities() throws Exception {
72        String xml = "<!DOCTYPE foo ["
73                + "  <!ENTITY a \"android\">"
74                + "]><foo>&a;</foo>";
75        XmlPullParser parser = newPullParser(xml);
76        assertEquals(XmlPullParser.START_TAG, parser.next());
77        assertEquals(XmlPullParser.TEXT, parser.next());
78        assertEquals("android", parser.getText());
79        assertEquals(XmlPullParser.END_TAG, parser.next());
80        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
81    }
82
83    public void testExternalDtdIsSilentlyIgnored() throws Exception {
84        String xml = "<!DOCTYPE foo SYSTEM \"http://127.0.0.1:1/no-such-file.dtd\"><foo></foo>";
85        XmlPullParser parser = newPullParser(xml);
86        assertEquals(XmlPullParser.START_TAG, parser.next());
87        assertEquals("foo", parser.getName());
88        assertEquals(XmlPullParser.END_TAG, parser.next());
89        assertEquals("foo", parser.getName());
90        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
91    }
92
93    public void testExternalAndInternalDtd() throws Exception {
94        String xml = "<!DOCTYPE foo SYSTEM \"http://127.0.0.1:1/no-such-file.dtd\" ["
95                + "  <!ENTITY a \"android\">"
96                + "]><foo>&a;</foo>";
97        XmlPullParser parser = newPullParser(xml);
98        assertEquals(XmlPullParser.START_TAG, parser.next());
99        assertEquals(XmlPullParser.TEXT, parser.next());
100        assertEquals("android", parser.getText());
101        assertEquals(XmlPullParser.END_TAG, parser.next());
102        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
103    }
104
105    public void testInternalEntitiesAreParsed() throws Exception {
106        String xml = "<!DOCTYPE foo ["
107                + "  <!ENTITY a \"&#38;#65;\">" // &#38; expands to '&', &#65; expands to 'A'
108                + "]><foo>&a;</foo>";
109        XmlPullParser parser = newPullParser(xml);
110        assertEquals(XmlPullParser.START_TAG, parser.next());
111        assertEquals(XmlPullParser.TEXT, parser.next());
112        assertEquals("A", parser.getText());
113        assertEquals(XmlPullParser.END_TAG, parser.next());
114        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
115    }
116
117    public void testFoolishlyRecursiveInternalEntities() throws Exception {
118        String xml = "<!DOCTYPE foo ["
119                + "  <!ENTITY a \"&#38;#38;#38;#38;\">" // expand &#38; to '&' only twice
120                + "]><foo>&a;</foo>";
121        XmlPullParser parser = newPullParser(xml);
122        assertEquals(XmlPullParser.START_TAG, parser.next());
123        assertEquals(XmlPullParser.TEXT, parser.next());
124        assertEquals("&#38;#38;", parser.getText());
125        assertEquals(XmlPullParser.END_TAG, parser.next());
126        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
127    }
128
129    /**
130     * Test that the output of {@code &#38;} is parsed, but {@code &amp;} isn't.
131     * http://www.w3.org/TR/2008/REC-xml-20081126/#sec-entexpand
132     */
133    public void testExpansionOfEntityAndCharacterReferences() throws Exception {
134        String xml = "<!DOCTYPE foo ["
135                + "<!ENTITY example \"<p>An ampersand (&#38;#38;) may be escaped\n"
136                + "numerically (&#38;#38;#38;) or with a general entity\n"
137                + "(&amp;amp;).</p>\" >"
138                + "]><foo>&example;</foo>";
139        XmlPullParser parser = newPullParser(xml);
140        assertEquals(XmlPullParser.START_TAG, parser.next());
141        assertEquals(XmlPullParser.START_TAG, parser.next());
142        assertEquals("p", parser.getName());
143        assertEquals(XmlPullParser.TEXT, parser.next());
144        assertEquals("An ampersand (&) may be escaped\n"
145                + "numerically (&#38;) or with a general entity\n"
146                + "(&amp;).", parser.getText());
147        assertEquals(XmlPullParser.END_TAG, parser.next());
148        assertEquals("p", parser.getName());
149        assertEquals(XmlPullParser.END_TAG, parser.next());
150        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
151    }
152
153    public void testGeneralEntitiesAreStoredUnresolved() throws Exception {
154        String xml = "<!DOCTYPE foo ["
155                + "<!ENTITY b \"&a;\" >"
156                + "<!ENTITY a \"android\" >"
157                + "]><foo>&a;</foo>";
158        XmlPullParser parser = newPullParser(xml);
159        assertEquals(XmlPullParser.START_TAG, parser.next());
160        assertEquals(XmlPullParser.TEXT, parser.next());
161        assertEquals("android", parser.getText());
162        assertEquals(XmlPullParser.END_TAG, parser.next());
163        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
164    }
165
166    public void testStructuredEntityAndNextToken() throws Exception {
167        String xml = "<!DOCTYPE foo [<!ENTITY bb \"<bar>baz<!--quux--></bar>\">]><foo>a&bb;c</foo>";
168        XmlPullParser parser = newPullParser(xml);
169        assertEquals(XmlPullParser.DOCDECL, parser.nextToken());
170        assertEquals(XmlPullParser.START_TAG, parser.nextToken());
171        assertEquals("foo", parser.getName());
172        assertEquals(XmlPullParser.TEXT, parser.nextToken());
173        assertEquals("a", parser.getText());
174        assertEquals(XmlPullParser.ENTITY_REF, parser.nextToken());
175        assertEquals("bb", parser.getName());
176        assertEquals("", parser.getText());
177        assertEquals(XmlPullParser.START_TAG, parser.nextToken());
178        assertEquals("bar", parser.getName());
179        assertEquals(XmlPullParser.TEXT, parser.nextToken());
180        assertEquals("baz", parser.getText());
181        assertEquals(XmlPullParser.COMMENT, parser.nextToken());
182        assertEquals("quux", parser.getText());
183        assertEquals(XmlPullParser.END_TAG, parser.nextToken());
184        assertEquals("bar", parser.getName());
185        assertEquals(XmlPullParser.TEXT, parser.nextToken());
186        assertEquals("c", parser.getText());
187        assertEquals(XmlPullParser.END_TAG, parser.next());
188        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
189    }
190
191    /**
192     * Android's Expat replaces external entities with the empty string.
193     */
194    public void testUsingExternalEntities() throws Exception {
195        String xml = "<!DOCTYPE foo ["
196                + "  <!ENTITY a SYSTEM \"http://localhost:1/no-such-file.xml\">"
197                + "]><foo>&a;</foo>";
198        XmlPullParser parser = newPullParser(xml);
199        assertEquals(XmlPullParser.START_TAG, parser.next());
200        // &a; is dropped!
201        assertEquals(XmlPullParser.END_TAG, parser.next());
202        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
203    }
204
205    /**
206     * Android's ExpatPullParser replaces missing entities with the empty string
207     * when an external DTD is declared.
208     */
209    public void testExternalDtdAndMissingEntity() throws Exception {
210        String xml = "<!DOCTYPE foo SYSTEM \"http://127.0.0.1:1/no-such-file.dtd\">"
211                + "<foo>&a;</foo>";
212        XmlPullParser parser = newPullParser(xml);
213        assertEquals(XmlPullParser.START_TAG, parser.next());
214        assertEquals(XmlPullParser.END_TAG, parser.next());
215        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
216    }
217
218
219    public void testExternalIdIsCaseSensitive() throws Exception {
220        // The spec requires 'SYSTEM' in upper case
221        assertParseFailure("<!DOCTYPE foo ["
222                + "  <!ENTITY a system \"http://localhost:1/no-such-file.xml\">"
223                + "]><foo/>");
224    }
225
226    /**
227     * Use a DTD to specify that {@code <foo>} only contains {@code <bar>} tags.
228     * Validating parsers react to this by dropping whitespace between the two
229     * tags.
230     */
231    public void testDtdDoesNotInformIgnorableWhitespace() throws Exception {
232        String xml = "<!DOCTYPE foo [\n"
233                + "  <!ELEMENT foo (bar)*>\n"
234                + "  <!ELEMENT bar ANY>\n"
235                + "]>"
236                + "<foo>  \n  <bar></bar>  \t  </foo>";
237        XmlPullParser parser = newPullParser(xml);
238        assertEquals(XmlPullParser.START_TAG, parser.next());
239        assertEquals("foo", parser.getName());
240        assertEquals(XmlPullParser.TEXT, parser.next());
241        assertEquals("  \n  ", parser.getText());
242        assertEquals(XmlPullParser.START_TAG, parser.next());
243        assertEquals("bar", parser.getName());
244        assertEquals(XmlPullParser.END_TAG, parser.next());
245        assertEquals("bar", parser.getName());
246        assertEquals(XmlPullParser.TEXT, parser.next());
247        assertEquals("  \t  ", parser.getText());
248        assertEquals(XmlPullParser.END_TAG, parser.next());
249        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
250    }
251
252    public void testEmptyDoesNotInformIgnorableWhitespace() throws Exception {
253        String xml = "<!DOCTYPE foo [\n"
254                + "  <!ELEMENT foo EMPTY>\n"
255                + "]>"
256                + "<foo>  \n  </foo>";
257        XmlPullParser parser = newPullParser(xml);
258        assertEquals(XmlPullParser.START_TAG, parser.next());
259        assertEquals("foo", parser.getName());
260        assertEquals(XmlPullParser.TEXT, parser.next());
261        assertEquals("  \n  ", parser.getText());
262        assertEquals(XmlPullParser.END_TAG, parser.next());
263        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
264    }
265
266    /**
267     * Test that the parser doesn't expand the entity attributes.
268     */
269    public void testAttributeOfTypeEntity() throws Exception {
270        String xml = "<!DOCTYPE foo [\n"
271                + "  <!ENTITY a \"android\">"
272                + "  <!ELEMENT foo ANY>\n"
273                + "  <!ATTLIST foo\n"
274                + "    bar ENTITY #IMPLIED>"
275                + "]>"
276                + "<foo bar=\"a\"></foo>";
277        XmlPullParser parser = newPullParser(xml);
278        assertEquals(XmlPullParser.START_TAG, parser.next());
279        assertEquals("foo", parser.getName());
280        assertEquals("a", parser.getAttributeValue(null, "bar"));
281        assertEquals(XmlPullParser.END_TAG, parser.next());
282        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
283    }
284
285    public void testTagStructureNotValidated() throws Exception {
286        String xml = "<!DOCTYPE foo [\n"
287                + "  <!ELEMENT foo (bar)*>\n"
288                + "  <!ELEMENT bar ANY>\n"
289                + "  <!ELEMENT baz ANY>\n"
290                + "]>"
291                + "<foo><bar/><bar/><baz/></foo>";
292        XmlPullParser parser = newPullParser(xml);
293        assertEquals(XmlPullParser.START_TAG, parser.next());
294        assertEquals("foo", parser.getName());
295        assertEquals(XmlPullParser.START_TAG, parser.next());
296        assertEquals("bar", parser.getName());
297        assertEquals(XmlPullParser.END_TAG, parser.next());
298        assertEquals(XmlPullParser.START_TAG, parser.next());
299        assertEquals("bar", parser.getName());
300        assertEquals(XmlPullParser.END_TAG, parser.next());
301        assertEquals(XmlPullParser.START_TAG, parser.next());
302        assertEquals("baz", parser.getName());
303        assertEquals(XmlPullParser.END_TAG, parser.next());
304        assertEquals(XmlPullParser.END_TAG, parser.next());
305        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
306    }
307
308    public void testAttributeDefaultValues() throws Exception {
309        String xml = "<!DOCTYPE foo [\n"
310                + "  <!ATTLIST bar\n"
311                + "    baz (a|b|c)  \"c\">"
312                + "]>"
313                + "<foo>"
314                + "<bar/>"
315                + "<bar baz=\"a\"/>"
316                + "</foo>";
317        XmlPullParser parser = newPullParser(xml);
318        assertEquals(XmlPullParser.START_TAG, parser.next());
319        assertEquals(XmlPullParser.START_TAG, parser.next());
320        assertEquals("bar", parser.getName());
321        assertEquals("c", parser.getAttributeValue(null, "baz"));
322        assertEquals(XmlPullParser.END_TAG, parser.next());
323        assertEquals(XmlPullParser.START_TAG, parser.next());
324        assertEquals("bar", parser.getName());
325        assertEquals("a", parser.getAttributeValue(null, "baz"));
326        assertEquals(XmlPullParser.END_TAG, parser.next());
327        assertEquals(XmlPullParser.END_TAG, parser.next());
328        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
329    }
330
331    public void testAttributeDefaultValueEntitiesExpanded() throws Exception {
332        String xml = "<!DOCTYPE foo [\n"
333                + "  <!ENTITY g \"ghi\">"
334                + "  <!ELEMENT foo ANY>\n"
335                + "  <!ATTLIST foo\n"
336                + "    bar CDATA \"abc &amp; def &g; jk\">"
337                + "]>"
338                + "<foo></foo>";
339        XmlPullParser parser = newPullParser(xml);
340        assertEquals(XmlPullParser.START_TAG, parser.next());
341        assertEquals("foo", parser.getName());
342        assertEquals("abc & def ghi jk", parser.getAttributeValue(null, "bar"));
343        assertEquals(XmlPullParser.END_TAG, parser.next());
344        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
345    }
346
347    public void testAttributeDefaultValuesAndNamespaces() throws Exception {
348        String xml = "<!DOCTYPE foo [\n"
349                + "  <!ATTLIST foo\n"
350                + "    bar:a CDATA \"android\">"
351                + "]>"
352                + "<foo xmlns:bar='http://bar'></foo>";
353        XmlPullParser parser = newPullParser(xml);
354        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
355        assertEquals(XmlPullParser.START_TAG, parser.next());
356        assertEquals("foo", parser.getName());
357        // In Expat, namespaces don't apply to default attributes
358        int index = indexOfAttributeWithName(parser, "bar:a");
359        assertEquals("", parser.getAttributeNamespace(index));
360        assertEquals("bar:a", parser.getAttributeName(index));
361        assertEquals("android", parser.getAttributeValue(index));
362        assertEquals("CDATA", parser.getAttributeType(index));
363        assertEquals(XmlPullParser.END_TAG, parser.next());
364        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
365    }
366
367    private int indexOfAttributeWithName(XmlPullParser parser, String name) {
368        for (int i = 0; i < parser.getAttributeCount(); i++) {
369            if (parser.getAttributeName(i).equals(name)) {
370                return i;
371            }
372        }
373        return -1;
374    }
375
376    public void testAttributeEntitiesExpandedEagerly() throws Exception {
377        assertParseFailure("<!DOCTYPE foo [\n"
378                + "  <!ELEMENT foo ANY>\n"
379                + "  <!ATTLIST foo\n"
380                + "    bar CDATA \"abc &amp; def &g; jk\">"
381                + "  <!ENTITY g \"ghi\">"
382                + "]>"
383                + "<foo></foo>");
384    }
385
386    public void testRequiredAttributesOmitted() throws Exception {
387        String xml = "<!DOCTYPE foo [\n"
388                + "  <!ELEMENT foo ANY>\n"
389                + "  <!ATTLIST foo\n"
390                + "    bar (a|b|c) #REQUIRED>"
391                + "]>"
392                + "<foo></foo>";
393        XmlPullParser parser = newPullParser(xml);
394        assertEquals(XmlPullParser.START_TAG, parser.next());
395        assertEquals("foo", parser.getName());
396        assertEquals(null, parser.getAttributeValue(null, "bar"));
397        assertEquals(XmlPullParser.END_TAG, parser.next());
398    }
399
400    public void testFixedAttributesWithConflictingValues() throws Exception {
401        String xml = "<!DOCTYPE foo [\n"
402                + "  <!ELEMENT foo ANY>\n"
403                + "  <!ATTLIST foo\n"
404                + "    bar (a|b|c) #FIXED \"c\">"
405                + "]>"
406                + "<foo bar=\"a\"></foo>";
407        XmlPullParser parser = newPullParser(xml);
408        assertEquals(XmlPullParser.START_TAG, parser.next());
409        assertEquals("foo", parser.getName());
410        assertEquals("a", parser.getAttributeValue(null, "bar"));
411        assertEquals(XmlPullParser.END_TAG, parser.next());
412    }
413
414    public void testParsingNotations() throws Exception {
415        String xml = "<!DOCTYPE foo [\n"
416                + "  <!NOTATION type-a PUBLIC \"application/a\"> \n"
417                + "  <!NOTATION type-b PUBLIC \"image/b\">\n"
418                + "  <!NOTATION type-c PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n"
419                + "     \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"> \n"
420                + "  <!ENTITY file          SYSTEM \"d.xml\">\n"
421                + "  <!ENTITY fileWithNdata SYSTEM \"e.bin\" NDATA type-b>\n"
422                + "]>"
423                + "<foo type=\"type-a\"/>";
424        XmlPullParser parser = newPullParser(xml);
425        assertEquals(XmlPullParser.START_TAG, parser.next());
426        assertEquals("foo", parser.getName());
427        assertEquals(XmlPullParser.END_TAG, parser.next());
428    }
429
430    public void testCommentsInDoctype() throws Exception {
431        String xml = "<!DOCTYPE foo ["
432                + "  <!-- ' -->"
433                + "]><foo>android</foo>";
434        XmlPullParser parser = newPullParser(xml);
435        assertEquals(XmlPullParser.START_TAG, parser.next());
436        assertEquals(XmlPullParser.TEXT, parser.next());
437        assertEquals("android", parser.getText());
438        assertEquals(XmlPullParser.END_TAG, parser.next());
439        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
440    }
441
442    public void testDoctypeNameOnly() throws Exception {
443        String xml = "<!DOCTYPE foo><foo></foo>";
444        XmlPullParser parser = newPullParser(xml);
445        assertEquals(XmlPullParser.START_TAG, parser.next());
446        assertEquals("foo", parser.getName());
447        assertEquals(XmlPullParser.END_TAG, parser.next());
448        assertEquals("foo", parser.getName());
449        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
450    }
451
452    public void testVeryLongEntities() throws Exception {
453        String a = repeat('a', READ_BUFFER_SIZE + 1);
454        String b = repeat('b', READ_BUFFER_SIZE + 1);
455        String c = repeat('c', READ_BUFFER_SIZE + 1);
456
457        String xml = "<!DOCTYPE foo [\n"
458                + "  <!ENTITY " + a + "  \"d &" + b + "; e\">"
459                + "  <!ENTITY " + b + "  \"f " + c + " g\">"
460                + "]>"
461                + "<foo>h &" + a + "; i</foo>";
462        XmlPullParser parser = newPullParser(xml);
463        assertEquals(XmlPullParser.START_TAG, parser.next());
464        assertEquals(XmlPullParser.TEXT, parser.next());
465        assertEquals("h d f " + c + " g e i", parser.getText());
466        assertEquals(XmlPullParser.END_TAG, parser.next());
467        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
468    }
469
470    public void testManuallyRegisteredEntitiesWithDoctypeParsing() throws Exception {
471        String xml = "<foo>&a;</foo>";
472        XmlPullParser parser = newPullParser(xml);
473        try {
474            parser.defineEntityReplacementText("a", "android");
475            fail();
476        } catch (UnsupportedOperationException expected) {
477        } catch (IllegalStateException expected) {
478        }
479    }
480
481    public void testDoctypeWithNextToken() throws Exception {
482        String xml = "<!DOCTYPE foo [<!ENTITY bb \"bar baz\">]><foo>a&bb;c</foo>";
483        XmlPullParser parser = newPullParser(xml);
484        assertEquals(XmlPullParser.DOCDECL, parser.nextToken());
485        assertEquals(" foo [<!ENTITY bb \"bar baz\">]", parser.getText());
486        assertNull(parser.getName());
487        assertEquals(XmlPullParser.START_TAG, parser.nextToken());
488        assertEquals(XmlPullParser.TEXT, parser.next());
489        assertEquals("abar bazc", parser.getText());
490        assertEquals(XmlPullParser.END_TAG, parser.next());
491        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
492    }
493
494    public void testDoctypeSpansBuffers() throws Exception {
495        char[] doctypeChars = new char[READ_BUFFER_SIZE + 1];
496        Arrays.fill(doctypeChars, 'x');
497        String doctypeBody = " foo [<!--" + new String(doctypeChars) + "-->]";
498        String xml = "<!DOCTYPE" + doctypeBody + "><foo/>";
499        XmlPullParser parser = newPullParser(xml);
500        assertEquals(XmlPullParser.DOCDECL, parser.nextToken());
501        assertEquals(doctypeBody, parser.getText());
502        assertEquals(XmlPullParser.START_TAG, parser.next());
503        assertEquals(XmlPullParser.END_TAG, parser.next());
504        assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
505    }
506
507    public void testDoctypeInDocumentElement() throws Exception {
508        assertParseFailure("<foo><!DOCTYPE foo></foo>");
509    }
510
511    public void testDoctypeAfterDocumentElement() throws Exception {
512        assertParseFailure("<foo/><!DOCTYPE foo>");
513    }
514
515    private void assertParseFailure(String xml) throws Exception {
516        XmlPullParser parser = newPullParser();
517        parser.setInput(new StringReader(xml));
518        try {
519            while (parser.next() != XmlPullParser.END_DOCUMENT) {
520            }
521            fail();
522        } catch (XmlPullParserException expected) {
523        }
524    }
525
526    private String repeat(char c, int length) {
527        char[] chars = new char[length];
528        Arrays.fill(chars, c);
529        return new String(chars);
530    }
531
532    private XmlPullParser newPullParser(String xml) throws XmlPullParserException {
533        XmlPullParser result = newPullParser();
534        result.setInput(new StringReader(xml));
535        return result;
536    }
537
538    /**
539     * Creates a new pull parser.
540     */
541    abstract XmlPullParser newPullParser() throws XmlPullParserException;
542}
543