1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package tests.xml; 18 19 import dalvik.annotation.KnownFailure; 20 import junit.framework.TestCase; 21 import org.w3c.dom.CDATASection; 22 import org.w3c.dom.Comment; 23 import org.w3c.dom.DOMConfiguration; 24 import org.w3c.dom.DOMError; 25 import org.w3c.dom.DOMErrorHandler; 26 import org.w3c.dom.DOMException; 27 import org.w3c.dom.Document; 28 import org.w3c.dom.Element; 29 import org.w3c.dom.Node; 30 import org.w3c.dom.NodeList; 31 import org.w3c.dom.ProcessingInstruction; 32 import org.w3c.dom.Text; 33 import org.xml.sax.InputSource; 34 35 import javax.xml.parsers.DocumentBuilderFactory; 36 import javax.xml.transform.OutputKeys; 37 import javax.xml.transform.Transformer; 38 import javax.xml.transform.TransformerException; 39 import javax.xml.transform.TransformerFactory; 40 import javax.xml.transform.dom.DOMSource; 41 import javax.xml.transform.stream.StreamResult; 42 import java.io.StringReader; 43 import java.io.StringWriter; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collections; 47 import java.util.List; 48 49 /** 50 * Tests the acceptance of various parameters on the DOM configuration. This 51 * test assumes the same set of parameters as the RI version 1.5. Perfectly 52 * correct DOM implementations may fail this test because it assumes certain 53 * parameters will be unsupported. 54 */ 55 public class NormalizeTest extends TestCase { 56 57 private Document document; 58 private DOMConfiguration domConfiguration; 59 60 String[] infosetImpliesFalse = { 61 "validate-if-schema", "entities", "datatype-normalization", "cdata-sections" }; 62 String[] infosetImpliesTrue = { "namespace-declarations", "well-formed", 63 "element-content-whitespace", "comments", "namespaces" }; 64 65 @Override protected void setUp() throws Exception { 66 document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 67 domConfiguration = document.getDomConfig(); 68 } 69 70 public void testCanonicalForm() { 71 assertEquals(false, domConfiguration.getParameter("canonical-form")); 72 assertSupported("canonical-form", false); 73 assertUnsupported("canonical-form", true); 74 } 75 76 public void testCdataSections() { 77 assertEquals(true, domConfiguration.getParameter("cdata-sections")); 78 assertSupported("cdata-sections", false); 79 assertSupported("cdata-sections", true); 80 } 81 82 public void testCheckCharacterNormalization() { 83 assertEquals(false, domConfiguration.getParameter("check-character-normalization")); 84 assertSupported("check-character-normalization", false); 85 assertUnsupported("check-character-normalization", true); 86 } 87 88 public void testComments() { 89 assertEquals(true, domConfiguration.getParameter("comments")); 90 assertSupported("comments", false); 91 assertSupported("comments", true); 92 } 93 94 public void testDatatypeNormalization() { 95 assertEquals(false, domConfiguration.getParameter("datatype-normalization")); 96 assertSupported("datatype-normalization", false); 97 assertSupported("datatype-normalization", true); 98 99 // setting this parameter to true should set validate to true... 100 domConfiguration.setParameter("validate", false); 101 domConfiguration.setParameter("datatype-normalization", true); 102 assertEquals(true, domConfiguration.getParameter("validate")); 103 104 // ...but the negative case isn't so 105 domConfiguration.setParameter("datatype-normalization", false); 106 assertEquals(true, domConfiguration.getParameter("validate")); 107 } 108 109 public void testElementContentWhitespace() { 110 assertEquals(true, domConfiguration.getParameter("element-content-whitespace")); 111 assertUnsupported("element-content-whitespace", false); 112 assertSupported("element-content-whitespace", true); 113 } 114 115 public void testEntities() { 116 assertEquals(true, domConfiguration.getParameter("entities")); 117 assertSupported("entities", false); 118 assertSupported("entities", true); 119 } 120 121 public void testErrorHandler() { 122 assertEquals(null, domConfiguration.getParameter("error-handler")); 123 assertSupported("error-handler", null); 124 assertSupported("error-handler", new DOMErrorHandler() { 125 public boolean handleError(DOMError error) { 126 return true; 127 } 128 }); 129 } 130 131 public void testInfoset() { 132 assertEquals(false, domConfiguration.getParameter("infoset")); 133 assertSupported("infoset", false); 134 assertSupported("infoset", true); 135 } 136 137 public void testSettingInfosetUpdatesImplied() { 138 // first clear those other parameters 139 for (String name : infosetImpliesFalse) { 140 if (domConfiguration.canSetParameter(name, true)) { 141 domConfiguration.setParameter(name, true); 142 } 143 } 144 for (String name : infosetImpliesTrue) { 145 if (domConfiguration.canSetParameter(name, false)) { 146 domConfiguration.setParameter(name, false); 147 } 148 } 149 150 // set infoset 151 domConfiguration.setParameter("infoset", true); 152 153 // now the parameters should all match what infoset implies 154 for (String name : infosetImpliesFalse) { 155 assertEquals(false, domConfiguration.getParameter(name)); 156 } 157 for (String name : infosetImpliesTrue) { 158 assertEquals(true, domConfiguration.getParameter(name)); 159 } 160 } 161 162 public void testSettingImpliedUpdatesInfoset() { 163 for (String name : infosetImpliesFalse) { 164 domConfiguration.setParameter("infoset", true); 165 if (domConfiguration.canSetParameter(name, true)) { 166 domConfiguration.setParameter(name, true); 167 assertEquals(false, domConfiguration.getParameter("infoset")); 168 } 169 } 170 171 for (String name : infosetImpliesTrue) { 172 domConfiguration.setParameter("infoset", true); 173 if (domConfiguration.canSetParameter(name, false)) { 174 domConfiguration.setParameter(name, false); 175 assertEquals(false, domConfiguration.getParameter("infoset")); 176 } 177 } 178 } 179 180 public void testNamespaces() { 181 assertEquals(true, domConfiguration.getParameter("namespaces")); 182 assertSupported("namespaces", false); 183 assertSupported("namespaces", true); 184 } 185 186 public void testNamespaceDeclarations() { 187 assertEquals(true, domConfiguration.getParameter("namespace-declarations")); 188 assertUnsupported("namespace-declarations", false); // supported in RI 6 189 assertSupported("namespace-declarations", true); 190 } 191 192 public void testNormalizeCharacters() { 193 assertEquals(false, domConfiguration.getParameter("normalize-characters")); 194 assertSupported("normalize-characters", false); 195 assertUnsupported("normalize-characters", true); 196 } 197 198 public void testSchemaLocation() { 199 assertEquals(null, domConfiguration.getParameter("schema-location")); 200 assertSupported("schema-location", "http://foo"); 201 assertSupported("schema-location", null); 202 } 203 204 /** 205 * This fails under the RI because setParameter() succeeds even though 206 * canSetParameter() returns false. 207 */ 208 @KnownFailure("Dalvik doesn't honor the schema-type parameter") 209 public void testSchemaTypeDtd() { 210 assertUnsupported("schema-type", "http://www.w3.org/TR/REC-xml"); // supported in RI v6 211 } 212 213 public void testSchemaTypeXmlSchema() { 214 assertEquals(null, domConfiguration.getParameter("schema-type")); 215 assertSupported("schema-type", null); 216 assertSupported("schema-type", "http://www.w3.org/2001/XMLSchema"); 217 } 218 219 public void testSplitCdataSections() { 220 assertEquals(true, domConfiguration.getParameter("split-cdata-sections")); 221 assertSupported("split-cdata-sections", false); 222 assertSupported("split-cdata-sections", true); 223 } 224 225 public void testValidate() { 226 assertEquals(false, domConfiguration.getParameter("validate")); 227 assertSupported("validate", false); 228 assertSupported("validate", true); 229 } 230 231 public void testValidateIfSchema() { 232 assertEquals(false, domConfiguration.getParameter("validate-if-schema")); 233 assertSupported("validate-if-schema", false); 234 assertUnsupported("validate-if-schema", true); 235 } 236 237 public void testWellFormed() { 238 assertEquals(true, domConfiguration.getParameter("well-formed")); 239 assertSupported("well-formed", false); 240 assertSupported("well-formed", true); 241 } 242 243 public void testMissingParameter() { 244 assertFalse(domConfiguration.canSetParameter("foo", true)); 245 try { 246 domConfiguration.getParameter("foo"); 247 fail(); 248 } catch (DOMException e) { 249 } 250 try { 251 domConfiguration.setParameter("foo", true); 252 fail(); 253 } catch (DOMException e) { 254 } 255 } 256 257 public void testNullKey() { 258 try { 259 domConfiguration.canSetParameter(null, true); 260 fail(); 261 } catch (NullPointerException e) { 262 } 263 try { 264 domConfiguration.getParameter(null); 265 fail(); 266 } catch (NullPointerException e) { 267 } 268 try { 269 domConfiguration.setParameter(null, true); 270 fail(); 271 } catch (NullPointerException e) { 272 } 273 } 274 275 public void testNullValue() { 276 String message = "This implementation's canSetParameter() disagrees" 277 + " with its setParameter()"; 278 try { 279 domConfiguration.setParameter("well-formed", null); 280 fail(message); 281 } catch (DOMException e) { 282 } 283 assertEquals(message, false, domConfiguration.canSetParameter("well-formed", null)); 284 } 285 286 public void testTypeMismatch() { 287 assertEquals(false, domConfiguration.canSetParameter("well-formed", "true")); 288 try { 289 domConfiguration.setParameter("well-formed", "true"); 290 fail(); 291 } catch (DOMException e) { 292 } 293 294 assertEquals(false, domConfiguration.canSetParameter("well-formed", new Object())); 295 try { 296 domConfiguration.setParameter("well-formed", new Object()); 297 fail(); 298 } catch (DOMException e) { 299 } 300 } 301 302 private void assertUnsupported(String name, Object value) { 303 String message = "This implementation's setParameter() supports an unexpected value: " 304 + name + "=" + value; 305 assertFalse(message, domConfiguration.canSetParameter(name, value)); 306 try { 307 domConfiguration.setParameter(name, value); 308 fail(message); 309 } catch (DOMException e) { 310 assertEquals(DOMException.NOT_SUPPORTED_ERR, e.code); 311 } 312 try { 313 domConfiguration.setParameter(name.toUpperCase(), value); 314 fail(message); 315 } catch (DOMException e) { 316 assertEquals(DOMException.NOT_SUPPORTED_ERR, e.code); 317 } 318 assertFalse(value.equals(domConfiguration.getParameter(name))); 319 } 320 321 private void assertSupported(String name, Object value) { 322 String message = "This implementation's canSetParameter() disagrees" 323 + " with its setParameter() for " + name + "=" + value; 324 try { 325 domConfiguration.setParameter(name, value); 326 } catch (DOMException e) { 327 if (domConfiguration.canSetParameter(name, value)) { 328 fail(message); 329 } else { 330 fail("This implementation's setParameter() doesn't support: " 331 + name + "=" + value); 332 } 333 } 334 assertTrue(message, domConfiguration.canSetParameter(name.toUpperCase(), value)); 335 assertTrue(message, domConfiguration.canSetParameter(name, value)); 336 assertEquals(value, domConfiguration.getParameter(name)); 337 domConfiguration.setParameter(name.toUpperCase(), value); 338 assertEquals(value, domConfiguration.getParameter(name.toUpperCase())); 339 } 340 341 public void testCdataSectionsNotHonoredByNodeNormalize() throws Exception { 342 String xml = "<foo>ABC<![CDATA[DEF]]>GHI</foo>"; 343 parse(xml); 344 domConfiguration.setParameter("cdata-sections", true); 345 document.getDocumentElement().normalize(); 346 assertEquals(xml, domToString(document)); 347 348 parse(xml); 349 domConfiguration.setParameter("cdata-sections", false); 350 document.getDocumentElement().normalize(); 351 assertEquals(xml, domToString(document)); 352 } 353 354 public void testCdataSectionsHonoredByDocumentNormalize() throws Exception { 355 String xml = "<foo>ABC<![CDATA[DEF]]>GHI</foo>"; 356 parse(xml); 357 domConfiguration.setParameter("cdata-sections", true); 358 document.normalizeDocument(); 359 assertEquals(xml, domToString(document)); 360 361 parse(xml); 362 domConfiguration.setParameter("cdata-sections", false); 363 document.normalizeDocument(); 364 String expected = xml.replace("<![CDATA[DEF]]>", "DEF"); 365 assertEquals(expected, domToString(document)); 366 } 367 368 public void testMergeAdjacentTextNodes() throws Exception { 369 document = createDocumentWithAdjacentTexts("abc", "def"); 370 document.getDocumentElement().normalize(); 371 assertChildren(document.getDocumentElement(), "abcdef"); 372 } 373 374 public void testMergeAdjacentEmptyTextNodes() throws Exception { 375 document = createDocumentWithAdjacentTexts("", "", ""); 376 document.getDocumentElement().normalize(); 377 assertChildren(document.getDocumentElement()); 378 } 379 380 public void testMergeAdjacentNodesWithNonTextSiblings() throws Exception { 381 document = createDocumentWithAdjacentTexts("abc", "def", "<br>", "ghi", "jkl"); 382 document.getDocumentElement().normalize(); 383 assertChildren(document.getDocumentElement(), "abcdef", "<br>", "ghijkl"); 384 } 385 386 public void testMergeAdjacentNodesEliminatesEmptyTexts() throws Exception { 387 document = createDocumentWithAdjacentTexts("", "", "<br>", "", "", "<br>", "", "<br>", ""); 388 document.getDocumentElement().normalize(); 389 assertChildren(document.getDocumentElement(), "<br>", "<br>", "<br>"); 390 } 391 392 public void testRetainingComments() throws Exception { 393 String xml = "<foo>ABC<!-- bar -->DEF<!-- baz -->GHI</foo>"; 394 parse(xml); 395 domConfiguration.setParameter("comments", true); 396 document.normalizeDocument(); 397 assertEquals(xml, domToString(document)); 398 } 399 400 public void testCommentContainingDoubleDash() throws Exception { 401 ErrorRecorder errorRecorder = new ErrorRecorder(); 402 domConfiguration.setParameter("error-handler", errorRecorder); 403 domConfiguration.setParameter("namespaces", false); 404 Element root = document.createElement("foo"); 405 document.appendChild(root); 406 root.appendChild(document.createComment("ABC -- DEF")); 407 document.normalizeDocument(); 408 errorRecorder.assertAllErrors(DOMError.SEVERITY_ERROR, "wf-invalid-character"); 409 } 410 411 public void testStrippingComments() throws Exception { 412 String xml = "<foo>ABC<!-- bar -->DEF<!-- baz -->GHI</foo>"; 413 parse(xml); 414 domConfiguration.setParameter("comments", false); 415 document.normalizeDocument(); 416 assertChildren(document.getDocumentElement(), "ABCDEFGHI"); 417 } 418 419 public void testSplittingCdataSectionsSplit() throws Exception { 420 ErrorRecorder errorRecorder = new ErrorRecorder(); 421 domConfiguration.setParameter("split-cdata-sections", true); 422 domConfiguration.setParameter("error-handler", errorRecorder); 423 domConfiguration.setParameter("namespaces", false); 424 Element root = document.createElement("foo"); 425 document.appendChild(root); 426 root.appendChild(document.createCDATASection("ABC]]>DEF]]>GHI")); 427 document.normalizeDocument(); 428 errorRecorder.assertAllErrors(DOMError.SEVERITY_WARNING, "cdata-sections-splitted"); 429 assertChildren(root, "<![CDATA[ABC]]]]>", "<![CDATA[>DEF]]]]>", "<![CDATA[>GHI]]>"); 430 } 431 432 public void testSplittingCdataSectionsReportError() throws Exception { 433 ErrorRecorder errorRecorder = new ErrorRecorder(); 434 domConfiguration.setParameter("split-cdata-sections", false); 435 domConfiguration.setParameter("error-handler", errorRecorder); 436 domConfiguration.setParameter("namespaces", false); 437 Element root = document.createElement("foo"); 438 document.appendChild(root); 439 root.appendChild(document.createCDATASection("ABC]]>DEF")); 440 document.normalizeDocument(); 441 errorRecorder.assertAllErrors(DOMError.SEVERITY_ERROR, "wf-invalid-character"); 442 } 443 444 public void testInvalidCharactersCdata() throws Exception { 445 ErrorRecorder errorRecorder = new ErrorRecorder(); 446 domConfiguration.setParameter("cdata-sections", true); 447 domConfiguration.setParameter("error-handler", errorRecorder); 448 domConfiguration.setParameter("namespaces", false); 449 Element root = document.createElement("foo"); 450 document.appendChild(root); 451 CDATASection cdata = document.createCDATASection(""); 452 root.appendChild(cdata); 453 454 for (int c = 0; c <= Character.MAX_VALUE; c++) { 455 cdata.setData(new String(new char[]{ 'A', 'B', (char) c })); 456 document.normalizeDocument(); 457 if (isValid((char) c)) { 458 assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors); 459 } else { 460 errorRecorder.assertAllErrors("For character " + c, 461 DOMError.SEVERITY_ERROR, "wf-invalid-character"); 462 } 463 } 464 } 465 466 public void testInvalidCharactersText() throws Exception { 467 ErrorRecorder errorRecorder = new ErrorRecorder(); 468 domConfiguration.setParameter("error-handler", errorRecorder); 469 domConfiguration.setParameter("namespaces", false); 470 Element root = document.createElement("foo"); 471 document.appendChild(root); 472 Text text = document.createTextNode(""); 473 root.appendChild(text); 474 475 for (int c = 0; c <= Character.MAX_VALUE; c++) { 476 text.setData(new String(new char[]{ 'A', 'B', (char) c })); 477 document.normalizeDocument(); 478 if (isValid((char) c)) { 479 assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors); 480 } else { 481 errorRecorder.assertAllErrors("For character " + c, 482 DOMError.SEVERITY_ERROR, "wf-invalid-character"); 483 } 484 } 485 } 486 487 public void testInvalidCharactersAttribute() throws Exception { 488 ErrorRecorder errorRecorder = new ErrorRecorder(); 489 domConfiguration.setParameter("error-handler", errorRecorder); 490 domConfiguration.setParameter("namespaces", false); 491 Element root = document.createElement("foo"); 492 document.appendChild(root); 493 494 for (int c = 0; c <= Character.MAX_VALUE; c++) { 495 root.setAttribute("bar", new String(new char[] { 'A', 'B', (char) c})); 496 document.normalizeDocument(); 497 if (isValid((char) c)) { 498 assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors); 499 } else { 500 errorRecorder.assertAllErrors("For character " + c, 501 DOMError.SEVERITY_ERROR, "wf-invalid-character"); 502 } 503 } 504 } 505 506 public void testInvalidCharactersComment() throws Exception { 507 ErrorRecorder errorRecorder = new ErrorRecorder(); 508 domConfiguration.setParameter("error-handler", errorRecorder); 509 domConfiguration.setParameter("namespaces", false); 510 Element root = document.createElement("foo"); 511 document.appendChild(root); 512 Comment comment = document.createComment(""); 513 root.appendChild(comment); 514 515 for (int c = 0; c <= Character.MAX_VALUE; c++) { 516 comment.setData(new String(new char[] { 'A', 'B', (char) c})); 517 document.normalizeDocument(); 518 if (isValid((char) c)) { 519 assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors); 520 } else { 521 errorRecorder.assertAllErrors("For character " + c, 522 DOMError.SEVERITY_ERROR, "wf-invalid-character"); 523 } 524 } 525 } 526 527 public void testInvalidCharactersProcessingInstructionData() throws Exception { 528 ErrorRecorder errorRecorder = new ErrorRecorder(); 529 domConfiguration.setParameter("error-handler", errorRecorder); 530 domConfiguration.setParameter("namespaces", false); 531 Element root = document.createElement("foo"); 532 document.appendChild(root); 533 ProcessingInstruction pi = document.createProcessingInstruction("foo", ""); 534 root.appendChild(pi); 535 536 for (int c = 0; c <= Character.MAX_VALUE; c++) { 537 pi.setData(new String(new char[] { 'A', 'B', (char) c})); 538 document.normalizeDocument(); 539 if (isValid((char) c)) { 540 assertEquals(Collections.<DOMError>emptyList(), errorRecorder.errors); 541 } else { 542 errorRecorder.assertAllErrors("For character " + c, 543 DOMError.SEVERITY_ERROR, "wf-invalid-character"); 544 } 545 } 546 } 547 548 // TODO: test for surrogates 549 550 private boolean isValid(char c) { 551 // as defined by http://www.w3.org/TR/REC-xml/#charsets. 552 return c == 0x9 || c == 0xA || c == 0xD || (c >= 0x20 && c <= 0xd7ff) 553 || (c >= 0xe000 && c <= 0xfffd); 554 } 555 556 private Document createDocumentWithAdjacentTexts(String... texts) throws Exception { 557 Document result = DocumentBuilderFactory.newInstance() 558 .newDocumentBuilder().newDocument(); 559 Element root = result.createElement("foo"); 560 result.appendChild(root); 561 for (String text : texts) { 562 if (text.equals("<br>")) { 563 root.appendChild(result.createElement("br")); 564 } else { 565 root.appendChild(result.createTextNode(text)); 566 } 567 } 568 return result; 569 } 570 571 private void assertChildren(Element element, String... texts) { 572 List<String> actual = new ArrayList<String>(); 573 NodeList nodes = element.getChildNodes(); 574 for (int i = 0; i < nodes.getLength(); i++) { 575 Node node = nodes.item(i); 576 if (node.getNodeType() == Node.TEXT_NODE) { 577 actual.add(((Text) node).getData()); 578 } else if (node.getNodeType() == Node.CDATA_SECTION_NODE) { 579 actual.add("<![CDATA[" + ((CDATASection) node).getData() + "]]>"); 580 } else { 581 actual.add("<" + node.getNodeName() + ">"); 582 } 583 } 584 assertEquals(Arrays.asList(texts), actual); 585 } 586 587 private void parse(String xml) throws Exception { 588 document = DocumentBuilderFactory.newInstance().newDocumentBuilder() 589 .parse(new InputSource(new StringReader(xml))); 590 domConfiguration = document.getDomConfig(); 591 } 592 593 private String domToString(Document document) throws TransformerException { 594 StringWriter writer = new StringWriter(); 595 Transformer transformer = TransformerFactory.newInstance() .newTransformer(); 596 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 597 transformer.transform(new DOMSource(document), new StreamResult(writer)); 598 return writer.toString(); 599 } 600 601 private class ErrorRecorder implements DOMErrorHandler { 602 private final List<DOMError> errors = new ArrayList<DOMError>(); 603 604 public boolean handleError(DOMError error) { 605 errors.add(error); 606 return true; 607 } 608 609 public void assertAllErrors(int severity, String type) { 610 assertAllErrors("Expected one or more " + type + " errors", severity, type); 611 } 612 613 public void assertAllErrors(String message, int severity, String type) { 614 assertFalse(message, errors.isEmpty()); 615 for (DOMError error : errors) { 616 assertEquals(message, severity, error.getSeverity()); 617 assertEquals(message, type, error.getType()); 618 } 619 errors.clear(); 620 } 621 } 622 } 623