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