/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package libcore.xml; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.Comment; import org.w3c.dom.DOMException; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.DocumentFragment; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.Entity; import org.w3c.dom.EntityReference; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Notation; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; import org.w3c.dom.TypeInfo; import org.w3c.dom.UserDataHandler; import static org.w3c.dom.UserDataHandler.NODE_ADOPTED; import static org.w3c.dom.UserDataHandler.NODE_CLONED; import static org.w3c.dom.UserDataHandler.NODE_IMPORTED; import static org.w3c.dom.UserDataHandler.NODE_RENAMED; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * Construct a DOM and then interrogate it. */ public class DomTest extends TestCase { private Transformer transformer; private DocumentBuilder builder; private DOMImplementation domImplementation; private final String xml = "<!DOCTYPE menu [" + " <!ENTITY sp \"Maple Syrup\">" + " <!NOTATION png SYSTEM \"image/png\">" + "]>" + "<menu>\n" + " <item xmlns=\"http://food\" xmlns:a=\"http://addons\">\n" + " <name a:standard=\"strawberry\" deluxe=\"&sp;\">Waffles</name>\n" + " <description xmlns=\"http://marketing\">Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)</description>\n" + " <a:option>Whipped Cream</a:option>\n" + " <a:option>&sp;</a:option>\n" + " <?wafflemaker square shape?>\n" + " <nutrition>\n" + " <a:vitamins xmlns:a=\"http://usda\">\n" + " <!-- add other vitamins? --> \n" + " <a:vitaminc>60%</a:vitaminc>\n" + " </a:vitamins>\n" + " </nutrition>\n" + " </item>\n" + "</menu>"; private Document document; private DocumentType doctype; private Entity sp; private Notation png; private Element menu; private Element item; private Attr itemXmlns; private Attr itemXmlnsA; private Element name; private Attr standard; private Attr deluxe; private Text waffles; private Element description; private Text descriptionText1; private CDATASection descriptionText2; private Text descriptionText3; private Element option1; private Element option2; private Node option2Reference; // resolved to Text on RI, an EntityReference on Dalvik private ProcessingInstruction wafflemaker; private Element nutrition; private Element vitamins; private Attr vitaminsXmlnsA; private Comment comment; private Element vitaminc; private Text vitamincText; private List<Node> allNodes; @Override protected void setUp() throws Exception { transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); builder = factory.newDocumentBuilder(); domImplementation = builder.getDOMImplementation(); document = builder.parse(new InputSource(new StringReader(xml))); // doctype nodes doctype = document.getDoctype(); if (doctype.getEntities() != null) { sp = (Entity) doctype.getEntities().item(0); } if (doctype.getNotations() != null) { png = (Notation) doctype.getNotations().item(0); } // document nodes menu = document.getDocumentElement(); item = (Element) menu.getChildNodes().item(1); itemXmlns = item.getAttributeNode("xmlns"); itemXmlnsA = item.getAttributeNode("xmlns:a"); name = (Element) item.getChildNodes().item(1); standard = name.getAttributeNode("a:standard"); deluxe = name.getAttributeNode("deluxe"); waffles = (Text) name.getChildNodes().item(0); description = (Element) item.getChildNodes().item(3); descriptionText1 = (Text) description.getChildNodes().item(0); descriptionText2 = (CDATASection) description.getChildNodes().item(1); descriptionText3 = (Text) description.getChildNodes().item(2); option1 = (Element) item.getChildNodes().item(5); option2 = (Element) item.getChildNodes().item(7); option2Reference = option2.getChildNodes().item(0); wafflemaker = (ProcessingInstruction) item.getChildNodes().item(9); nutrition = (Element) item.getChildNodes().item(11); vitamins = (Element) nutrition.getChildNodes().item(1); vitaminsXmlnsA = vitamins.getAttributeNode("xmlns:a"); comment = (Comment) vitamins.getChildNodes().item(1); vitaminc = (Element) vitamins.getChildNodes().item(3); vitamincText = (Text) vitaminc.getChildNodes().item(0); allNodes = new ArrayList<Node>(); if (sp != null) { allNodes.add(sp); } if (png != null) { allNodes.add(png); } allNodes.addAll(Arrays.asList(document, doctype, menu, item, itemXmlns, itemXmlnsA, name, standard, deluxe, waffles, description, descriptionText1, descriptionText2, descriptionText3, option1, option2, option2Reference, wafflemaker, nutrition, vitamins, vitaminsXmlnsA, comment, vitaminc, vitamincText)); } /** * Android's parsed DOM doesn't include entity declarations. These nodes will * only be tested for implementations that support them. */ public void testEntityDeclarations() { assertNotNull("This implementation does not parse entity declarations", sp); } /** * Android's parsed DOM doesn't include notations. These nodes will only be * tested for implementations that support them. */ public void testNotations() { assertNotNull("This implementation does not parse notations", png); } public void testLookupNamespaceURIByPrefix() { assertEquals(null, doctype.lookupNamespaceURI("a")); if (sp != null) { assertEquals(null, sp.lookupNamespaceURI("a")); } if (png != null) { assertEquals(null, png.lookupNamespaceURI("a")); } assertEquals(null, document.lookupNamespaceURI("a")); assertEquals(null, menu.lookupNamespaceURI("a")); assertEquals("http://addons", item.lookupNamespaceURI("a")); assertEquals("http://addons", itemXmlns.lookupNamespaceURI("a")); assertEquals("http://addons", itemXmlnsA.lookupNamespaceURI("a")); assertEquals("http://addons", name.lookupNamespaceURI("a")); assertEquals("http://addons", standard.lookupNamespaceURI("a")); assertEquals("http://addons", deluxe.lookupNamespaceURI("a")); assertEquals("http://addons", description.lookupNamespaceURI("a")); assertEquals("http://addons", descriptionText1.lookupNamespaceURI("a")); assertEquals("http://addons", descriptionText2.lookupNamespaceURI("a")); assertEquals("http://addons", descriptionText3.lookupNamespaceURI("a")); assertEquals("http://addons", option1.lookupNamespaceURI("a")); assertEquals("http://addons", option2.lookupNamespaceURI("a")); assertEquals("http://addons", option2Reference.lookupNamespaceURI("a")); assertEquals("http://addons", wafflemaker.lookupNamespaceURI("a")); assertEquals("http://addons", nutrition.lookupNamespaceURI("a")); assertEquals("http://usda", vitamins.lookupNamespaceURI("a")); assertEquals("http://usda", vitaminsXmlnsA.lookupNamespaceURI("a")); assertEquals("http://usda", comment.lookupNamespaceURI("a")); assertEquals("http://usda", vitaminc.lookupNamespaceURI("a")); assertEquals("http://usda", vitamincText.lookupNamespaceURI("a")); } public void testLookupNamespaceURIWithNullPrefix() { assertEquals(null, document.lookupNamespaceURI(null)); assertEquals(null, doctype.lookupNamespaceURI(null)); if (sp != null) { assertEquals(null, sp.lookupNamespaceURI(null)); } if (png != null) { assertEquals(null, png.lookupNamespaceURI(null)); } assertEquals(null, menu.lookupNamespaceURI(null)); assertEquals("http://food", item.lookupNamespaceURI(null)); assertEquals("http://food", itemXmlns.lookupNamespaceURI(null)); assertEquals("http://food", itemXmlnsA.lookupNamespaceURI(null)); assertEquals("http://food", name.lookupNamespaceURI(null)); assertEquals("http://food", standard.lookupNamespaceURI(null)); assertEquals("http://food", deluxe.lookupNamespaceURI(null)); assertEquals("http://marketing", description.lookupNamespaceURI(null)); assertEquals("http://marketing", descriptionText1.lookupNamespaceURI(null)); assertEquals("http://marketing", descriptionText2.lookupNamespaceURI(null)); assertEquals("http://marketing", descriptionText3.lookupNamespaceURI(null)); assertEquals("http://food", option1.lookupNamespaceURI(null)); assertEquals("http://food", option2.lookupNamespaceURI(null)); assertEquals("http://food", option2Reference.lookupNamespaceURI(null)); assertEquals("http://food", wafflemaker.lookupNamespaceURI(null)); assertEquals("http://food", nutrition.lookupNamespaceURI(null)); assertEquals("http://food", vitamins.lookupNamespaceURI(null)); assertEquals("http://food", vitaminsXmlnsA.lookupNamespaceURI(null)); assertEquals("http://food", comment.lookupNamespaceURI(null)); assertEquals("http://food", vitaminc.lookupNamespaceURI(null)); assertEquals("http://food", vitamincText.lookupNamespaceURI(null)); } public void testLookupNamespaceURIWithXmlnsPrefix() { for (Node node : allNodes) { assertEquals(null, node.lookupNamespaceURI("xmlns")); } } public void testLookupPrefixWithShadowedUri() { assertEquals(null, document.lookupPrefix("http://addons")); assertEquals(null, doctype.lookupPrefix("http://addons")); if (sp != null) { assertEquals(null, sp.lookupPrefix("http://addons")); } if (png != null) { assertEquals(null, png.lookupPrefix("http://addons")); } assertEquals(null, menu.lookupPrefix("http://addons")); assertEquals("a", item.lookupPrefix("http://addons")); assertEquals("a", itemXmlns.lookupPrefix("http://addons")); assertEquals("a", itemXmlnsA.lookupPrefix("http://addons")); assertEquals("a", name.lookupPrefix("http://addons")); assertEquals("a", standard.lookupPrefix("http://addons")); assertEquals("a", deluxe.lookupPrefix("http://addons")); assertEquals("a", description.lookupPrefix("http://addons")); assertEquals("a", descriptionText1.lookupPrefix("http://addons")); assertEquals("a", descriptionText2.lookupPrefix("http://addons")); assertEquals("a", descriptionText3.lookupPrefix("http://addons")); assertEquals("a", option1.lookupPrefix("http://addons")); assertEquals("a", option2.lookupPrefix("http://addons")); assertEquals("a", option2Reference.lookupPrefix("http://addons")); assertEquals("a", wafflemaker.lookupPrefix("http://addons")); assertEquals("a", nutrition.lookupPrefix("http://addons")); assertEquals(null, vitamins.lookupPrefix("http://addons")); assertEquals(null, vitaminsXmlnsA.lookupPrefix("http://addons")); assertEquals(null, comment.lookupPrefix("http://addons")); assertEquals(null, vitaminc.lookupPrefix("http://addons")); assertEquals(null, vitamincText.lookupPrefix("http://addons")); } public void testLookupPrefixWithUnusedUri() { for (Node node : allNodes) { assertEquals(null, node.lookupPrefix("http://unused")); } } public void testLookupPrefixWithNullUri() { for (Node node : allNodes) { assertEquals(null, node.lookupPrefix(null)); } } public void testLookupPrefixWithShadowingUri() { assertEquals(null, document.lookupPrefix("http://usda")); assertEquals(null, doctype.lookupPrefix("http://usda")); if (sp != null) { assertEquals(null, sp.lookupPrefix("http://usda")); } if (png != null) { assertEquals(null, png.lookupPrefix("http://usda")); } assertEquals(null, menu.lookupPrefix("http://usda")); assertEquals(null, item.lookupPrefix("http://usda")); assertEquals(null, itemXmlns.lookupPrefix("http://usda")); assertEquals(null, itemXmlnsA.lookupPrefix("http://usda")); assertEquals(null, name.lookupPrefix("http://usda")); assertEquals(null, standard.lookupPrefix("http://usda")); assertEquals(null, deluxe.lookupPrefix("http://usda")); assertEquals(null, description.lookupPrefix("http://usda")); assertEquals(null, descriptionText1.lookupPrefix("http://usda")); assertEquals(null, descriptionText2.lookupPrefix("http://usda")); assertEquals(null, descriptionText3.lookupPrefix("http://usda")); assertEquals(null, option1.lookupPrefix("http://usda")); assertEquals(null, option2.lookupPrefix("http://usda")); assertEquals(null, option2Reference.lookupPrefix("http://usda")); assertEquals(null, wafflemaker.lookupPrefix("http://usda")); assertEquals(null, nutrition.lookupPrefix("http://usda")); assertEquals("a", vitamins.lookupPrefix("http://usda")); assertEquals("a", vitaminsXmlnsA.lookupPrefix("http://usda")); assertEquals("a", comment.lookupPrefix("http://usda")); assertEquals("a", vitaminc.lookupPrefix("http://usda")); assertEquals("a", vitamincText.lookupPrefix("http://usda")); } public void testIsDefaultNamespace() { assertFalse(document.isDefaultNamespace("http://food")); assertFalse(doctype.isDefaultNamespace("http://food")); if (sp != null) { assertFalse(sp.isDefaultNamespace("http://food")); } if (png != null) { assertFalse(png.isDefaultNamespace("http://food")); } assertFalse(menu.isDefaultNamespace("http://food")); assertTrue(item.isDefaultNamespace("http://food")); assertTrue(itemXmlns.isDefaultNamespace("http://food")); assertTrue(itemXmlnsA.isDefaultNamespace("http://food")); assertTrue(name.isDefaultNamespace("http://food")); assertTrue(standard.isDefaultNamespace("http://food")); assertTrue(deluxe.isDefaultNamespace("http://food")); assertFalse(description.isDefaultNamespace("http://food")); assertFalse(descriptionText1.isDefaultNamespace("http://food")); assertFalse(descriptionText2.isDefaultNamespace("http://food")); assertFalse(descriptionText3.isDefaultNamespace("http://food")); assertTrue(option1.isDefaultNamespace("http://food")); assertTrue(option2.isDefaultNamespace("http://food")); assertTrue(option2Reference.isDefaultNamespace("http://food")); assertTrue(wafflemaker.isDefaultNamespace("http://food")); assertTrue(nutrition.isDefaultNamespace("http://food")); assertTrue(vitamins.isDefaultNamespace("http://food")); assertTrue(vitaminsXmlnsA.isDefaultNamespace("http://food")); assertTrue(comment.isDefaultNamespace("http://food")); assertTrue(vitaminc.isDefaultNamespace("http://food")); assertTrue(vitamincText.isDefaultNamespace("http://food")); } /** * Xerces fails this test. It returns false always for entity, notation, * document fragment and document type nodes. This contradicts its own * behaviour on lookupNamespaceURI(null). */ public void testIsDefaultNamespaceNull_XercesBugs() { String message = "isDefaultNamespace() should be consistent with lookupNamespaceURI(null)"; assertTrue(message, doctype.isDefaultNamespace(null)); if (sp != null) { assertTrue(message, sp.isDefaultNamespace(null)); } if (png != null) { assertTrue(message, png.isDefaultNamespace(null)); } } public void testIsDefaultNamespaceNull() { assertTrue(document.isDefaultNamespace(null)); assertTrue(menu.isDefaultNamespace(null)); assertFalse(item.isDefaultNamespace(null)); assertFalse(itemXmlns.isDefaultNamespace(null)); assertFalse(itemXmlnsA.isDefaultNamespace(null)); assertFalse(name.isDefaultNamespace(null)); assertFalse(standard.isDefaultNamespace(null)); assertFalse(deluxe.isDefaultNamespace(null)); assertFalse(description.isDefaultNamespace(null)); assertFalse(descriptionText1.isDefaultNamespace(null)); assertFalse(descriptionText2.isDefaultNamespace(null)); assertFalse(descriptionText3.isDefaultNamespace(null)); assertFalse(option1.isDefaultNamespace(null)); assertFalse(option2.isDefaultNamespace(null)); assertFalse(option2Reference.isDefaultNamespace(null)); assertFalse(wafflemaker.isDefaultNamespace(null)); assertFalse(nutrition.isDefaultNamespace(null)); assertFalse(vitamins.isDefaultNamespace(null)); assertFalse(vitaminsXmlnsA.isDefaultNamespace(null)); assertFalse(comment.isDefaultNamespace(null)); assertFalse(vitaminc.isDefaultNamespace(null)); assertFalse(vitamincText.isDefaultNamespace(null)); } public void testDoctypeSetTextContent() throws TransformerException { String original = domToString(document); doctype.setTextContent("foobar"); // strangely, this is specified to no-op assertEquals(original, domToString(document)); } public void testDocumentSetTextContent() throws TransformerException { String original = domToString(document); document.setTextContent("foobar"); // strangely, this is specified to no-op assertEquals(original, domToString(document)); } public void testElementSetTextContent() throws TransformerException { String original = domToString(document); nutrition.setTextContent("foobar"); String expected = original.replaceFirst( "(?s)<nutrition>.*</nutrition>", "<nutrition>foobar</nutrition>"); assertEquals(expected, domToString(document)); } public void testEntitySetTextContent() throws TransformerException { if (sp == null) { return; } try { sp.setTextContent("foobar"); fail(); // is this implementation-specific behaviour? } catch (DOMException e) { } } public void testNotationSetTextContent() throws TransformerException { if (png == null) { return; } String original = domToString(document); png.setTextContent("foobar"); String expected = original.replace("image/png", "foobar"); assertEquals(expected, domToString(document)); } /** * Tests setTextContent on entity references. Although the other tests can * act on a parsed DOM, this needs to use a programmatically constructed DOM * because the parser may have replaced the entity reference with the * corresponding text. */ public void testEntityReferenceSetTextContent() throws TransformerException { document = builder.newDocument(); Element root = document.createElement("menu"); document.appendChild(root); EntityReference entityReference = document.createEntityReference("sp"); root.appendChild(entityReference); try { entityReference.setTextContent("Lite Syrup"); fail(); } catch (DOMException e) { } } public void testAttributeSetTextContent() throws TransformerException { String original = domToString(document); standard.setTextContent("foobar"); String expected = original.replace("standard=\"strawberry\"", "standard=\"foobar\""); assertEquals(expected, domToString(document)); } public void testTextSetTextContent() throws TransformerException { String original = domToString(document); descriptionText1.setTextContent("foobar"); String expected = original.replace(">Belgian<!", ">foobar<!"); assertEquals(expected, domToString(document)); } public void testCdataSetTextContent() throws TransformerException { String original = domToString(document); descriptionText2.setTextContent("foobar"); String expected = original.replace( " waffles & strawberries (< 5g ", "foobar"); assertEquals(expected, domToString(document)); } public void testProcessingInstructionSetTextContent() throws TransformerException { String original = domToString(document); wafflemaker.setTextContent("foobar"); String expected = original.replace(" square shape?>", " foobar?>"); assertEquals(expected, domToString(document)); } public void testCommentSetTextContent() throws TransformerException { String original = domToString(document); comment.setTextContent("foobar"); String expected = original.replace("-- add other vitamins? --", "--foobar--"); assertEquals(expected, domToString(document)); } public void testCoreFeature() { assertFeature("Core", null); assertFeature("Core", ""); assertFeature("Core", "1.0"); assertFeature("Core", "2.0"); assertFeature("Core", "3.0"); assertFeature("CORE", "3.0"); assertFeature("+Core", "3.0"); assertNoFeature("Core", "4.0"); } public void testXmlFeature() { assertFeature("XML", null); assertFeature("XML", ""); assertFeature("XML", "1.0"); assertFeature("XML", "2.0"); assertFeature("XML", "3.0"); assertFeature("Xml", "3.0"); assertFeature("+XML", "3.0"); assertNoFeature("XML", "4.0"); } /** * The RI fails this test. * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Document3-version */ public void testXmlVersionFeature() { assertFeature("XMLVersion", null); assertFeature("XMLVersion", ""); assertFeature("XMLVersion", "1.0"); assertFeature("XMLVersion", "1.1"); assertFeature("XMLVERSION", "1.1"); assertFeature("+XMLVersion", "1.1"); assertNoFeature("XMLVersion", "1.2"); assertNoFeature("XMLVersion", "2.0"); assertNoFeature("XMLVersion", "2.0"); } public void testLoadSaveFeature() { assertFeature("LS", "3.0"); } public void testElementTraversalFeature() { assertFeature("ElementTraversal", "1.0"); } private void assertFeature(String feature, String version) { String message = "This implementation is expected to support " + feature + " v. " + version + " but does not."; assertTrue(message, domImplementation.hasFeature(feature, version)); assertNotNull(message, domImplementation.getFeature(feature, version)); } private void assertNoFeature(String feature, String version) { assertFalse(domImplementation.hasFeature(feature, version)); assertNull(domImplementation.getFeature(feature, version)); } public void testIsSupported() { // we don't independently test the features; instead just assume the // implementation calls through to hasFeature (as tested above) for (Node node : allNodes) { assertTrue(node.isSupported("XML", null)); assertTrue(node.isSupported("XML", "3.0")); assertFalse(node.isSupported("foo", null)); assertFalse(node.isSupported("foo", "bar")); } } public void testGetFeature() { // we don't independently test the features; instead just assume the // implementation calls through to hasFeature (as tested above) for (Node node : allNodes) { assertSame(node, node.getFeature("XML", null)); assertSame(node, node.getFeature("XML", "3.0")); assertNull(node.getFeature("foo", null)); assertNull(node.getFeature("foo", "bar")); } } public void testNodeEqualsPositive() throws Exception { DomTest copy = new DomTest(); copy.setUp(); for (int i = 0; i < allNodes.size(); i++) { Node a = allNodes.get(i); Node b = copy.allNodes.get(i); assertTrue(a.isEqualNode(b)); } } public void testNodeEqualsNegative() throws Exception { for (Node a : allNodes) { for (Node b : allNodes) { assertEquals(a == b, a.isEqualNode(b)); } } } public void testNodeEqualsNegativeRecursive() throws Exception { DomTest copy = new DomTest(); copy.setUp(); copy.vitaminc.setTextContent("55%"); // changing anything about a node should break equality for all parents assertFalse(document.isEqualNode(copy.document)); assertFalse(menu.isEqualNode(copy.menu)); assertFalse(item.isEqualNode(copy.item)); assertFalse(nutrition.isEqualNode(copy.nutrition)); assertFalse(vitamins.isEqualNode(copy.vitamins)); assertFalse(vitaminc.isEqualNode(copy.vitaminc)); // but not siblings assertTrue(doctype.isEqualNode(copy.doctype)); assertTrue(description.isEqualNode(copy.description)); assertTrue(option1.isEqualNode(copy.option1)); } public void testNodeEqualsNull() { for (Node node : allNodes) { try { node.isEqualNode(null); fail(); } catch (NullPointerException e) { } } } public void testIsElementContentWhitespaceWithoutDeclaration() throws Exception { String xml = "<menu> <item/> </menu>"; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); Text text = (Text) factory.newDocumentBuilder() .parse(new InputSource(new StringReader(xml))) .getDocumentElement().getChildNodes().item(0); assertFalse(text.isElementContentWhitespace()); } public void testIsElementContentWhitespaceWithDeclaration() throws Exception { String xml = "<!DOCTYPE menu [\n" + " <!ELEMENT menu (item)*>\n" + " <!ELEMENT item (#PCDATA)>\n" + "]><menu> <item/> </menu>"; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); Text text = (Text) factory.newDocumentBuilder() .parse(new InputSource(new StringReader(xml))) .getDocumentElement().getChildNodes().item(0); assertTrue("This implementation does not recognize element content whitespace", text.isElementContentWhitespace()); } public void testGetWholeTextFirst() { assertEquals("Belgian waffles & strawberries (< 5g of fat)", descriptionText1.getWholeText()); } public void testGetWholeTextMiddle() { assertEquals("This implementation doesn't include preceding nodes in getWholeText()", "Belgian waffles & strawberries (< 5g of fat)", descriptionText2.getWholeText()); } public void testGetWholeTextLast() { assertEquals("This implementation doesn't include preceding nodes in getWholeText()", "Belgian waffles & strawberries (< 5g of fat)", descriptionText3.getWholeText()); } public void testGetWholeTextOnly() { assertEquals("60%", vitamincText.getWholeText()); } public void testGetWholeTextWithEntityReference() { EntityReference spReference = document.createEntityReference("sp"); description.insertBefore(spReference, descriptionText2); assertEquals("This implementation doesn't resolve entity references in getWholeText()", "BelgianMaple Syrup waffles & strawberries (< 5g of fat)", descriptionText1.getWholeText()); } public void testReplaceWholeTextFirst() throws TransformerException { String original = domToString(document); Text replacement = descriptionText1.replaceWholeText("Eggos"); assertSame(descriptionText1, replacement); String expected = original.replace( "Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)", "Eggos"); assertEquals(expected, domToString(document)); } public void testReplaceWholeTextMiddle() throws TransformerException { String original = domToString(document); Text replacement = descriptionText2.replaceWholeText("Eggos"); assertSame(descriptionText2, replacement); String expected = original.replace( "Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)", "<![CDATA[Eggos]]>"); assertEquals("This implementation doesn't remove preceding nodes in replaceWholeText()", expected, domToString(document)); } public void testReplaceWholeTextLast() throws TransformerException { String original = domToString(document); Text replacement = descriptionText3.replaceWholeText("Eggos"); assertSame(descriptionText3, replacement); String expected = original.replace( "Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)", "Eggos"); assertEquals("This implementation doesn't remove preceding nodes in replaceWholeText()", expected, domToString(document)); } public void testReplaceWholeTextOnly() throws TransformerException { String original = domToString(document); Text replacement = vitamincText.replaceWholeText("70%"); assertEquals(Node.TEXT_NODE, replacement.getNodeType()); assertSame(vitamincText, replacement); String expected = original.replace("60%", "70%"); assertEquals(expected, domToString(document)); } public void testReplaceWholeTextFirstWithNull() throws TransformerException { String original = domToString(document); assertNull(descriptionText1.replaceWholeText(null)); String expected = original.replaceFirst(">.*</description>", "/>"); assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)", expected, domToString(document)); } public void testReplaceWholeTextMiddleWithNull() throws TransformerException { String original = domToString(document); assertNull(descriptionText2.replaceWholeText(null)); String expected = original.replaceFirst(">.*</description>", "/>"); assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)", expected, domToString(document)); } public void testReplaceWholeTextLastWithNull() throws TransformerException { String original = domToString(document); assertNull(descriptionText3.replaceWholeText(null)); String expected = original.replaceFirst(">.*</description>", "/>"); assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)", expected, domToString(document)); } public void testReplaceWholeTextFirstWithEmptyString() throws TransformerException { String original = domToString(document); assertNull(descriptionText1.replaceWholeText("")); String expected = original.replaceFirst(">.*</description>", "/>"); assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)", expected, domToString(document)); } public void testReplaceWholeTextOnlyWithEmptyString() throws TransformerException { String original = domToString(document); assertNull(vitamincText.replaceWholeText("")); String expected = original.replaceFirst(">.*</a:vitaminc>", "/>"); assertEquals(expected, domToString(document)); } public void testUserDataAttachments() { Object a = new Object(); Object b = new Object(); for (Node node : allNodes) { node.setUserData("a", a, null); node.setUserData("b", b, null); } for (Node node : allNodes) { assertSame(a, node.getUserData("a")); assertSame(b, node.getUserData("b")); assertEquals(null, node.getUserData("c")); assertEquals(null, node.getUserData("A")); } } public void testUserDataRejectsNullKey() { try { menu.setUserData(null, "apple", null); fail(); } catch (NullPointerException e) { } try { menu.getUserData(null); fail(); } catch (NullPointerException e) { } } public void testValueOfNewAttributesIsEmptyString() { assertEquals("", document.createAttribute("bar").getValue()); assertEquals("", document.createAttributeNS("http://foo", "bar").getValue()); } public void testCloneNode() throws Exception { document = builder.parse(new InputSource(new StringReader("<menu " + "xmlns:f=\"http://food\" xmlns:a=\"http://addons\">" + "<f:item a:standard=\"strawberry\" deluxe=\"yes\">Waffles</f:item></menu>"))); name = (Element) document.getFirstChild().getFirstChild(); Element clonedName = (Element) name.cloneNode(true); assertNull(clonedName.getParentNode()); assertNull(clonedName.getNextSibling()); assertNull(clonedName.getPreviousSibling()); assertEquals("http://food", clonedName.getNamespaceURI()); assertEquals("f:item", clonedName.getNodeName()); assertEquals("item", clonedName.getLocalName()); assertEquals("http://food", clonedName.getNamespaceURI()); assertEquals("yes", clonedName.getAttribute("deluxe")); assertEquals("strawberry", clonedName.getAttribute("a:standard")); assertEquals("strawberry", clonedName.getAttributeNS("http://addons", "standard")); assertEquals(1, name.getChildNodes().getLength()); Text clonedChild = (Text) clonedName.getFirstChild(); assertSame(clonedName, clonedChild.getParentNode()); assertNull(clonedChild.getNextSibling()); assertNull(clonedChild.getPreviousSibling()); assertEquals("Waffles", clonedChild.getTextContent()); } /** * We can't use the namespace-aware factory method for non-namespace-aware * nodes. http://code.google.com/p/android/issues/detail?id=2735 */ public void testCloneNodeNotNamespaceAware() throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(false); builder = factory.newDocumentBuilder(); document = builder.parse(new InputSource(new StringReader("<menu " + "xmlns:f=\"http://food\" xmlns:a=\"http://addons\">" + "<f:item a:standard=\"strawberry\" deluxe=\"yes\">Waffles</f:item></menu>"))); name = (Element) document.getFirstChild().getFirstChild(); Element clonedName = (Element) name.cloneNode(true); assertNull(clonedName.getNamespaceURI()); assertEquals("f:item", clonedName.getNodeName()); assertNull(clonedName.getLocalName()); assertNull(clonedName.getNamespaceURI()); assertEquals("yes", clonedName.getAttribute("deluxe")); assertEquals("strawberry", clonedName.getAttribute("a:standard")); assertEquals("", clonedName.getAttributeNS("http://addons", "standard")); } /** * A shallow clone requires cloning the attributes but not the child nodes. */ public void testUserDataHandlerNotifiedOfShallowClones() { RecordingHandler handler = new RecordingHandler(); name.setUserData("a", "apple", handler); name.setUserData("b", "banana", handler); standard.setUserData("c", "cat", handler); waffles.setUserData("d", "dog", handler); Element clonedName = (Element) name.cloneNode(false); Attr clonedStandard = clonedName.getAttributeNode("a:standard"); Set<String> expected = new HashSet<String>(); expected.add(notification(NODE_CLONED, "a", "apple", name, clonedName)); expected.add(notification(NODE_CLONED, "b", "banana", name, clonedName)); expected.add(notification(NODE_CLONED, "c", "cat", standard, clonedStandard)); assertEquals(expected, handler.calls); } /** * A deep clone requires cloning both the attributes and the child nodes. */ public void testUserDataHandlerNotifiedOfDeepClones() { RecordingHandler handler = new RecordingHandler(); name.setUserData("a", "apple", handler); name.setUserData("b", "banana", handler); standard.setUserData("c", "cat", handler); waffles.setUserData("d", "dog", handler); Element clonedName = (Element) name.cloneNode(true); Attr clonedStandard = clonedName.getAttributeNode("a:standard"); Text clonedWaffles = (Text) clonedName.getChildNodes().item(0); Set<String> expected = new HashSet<String>(); expected.add(notification(NODE_CLONED, "a", "apple", name, clonedName)); expected.add(notification(NODE_CLONED, "b", "banana", name, clonedName)); expected.add(notification(NODE_CLONED, "c", "cat", standard, clonedStandard)); expected.add(notification(NODE_CLONED, "d", "dog", waffles, clonedWaffles)); assertEquals(expected, handler.calls); } /** * A shallow import requires importing the attributes but not the child * nodes. */ public void testUserDataHandlerNotifiedOfShallowImports() { RecordingHandler handler = new RecordingHandler(); name.setUserData("a", "apple", handler); name.setUserData("b", "banana", handler); standard.setUserData("c", "cat", handler); waffles.setUserData("d", "dog", handler); Document newDocument = builder.newDocument(); Element importedName = (Element) newDocument.importNode(name, false); Attr importedStandard = importedName.getAttributeNode("a:standard"); Set<String> expected = new HashSet<String>(); expected.add(notification(NODE_IMPORTED, "a", "apple", name, importedName)); expected.add(notification(NODE_IMPORTED, "b", "banana", name, importedName)); expected.add(notification(NODE_IMPORTED, "c", "cat", standard, importedStandard)); assertEquals(expected, handler.calls); } /** * A deep import requires cloning both the attributes and the child nodes. */ public void testUserDataHandlerNotifiedOfDeepImports() { RecordingHandler handler = new RecordingHandler(); name.setUserData("a", "apple", handler); name.setUserData("b", "banana", handler); standard.setUserData("c", "cat", handler); waffles.setUserData("d", "dog", handler); Document newDocument = builder.newDocument(); Element importedName = (Element) newDocument.importNode(name, true); Attr importedStandard = importedName.getAttributeNode("a:standard"); Text importedWaffles = (Text) importedName.getChildNodes().item(0); Set<String> expected = new HashSet<String>(); expected.add(notification(NODE_IMPORTED, "a", "apple", name, importedName)); expected.add(notification(NODE_IMPORTED, "b", "banana", name, importedName)); expected.add(notification(NODE_IMPORTED, "c", "cat", standard, importedStandard)); expected.add(notification(NODE_IMPORTED, "d", "dog", waffles, importedWaffles)); assertEquals(expected, handler.calls); } public void testImportNodeDeep() throws TransformerException { String original = domToStringStripElementWhitespace(document); Document newDocument = builder.newDocument(); Element importedItem = (Element) newDocument.importNode(item, true); assertDetached(item.getParentNode(), importedItem); newDocument.appendChild(importedItem); String expected = original.replaceAll("</?menu>", ""); assertEquals(expected, domToStringStripElementWhitespace(newDocument)); } public void testImportNodeShallow() throws TransformerException { Document newDocument = builder.newDocument(); Element importedItem = (Element) newDocument.importNode(item, false); assertDetached(item.getParentNode(), importedItem); newDocument.appendChild(importedItem); assertEquals("<item xmlns=\"http://food\" xmlns:a=\"http://addons\"/>", domToString(newDocument)); } public void testNodeAdoption() throws Exception { for (Node node : allNodes) { if (node == document || node == doctype || node == sp || node == png) { assertNotAdoptable(node); } else { adoptAndCheck(node); } } } private void assertNotAdoptable(Node node) { try { builder.newDocument().adoptNode(node); fail(); } catch (DOMException e) { } } /** * Adopts the node into another document, then adopts the root element, and * then attaches the adopted node in the proper place. The net result should * be that the document's entire contents have moved to another document. */ private void adoptAndCheck(Node node) throws Exception { String original = domToString(document); Document newDocument = builder.newDocument(); // remember where to insert the node in the new document boolean isAttribute = node.getNodeType() == Node.ATTRIBUTE_NODE; Node parent = isAttribute ? ((Attr) node).getOwnerElement() : node.getParentNode(); Node nextSibling = node.getNextSibling(); // move the node and make sure it was detached assertSame(node, newDocument.adoptNode(node)); assertDetached(parent, node); // move the rest of the document and wire the adopted back into place assertSame(menu, newDocument.adoptNode(menu)); newDocument.appendChild(menu); if (isAttribute) { ((Element) parent).setAttributeNodeNS((Attr) node); } else if (nextSibling != null) { parent.insertBefore(node, nextSibling); } else if (parent != document) { parent.appendChild(node); } assertEquals(original, domToString(newDocument)); document = newDocument; } private void assertDetached(Node formerParent, Node node) { assertNull(node.getParentNode()); NodeList children = formerParent.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { assertTrue(children.item(i) != node); } if (node.getNodeType() == Node.ATTRIBUTE_NODE) { assertNull(((Attr) node).getOwnerElement()); NamedNodeMap attributes = formerParent.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { assertTrue(attributes.item(i) != node); } } } public void testAdoptionImmediatelyAfterParsing() throws Exception { Document newDocument = builder.newDocument(); try { assertSame(name, newDocument.adoptNode(name)); assertSame(newDocument, name.getOwnerDocument()); assertSame(newDocument, standard.getOwnerDocument()); assertSame(newDocument, waffles.getOwnerDocument()); } catch (Throwable e) { AssertionFailedError failure = new AssertionFailedError( "This implementation fails to adopt nodes before the " + "document has been traversed"); failure.initCause(e); throw failure; } } /** * There should be notifications for adopted node itself but none of its * children. The DOM spec is vague on this, so we're consistent with the RI. */ public void testUserDataHandlerNotifiedOfOnlyShallowAdoptions() throws Exception { /* * Force a traversal of the document, otherwise this test may fail for * an unrelated reason on version 5 of the RI. That behavior is * exercised by testAdoptionImmediatelyAfterParsing(). */ domToString(document); RecordingHandler handler = new RecordingHandler(); name.setUserData("a", "apple", handler); name.setUserData("b", "banana", handler); standard.setUserData("c", "cat", handler); waffles.setUserData("d", "dog", handler); Document newDocument = builder.newDocument(); assertSame(name, newDocument.adoptNode(name)); assertSame(newDocument, name.getOwnerDocument()); assertSame(newDocument, standard.getOwnerDocument()); assertSame(newDocument, waffles.getOwnerDocument()); Set<String> expected = new HashSet<String>(); expected.add(notification(NODE_ADOPTED, "a", "apple", name, null)); expected.add(notification(NODE_ADOPTED, "b", "banana", name, null)); assertEquals(expected, handler.calls); } public void testBaseUriRelativeUriResolution() throws Exception { File file = File.createTempFile("DomTest.java", "xml"); File parentFile = file.getParentFile(); FileWriter writer = new FileWriter(file); writer.write("<a>" + " <b xml:base=\"b1/b2\">" + " <c>" + " <d xml:base=\"../d1/d2\"><e/></d>" + " </c>" + " </b>" + " <h xml:base=\"h1/h2/\">" + " <i xml:base=\"../i1/i2\"/>" + " </h>" + "</a>"); writer.close(); document = builder.parse(file); assertFileUriEquals("", file.getPath(), document.getBaseURI()); assertFileUriEquals("", file.getPath(), document.getDocumentURI()); Element a = document.getDocumentElement(); assertFileUriEquals("", file.getPath(), a.getBaseURI()); String message = "This implementation's getBaseURI() doesn't handle relative URIs"; Element b = (Element) a.getChildNodes().item(1); Element c = (Element) b.getChildNodes().item(1); Element d = (Element) c.getChildNodes().item(1); Element e = (Element) d.getChildNodes().item(0); Element h = (Element) a.getChildNodes().item(3); Element i = (Element) h.getChildNodes().item(1); assertFileUriEquals(message, parentFile + "/b1/b2", b.getBaseURI()); assertFileUriEquals(message, parentFile + "/b1/b2", c.getBaseURI()); assertFileUriEquals(message, parentFile + "/d1/d2", d.getBaseURI()); assertFileUriEquals(message, parentFile + "/d1/d2", e.getBaseURI()); assertFileUriEquals(message, parentFile + "/h1/h2/", h.getBaseURI()); assertFileUriEquals(message, parentFile + "/h1/i1/i2", i.getBaseURI()); } /** * Regrettably both "file:/tmp/foo.txt" and "file:///tmp/foo.txt" are * legal URIs, and different implementations emit different forms. */ private void assertFileUriEquals( String message, String expectedFile, String actual) { if (!("file:" + expectedFile).equals(actual) && !("file://" + expectedFile).equals(actual)) { fail("Expected URI for: " + expectedFile + " but was " + actual + ". " + message); } } /** * According to the <a href="http://www.w3.org/TR/xmlbase/">XML Base</a> * spec, fragments (like "#frag" or "") should not be dereferenced. */ public void testBaseUriResolutionWithHashes() throws Exception { document = builder.parse(new InputSource(new StringReader( "<a xml:base=\"http://a1/a2\">" + " <b xml:base=\"b1#b2\"/>" + " <c xml:base=\"#c1\">" + " <d xml:base=\"\"/>" + " </c>" + " <e xml:base=\"\"/>" + "</a>"))); Element a = document.getDocumentElement(); assertEquals("http://a1/a2", a.getBaseURI()); String message = "This implementation's getBaseURI() doesn't handle " + "relative URIs with hashes"; Element b = (Element) a.getChildNodes().item(1); Element c = (Element) a.getChildNodes().item(3); Element d = (Element) c.getChildNodes().item(1); Element e = (Element) a.getChildNodes().item(5); assertEquals(message, "http://a1/b1#b2", b.getBaseURI()); assertEquals(message, "http://a1/a2#c1", c.getBaseURI()); assertEquals(message, "http://a1/a2#c1", d.getBaseURI()); assertEquals(message, "http://a1/a2", e.getBaseURI()); } public void testBaseUriInheritedForProcessingInstructions() { document.setDocumentURI("http://d1/d2"); assertEquals("http://d1/d2", wafflemaker.getBaseURI()); } public void testBaseUriInheritedForEntities() { if (sp == null) { return; } document.setDocumentURI("http://d1/d2"); assertEquals("http://d1/d2", sp.getBaseURI()); } public void testBaseUriNotInheritedForNotations() { if (png == null) { return; } document.setDocumentURI("http://d1/d2"); assertNull(png.getBaseURI()); } public void testBaseUriNotInheritedForDoctypes() { document.setDocumentURI("http://d1/d2"); assertNull(doctype.getBaseURI()); } public void testBaseUriNotInheritedForAttributes() { document.setDocumentURI("http://d1/d2"); assertNull(itemXmlns.getBaseURI()); assertNull(itemXmlnsA.getBaseURI()); assertNull(standard.getBaseURI()); assertNull(vitaminsXmlnsA.getBaseURI()); } public void testBaseUriNotInheritedForTextsOrCdatas() { document.setDocumentURI("http://d1/d2"); assertNull(descriptionText1.getBaseURI()); assertNull(descriptionText2.getBaseURI()); assertNull(option2Reference.getBaseURI()); } public void testBaseUriNotInheritedForComments() { document.setDocumentURI("http://d1/d2"); assertNull(descriptionText1.getBaseURI()); assertNull(descriptionText2.getBaseURI()); } public void testBaseUriNotInheritedForEntityReferences() { document.setDocumentURI("http://d1/d2"); assertNull(option2Reference.getBaseURI()); } public void testProgrammaticElementIds() { vitaminc.setAttribute("name", "c"); assertFalse(vitaminc.getAttributeNode("name").isId()); assertNull(document.getElementById("c")); // set the ID attribute... vitaminc.setIdAttribute("name", true); assertTrue(vitaminc.getAttributeNode("name").isId()); assertSame(vitaminc, document.getElementById("c")); // ... and then take it away vitaminc.setIdAttribute("name", false); assertFalse(vitaminc.getAttributeNode("name").isId()); assertNull(document.getElementById("c")); } public void testMultipleIdsOnOneElement() { vitaminc.setAttribute("name", "c"); vitaminc.setIdAttribute("name", true); vitaminc.setAttribute("atc", "a11g"); vitaminc.setIdAttribute("atc", true); assertTrue(vitaminc.getAttributeNode("name").isId()); assertTrue(vitaminc.getAttributeNode("atc").isId()); assertSame(vitaminc, document.getElementById("c")); assertSame(vitaminc, document.getElementById("a11g")); assertNull(document.getElementById("g")); } public void testAttributeNamedIdIsNotAnIdByDefault() { String message = "This implementation incorrectly interprets the " + "\"id\" attribute as an identifier by default."; vitaminc.setAttribute("id", "c"); assertNull(message, document.getElementById("c")); } public void testElementTypeInfo() { TypeInfo typeInfo = description.getSchemaTypeInfo(); assertNull(typeInfo.getTypeName()); assertNull(typeInfo.getTypeNamespace()); assertFalse(typeInfo.isDerivedFrom("x", "y", TypeInfo.DERIVATION_UNION)); } public void testAttributeTypeInfo() { TypeInfo typeInfo = standard.getSchemaTypeInfo(); assertNull(typeInfo.getTypeName()); assertNull(typeInfo.getTypeNamespace()); assertFalse(typeInfo.isDerivedFrom("x", "y", TypeInfo.DERIVATION_UNION)); } public void testRenameElement() { document.renameNode(description, null, "desc"); assertEquals("desc", description.getTagName()); assertEquals("desc", description.getLocalName()); assertEquals(null, description.getPrefix()); assertEquals(null, description.getNamespaceURI()); } public void testRenameElementWithPrefix() { try { document.renameNode(description, null, "a:desc"); fail(); } catch (DOMException e) { } } public void testRenameElementWithNamespace() { document.renameNode(description, "http://sales", "desc"); assertEquals("desc", description.getTagName()); assertEquals("desc", description.getLocalName()); assertEquals(null, description.getPrefix()); assertEquals("http://sales", description.getNamespaceURI()); } public void testRenameElementWithPrefixAndNamespace() { document.renameNode(description, "http://sales", "a:desc"); assertEquals("a:desc", description.getTagName()); assertEquals("desc", description.getLocalName()); assertEquals("a", description.getPrefix()); assertEquals("http://sales", description.getNamespaceURI()); } public void testRenameAttribute() { document.renameNode(deluxe, null, "special"); assertEquals("special", deluxe.getName()); assertEquals("special", deluxe.getLocalName()); assertEquals(null, deluxe.getPrefix()); assertEquals(null, deluxe.getNamespaceURI()); } public void testRenameAttributeWithPrefix() { try { document.renameNode(deluxe, null, "a:special"); fail(); } catch (DOMException e) { } } public void testRenameAttributeWithNamespace() { document.renameNode(deluxe, "http://sales", "special"); assertEquals("special", deluxe.getName()); assertEquals("special", deluxe.getLocalName()); assertEquals(null, deluxe.getPrefix()); assertEquals("http://sales", deluxe.getNamespaceURI()); } public void testRenameAttributeWithPrefixAndNamespace() { document.renameNode(deluxe, "http://sales", "a:special"); assertEquals("a:special", deluxe.getName()); assertEquals("special", deluxe.getLocalName()); assertEquals("a", deluxe.getPrefix()); assertEquals("http://sales", deluxe.getNamespaceURI()); } public void testUserDataHandlerNotifiedOfRenames() { RecordingHandler handler = new RecordingHandler(); description.setUserData("a", "apple", handler); deluxe.setUserData("b", "banana", handler); standard.setUserData("c", "cat", handler); document.renameNode(deluxe, null, "special"); document.renameNode(description, null, "desc"); Set<String> expected = new HashSet<String>(); expected.add(notification(NODE_RENAMED, "a", "apple", description, null)); expected.add(notification(NODE_RENAMED, "b", "banana", deluxe, null)); assertEquals(expected, handler.calls); } public void testRenameToInvalid() { try { document.renameNode(description, null, "xmlns:foo"); fail(); } catch (DOMException e) { } try { document.renameNode(description, null, "xml:foo"); fail(); } catch (DOMException e) { } try { document.renameNode(deluxe, null, "xmlns"); fail(); } catch (DOMException e) { } } public void testRenameNodeOtherThanElementOrAttribute() { for (Node node : allNodes) { if (node.getNodeType() == Node.ATTRIBUTE_NODE || node.getNodeType() == Node.ELEMENT_NODE) { continue; } try { document.renameNode(node, null, "foo"); fail(); } catch (DOMException e) { } } } public void testDocumentDoesNotHaveWhitespaceChildren() throws IOException, SAXException { String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n" + " <foo/>\n" + " \n"; document = builder.parse(new InputSource(new StringReader(xml))); assertEquals("Document nodes shouldn't have text children", 1, document.getChildNodes().getLength()); } public void testDocumentAddChild() throws IOException, SAXException { try { document.appendChild(document.createTextNode(" ")); fail("Document nodes shouldn't accept child nodes"); } catch (DOMException e) { } } public void testIterateForwardsThroughInnerNodeSiblings() throws Exception { document = builder.parse(new InputSource(new StringReader( "<root><child/><child/></root>"))); Node root = document.getDocumentElement(); Node current = root.getChildNodes().item(0); while (current.getNextSibling() != null) { current = current.getNextSibling(); } assertEquals(root.getChildNodes().item(root.getChildNodes().getLength() - 1), current); } public void testIterateBackwardsThroughInnerNodeSiblings() throws Exception { document = builder.parse(new InputSource(new StringReader( "<root><child/><child/></root>"))); Node root = document.getDocumentElement(); Node current = root.getChildNodes().item(root.getChildNodes().getLength() - 1); while (current.getPreviousSibling() != null) { current = current.getPreviousSibling(); } assertEquals(root.getChildNodes().item(0), current); } public void testIterateForwardsThroughLeafNodeSiblings() throws Exception { document = builder.parse(new InputSource(new StringReader( "<root> <!-- --> </root>"))); Node root = document.getDocumentElement(); Node current = root.getChildNodes().item(0); while (current.getNextSibling() != null) { current = current.getNextSibling(); } assertEquals(root.getChildNodes().item(root.getChildNodes().getLength() - 1), current); } public void testIterateBackwardsThroughLeafNodeSiblings() throws Exception { document = builder.parse(new InputSource(new StringReader( "<root> <!-- --> </root>"))); Node root = document.getDocumentElement(); Node current = root.getChildNodes().item(root.getChildNodes().getLength() - 1); while (current.getPreviousSibling() != null) { current = current.getPreviousSibling(); } assertEquals(root.getChildNodes().item(0), current); } public void testPublicIdAndSystemId() throws Exception { document = builder.parse(new InputSource(new StringReader( " <!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\"" + " \"http://www.w3.org/TR/html4/strict.dtd\">" + "<html></html>"))); doctype = document.getDoctype(); assertEquals("html", doctype.getName()); assertEquals("-//W3C//DTD HTML 4.01//EN", doctype.getPublicId()); assertEquals("http://www.w3.org/TR/html4/strict.dtd", doctype.getSystemId()); } public void testSystemIdOnly() throws Exception { document = builder.parse(new InputSource(new StringReader( " <!DOCTYPE html SYSTEM \"http://www.w3.org/TR/html4/strict.dtd\">" + "<html></html>"))); doctype = document.getDoctype(); assertEquals("html", doctype.getName()); assertNull(doctype.getPublicId()); assertEquals("http://www.w3.org/TR/html4/strict.dtd", doctype.getSystemId()); } public void testSingleQuotedPublicIdAndSystemId() throws Exception { document = builder.parse(new InputSource(new StringReader( " <!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01//EN'" + " 'http://www.w3.org/TR/html4/strict.dtd'>" + "<html></html>"))); doctype = document.getDoctype(); assertEquals("html", doctype.getName()); assertEquals("-//W3C//DTD HTML 4.01//EN", doctype.getPublicId()); assertEquals("http://www.w3.org/TR/html4/strict.dtd", doctype.getSystemId()); } public void testGetElementsByTagNameNs() { NodeList elements = item.getElementsByTagNameNS("http://addons", "option"); assertEquals(option1, elements.item(0)); assertEquals(option2, elements.item(1)); assertEquals(2, elements.getLength()); } public void testGetElementsByTagNameWithNamespacePrefix() { NodeList elements = item.getElementsByTagName("a:option"); assertEquals(option1, elements.item(0)); assertEquals(option2, elements.item(1)); assertEquals(2, elements.getLength()); } // http://code.google.com/p/android/issues/detail?id=17907 public void testGetElementsByTagNameWithoutNamespacePrefix() { NodeList elements = item.getElementsByTagName("nutrition"); assertEquals(nutrition, elements.item(0)); assertEquals(1, elements.getLength()); } public void testGetElementsByTagNameWithWildcard() { NodeList elements = item.getElementsByTagName("*"); assertEquals(name, elements.item(0)); assertEquals(description, elements.item(1)); assertEquals(option1, elements.item(2)); assertEquals(option2, elements.item(3)); assertEquals(nutrition, elements.item(4)); assertEquals(vitamins, elements.item(5)); assertEquals(vitaminc, elements.item(6)); assertEquals(7, elements.getLength()); } public void testGetElementsByTagNameNsWithWildcard() { NodeList elements = item.getElementsByTagNameNS("*", "*"); assertEquals(name, elements.item(0)); assertEquals(description, elements.item(1)); assertEquals(option1, elements.item(2)); assertEquals(option2, elements.item(3)); assertEquals(nutrition, elements.item(4)); assertEquals(vitamins, elements.item(5)); assertEquals(vitaminc, elements.item(6)); assertEquals(7, elements.getLength()); } /** * Documents shouldn't contain document fragments. * http://code.google.com/p/android/issues/detail?id=2735 */ public void testAddingADocumentFragmentAddsItsChildren() { Element a = document.createElement("a"); Element b = document.createElement("b"); Element c = document.createElement("c"); DocumentFragment fragment = document.createDocumentFragment(); fragment.appendChild(a); fragment.appendChild(b); fragment.appendChild(c); Node returned = menu.appendChild(fragment); assertSame(fragment, returned); NodeList children = menu.getChildNodes(); assertEquals(6, children.getLength()); assertTrue(children.item(0) instanceof Text); // whitespace assertEquals(item, children.item(1)); assertTrue(children.item(2) instanceof Text); // whitespace assertEquals(a, children.item(3)); assertEquals(b, children.item(4)); assertEquals(c, children.item(5)); } public void testReplacingWithADocumentFragmentInsertsItsChildren() { Element a = document.createElement("a"); Element b = document.createElement("b"); Element c = document.createElement("c"); DocumentFragment fragment = document.createDocumentFragment(); fragment.appendChild(a); fragment.appendChild(b); fragment.appendChild(c); Node returned = menu.replaceChild(fragment, item); assertSame(item, returned); NodeList children = menu.getChildNodes(); assertEquals(5, children.getLength()); assertTrue(children.item(0) instanceof Text); // whitespace assertEquals(a, children.item(1)); assertEquals(b, children.item(2)); assertEquals(c, children.item(3)); assertTrue(children.item(4) instanceof Text); // whitespace } public void testCoalescingOffByDefault() { assertFalse(DocumentBuilderFactory.newInstance().isCoalescing()); } public void testCoalescingOn() throws Exception { String xml = "<foo>abc<![CDATA[def]]>ghi</foo>"; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setCoalescing(true); document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml))); Element documentElement = document.getDocumentElement(); Text text = (Text) documentElement.getFirstChild(); assertEquals("abcdefghi", text.getTextContent()); assertNull(text.getNextSibling()); } public void testCoalescingOff() throws Exception { String xml = "<foo>abc<![CDATA[def]]>ghi</foo>"; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setCoalescing(false); document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml))); Element documentElement = document.getDocumentElement(); Text abc = (Text) documentElement.getFirstChild(); assertEquals("abc", abc.getTextContent()); CDATASection def = (CDATASection) abc.getNextSibling(); assertEquals("def", def.getTextContent()); Text ghi = (Text) def.getNextSibling(); assertEquals("ghi", ghi.getTextContent()); assertNull(ghi.getNextSibling()); } public void testExpandingEntityReferencesOnByDefault() { assertTrue(DocumentBuilderFactory.newInstance().isExpandEntityReferences()); } public void testExpandingEntityReferencesOn() throws Exception { String xml = "<!DOCTYPE foo [ <!ENTITY def \"DEF\"> ]>" + "<foo>abc&def;ghi</foo>"; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setExpandEntityReferences(true); document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml))); Element documentElement = document.getDocumentElement(); Text text = (Text) documentElement.getFirstChild(); assertEquals("This implementation doesn't expand entity references", "abcDEFghi", text.getTextContent()); assertNull(text.getNextSibling()); } public void testExpandingEntityReferencesOff() throws Exception { String xml = "<!DOCTYPE foo [ <!ENTITY def \"DEF\"> ]>" + "<foo>abc&def;ghi</foo>"; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setExpandEntityReferences(false); document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml))); Element documentElement = document.getDocumentElement(); Text abc = (Text) documentElement.getFirstChild(); assertEquals("abc", abc.getTextContent()); EntityReference def = (EntityReference) abc.getNextSibling(); assertEquals("def", def.getNodeName()); Text ghi = (Text) def.getNextSibling(); assertNull(ghi.getNextSibling()); /* * We expect the entity reference to contain one child Text node "DEF". * The RI's entity reference contains no children. Instead it stashes * "DEF" in the next sibling node. */ assertEquals("Expected text value only and no expanded entity data", "ghi", ghi.getTextContent()); NodeList defChildren = def.getChildNodes(); assertEquals("This implementation doesn't include children in entity references", 1, defChildren.getLength()); assertEquals("DEF", defChildren.item(0).getTextContent()); } /** * Predefined entities should always be expanded. * https://code.google.com/p/android/issues/detail?id=225 */ public void testExpandingEntityReferencesOffDoesNotImpactPredefinedEntities() throws Exception { String xml = "<foo>abc&def</foo>"; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setExpandEntityReferences(false); document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml))); Element documentElement = document.getDocumentElement(); Text text = (Text) documentElement.getFirstChild(); assertEquals("abc&def", text.getTextContent()); assertNull(text.getNextSibling()); } public void testExpandingEntityReferencesOffDoesNotImpactCharacterEntities() throws Exception { String xml = "<foo>abc&def&ghi</foo>"; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setExpandEntityReferences(false); document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml))); Element documentElement = document.getDocumentElement(); Text text = (Text) documentElement.getFirstChild(); assertEquals("abc&def&ghi", text.getTextContent()); assertNull(text.getNextSibling()); } public void testBomAndByteInput() throws Exception { byte[] xml = { (byte) 0xef, (byte) 0xbb, (byte) 0xbf, '<', 'i', 'n', 'p', 'u', 't', '/', '>' }; document = builder.parse(new InputSource(new ByteArrayInputStream(xml))); assertEquals("input", document.getDocumentElement().getNodeName()); } public void testBomAndByteInputWithExplicitCharset() throws Exception { byte[] xml = { (byte) 0xef, (byte) 0xbb, (byte) 0xbf, '<', 'i', 'n', 'p', 'u', 't', '/', '>' }; InputSource inputSource = new InputSource(new ByteArrayInputStream(xml)); inputSource.setEncoding("UTF-8"); document = builder.parse(inputSource); assertEquals("input", document.getDocumentElement().getNodeName()); } public void testBomAndCharacterInput() throws Exception { InputSource inputSource = new InputSource(new StringReader("\ufeff<input/>")); inputSource.setEncoding("UTF-8"); try { builder.parse(inputSource); fail(); } catch (SAXException expected) { } } private class RecordingHandler implements UserDataHandler { final Set<String> calls = new HashSet<String>(); public void handle(short operation, String key, Object data, Node src, Node dst) { calls.add(notification(operation, key, data, src, dst)); } } private String notification(short operation, String key, Object data, Node src, Node dst) { return "op:" + operation + " key:" + key + " data:" + data + " src:" + src + " dst:" + dst; } private String domToString(Document document) throws TransformerException { StringWriter writer = new StringWriter(); transformer.transform(new DOMSource(document), new StreamResult(writer)); String result = writer.toString(); /* * Hack: swap <name>'s a:standard attribute and deluxe attribute if * they're out of order. Some document transformations reorder the * attributes, which causes pain when we try to use String comparison on * them. */ Matcher attributeMatcher = Pattern.compile(" a:standard=\"[^\"]+\"").matcher(result); if (attributeMatcher.find()) { result = result.substring(0, attributeMatcher.start()) + result.substring(attributeMatcher.end()); int insertionPoint = result.indexOf(" deluxe=\""); result = result.substring(0, insertionPoint) + attributeMatcher.group() + result.substring(insertionPoint); } return result; } private String domToStringStripElementWhitespace(Document document) throws TransformerException { return domToString(document).replaceAll("(?m)>\\s+<", "><"); } }