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 
17 package libcore.xml;
18 
19 import java.io.ByteArrayInputStream;
20 import java.io.File;
21 import java.io.FileWriter;
22 import java.io.IOException;
23 import java.io.StringReader;
24 import java.io.StringWriter;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Set;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 import javax.xml.parsers.DocumentBuilder;
33 import javax.xml.parsers.DocumentBuilderFactory;
34 import javax.xml.transform.OutputKeys;
35 import javax.xml.transform.Transformer;
36 import javax.xml.transform.TransformerException;
37 import javax.xml.transform.TransformerFactory;
38 import javax.xml.transform.dom.DOMSource;
39 import javax.xml.transform.stream.StreamResult;
40 import junit.framework.AssertionFailedError;
41 import junit.framework.TestCase;
42 import org.w3c.dom.Attr;
43 import org.w3c.dom.CDATASection;
44 import org.w3c.dom.Comment;
45 import org.w3c.dom.DOMException;
46 import org.w3c.dom.DOMImplementation;
47 import org.w3c.dom.Document;
48 import org.w3c.dom.DocumentFragment;
49 import org.w3c.dom.DocumentType;
50 import org.w3c.dom.Element;
51 import org.w3c.dom.Entity;
52 import org.w3c.dom.EntityReference;
53 import org.w3c.dom.NamedNodeMap;
54 import org.w3c.dom.Node;
55 import org.w3c.dom.NodeList;
56 import org.w3c.dom.Notation;
57 import org.w3c.dom.ProcessingInstruction;
58 import org.w3c.dom.Text;
59 import org.w3c.dom.TypeInfo;
60 import org.w3c.dom.UserDataHandler;
61 import static org.w3c.dom.UserDataHandler.NODE_ADOPTED;
62 import static org.w3c.dom.UserDataHandler.NODE_CLONED;
63 import static org.w3c.dom.UserDataHandler.NODE_IMPORTED;
64 import static org.w3c.dom.UserDataHandler.NODE_RENAMED;
65 import org.xml.sax.InputSource;
66 import org.xml.sax.SAXException;
67 
68 /**
69  * Construct a DOM and then interrogate it.
70  */
71 public 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 
setUp()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      */
testEntityDeclarations()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      */
testNotations()196     public void testNotations() {
197         assertNotNull("This implementation does not parse notations", png);
198     }
199 
testLookupNamespaceURIByPrefix()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 
testLookupNamespaceURIWithNullPrefix()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 
testLookupNamespaceURIWithXmlnsPrefix()264     public void testLookupNamespaceURIWithXmlnsPrefix() {
265         for (Node node : allNodes) {
266             assertEquals(null, node.lookupNamespaceURI("xmlns"));
267         }
268     }
269 
testLookupPrefixWithShadowedUri()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 
testLookupPrefixWithUnusedUri()302     public void testLookupPrefixWithUnusedUri() {
303         for (Node node : allNodes) {
304             assertEquals(null, node.lookupPrefix("http://unused"));
305         }
306     }
307 
testLookupPrefixWithNullUri()308     public void testLookupPrefixWithNullUri() {
309         for (Node node : allNodes) {
310             assertEquals(null, node.lookupPrefix(null));
311         }
312     }
313 
testLookupPrefixWithShadowingUri()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 
testIsDefaultNamespace()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      */
testIsDefaultNamespaceNull_XercesBugs()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 
testIsDefaultNamespaceNull()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 
testDoctypeSetTextContent()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 
testDocumentSetTextContent()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 
testElementSetTextContent()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 
testEntitySetTextContent()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 
testNotationSetTextContent()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      */
testEntityReferenceSetTextContent()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 
testAttributeSetTextContent()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 
testTextSetTextContent()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 
testCdataSetTextContent()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 
testProcessingInstructionSetTextContent()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 
testCommentSetTextContent()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 
testCoreFeature()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 
testXmlFeature()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      */
testXmlVersionFeature()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 
testLoadSaveFeature()555     public void testLoadSaveFeature() {
556         assertFeature("LS", "3.0");
557     }
558 
testElementTraversalFeature()559     public void testElementTraversalFeature() {
560         assertFeature("ElementTraversal", "1.0");
561     }
562 
assertFeature(String feature, String version)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 
assertNoFeature(String feature, String version)570     private void assertNoFeature(String feature, String version) {
571         assertFalse(domImplementation.hasFeature(feature, version));
572         assertNull(domImplementation.getFeature(feature, version));
573     }
574 
testIsSupported()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 
testGetFeature()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 
testNodeEqualsPositive()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 
testNodeEqualsNegative()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 
testNodeEqualsNegativeRecursive()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 
testNodeEqualsNull()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 
testIsElementContentWhitespaceWithoutDeclaration()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 
testIsElementContentWhitespaceWithDeclaration()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 
testGetWholeTextFirst()669     public void testGetWholeTextFirst() {
670         assertEquals("Belgian waffles & strawberries (< 5g of fat)",
671                 descriptionText1.getWholeText());
672     }
673 
testGetWholeTextMiddle()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 
testGetWholeTextLast()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 
testGetWholeTextOnly()684     public void testGetWholeTextOnly() {
685         assertEquals("60%", vitamincText.getWholeText());
686     }
687 
testGetWholeTextWithEntityReference()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 
testReplaceWholeTextFirst()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 
testReplaceWholeTextMiddle()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 
testReplaceWholeTextLast()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 
testReplaceWholeTextOnly()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 
testReplaceWholeTextFirstWithNull()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 
testReplaceWholeTextMiddleWithNull()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 
testReplaceWholeTextLastWithNull()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 
testReplaceWholeTextFirstWithEmptyString()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 
testReplaceWholeTextOnlyWithEmptyString()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 
testUserDataAttachments()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 
testUserDataRejectsNullKey()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 
testValueOfNewAttributesIsEmptyString()802     public void testValueOfNewAttributesIsEmptyString() {
803         assertEquals("", document.createAttribute("bar").getValue());
804         assertEquals("", document.createAttributeNS("http://foo", "bar").getValue());
805     }
806 
testCloneNode()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      */
testCloneNodeNotNamespaceAware()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      */
testUserDataHandlerNotifiedOfShallowClones()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      */
testUserDataHandlerNotifiedOfDeepClones()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      */
testUserDataHandlerNotifiedOfShallowImports()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      */
testUserDataHandlerNotifiedOfDeepImports()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 
testImportNodeDeep()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 
testImportNodeShallow()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 
testNodeAdoption()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 
assertNotAdoptable(Node node)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      */
adoptAndCheck(Node node)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 
assertDetached(Node formerParent, Node node)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 
testAdoptionImmediatelyAfterParsing()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      */
testUserDataHandlerNotifiedOfOnlyShallowAdoptions()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 
testBaseUriRelativeUriResolution()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      */
assertFileUriEquals( String message, String expectedFile, String actual)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      */
testBaseUriResolutionWithHashes()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 
testBaseUriInheritedForProcessingInstructions()1156     public void testBaseUriInheritedForProcessingInstructions() {
1157         document.setDocumentURI("http://d1/d2");
1158         assertEquals("http://d1/d2", wafflemaker.getBaseURI());
1159     }
1160 
testBaseUriInheritedForEntities()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 
testBaseUriNotInheritedForNotations()1169     public void testBaseUriNotInheritedForNotations() {
1170         if (png == null) {
1171             return;
1172         }
1173         document.setDocumentURI("http://d1/d2");
1174         assertNull(png.getBaseURI());
1175     }
1176 
testBaseUriNotInheritedForDoctypes()1177     public void testBaseUriNotInheritedForDoctypes() {
1178         document.setDocumentURI("http://d1/d2");
1179         assertNull(doctype.getBaseURI());
1180     }
1181 
testBaseUriNotInheritedForAttributes()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 
testBaseUriNotInheritedForTextsOrCdatas()1190     public void testBaseUriNotInheritedForTextsOrCdatas() {
1191         document.setDocumentURI("http://d1/d2");
1192         assertNull(descriptionText1.getBaseURI());
1193         assertNull(descriptionText2.getBaseURI());
1194         assertNull(option2Reference.getBaseURI());
1195     }
1196 
testBaseUriNotInheritedForComments()1197     public void testBaseUriNotInheritedForComments() {
1198         document.setDocumentURI("http://d1/d2");
1199         assertNull(descriptionText1.getBaseURI());
1200         assertNull(descriptionText2.getBaseURI());
1201     }
1202 
testBaseUriNotInheritedForEntityReferences()1203     public void testBaseUriNotInheritedForEntityReferences() {
1204         document.setDocumentURI("http://d1/d2");
1205         assertNull(option2Reference.getBaseURI());
1206     }
1207 
testProgrammaticElementIds()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 
testMultipleIdsOnOneElement()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 
testAttributeNamedIdIsNotAnIdByDefault()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 
testElementTypeInfo()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 
testAttributeTypeInfo()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 
testRenameElement()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 
testRenameElementWithPrefix()1266     public void testRenameElementWithPrefix() {
1267         try {
1268             document.renameNode(description, null, "a:desc");
1269             fail();
1270         } catch (DOMException e) {
1271         }
1272     }
1273 
testRenameElementWithNamespace()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 
testRenameElementWithPrefixAndNamespace()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 
testRenameAttribute()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 
testRenameAttributeWithPrefix()1298     public void testRenameAttributeWithPrefix() {
1299         try {
1300             document.renameNode(deluxe, null, "a:special");
1301             fail();
1302         } catch (DOMException e) {
1303         }
1304     }
1305 
testRenameAttributeWithNamespace()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 
testRenameAttributeWithPrefixAndNamespace()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 
testUserDataHandlerNotifiedOfRenames()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 
testRenameToInvalid()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 
testRenameNodeOtherThanElementOrAttribute()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 
testDocumentDoesNotHaveWhitespaceChildren()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 
testDocumentAddChild()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 
testIterateForwardsThroughInnerNodeSiblings()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 
testIterateBackwardsThroughInnerNodeSiblings()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 
testIterateForwardsThroughLeafNodeSiblings()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 
testIterateBackwardsThroughLeafNodeSiblings()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 
testPublicIdAndSystemId()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 
testSystemIdOnly()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 
testSingleQuotedPublicIdAndSystemId()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 
testGetElementsByTagNameNs()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 
testGetElementsByTagNameWithNamespacePrefix()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
testGetElementsByTagNameWithoutNamespacePrefix()1480     public void testGetElementsByTagNameWithoutNamespacePrefix() {
1481         NodeList elements = item.getElementsByTagName("nutrition");
1482         assertEquals(nutrition, elements.item(0));
1483         assertEquals(1, elements.getLength());
1484     }
1485 
testGetElementsByTagNameWithWildcard()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 
testGetElementsByTagNameNsWithWildcard()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      */
testAddingADocumentFragmentAddsItsChildren()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 
testReplacingWithADocumentFragmentInsertsItsChildren()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 
testCoalescingOffByDefault()1555     public void testCoalescingOffByDefault() {
1556         assertFalse(DocumentBuilderFactory.newInstance().isCoalescing());
1557     }
1558 
testCoalescingOn()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 
testCoalescingOff()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 
testExpandingEntityReferencesOnByDefault()1585     public void testExpandingEntityReferencesOnByDefault() {
1586         assertTrue(DocumentBuilderFactory.newInstance().isExpandEntityReferences());
1587     }
1588 
testExpandingEntityReferencesOn()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 
testExpandingEntityReferencesOff()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      */
testExpandingEntityReferencesOffDoesNotImpactPredefinedEntities()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 
testExpandingEntityReferencesOffDoesNotImpactCharacterEntities()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
testInsertBefore()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 
testBomAndByteInput()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 
testBomAndByteInputWithExplicitCharset()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 
testBomAndCharacterInput()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>();
handle(short operation, String key, Object data, Node src, Node dst)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 
notification(short operation, String key, Object data, Node src, Node dst)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 
domToString(Document document)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 
domToStringStripElementWhitespace(Document document)1736     private String domToStringStripElementWhitespace(Document document)
1737             throws TransformerException {
1738         return domToString(document).replaceAll("(?m)>\\s+<", "><");
1739     }
1740 }
1741