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.ByteArrayInputStream;
20import java.io.File;
21import java.io.FileWriter;
22import java.io.IOException;
23import java.io.StringReader;
24import java.io.StringWriter;
25import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.HashSet;
28import java.util.List;
29import java.util.Set;
30import java.util.regex.Matcher;
31import java.util.regex.Pattern;
32import javax.xml.parsers.DocumentBuilder;
33import javax.xml.parsers.DocumentBuilderFactory;
34import javax.xml.transform.OutputKeys;
35import javax.xml.transform.Transformer;
36import javax.xml.transform.TransformerException;
37import javax.xml.transform.TransformerFactory;
38import javax.xml.transform.dom.DOMSource;
39import javax.xml.transform.stream.StreamResult;
40import junit.framework.AssertionFailedError;
41import junit.framework.TestCase;
42import org.w3c.dom.Attr;
43import org.w3c.dom.CDATASection;
44import org.w3c.dom.Comment;
45import org.w3c.dom.DOMException;
46import org.w3c.dom.DOMImplementation;
47import org.w3c.dom.Document;
48import org.w3c.dom.DocumentFragment;
49import org.w3c.dom.DocumentType;
50import org.w3c.dom.Element;
51import org.w3c.dom.Entity;
52import org.w3c.dom.EntityReference;
53import org.w3c.dom.NamedNodeMap;
54import org.w3c.dom.Node;
55import org.w3c.dom.NodeList;
56import org.w3c.dom.Notation;
57import org.w3c.dom.ProcessingInstruction;
58import org.w3c.dom.Text;
59import org.w3c.dom.TypeInfo;
60import org.w3c.dom.UserDataHandler;
61import static org.w3c.dom.UserDataHandler.NODE_ADOPTED;
62import static org.w3c.dom.UserDataHandler.NODE_CLONED;
63import static org.w3c.dom.UserDataHandler.NODE_IMPORTED;
64import static org.w3c.dom.UserDataHandler.NODE_RENAMED;
65import org.xml.sax.InputSource;
66import org.xml.sax.SAXException;
67
68/**
69 * Construct a DOM and then interrogate it.
70 */
71public class DomTest extends TestCase {
72
73    private Transformer transformer;
74    private DocumentBuilder builder;
75    private DOMImplementation domImplementation;
76
77    private final String xml
78            = "<!DOCTYPE menu ["
79            + "  <!ENTITY sp \"Maple Syrup\">"
80            + "  <!NOTATION png SYSTEM \"image/png\">"
81            + "]>"
82            + "<menu>\n"
83            + "  <item xmlns=\"http://food\" xmlns:a=\"http://addons\">\n"
84            + "    <name a:standard=\"strawberry\" deluxe=\"&sp;\">Waffles</name>\n"
85            + "    <description xmlns=\"http://marketing\">Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)</description>\n"
86            + "    <a:option>Whipped Cream</a:option>\n"
87            + "    <a:option>&sp;</a:option>\n"
88            + "    <?wafflemaker square shape?>\n"
89            + "    <nutrition>\n"
90            + "      <a:vitamins xmlns:a=\"http://usda\">\n"
91            + "        <!-- add other vitamins? --> \n"
92            + "        <a:vitaminc>60%</a:vitaminc>\n"
93            + "      </a:vitamins>\n"
94            + "    </nutrition>\n"
95            + "  </item>\n"
96            + "</menu>";
97
98    private Document document;
99    private DocumentType doctype;
100    private Entity sp;
101    private Notation png;
102    private Element menu;
103    private Element item;
104    private Attr itemXmlns;
105    private Attr itemXmlnsA;
106    private Element name;
107    private Attr standard;
108    private Attr deluxe;
109    private Text waffles;
110    private Element description;
111    private Text descriptionText1;
112    private CDATASection descriptionText2;
113    private Text descriptionText3;
114    private Element option1;
115    private Element option2;
116    private Node option2Reference; // resolved to Text on RI, an EntityReference on Dalvik
117    private ProcessingInstruction wafflemaker;
118    private Element nutrition;
119    private Element vitamins;
120    private Attr vitaminsXmlnsA;
121    private Comment comment;
122    private Element vitaminc;
123    private Text vitamincText;
124    private List<Node> allNodes;
125
126    @Override protected void setUp() throws Exception {
127        transformer = TransformerFactory.newInstance().newTransformer();
128        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
129        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
130        factory.setNamespaceAware(true);
131        builder = factory.newDocumentBuilder();
132        domImplementation = builder.getDOMImplementation();
133        document = builder.parse(new InputSource(new StringReader(xml)));
134
135        // doctype nodes
136        doctype = document.getDoctype();
137        if (doctype.getEntities() != null) {
138            sp = (Entity) doctype.getEntities().item(0);
139        }
140        if (doctype.getNotations() != null) {
141            png = (Notation) doctype.getNotations().item(0);
142        }
143
144        // document nodes
145        menu = document.getDocumentElement();
146        item = (Element) menu.getChildNodes().item(1);
147        itemXmlns = item.getAttributeNode("xmlns");
148        itemXmlnsA = item.getAttributeNode("xmlns:a");
149        name = (Element) item.getChildNodes().item(1);
150        standard = name.getAttributeNode("a:standard");
151        deluxe = name.getAttributeNode("deluxe");
152        waffles = (Text) name.getChildNodes().item(0);
153        description = (Element) item.getChildNodes().item(3);
154        descriptionText1 = (Text) description.getChildNodes().item(0);
155        descriptionText2 = (CDATASection) description.getChildNodes().item(1);
156        descriptionText3 = (Text) description.getChildNodes().item(2);
157        option1 = (Element) item.getChildNodes().item(5);
158        option2 = (Element) item.getChildNodes().item(7);
159        option2Reference = option2.getChildNodes().item(0);
160        wafflemaker = (ProcessingInstruction) item.getChildNodes().item(9);
161        nutrition = (Element) item.getChildNodes().item(11);
162        vitamins = (Element) nutrition.getChildNodes().item(1);
163        vitaminsXmlnsA = vitamins.getAttributeNode("xmlns:a");
164        comment = (Comment) vitamins.getChildNodes().item(1);
165        vitaminc = (Element) vitamins.getChildNodes().item(3);
166        vitamincText = (Text) vitaminc.getChildNodes().item(0);
167
168        allNodes = new ArrayList<Node>();
169
170        if (sp != null) {
171            allNodes.add(sp);
172        }
173        if (png != null) {
174            allNodes.add(png);
175        }
176
177        allNodes.addAll(Arrays.asList(document, doctype, menu, item, itemXmlns,
178                itemXmlnsA, name, standard, deluxe, waffles, description,
179                descriptionText1, descriptionText2, descriptionText3, option1,
180                option2, option2Reference, wafflemaker, nutrition, vitamins,
181                vitaminsXmlnsA, comment, vitaminc, vitamincText));
182    }
183
184    /**
185     * Android's parsed DOM doesn't include entity declarations. These nodes will
186     * only be tested for implementations that support them.
187     */
188    public void testEntityDeclarations() {
189        assertNotNull("This implementation does not parse entity declarations", sp);
190    }
191
192    /**
193     * Android's parsed DOM doesn't include notations. These nodes will only be
194     * tested for implementations that support them.
195     */
196    public void testNotations() {
197        assertNotNull("This implementation does not parse notations", png);
198    }
199
200    public void testLookupNamespaceURIByPrefix() {
201        assertEquals(null, doctype.lookupNamespaceURI("a"));
202        if (sp != null) {
203            assertEquals(null, sp.lookupNamespaceURI("a"));
204        }
205        if (png != null) {
206            assertEquals(null, png.lookupNamespaceURI("a"));
207        }
208        assertEquals(null, document.lookupNamespaceURI("a"));
209        assertEquals(null, menu.lookupNamespaceURI("a"));
210        assertEquals("http://addons", item.lookupNamespaceURI("a"));
211        assertEquals("http://addons", itemXmlns.lookupNamespaceURI("a"));
212        assertEquals("http://addons", itemXmlnsA.lookupNamespaceURI("a"));
213        assertEquals("http://addons", name.lookupNamespaceURI("a"));
214        assertEquals("http://addons", standard.lookupNamespaceURI("a"));
215        assertEquals("http://addons", deluxe.lookupNamespaceURI("a"));
216        assertEquals("http://addons", description.lookupNamespaceURI("a"));
217        assertEquals("http://addons", descriptionText1.lookupNamespaceURI("a"));
218        assertEquals("http://addons", descriptionText2.lookupNamespaceURI("a"));
219        assertEquals("http://addons", descriptionText3.lookupNamespaceURI("a"));
220        assertEquals("http://addons", option1.lookupNamespaceURI("a"));
221        assertEquals("http://addons", option2.lookupNamespaceURI("a"));
222        assertEquals("http://addons", option2Reference.lookupNamespaceURI("a"));
223        assertEquals("http://addons", wafflemaker.lookupNamespaceURI("a"));
224        assertEquals("http://addons", nutrition.lookupNamespaceURI("a"));
225        assertEquals("http://usda", vitamins.lookupNamespaceURI("a"));
226        assertEquals("http://usda", vitaminsXmlnsA.lookupNamespaceURI("a"));
227        assertEquals("http://usda", comment.lookupNamespaceURI("a"));
228        assertEquals("http://usda", vitaminc.lookupNamespaceURI("a"));
229        assertEquals("http://usda", vitamincText.lookupNamespaceURI("a"));
230    }
231
232    public void testLookupNamespaceURIWithNullPrefix() {
233        assertEquals(null, document.lookupNamespaceURI(null));
234        assertEquals(null, doctype.lookupNamespaceURI(null));
235        if (sp != null) {
236            assertEquals(null, sp.lookupNamespaceURI(null));
237        }
238        if (png != null) {
239            assertEquals(null, png.lookupNamespaceURI(null));
240        }
241        assertEquals(null, menu.lookupNamespaceURI(null));
242        assertEquals("http://food", item.lookupNamespaceURI(null));
243        assertEquals("http://food", itemXmlns.lookupNamespaceURI(null));
244        assertEquals("http://food", itemXmlnsA.lookupNamespaceURI(null));
245        assertEquals("http://food", name.lookupNamespaceURI(null));
246        assertEquals("http://food", standard.lookupNamespaceURI(null));
247        assertEquals("http://food", deluxe.lookupNamespaceURI(null));
248        assertEquals("http://marketing", description.lookupNamespaceURI(null));
249        assertEquals("http://marketing", descriptionText1.lookupNamespaceURI(null));
250        assertEquals("http://marketing", descriptionText2.lookupNamespaceURI(null));
251        assertEquals("http://marketing", descriptionText3.lookupNamespaceURI(null));
252        assertEquals("http://food", option1.lookupNamespaceURI(null));
253        assertEquals("http://food", option2.lookupNamespaceURI(null));
254        assertEquals("http://food", option2Reference.lookupNamespaceURI(null));
255        assertEquals("http://food", wafflemaker.lookupNamespaceURI(null));
256        assertEquals("http://food", nutrition.lookupNamespaceURI(null));
257        assertEquals("http://food", vitamins.lookupNamespaceURI(null));
258        assertEquals("http://food", vitaminsXmlnsA.lookupNamespaceURI(null));
259        assertEquals("http://food", comment.lookupNamespaceURI(null));
260        assertEquals("http://food", vitaminc.lookupNamespaceURI(null));
261        assertEquals("http://food", vitamincText.lookupNamespaceURI(null));
262    }
263
264    public void testLookupNamespaceURIWithXmlnsPrefix() {
265        for (Node node : allNodes) {
266            assertEquals(null, node.lookupNamespaceURI("xmlns"));
267        }
268    }
269
270    public void testLookupPrefixWithShadowedUri() {
271        assertEquals(null, document.lookupPrefix("http://addons"));
272        assertEquals(null, doctype.lookupPrefix("http://addons"));
273        if (sp != null) {
274            assertEquals(null, sp.lookupPrefix("http://addons"));
275        }
276        if (png != null) {
277            assertEquals(null, png.lookupPrefix("http://addons"));
278        }
279        assertEquals(null, menu.lookupPrefix("http://addons"));
280        assertEquals("a", item.lookupPrefix("http://addons"));
281        assertEquals("a", itemXmlns.lookupPrefix("http://addons"));
282        assertEquals("a", itemXmlnsA.lookupPrefix("http://addons"));
283        assertEquals("a", name.lookupPrefix("http://addons"));
284        assertEquals("a", standard.lookupPrefix("http://addons"));
285        assertEquals("a", deluxe.lookupPrefix("http://addons"));
286        assertEquals("a", description.lookupPrefix("http://addons"));
287        assertEquals("a", descriptionText1.lookupPrefix("http://addons"));
288        assertEquals("a", descriptionText2.lookupPrefix("http://addons"));
289        assertEquals("a", descriptionText3.lookupPrefix("http://addons"));
290        assertEquals("a", option1.lookupPrefix("http://addons"));
291        assertEquals("a", option2.lookupPrefix("http://addons"));
292        assertEquals("a", option2Reference.lookupPrefix("http://addons"));
293        assertEquals("a", wafflemaker.lookupPrefix("http://addons"));
294        assertEquals("a", nutrition.lookupPrefix("http://addons"));
295        assertEquals(null, vitamins.lookupPrefix("http://addons"));
296        assertEquals(null, vitaminsXmlnsA.lookupPrefix("http://addons"));
297        assertEquals(null, comment.lookupPrefix("http://addons"));
298        assertEquals(null, vitaminc.lookupPrefix("http://addons"));
299        assertEquals(null, vitamincText.lookupPrefix("http://addons"));
300    }
301
302    public void testLookupPrefixWithUnusedUri() {
303        for (Node node : allNodes) {
304            assertEquals(null, node.lookupPrefix("http://unused"));
305        }
306    }
307
308    public void testLookupPrefixWithNullUri() {
309        for (Node node : allNodes) {
310            assertEquals(null, node.lookupPrefix(null));
311        }
312    }
313
314    public void testLookupPrefixWithShadowingUri() {
315        assertEquals(null, document.lookupPrefix("http://usda"));
316        assertEquals(null, doctype.lookupPrefix("http://usda"));
317        if (sp != null) {
318            assertEquals(null, sp.lookupPrefix("http://usda"));
319        }
320        if (png != null) {
321            assertEquals(null, png.lookupPrefix("http://usda"));
322        }
323        assertEquals(null, menu.lookupPrefix("http://usda"));
324        assertEquals(null, item.lookupPrefix("http://usda"));
325        assertEquals(null, itemXmlns.lookupPrefix("http://usda"));
326        assertEquals(null, itemXmlnsA.lookupPrefix("http://usda"));
327        assertEquals(null, name.lookupPrefix("http://usda"));
328        assertEquals(null, standard.lookupPrefix("http://usda"));
329        assertEquals(null, deluxe.lookupPrefix("http://usda"));
330        assertEquals(null, description.lookupPrefix("http://usda"));
331        assertEquals(null, descriptionText1.lookupPrefix("http://usda"));
332        assertEquals(null, descriptionText2.lookupPrefix("http://usda"));
333        assertEquals(null, descriptionText3.lookupPrefix("http://usda"));
334        assertEquals(null, option1.lookupPrefix("http://usda"));
335        assertEquals(null, option2.lookupPrefix("http://usda"));
336        assertEquals(null, option2Reference.lookupPrefix("http://usda"));
337        assertEquals(null, wafflemaker.lookupPrefix("http://usda"));
338        assertEquals(null, nutrition.lookupPrefix("http://usda"));
339        assertEquals("a", vitamins.lookupPrefix("http://usda"));
340        assertEquals("a", vitaminsXmlnsA.lookupPrefix("http://usda"));
341        assertEquals("a", comment.lookupPrefix("http://usda"));
342        assertEquals("a", vitaminc.lookupPrefix("http://usda"));
343        assertEquals("a", vitamincText.lookupPrefix("http://usda"));
344    }
345
346    public void testIsDefaultNamespace() {
347        assertFalse(document.isDefaultNamespace("http://food"));
348        assertFalse(doctype.isDefaultNamespace("http://food"));
349        if (sp != null) {
350            assertFalse(sp.isDefaultNamespace("http://food"));
351        }
352        if (png != null) {
353            assertFalse(png.isDefaultNamespace("http://food"));
354        }
355        assertFalse(menu.isDefaultNamespace("http://food"));
356        assertTrue(item.isDefaultNamespace("http://food"));
357        assertTrue(itemXmlns.isDefaultNamespace("http://food"));
358        assertTrue(itemXmlnsA.isDefaultNamespace("http://food"));
359        assertTrue(name.isDefaultNamespace("http://food"));
360        assertTrue(standard.isDefaultNamespace("http://food"));
361        assertTrue(deluxe.isDefaultNamespace("http://food"));
362        assertFalse(description.isDefaultNamespace("http://food"));
363        assertFalse(descriptionText1.isDefaultNamespace("http://food"));
364        assertFalse(descriptionText2.isDefaultNamespace("http://food"));
365        assertFalse(descriptionText3.isDefaultNamespace("http://food"));
366        assertTrue(option1.isDefaultNamespace("http://food"));
367        assertTrue(option2.isDefaultNamespace("http://food"));
368        assertTrue(option2Reference.isDefaultNamespace("http://food"));
369        assertTrue(wafflemaker.isDefaultNamespace("http://food"));
370        assertTrue(nutrition.isDefaultNamespace("http://food"));
371        assertTrue(vitamins.isDefaultNamespace("http://food"));
372        assertTrue(vitaminsXmlnsA.isDefaultNamespace("http://food"));
373        assertTrue(comment.isDefaultNamespace("http://food"));
374        assertTrue(vitaminc.isDefaultNamespace("http://food"));
375        assertTrue(vitamincText.isDefaultNamespace("http://food"));
376    }
377
378    /**
379     * Xerces fails this test. It returns false always for entity, notation,
380     * document fragment and document type nodes. This contradicts its own
381     * behaviour on lookupNamespaceURI(null).
382     */
383    public void testIsDefaultNamespaceNull_XercesBugs() {
384        String message = "isDefaultNamespace() should be consistent with lookupNamespaceURI(null)";
385        assertTrue(message, doctype.isDefaultNamespace(null));
386        if (sp != null) {
387            assertTrue(message, sp.isDefaultNamespace(null));
388        }
389        if (png != null) {
390            assertTrue(message, png.isDefaultNamespace(null));
391        }
392    }
393
394    public void testIsDefaultNamespaceNull() {
395        assertTrue(document.isDefaultNamespace(null));
396        assertTrue(menu.isDefaultNamespace(null));
397        assertFalse(item.isDefaultNamespace(null));
398        assertFalse(itemXmlns.isDefaultNamespace(null));
399        assertFalse(itemXmlnsA.isDefaultNamespace(null));
400        assertFalse(name.isDefaultNamespace(null));
401        assertFalse(standard.isDefaultNamespace(null));
402        assertFalse(deluxe.isDefaultNamespace(null));
403        assertFalse(description.isDefaultNamespace(null));
404        assertFalse(descriptionText1.isDefaultNamespace(null));
405        assertFalse(descriptionText2.isDefaultNamespace(null));
406        assertFalse(descriptionText3.isDefaultNamespace(null));
407        assertFalse(option1.isDefaultNamespace(null));
408        assertFalse(option2.isDefaultNamespace(null));
409        assertFalse(option2Reference.isDefaultNamespace(null));
410        assertFalse(wafflemaker.isDefaultNamespace(null));
411        assertFalse(nutrition.isDefaultNamespace(null));
412        assertFalse(vitamins.isDefaultNamespace(null));
413        assertFalse(vitaminsXmlnsA.isDefaultNamespace(null));
414        assertFalse(comment.isDefaultNamespace(null));
415        assertFalse(vitaminc.isDefaultNamespace(null));
416        assertFalse(vitamincText.isDefaultNamespace(null));
417    }
418
419    public void testDoctypeSetTextContent() throws TransformerException {
420        String original = domToString(document);
421        doctype.setTextContent("foobar"); // strangely, this is specified to no-op
422        assertEquals(original, domToString(document));
423    }
424
425    public void testDocumentSetTextContent() throws TransformerException {
426        String original = domToString(document);
427        document.setTextContent("foobar"); // strangely, this is specified to no-op
428        assertEquals(original, domToString(document));
429    }
430
431    public void testElementSetTextContent() throws TransformerException {
432        String original = domToString(document);
433        nutrition.setTextContent("foobar");
434        String expected = original.replaceFirst(
435                "(?s)<nutrition>.*</nutrition>", "<nutrition>foobar</nutrition>");
436        assertEquals(expected, domToString(document));
437    }
438
439    public void testEntitySetTextContent() throws TransformerException {
440        if (sp == null) {
441            return;
442        }
443        try {
444            sp.setTextContent("foobar");
445            fail(); // is this implementation-specific behaviour?
446        } catch (DOMException e) {
447        }
448    }
449
450    public void testNotationSetTextContent() throws TransformerException {
451        if (png == null) {
452            return;
453        }
454        String original = domToString(document);
455        png.setTextContent("foobar");
456        String expected = original.replace("image/png", "foobar");
457        assertEquals(expected, domToString(document));
458    }
459
460    /**
461     * Tests setTextContent on entity references. Although the other tests can
462     * act on a parsed DOM, this needs to use a programmatically constructed DOM
463     * because the parser may have replaced the entity reference with the
464     * corresponding text.
465     */
466    public void testEntityReferenceSetTextContent() throws TransformerException {
467        document = builder.newDocument();
468        Element root = document.createElement("menu");
469        document.appendChild(root);
470
471        EntityReference entityReference = document.createEntityReference("sp");
472        root.appendChild(entityReference);
473
474        try {
475            entityReference.setTextContent("Lite Syrup");
476            fail();
477        } catch (DOMException e) {
478        }
479    }
480
481    public void testAttributeSetTextContent() throws TransformerException {
482        String original = domToString(document);
483        standard.setTextContent("foobar");
484        String expected = original.replace("standard=\"strawberry\"", "standard=\"foobar\"");
485        assertEquals(expected, domToString(document));
486    }
487
488    public void testTextSetTextContent() throws TransformerException {
489        String original = domToString(document);
490        descriptionText1.setTextContent("foobar");
491        String expected = original.replace(">Belgian<!", ">foobar<!");
492        assertEquals(expected, domToString(document));
493    }
494
495    public void testCdataSetTextContent() throws TransformerException {
496        String original = domToString(document);
497        descriptionText2.setTextContent("foobar");
498        String expected = original.replace(
499                " waffles & strawberries (< 5g ", "foobar");
500        assertEquals(expected, domToString(document));
501    }
502
503    public void testProcessingInstructionSetTextContent() throws TransformerException {
504        String original = domToString(document);
505        wafflemaker.setTextContent("foobar");
506        String expected = original.replace(" square shape?>", " foobar?>");
507        assertEquals(expected, domToString(document));
508    }
509
510    public void testCommentSetTextContent() throws TransformerException {
511        String original = domToString(document);
512        comment.setTextContent("foobar");
513        String expected = original.replace("-- add other vitamins? --", "--foobar--");
514        assertEquals(expected, domToString(document));
515    }
516
517    public void testCoreFeature() {
518        assertFeature("Core", null);
519        assertFeature("Core", "");
520        assertFeature("Core", "1.0");
521        assertFeature("Core", "2.0");
522        assertFeature("Core", "3.0");
523        assertFeature("CORE", "3.0");
524        assertFeature("+Core", "3.0");
525        assertNoFeature("Core", "4.0");
526    }
527
528    public void testXmlFeature() {
529        assertFeature("XML", null);
530        assertFeature("XML", "");
531        assertFeature("XML", "1.0");
532        assertFeature("XML", "2.0");
533        assertFeature("XML", "3.0");
534        assertFeature("Xml", "3.0");
535        assertFeature("+XML", "3.0");
536        assertNoFeature("XML", "4.0");
537    }
538
539    /**
540     * The RI fails this test.
541     * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Document3-version
542     */
543    public void testXmlVersionFeature() {
544        assertFeature("XMLVersion", null);
545        assertFeature("XMLVersion", "");
546        assertFeature("XMLVersion", "1.0");
547        assertFeature("XMLVersion", "1.1");
548        assertFeature("XMLVERSION", "1.1");
549        assertFeature("+XMLVersion", "1.1");
550        assertNoFeature("XMLVersion", "1.2");
551        assertNoFeature("XMLVersion", "2.0");
552        assertNoFeature("XMLVersion", "2.0");
553    }
554
555    public void testLoadSaveFeature() {
556        assertFeature("LS", "3.0");
557    }
558
559    public void testElementTraversalFeature() {
560        assertFeature("ElementTraversal", "1.0");
561    }
562
563    private void assertFeature(String feature, String version) {
564        String message = "This implementation is expected to support "
565                + feature + " v. " + version + " but does not.";
566        assertTrue(message, domImplementation.hasFeature(feature, version));
567        assertNotNull(message, domImplementation.getFeature(feature, version));
568    }
569
570    private void assertNoFeature(String feature, String version) {
571        assertFalse(domImplementation.hasFeature(feature, version));
572        assertNull(domImplementation.getFeature(feature, version));
573    }
574
575    public void testIsSupported() {
576        // we don't independently test the features; instead just assume the
577        // implementation calls through to hasFeature (as tested above)
578        for (Node node : allNodes) {
579            assertTrue(node.isSupported("XML", null));
580            assertTrue(node.isSupported("XML", "3.0"));
581            assertFalse(node.isSupported("foo", null));
582            assertFalse(node.isSupported("foo", "bar"));
583        }
584    }
585
586    public void testGetFeature() {
587        // we don't independently test the features; instead just assume the
588        // implementation calls through to hasFeature (as tested above)
589        for (Node node : allNodes) {
590            assertSame(node, node.getFeature("XML", null));
591            assertSame(node, node.getFeature("XML", "3.0"));
592            assertNull(node.getFeature("foo", null));
593            assertNull(node.getFeature("foo", "bar"));
594        }
595    }
596
597    public void testNodeEqualsPositive() throws Exception {
598        DomTest copy = new DomTest();
599        copy.setUp();
600
601        for (int i = 0; i < allNodes.size(); i++) {
602            Node a = allNodes.get(i);
603            Node b = copy.allNodes.get(i);
604            assertTrue(a.isEqualNode(b));
605        }
606    }
607
608    public void testNodeEqualsNegative() throws Exception {
609        for (Node a : allNodes) {
610            for (Node b : allNodes) {
611                assertEquals(a == b, a.isEqualNode(b));
612            }
613        }
614    }
615
616    public void testNodeEqualsNegativeRecursive() throws Exception {
617        DomTest copy = new DomTest();
618        copy.setUp();
619        copy.vitaminc.setTextContent("55%");
620
621        // changing anything about a node should break equality for all parents
622        assertFalse(document.isEqualNode(copy.document));
623        assertFalse(menu.isEqualNode(copy.menu));
624        assertFalse(item.isEqualNode(copy.item));
625        assertFalse(nutrition.isEqualNode(copy.nutrition));
626        assertFalse(vitamins.isEqualNode(copy.vitamins));
627        assertFalse(vitaminc.isEqualNode(copy.vitaminc));
628
629        // but not siblings
630        assertTrue(doctype.isEqualNode(copy.doctype));
631        assertTrue(description.isEqualNode(copy.description));
632        assertTrue(option1.isEqualNode(copy.option1));
633    }
634
635    public void testNodeEqualsNull() {
636        for (Node node : allNodes) {
637            try {
638                node.isEqualNode(null);
639                fail();
640            } catch (NullPointerException e) {
641            }
642        }
643    }
644
645    public void testIsElementContentWhitespaceWithoutDeclaration() throws Exception {
646        String xml = "<menu>    <item/>   </menu>";
647
648        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
649        Text text = (Text) factory.newDocumentBuilder()
650                .parse(new InputSource(new StringReader(xml)))
651                .getDocumentElement().getChildNodes().item(0);
652        assertFalse(text.isElementContentWhitespace());
653    }
654
655    public void testIsElementContentWhitespaceWithDeclaration() throws Exception {
656        String xml = "<!DOCTYPE menu [\n"
657                + "  <!ELEMENT menu (item)*>\n"
658                + "  <!ELEMENT item (#PCDATA)>\n"
659                + "]><menu>    <item/>   </menu>";
660
661        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
662        Text text = (Text) factory.newDocumentBuilder()
663                .parse(new InputSource(new StringReader(xml)))
664                .getDocumentElement().getChildNodes().item(0);
665        assertTrue("This implementation does not recognize element content whitespace",
666                text.isElementContentWhitespace());
667    }
668
669    public void testGetWholeTextFirst() {
670        assertEquals("Belgian waffles & strawberries (< 5g of fat)",
671                descriptionText1.getWholeText());
672    }
673
674    public void testGetWholeTextMiddle() {
675        assertEquals("This implementation doesn't include preceding nodes in getWholeText()",
676                "Belgian waffles & strawberries (< 5g of fat)", descriptionText2.getWholeText());
677    }
678
679    public void testGetWholeTextLast() {
680        assertEquals("This implementation doesn't include preceding nodes in getWholeText()",
681                "Belgian waffles & strawberries (< 5g of fat)", descriptionText3.getWholeText());
682    }
683
684    public void testGetWholeTextOnly() {
685        assertEquals("60%", vitamincText.getWholeText());
686    }
687
688    public void testGetWholeTextWithEntityReference() {
689        EntityReference spReference = document.createEntityReference("sp");
690        description.insertBefore(spReference, descriptionText2);
691
692        assertEquals("This implementation doesn't resolve entity references in getWholeText()",
693                "BelgianMaple Syrup waffles & strawberries (< 5g of fat)",
694                descriptionText1.getWholeText());
695    }
696
697    public void testReplaceWholeTextFirst() throws TransformerException {
698        String original = domToString(document);
699        Text replacement = descriptionText1.replaceWholeText("Eggos");
700        assertSame(descriptionText1, replacement);
701        String expected = original.replace(
702                "Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)", "Eggos");
703        assertEquals(expected, domToString(document));
704    }
705
706    public void testReplaceWholeTextMiddle() throws TransformerException {
707        String original = domToString(document);
708        Text replacement = descriptionText2.replaceWholeText("Eggos");
709        assertSame(descriptionText2, replacement);
710        String expected = original.replace(
711                "Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)", "<![CDATA[Eggos]]>");
712        assertEquals("This implementation doesn't remove preceding nodes in replaceWholeText()",
713                expected, domToString(document));
714    }
715
716    public void testReplaceWholeTextLast() throws TransformerException {
717        String original = domToString(document);
718        Text replacement = descriptionText3.replaceWholeText("Eggos");
719        assertSame(descriptionText3, replacement);
720        String expected = original.replace(
721                "Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)", "Eggos");
722        assertEquals("This implementation doesn't remove preceding nodes in replaceWholeText()",
723                expected, domToString(document));
724    }
725
726    public void testReplaceWholeTextOnly() throws TransformerException {
727        String original = domToString(document);
728        Text replacement = vitamincText.replaceWholeText("70%");
729        assertEquals(Node.TEXT_NODE, replacement.getNodeType());
730        assertSame(vitamincText, replacement);
731        String expected = original.replace("60%", "70%");
732        assertEquals(expected, domToString(document));
733    }
734
735    public void testReplaceWholeTextFirstWithNull() throws TransformerException {
736        String original = domToString(document);
737        assertNull(descriptionText1.replaceWholeText(null));
738        String expected = original.replaceFirst(">.*</description>", "/>");
739        assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)",
740                expected, domToString(document));
741    }
742
743    public void testReplaceWholeTextMiddleWithNull() throws TransformerException {
744        String original = domToString(document);
745        assertNull(descriptionText2.replaceWholeText(null));
746        String expected = original.replaceFirst(">.*</description>", "/>");
747        assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)",
748                expected, domToString(document));
749    }
750
751    public void testReplaceWholeTextLastWithNull() throws TransformerException {
752        String original = domToString(document);
753        assertNull(descriptionText3.replaceWholeText(null));
754        String expected = original.replaceFirst(">.*</description>", "/>");
755        assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)",
756                expected, domToString(document));
757    }
758
759    public void testReplaceWholeTextFirstWithEmptyString() throws TransformerException {
760        String original = domToString(document);
761        assertNull(descriptionText1.replaceWholeText(""));
762        String expected = original.replaceFirst(">.*</description>", "/>");
763        assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)",
764                expected, domToString(document));
765    }
766
767    public void testReplaceWholeTextOnlyWithEmptyString() throws TransformerException {
768        String original = domToString(document);
769        assertNull(vitamincText.replaceWholeText(""));
770        String expected = original.replaceFirst(">.*</a:vitaminc>", "/>");
771        assertEquals(expected, domToString(document));
772    }
773
774    public void testUserDataAttachments() {
775        Object a = new Object();
776        Object b = new Object();
777        for (Node node : allNodes) {
778            node.setUserData("a", a, null);
779            node.setUserData("b", b, null);
780        }
781        for (Node node : allNodes) {
782            assertSame(a, node.getUserData("a"));
783            assertSame(b, node.getUserData("b"));
784            assertEquals(null, node.getUserData("c"));
785            assertEquals(null, node.getUserData("A"));
786        }
787    }
788
789    public void testUserDataRejectsNullKey() {
790        try {
791            menu.setUserData(null, "apple", null);
792            fail();
793        } catch (NullPointerException e) {
794        }
795        try {
796            menu.getUserData(null);
797            fail();
798        } catch (NullPointerException e) {
799        }
800    }
801
802    public void testValueOfNewAttributesIsEmptyString() {
803        assertEquals("", document.createAttribute("bar").getValue());
804        assertEquals("", document.createAttributeNS("http://foo", "bar").getValue());
805    }
806
807    public void testCloneNode() throws Exception {
808        document = builder.parse(new InputSource(new StringReader("<menu "
809                + "xmlns:f=\"http://food\" xmlns:a=\"http://addons\">"
810                + "<f:item a:standard=\"strawberry\" deluxe=\"yes\">Waffles</f:item></menu>")));
811        name = (Element) document.getFirstChild().getFirstChild();
812
813        Element clonedName = (Element) name.cloneNode(true);
814        assertNull(clonedName.getParentNode());
815        assertNull(clonedName.getNextSibling());
816        assertNull(clonedName.getPreviousSibling());
817        assertEquals("http://food", clonedName.getNamespaceURI());
818        assertEquals("f:item", clonedName.getNodeName());
819        assertEquals("item", clonedName.getLocalName());
820        assertEquals("http://food", clonedName.getNamespaceURI());
821        assertEquals("yes", clonedName.getAttribute("deluxe"));
822        assertEquals("strawberry", clonedName.getAttribute("a:standard"));
823        assertEquals("strawberry", clonedName.getAttributeNS("http://addons", "standard"));
824        assertEquals(1, name.getChildNodes().getLength());
825
826        Text clonedChild = (Text) clonedName.getFirstChild();
827        assertSame(clonedName, clonedChild.getParentNode());
828        assertNull(clonedChild.getNextSibling());
829        assertNull(clonedChild.getPreviousSibling());
830        assertEquals("Waffles", clonedChild.getTextContent());
831    }
832
833    /**
834     * We can't use the namespace-aware factory method for non-namespace-aware
835     * nodes. http://code.google.com/p/android/issues/detail?id=2735
836     */
837    public void testCloneNodeNotNamespaceAware() throws Exception {
838        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
839        factory.setNamespaceAware(false);
840        builder = factory.newDocumentBuilder();
841        document = builder.parse(new InputSource(new StringReader("<menu "
842                + "xmlns:f=\"http://food\" xmlns:a=\"http://addons\">"
843                + "<f:item a:standard=\"strawberry\" deluxe=\"yes\">Waffles</f:item></menu>")));
844        name = (Element) document.getFirstChild().getFirstChild();
845
846        Element clonedName = (Element) name.cloneNode(true);
847        assertNull(clonedName.getNamespaceURI());
848        assertEquals("f:item", clonedName.getNodeName());
849        assertNull(clonedName.getLocalName());
850        assertNull(clonedName.getNamespaceURI());
851        assertEquals("yes", clonedName.getAttribute("deluxe"));
852        assertEquals("strawberry", clonedName.getAttribute("a:standard"));
853        assertEquals("", clonedName.getAttributeNS("http://addons", "standard"));
854    }
855
856    /**
857     * A shallow clone requires cloning the attributes but not the child nodes.
858     */
859    public void testUserDataHandlerNotifiedOfShallowClones() {
860        RecordingHandler handler = new RecordingHandler();
861        name.setUserData("a", "apple", handler);
862        name.setUserData("b", "banana", handler);
863        standard.setUserData("c", "cat", handler);
864        waffles.setUserData("d", "dog", handler);
865
866        Element clonedName = (Element) name.cloneNode(false);
867        Attr clonedStandard = clonedName.getAttributeNode("a:standard");
868
869        Set<String> expected = new HashSet<String>();
870        expected.add(notification(NODE_CLONED, "a", "apple", name, clonedName));
871        expected.add(notification(NODE_CLONED, "b", "banana", name, clonedName));
872        expected.add(notification(NODE_CLONED, "c", "cat", standard, clonedStandard));
873        assertEquals(expected, handler.calls);
874    }
875
876    /**
877     * A deep clone requires cloning both the attributes and the child nodes.
878     */
879    public void testUserDataHandlerNotifiedOfDeepClones() {
880        RecordingHandler handler = new RecordingHandler();
881        name.setUserData("a", "apple", handler);
882        name.setUserData("b", "banana", handler);
883        standard.setUserData("c", "cat", handler);
884        waffles.setUserData("d", "dog", handler);
885
886        Element clonedName = (Element) name.cloneNode(true);
887        Attr clonedStandard = clonedName.getAttributeNode("a:standard");
888        Text clonedWaffles = (Text) clonedName.getChildNodes().item(0);
889
890        Set<String> expected = new HashSet<String>();
891        expected.add(notification(NODE_CLONED, "a", "apple", name, clonedName));
892        expected.add(notification(NODE_CLONED, "b", "banana", name, clonedName));
893        expected.add(notification(NODE_CLONED, "c", "cat", standard, clonedStandard));
894        expected.add(notification(NODE_CLONED, "d", "dog", waffles, clonedWaffles));
895        assertEquals(expected, handler.calls);
896    }
897
898    /**
899     * A shallow import requires importing the attributes but not the child
900     * nodes.
901     */
902    public void testUserDataHandlerNotifiedOfShallowImports() {
903        RecordingHandler handler = new RecordingHandler();
904        name.setUserData("a", "apple", handler);
905        name.setUserData("b", "banana", handler);
906        standard.setUserData("c", "cat", handler);
907        waffles.setUserData("d", "dog", handler);
908
909        Document newDocument = builder.newDocument();
910        Element importedName = (Element) newDocument.importNode(name, false);
911        Attr importedStandard = importedName.getAttributeNode("a:standard");
912
913        Set<String> expected = new HashSet<String>();
914        expected.add(notification(NODE_IMPORTED, "a", "apple", name, importedName));
915        expected.add(notification(NODE_IMPORTED, "b", "banana", name, importedName));
916        expected.add(notification(NODE_IMPORTED, "c", "cat", standard, importedStandard));
917        assertEquals(expected, handler.calls);
918    }
919
920    /**
921     * A deep import requires cloning both the attributes and the child nodes.
922     */
923    public void testUserDataHandlerNotifiedOfDeepImports() {
924        RecordingHandler handler = new RecordingHandler();
925        name.setUserData("a", "apple", handler);
926        name.setUserData("b", "banana", handler);
927        standard.setUserData("c", "cat", handler);
928        waffles.setUserData("d", "dog", handler);
929
930        Document newDocument = builder.newDocument();
931        Element importedName = (Element) newDocument.importNode(name, true);
932        Attr importedStandard = importedName.getAttributeNode("a:standard");
933        Text importedWaffles = (Text) importedName.getChildNodes().item(0);
934
935        Set<String> expected = new HashSet<String>();
936        expected.add(notification(NODE_IMPORTED, "a", "apple", name, importedName));
937        expected.add(notification(NODE_IMPORTED, "b", "banana", name, importedName));
938        expected.add(notification(NODE_IMPORTED, "c", "cat", standard, importedStandard));
939        expected.add(notification(NODE_IMPORTED, "d", "dog", waffles, importedWaffles));
940        assertEquals(expected, handler.calls);
941    }
942
943    public void testImportNodeDeep() throws TransformerException {
944        String original = domToStringStripElementWhitespace(document);
945
946        Document newDocument = builder.newDocument();
947        Element importedItem = (Element) newDocument.importNode(item, true);
948        assertDetached(item.getParentNode(), importedItem);
949
950        newDocument.appendChild(importedItem);
951        String expected = original.replaceAll("</?menu>", "");
952        assertEquals(expected, domToStringStripElementWhitespace(newDocument));
953    }
954
955    public void testImportNodeShallow() throws TransformerException {
956        Document newDocument = builder.newDocument();
957        Element importedItem = (Element) newDocument.importNode(item, false);
958        assertDetached(item.getParentNode(), importedItem);
959
960        newDocument.appendChild(importedItem);
961        assertEquals("<item xmlns=\"http://food\" xmlns:a=\"http://addons\"/>",
962                domToString(newDocument));
963    }
964
965    public void testNodeAdoption() throws Exception {
966        for (Node node : allNodes) {
967            if (node == document || node == doctype || node == sp || node == png) {
968                assertNotAdoptable(node);
969            } else {
970                adoptAndCheck(node);
971            }
972        }
973    }
974
975    private void assertNotAdoptable(Node node) {
976        try {
977            builder.newDocument().adoptNode(node);
978            fail();
979        } catch (DOMException e) {
980        }
981    }
982
983    /**
984     * Adopts the node into another document, then adopts the root element, and
985     * then attaches the adopted node in the proper place. The net result should
986     * be that the document's entire contents have moved to another document.
987     */
988    private void adoptAndCheck(Node node) throws Exception {
989        String original = domToString(document);
990        Document newDocument = builder.newDocument();
991
992        // remember where to insert the node in the new document
993        boolean isAttribute = node.getNodeType() == Node.ATTRIBUTE_NODE;
994        Node parent = isAttribute
995                ? ((Attr) node).getOwnerElement() : node.getParentNode();
996        Node nextSibling = node.getNextSibling();
997
998        // move the node and make sure it was detached
999        assertSame(node, newDocument.adoptNode(node));
1000        assertDetached(parent, node);
1001
1002        // move the rest of the document and wire the adopted back into place
1003        assertSame(menu, newDocument.adoptNode(menu));
1004        newDocument.appendChild(menu);
1005        if (isAttribute) {
1006            ((Element) parent).setAttributeNodeNS((Attr) node);
1007        } else if (nextSibling != null) {
1008            parent.insertBefore(node, nextSibling);
1009        } else if (parent != document) {
1010            parent.appendChild(node);
1011        }
1012
1013        assertEquals(original, domToString(newDocument));
1014        document = newDocument;
1015    }
1016
1017    private void assertDetached(Node formerParent, Node node) {
1018        assertNull(node.getParentNode());
1019        NodeList children = formerParent.getChildNodes();
1020        for (int i = 0; i < children.getLength(); i++) {
1021            assertTrue(children.item(i) != node);
1022        }
1023        if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
1024            assertNull(((Attr) node).getOwnerElement());
1025            NamedNodeMap attributes = formerParent.getAttributes();
1026            for (int i = 0; i < attributes.getLength(); i++) {
1027                assertTrue(attributes.item(i) != node);
1028            }
1029        }
1030    }
1031
1032    public void testAdoptionImmediatelyAfterParsing() throws Exception {
1033        Document newDocument = builder.newDocument();
1034        try {
1035            assertSame(name, newDocument.adoptNode(name));
1036            assertSame(newDocument, name.getOwnerDocument());
1037            assertSame(newDocument, standard.getOwnerDocument());
1038            assertSame(newDocument, waffles.getOwnerDocument());
1039        } catch (Throwable e) {
1040            AssertionFailedError failure = new AssertionFailedError(
1041                    "This implementation fails to adopt nodes before the "
1042                            + "document has been traversed");
1043            failure.initCause(e);
1044            throw failure;
1045        }
1046    }
1047
1048    /**
1049     * There should be notifications for adopted node itself but none of its
1050     * children. The DOM spec is vague on this, so we're consistent with the RI.
1051     */
1052    public void testUserDataHandlerNotifiedOfOnlyShallowAdoptions() throws Exception {
1053        /*
1054         * Force a traversal of the document, otherwise this test may fail for
1055         * an unrelated reason on version 5 of the RI. That behavior is
1056         * exercised by testAdoptionImmediatelyAfterParsing().
1057         */
1058        domToString(document);
1059
1060        RecordingHandler handler = new RecordingHandler();
1061        name.setUserData("a", "apple", handler);
1062        name.setUserData("b", "banana", handler);
1063        standard.setUserData("c", "cat", handler);
1064        waffles.setUserData("d", "dog", handler);
1065
1066        Document newDocument = builder.newDocument();
1067        assertSame(name, newDocument.adoptNode(name));
1068        assertSame(newDocument, name.getOwnerDocument());
1069        assertSame(newDocument, standard.getOwnerDocument());
1070        assertSame(newDocument, waffles.getOwnerDocument());
1071
1072        Set<String> expected = new HashSet<String>();
1073        expected.add(notification(NODE_ADOPTED, "a", "apple", name, null));
1074        expected.add(notification(NODE_ADOPTED, "b", "banana", name, null));
1075        assertEquals(expected, handler.calls);
1076    }
1077
1078    public void testBaseUriRelativeUriResolution() throws Exception {
1079        File file = File.createTempFile("DomTest.java", "xml");
1080        File parentFile = file.getParentFile();
1081        FileWriter writer = new FileWriter(file);
1082        writer.write("<a>"
1083                + "  <b xml:base=\"b1/b2\">"
1084                + "    <c>"
1085                + "      <d xml:base=\"../d1/d2\"><e/></d>"
1086                + "    </c>"
1087                + "  </b>"
1088                + "  <h xml:base=\"h1/h2/\">"
1089                + "    <i xml:base=\"../i1/i2\"/>"
1090                + "  </h>"
1091                + "</a>");
1092        writer.close();
1093        document = builder.parse(file);
1094
1095        assertFileUriEquals("", file.getPath(), document.getBaseURI());
1096        assertFileUriEquals("", file.getPath(), document.getDocumentURI());
1097        Element a = document.getDocumentElement();
1098        assertFileUriEquals("", file.getPath(), a.getBaseURI());
1099
1100        String message = "This implementation's getBaseURI() doesn't handle relative URIs";
1101        Element b = (Element) a.getChildNodes().item(1);
1102        Element c = (Element) b.getChildNodes().item(1);
1103        Element d = (Element) c.getChildNodes().item(1);
1104        Element e = (Element) d.getChildNodes().item(0);
1105        Element h = (Element) a.getChildNodes().item(3);
1106        Element i = (Element) h.getChildNodes().item(1);
1107        assertFileUriEquals(message, parentFile + "/b1/b2", b.getBaseURI());
1108        assertFileUriEquals(message, parentFile + "/b1/b2", c.getBaseURI());
1109        assertFileUriEquals(message, parentFile + "/d1/d2", d.getBaseURI());
1110        assertFileUriEquals(message, parentFile + "/d1/d2", e.getBaseURI());
1111        assertFileUriEquals(message, parentFile + "/h1/h2/", h.getBaseURI());
1112        assertFileUriEquals(message, parentFile + "/h1/i1/i2", i.getBaseURI());
1113    }
1114
1115    /**
1116     * Regrettably both "file:/tmp/foo.txt" and "file:///tmp/foo.txt" are
1117     * legal URIs, and different implementations emit different forms.
1118     */
1119    private void assertFileUriEquals(
1120            String message, String expectedFile, String actual) {
1121        if (!("file:" + expectedFile).equals(actual)
1122                && !("file://" + expectedFile).equals(actual)) {
1123            fail("Expected URI for: " + expectedFile
1124                    + " but was " + actual + ". " + message);
1125        }
1126    }
1127
1128    /**
1129     * According to the <a href="http://www.w3.org/TR/xmlbase/">XML Base</a>
1130     * spec, fragments (like "#frag" or "") should not be dereferenced.
1131     */
1132    public void testBaseUriResolutionWithHashes() throws Exception {
1133        document = builder.parse(new InputSource(new StringReader(
1134                "<a xml:base=\"http://a1/a2\">"
1135                        + "  <b xml:base=\"b1#b2\"/>"
1136                        + "  <c xml:base=\"#c1\">"
1137                        + "    <d xml:base=\"\"/>"
1138                        + "  </c>"
1139                        + "  <e xml:base=\"\"/>"
1140                        + "</a>")));
1141        Element a = document.getDocumentElement();
1142        assertEquals("http://a1/a2", a.getBaseURI());
1143
1144        String message = "This implementation's getBaseURI() doesn't handle "
1145                + "relative URIs with hashes";
1146        Element b = (Element) a.getChildNodes().item(1);
1147        Element c = (Element) a.getChildNodes().item(3);
1148        Element d = (Element) c.getChildNodes().item(1);
1149        Element e = (Element) a.getChildNodes().item(5);
1150        assertEquals(message, "http://a1/b1#b2", b.getBaseURI());
1151        assertEquals(message, "http://a1/a2#c1", c.getBaseURI());
1152        assertEquals(message, "http://a1/a2#c1", d.getBaseURI());
1153        assertEquals(message, "http://a1/a2", e.getBaseURI());
1154    }
1155
1156    public void testBaseUriInheritedForProcessingInstructions() {
1157        document.setDocumentURI("http://d1/d2");
1158        assertEquals("http://d1/d2", wafflemaker.getBaseURI());
1159    }
1160
1161    public void testBaseUriInheritedForEntities() {
1162        if (sp == null) {
1163            return;
1164        }
1165        document.setDocumentURI("http://d1/d2");
1166        assertEquals("http://d1/d2", sp.getBaseURI());
1167    }
1168
1169    public void testBaseUriNotInheritedForNotations() {
1170        if (png == null) {
1171            return;
1172        }
1173        document.setDocumentURI("http://d1/d2");
1174        assertNull(png.getBaseURI());
1175    }
1176
1177    public void testBaseUriNotInheritedForDoctypes() {
1178        document.setDocumentURI("http://d1/d2");
1179        assertNull(doctype.getBaseURI());
1180    }
1181
1182    public void testBaseUriNotInheritedForAttributes() {
1183        document.setDocumentURI("http://d1/d2");
1184        assertNull(itemXmlns.getBaseURI());
1185        assertNull(itemXmlnsA.getBaseURI());
1186        assertNull(standard.getBaseURI());
1187        assertNull(vitaminsXmlnsA.getBaseURI());
1188    }
1189
1190    public void testBaseUriNotInheritedForTextsOrCdatas() {
1191        document.setDocumentURI("http://d1/d2");
1192        assertNull(descriptionText1.getBaseURI());
1193        assertNull(descriptionText2.getBaseURI());
1194        assertNull(option2Reference.getBaseURI());
1195    }
1196
1197    public void testBaseUriNotInheritedForComments() {
1198        document.setDocumentURI("http://d1/d2");
1199        assertNull(descriptionText1.getBaseURI());
1200        assertNull(descriptionText2.getBaseURI());
1201    }
1202
1203    public void testBaseUriNotInheritedForEntityReferences() {
1204        document.setDocumentURI("http://d1/d2");
1205        assertNull(option2Reference.getBaseURI());
1206    }
1207
1208    public void testProgrammaticElementIds() {
1209        vitaminc.setAttribute("name", "c");
1210        assertFalse(vitaminc.getAttributeNode("name").isId());
1211        assertNull(document.getElementById("c"));
1212
1213        // set the ID attribute...
1214        vitaminc.setIdAttribute("name", true);
1215        assertTrue(vitaminc.getAttributeNode("name").isId());
1216        assertSame(vitaminc, document.getElementById("c"));
1217
1218        // ... and then take it away
1219        vitaminc.setIdAttribute("name", false);
1220        assertFalse(vitaminc.getAttributeNode("name").isId());
1221        assertNull(document.getElementById("c"));
1222    }
1223
1224    public void testMultipleIdsOnOneElement() {
1225        vitaminc.setAttribute("name", "c");
1226        vitaminc.setIdAttribute("name", true);
1227        vitaminc.setAttribute("atc", "a11g");
1228        vitaminc.setIdAttribute("atc", true);
1229
1230        assertTrue(vitaminc.getAttributeNode("name").isId());
1231        assertTrue(vitaminc.getAttributeNode("atc").isId());
1232        assertSame(vitaminc, document.getElementById("c"));
1233        assertSame(vitaminc, document.getElementById("a11g"));
1234        assertNull(document.getElementById("g"));
1235    }
1236
1237    public void testAttributeNamedIdIsNotAnIdByDefault() {
1238        String message = "This implementation incorrectly interprets the "
1239                + "\"id\" attribute as an identifier by default.";
1240        vitaminc.setAttribute("id", "c");
1241        assertNull(message, document.getElementById("c"));
1242    }
1243
1244    public void testElementTypeInfo() {
1245        TypeInfo typeInfo = description.getSchemaTypeInfo();
1246        assertNull(typeInfo.getTypeName());
1247        assertNull(typeInfo.getTypeNamespace());
1248        assertFalse(typeInfo.isDerivedFrom("x", "y", TypeInfo.DERIVATION_UNION));
1249    }
1250
1251    public void testAttributeTypeInfo() {
1252        TypeInfo typeInfo = standard.getSchemaTypeInfo();
1253        assertNull(typeInfo.getTypeName());
1254        assertNull(typeInfo.getTypeNamespace());
1255        assertFalse(typeInfo.isDerivedFrom("x", "y", TypeInfo.DERIVATION_UNION));
1256    }
1257
1258    public void testRenameElement() {
1259        document.renameNode(description, null, "desc");
1260        assertEquals("desc", description.getTagName());
1261        assertEquals("desc", description.getLocalName());
1262        assertEquals(null, description.getPrefix());
1263        assertEquals(null, description.getNamespaceURI());
1264    }
1265
1266    public void testRenameElementWithPrefix() {
1267        try {
1268            document.renameNode(description, null, "a:desc");
1269            fail();
1270        } catch (DOMException e) {
1271        }
1272    }
1273
1274    public void testRenameElementWithNamespace() {
1275        document.renameNode(description, "http://sales", "desc");
1276        assertEquals("desc", description.getTagName());
1277        assertEquals("desc", description.getLocalName());
1278        assertEquals(null, description.getPrefix());
1279        assertEquals("http://sales", description.getNamespaceURI());
1280    }
1281
1282    public void testRenameElementWithPrefixAndNamespace() {
1283        document.renameNode(description, "http://sales", "a:desc");
1284        assertEquals("a:desc", description.getTagName());
1285        assertEquals("desc", description.getLocalName());
1286        assertEquals("a", description.getPrefix());
1287        assertEquals("http://sales", description.getNamespaceURI());
1288    }
1289
1290    public void testRenameAttribute() {
1291        document.renameNode(deluxe, null, "special");
1292        assertEquals("special", deluxe.getName());
1293        assertEquals("special", deluxe.getLocalName());
1294        assertEquals(null, deluxe.getPrefix());
1295        assertEquals(null, deluxe.getNamespaceURI());
1296    }
1297
1298    public void testRenameAttributeWithPrefix() {
1299        try {
1300            document.renameNode(deluxe, null, "a:special");
1301            fail();
1302        } catch (DOMException e) {
1303        }
1304    }
1305
1306    public void testRenameAttributeWithNamespace() {
1307        document.renameNode(deluxe, "http://sales", "special");
1308        assertEquals("special", deluxe.getName());
1309        assertEquals("special", deluxe.getLocalName());
1310        assertEquals(null, deluxe.getPrefix());
1311        assertEquals("http://sales", deluxe.getNamespaceURI());
1312    }
1313
1314    public void testRenameAttributeWithPrefixAndNamespace() {
1315        document.renameNode(deluxe, "http://sales", "a:special");
1316        assertEquals("a:special", deluxe.getName());
1317        assertEquals("special", deluxe.getLocalName());
1318        assertEquals("a", deluxe.getPrefix());
1319        assertEquals("http://sales", deluxe.getNamespaceURI());
1320    }
1321
1322    public void testUserDataHandlerNotifiedOfRenames() {
1323        RecordingHandler handler = new RecordingHandler();
1324        description.setUserData("a", "apple", handler);
1325        deluxe.setUserData("b", "banana", handler);
1326        standard.setUserData("c", "cat", handler);
1327
1328        document.renameNode(deluxe, null, "special");
1329        document.renameNode(description, null, "desc");
1330
1331        Set<String> expected = new HashSet<String>();
1332        expected.add(notification(NODE_RENAMED, "a", "apple", description, null));
1333        expected.add(notification(NODE_RENAMED, "b", "banana", deluxe, null));
1334        assertEquals(expected, handler.calls);
1335    }
1336
1337    public void testRenameToInvalid() {
1338        try {
1339            document.renameNode(description, null, "xmlns:foo");
1340            fail();
1341        } catch (DOMException e) {
1342        }
1343        try {
1344            document.renameNode(description, null, "xml:foo");
1345            fail();
1346        } catch (DOMException e) {
1347        }
1348        try {
1349            document.renameNode(deluxe, null, "xmlns");
1350            fail();
1351        } catch (DOMException e) {
1352        }
1353    }
1354
1355    public void testRenameNodeOtherThanElementOrAttribute() {
1356        for (Node node : allNodes) {
1357            if (node.getNodeType() == Node.ATTRIBUTE_NODE
1358                    || node.getNodeType() == Node.ELEMENT_NODE) {
1359                continue;
1360            }
1361
1362            try {
1363                document.renameNode(node, null, "foo");
1364                fail();
1365            } catch (DOMException e) {
1366            }
1367        }
1368    }
1369
1370    public void testDocumentDoesNotHaveWhitespaceChildren()
1371            throws IOException, SAXException {
1372        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>  \n"
1373                + "   <foo/>\n"
1374                + "  \n";
1375        document = builder.parse(new InputSource(new StringReader(xml)));
1376        assertEquals("Document nodes shouldn't have text children",
1377                1, document.getChildNodes().getLength());
1378    }
1379
1380    public void testDocumentAddChild()
1381            throws IOException, SAXException {
1382        try {
1383            document.appendChild(document.createTextNode("   "));
1384            fail("Document nodes shouldn't accept child nodes");
1385        } catch (DOMException e) {
1386        }
1387    }
1388
1389    public void testIterateForwardsThroughInnerNodeSiblings() throws Exception {
1390        document = builder.parse(new InputSource(new StringReader(
1391                "<root><child/><child/></root>")));
1392        Node root = document.getDocumentElement();
1393        Node current = root.getChildNodes().item(0);
1394        while (current.getNextSibling() != null) {
1395            current = current.getNextSibling();
1396        }
1397        assertEquals(root.getChildNodes().item(root.getChildNodes().getLength() - 1), current);
1398    }
1399
1400    public void testIterateBackwardsThroughInnerNodeSiblings() throws Exception {
1401        document = builder.parse(new InputSource(new StringReader(
1402                "<root><child/><child/></root>")));
1403        Node root = document.getDocumentElement();
1404        Node current = root.getChildNodes().item(root.getChildNodes().getLength() - 1);
1405        while (current.getPreviousSibling() != null) {
1406            current = current.getPreviousSibling();
1407        }
1408        assertEquals(root.getChildNodes().item(0), current);
1409    }
1410
1411    public void testIterateForwardsThroughLeafNodeSiblings() throws Exception {
1412        document = builder.parse(new InputSource(new StringReader(
1413                "<root> <!-- --> </root>")));
1414        Node root = document.getDocumentElement();
1415        Node current = root.getChildNodes().item(0);
1416        while (current.getNextSibling() != null) {
1417            current = current.getNextSibling();
1418        }
1419        assertEquals(root.getChildNodes().item(root.getChildNodes().getLength() - 1), current);
1420    }
1421
1422    public void testIterateBackwardsThroughLeafNodeSiblings() throws Exception {
1423        document = builder.parse(new InputSource(new StringReader(
1424                "<root> <!-- --> </root>")));
1425        Node root = document.getDocumentElement();
1426        Node current = root.getChildNodes().item(root.getChildNodes().getLength() - 1);
1427        while (current.getPreviousSibling() != null) {
1428            current = current.getPreviousSibling();
1429        }
1430        assertEquals(root.getChildNodes().item(0), current);
1431    }
1432
1433    public void testPublicIdAndSystemId() throws Exception {
1434        document = builder.parse(new InputSource(new StringReader(
1435                " <!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\""
1436                        + " \"http://www.w3.org/TR/html4/strict.dtd\">"
1437                        + "<html></html>")));
1438        doctype = document.getDoctype();
1439        assertEquals("html", doctype.getName());
1440        assertEquals("-//W3C//DTD HTML 4.01//EN", doctype.getPublicId());
1441        assertEquals("http://www.w3.org/TR/html4/strict.dtd", doctype.getSystemId());
1442    }
1443
1444    public void testSystemIdOnly() throws Exception {
1445        document = builder.parse(new InputSource(new StringReader(
1446                " <!DOCTYPE html SYSTEM \"http://www.w3.org/TR/html4/strict.dtd\">"
1447                        + "<html></html>")));
1448        doctype = document.getDoctype();
1449        assertEquals("html", doctype.getName());
1450        assertNull(doctype.getPublicId());
1451        assertEquals("http://www.w3.org/TR/html4/strict.dtd", doctype.getSystemId());
1452    }
1453
1454    public void testSingleQuotedPublicIdAndSystemId() throws Exception {
1455        document = builder.parse(new InputSource(new StringReader(
1456                " <!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01//EN'"
1457                        + " 'http://www.w3.org/TR/html4/strict.dtd'>"
1458                        + "<html></html>")));
1459        doctype = document.getDoctype();
1460        assertEquals("html", doctype.getName());
1461        assertEquals("-//W3C//DTD HTML 4.01//EN", doctype.getPublicId());
1462        assertEquals("http://www.w3.org/TR/html4/strict.dtd", doctype.getSystemId());
1463    }
1464
1465    public void testGetElementsByTagNameNs() {
1466        NodeList elements = item.getElementsByTagNameNS("http://addons", "option");
1467        assertEquals(option1, elements.item(0));
1468        assertEquals(option2, elements.item(1));
1469        assertEquals(2, elements.getLength());
1470    }
1471
1472    public void testGetElementsByTagNameWithNamespacePrefix() {
1473        NodeList elements = item.getElementsByTagName("a:option");
1474        assertEquals(option1, elements.item(0));
1475        assertEquals(option2, elements.item(1));
1476        assertEquals(2, elements.getLength());
1477    }
1478
1479    // http://code.google.com/p/android/issues/detail?id=17907
1480    public void testGetElementsByTagNameWithoutNamespacePrefix() {
1481        NodeList elements = item.getElementsByTagName("nutrition");
1482        assertEquals(nutrition, elements.item(0));
1483        assertEquals(1, elements.getLength());
1484    }
1485
1486    public void testGetElementsByTagNameWithWildcard() {
1487        NodeList elements = item.getElementsByTagName("*");
1488        assertEquals(name, elements.item(0));
1489        assertEquals(description, elements.item(1));
1490        assertEquals(option1, elements.item(2));
1491        assertEquals(option2, elements.item(3));
1492        assertEquals(nutrition, elements.item(4));
1493        assertEquals(vitamins, elements.item(5));
1494        assertEquals(vitaminc, elements.item(6));
1495        assertEquals(7, elements.getLength());
1496    }
1497
1498    public void testGetElementsByTagNameNsWithWildcard() {
1499        NodeList elements = item.getElementsByTagNameNS("*", "*");
1500        assertEquals(name, elements.item(0));
1501        assertEquals(description, elements.item(1));
1502        assertEquals(option1, elements.item(2));
1503        assertEquals(option2, elements.item(3));
1504        assertEquals(nutrition, elements.item(4));
1505        assertEquals(vitamins, elements.item(5));
1506        assertEquals(vitaminc, elements.item(6));
1507        assertEquals(7, elements.getLength());
1508    }
1509
1510    /**
1511     * Documents shouldn't contain document fragments.
1512     * http://code.google.com/p/android/issues/detail?id=2735
1513     */
1514    public void testAddingADocumentFragmentAddsItsChildren() {
1515        Element a = document.createElement("a");
1516        Element b = document.createElement("b");
1517        Element c = document.createElement("c");
1518        DocumentFragment fragment = document.createDocumentFragment();
1519        fragment.appendChild(a);
1520        fragment.appendChild(b);
1521        fragment.appendChild(c);
1522
1523        Node returned = menu.appendChild(fragment);
1524        assertSame(fragment, returned);
1525        NodeList children = menu.getChildNodes();
1526        assertEquals(6, children.getLength());
1527        assertTrue(children.item(0) instanceof Text); // whitespace
1528        assertEquals(item, children.item(1));
1529        assertTrue(children.item(2) instanceof Text); // whitespace
1530        assertEquals(a, children.item(3));
1531        assertEquals(b, children.item(4));
1532        assertEquals(c, children.item(5));
1533    }
1534
1535    public void testReplacingWithADocumentFragmentInsertsItsChildren() {
1536        Element a = document.createElement("a");
1537        Element b = document.createElement("b");
1538        Element c = document.createElement("c");
1539        DocumentFragment fragment = document.createDocumentFragment();
1540        fragment.appendChild(a);
1541        fragment.appendChild(b);
1542        fragment.appendChild(c);
1543
1544        Node returned = menu.replaceChild(fragment, item);
1545        assertSame(item, returned);
1546        NodeList children = menu.getChildNodes();
1547        assertEquals(5, children.getLength());
1548        assertTrue(children.item(0) instanceof Text); // whitespace
1549        assertEquals(a, children.item(1));
1550        assertEquals(b, children.item(2));
1551        assertEquals(c, children.item(3));
1552        assertTrue(children.item(4) instanceof Text); // whitespace
1553    }
1554
1555    public void testCoalescingOffByDefault() {
1556        assertFalse(DocumentBuilderFactory.newInstance().isCoalescing());
1557    }
1558
1559    public void testCoalescingOn() throws Exception {
1560        String xml = "<foo>abc<![CDATA[def]]>ghi</foo>";
1561        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1562        factory.setCoalescing(true);
1563        document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
1564        Element documentElement = document.getDocumentElement();
1565        Text text = (Text) documentElement.getFirstChild();
1566        assertEquals("abcdefghi", text.getTextContent());
1567        assertNull(text.getNextSibling());
1568    }
1569
1570    public void testCoalescingOff() throws Exception {
1571        String xml = "<foo>abc<![CDATA[def]]>ghi</foo>";
1572        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1573        factory.setCoalescing(false);
1574        document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
1575        Element documentElement = document.getDocumentElement();
1576        Text abc = (Text) documentElement.getFirstChild();
1577        assertEquals("abc", abc.getTextContent());
1578        CDATASection def = (CDATASection) abc.getNextSibling();
1579        assertEquals("def", def.getTextContent());
1580        Text ghi = (Text) def.getNextSibling();
1581        assertEquals("ghi", ghi.getTextContent());
1582        assertNull(ghi.getNextSibling());
1583    }
1584
1585    public void testExpandingEntityReferencesOnByDefault() {
1586        assertTrue(DocumentBuilderFactory.newInstance().isExpandEntityReferences());
1587    }
1588
1589    public void testExpandingEntityReferencesOn() throws Exception {
1590        String xml = "<!DOCTYPE foo [ <!ENTITY def \"DEF\"> ]>"
1591                + "<foo>abc&def;ghi</foo>";
1592        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1593        factory.setExpandEntityReferences(true);
1594        document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
1595        Element documentElement = document.getDocumentElement();
1596        Text text = (Text) documentElement.getFirstChild();
1597        assertEquals("This implementation doesn't expand entity references",
1598                "abcDEFghi", text.getTextContent());
1599        assertNull(text.getNextSibling());
1600    }
1601
1602    public void testExpandingEntityReferencesOff() throws Exception {
1603        String xml = "<!DOCTYPE foo [ <!ENTITY def \"DEF\"> ]>"
1604                + "<foo>abc&def;ghi</foo>";
1605        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1606        factory.setExpandEntityReferences(false);
1607
1608        document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
1609        Element documentElement = document.getDocumentElement();
1610        Text abc = (Text) documentElement.getFirstChild();
1611        assertEquals("abc", abc.getTextContent());
1612
1613        EntityReference def = (EntityReference) abc.getNextSibling();
1614        assertEquals("def", def.getNodeName());
1615
1616        Text ghi = (Text) def.getNextSibling();
1617        assertNull(ghi.getNextSibling());
1618
1619        /*
1620         * We expect the entity reference to contain one child Text node "DEF".
1621         * The RI's entity reference contains no children. Instead it stashes
1622         * "DEF" in the next sibling node.
1623         */
1624        assertEquals("Expected text value only and no expanded entity data",
1625                "ghi", ghi.getTextContent());
1626        NodeList defChildren = def.getChildNodes();
1627        assertEquals("This implementation doesn't include children in entity references",
1628                1, defChildren.getLength());
1629        assertEquals("DEF", defChildren.item(0).getTextContent());
1630    }
1631
1632    /**
1633     * Predefined entities should always be expanded.
1634     * https://code.google.com/p/android/issues/detail?id=225
1635     */
1636    public void testExpandingEntityReferencesOffDoesNotImpactPredefinedEntities() throws Exception {
1637        String xml = "<foo>abc&amp;def</foo>";
1638        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1639        factory.setExpandEntityReferences(false);
1640        document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
1641        Element documentElement = document.getDocumentElement();
1642        Text text = (Text) documentElement.getFirstChild();
1643        assertEquals("abc&def", text.getTextContent());
1644        assertNull(text.getNextSibling());
1645    }
1646
1647    public void testExpandingEntityReferencesOffDoesNotImpactCharacterEntities() throws Exception {
1648        String xml = "<foo>abc&#38;def&#x26;ghi</foo>";
1649        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1650        factory.setExpandEntityReferences(false);
1651        document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
1652        Element documentElement = document.getDocumentElement();
1653        Text text = (Text) documentElement.getFirstChild();
1654        assertEquals("abc&def&ghi", text.getTextContent());
1655        assertNull(text.getNextSibling());
1656    }
1657
1658    // http://code.google.com/p/android/issues/detail?id=24530
1659    public void testInsertBefore() throws Exception {
1660        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1661        Document d = factory.newDocumentBuilder().newDocument();
1662        d.appendChild(d.createElement("root"));
1663        d.getFirstChild().insertBefore(d.createElement("foo"), null);
1664        assertEquals("foo", d.getFirstChild().getFirstChild().getNodeName());
1665        assertEquals("foo", d.getFirstChild().getLastChild().getNodeName());
1666        d.getFirstChild().insertBefore(d.createElement("bar"), null);
1667        assertEquals("foo", d.getFirstChild().getFirstChild().getNodeName());
1668        assertEquals("bar", d.getFirstChild().getLastChild().getNodeName());
1669    }
1670
1671    public void testBomAndByteInput() throws Exception {
1672        byte[] xml = {
1673                (byte) 0xef, (byte) 0xbb, (byte) 0xbf,
1674                '<', 'i', 'n', 'p', 'u', 't', '/', '>'
1675        };
1676        document = builder.parse(new InputSource(new ByteArrayInputStream(xml)));
1677        assertEquals("input", document.getDocumentElement().getNodeName());
1678    }
1679
1680    public void testBomAndByteInputWithExplicitCharset() throws Exception {
1681        byte[] xml = {
1682                (byte) 0xef, (byte) 0xbb, (byte) 0xbf,
1683                '<', 'i', 'n', 'p', 'u', 't', '/', '>'
1684        };
1685        InputSource inputSource = new InputSource(new ByteArrayInputStream(xml));
1686        inputSource.setEncoding("UTF-8");
1687        document = builder.parse(inputSource);
1688        assertEquals("input", document.getDocumentElement().getNodeName());
1689    }
1690
1691    public void testBomAndCharacterInput() throws Exception {
1692        InputSource inputSource = new InputSource(new StringReader("\ufeff<input/>"));
1693        inputSource.setEncoding("UTF-8");
1694        try {
1695            builder.parse(inputSource);
1696            fail();
1697        } catch (SAXException expected) {
1698        }
1699    }
1700
1701    private class RecordingHandler implements UserDataHandler {
1702        final Set<String> calls = new HashSet<String>();
1703        public void handle(short operation, String key, Object data, Node src, Node dst) {
1704            calls.add(notification(operation, key, data, src, dst));
1705        }
1706    }
1707
1708    private String notification(short operation, String key, Object data, Node src, Node dst) {
1709        return "op:" + operation + " key:" + key + " data:" + data + " src:" + src + " dst:" + dst;
1710    }
1711
1712    private String domToString(Document document) throws TransformerException {
1713        StringWriter writer = new StringWriter();
1714        transformer.transform(new DOMSource(document), new StreamResult(writer));
1715        String result = writer.toString();
1716
1717        /*
1718         * Hack: swap <name>'s a:standard attribute and deluxe attribute if
1719         * they're out of order. Some document transformations reorder the
1720         * attributes, which causes pain when we try to use String comparison on
1721         * them.
1722         */
1723        Matcher attributeMatcher = Pattern.compile(" a:standard=\"[^\"]+\"").matcher(result);
1724        if (attributeMatcher.find()) {
1725            result = result.substring(0, attributeMatcher.start())
1726                    + result.substring(attributeMatcher.end());
1727            int insertionPoint = result.indexOf(" deluxe=\"");
1728            result = result.substring(0, insertionPoint)
1729                    + attributeMatcher.group()
1730                    + result.substring(insertionPoint);
1731        }
1732
1733        return result;
1734    }
1735
1736    private String domToStringStripElementWhitespace(Document document)
1737            throws TransformerException {
1738        return domToString(document).replaceAll("(?m)>\\s+<", "><");
1739    }
1740}
1741