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