1 /* 2 * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package common; 25 26 import static org.testng.Assert.assertEquals; 27 28 import java.io.IOException; 29 import java.io.StringReader; 30 import java.io.StringWriter; 31 32 import javax.xml.parsers.DocumentBuilder; 33 import javax.xml.parsers.DocumentBuilderFactory; 34 import javax.xml.parsers.ParserConfigurationException; 35 import javax.xml.transform.OutputKeys; 36 import javax.xml.transform.Transformer; 37 import javax.xml.transform.TransformerFactory; 38 import javax.xml.transform.dom.DOMSource; 39 import javax.xml.transform.stream.StreamResult; 40 import javax.xml.transform.stream.StreamSource; 41 42 import org.testng.Assert; 43 import org.testng.annotations.DataProvider; 44 import org.testng.annotations.Listeners; 45 import org.testng.annotations.Test; 46 import org.w3c.dom.DOMConfiguration; 47 import org.w3c.dom.DOMImplementation; 48 import org.w3c.dom.Document; 49 import org.w3c.dom.Node; 50 import org.w3c.dom.Text; 51 import org.w3c.dom.bootstrap.DOMImplementationRegistry; 52 import org.w3c.dom.ls.DOMImplementationLS; 53 import org.w3c.dom.ls.LSOutput; 54 import org.w3c.dom.ls.LSSerializer; 55 import org.xml.sax.InputSource; 56 import org.xml.sax.SAXException; 57 58 59 /* 60 * @test 61 * @bug 6439439 8087303 62 * @library /javax/xml/jaxp/libs /javax/xml/jaxp/unittest 63 * @run testng/othervm -DrunSecMngr=true common.PrettyPrintTest 64 * @run testng/othervm common.PrettyPrintTest 65 * @summary Test serializing xml and html with indentation. 66 */ 67 @Listeners({jaxp.library.BasePolicy.class}) 68 public class PrettyPrintTest { 69 /* 70 * test pure text content, CDATA, elements only, text and element, 71 * whitespace and element, xml:space property and nested xml:space property, 72 * mixed node types, sequent Text nodes. 73 */ 74 @DataProvider(name = "xml-data") 75 public Object[][] xmlData() throws Exception { 76 return new Object[][] { 77 { newTextNode(" abc def \nline2 &a \n test"), 78 " abc def \n" + 79 "line2 &a \n" + 80 " test" }, 81 { toXmlDocument("<a><![CDATA[ ]]></a>"), 82 "<a>\n" + 83 " <![CDATA[ ]]>\n" + 84 "</a>\n" }, 85 { toXmlDocument("<a><![CDATA[ abc def \nline2 &a \n test]]></a>"), 86 "<a>\n" + 87 " <![CDATA[ abc def \n" + 88 "line2 &a \n" + 89 " test]]>\n" + 90 "</a>\n" }, 91 { toXmlDocument("<rss version=\"2.0\"><ns:test id='i001'><ns:child></ns:child></ns:test></rss>"), 92 "<rss version=\"2.0\">\n" + 93 " <ns:test id=\"i001\">\n" + 94 " <ns:child/>\n" + 95 " </ns:test>\n" + 96 "</rss>\n" }, 97 { toXmlDocument("<rss version=\"2.0\"><ns:test id='i001'><ns:child>child1</ns:child>" 98 + "<test att1='v1'> abc test</test></ns:test></rss>"), 99 "<rss version=\"2.0\">\n" + 100 " <ns:test id=\"i001\">\n" + 101 " <ns:child>child1</ns:child>\n" + 102 " <test att1=\"v1\"> abc test</test>\n" + 103 " </ns:test>\n" + 104 "</rss>\n" }, 105 { toXmlDocument("<rss version=\"2.0\"><channel> <title>Java Tutorials and Examples 1</title> " 106 + "<language>en-us</language></channel><a><b> </b></a> <c>\n \n</c></rss>"), 107 "<rss version=\"2.0\">\n" + 108 " <channel>\n" + 109 " <title>Java Tutorials and Examples 1</title>\n" + 110 " <language>en-us</language>\n" + 111 " </channel>\n" + 112 " <a>\n" + 113 " <b/>\n" + 114 " </a>\n" + 115 " <c/>\n" + 116 "</rss>\n" }, 117 { toXmlDocument("<rss version=\"2.0\"><channel xml:space=\"preserve\"> " 118 + "<title>Java Tutorials and Examples 1</title> <language>en-us</language></channel></rss>"), 119 "<rss version=\"2.0\">\n" + 120 " <channel xml:space=\"preserve\"> <title>Java Tutorials and Examples 1</title> <language>en-us</language></channel>\n" + 121 "</rss>\n" }, 122 { toXmlDocument("<rss><layer1 xml:space=\"preserve\"> <title>Java </title> " 123 + "<layer2 xml:space=\"asfsa\"> <layer3> " 124 + "<layer4 xml:space=\"default\"> <l5>5</l5> \n </layer4>" 125 + " </layer3> </layer2> " 126 + "<layer2 xml:space=\"default\"> " 127 + "<layer3> <l4> </l4> </layer3> " 128 + "<layer3 xml:space=\"preserve\"> <l4> </l4> </layer3>" 129 + "</layer2> </layer1></rss>"), 130 "<rss>\n" + 131 " <layer1 xml:space=\"preserve\"> <title>Java </title> <layer2 xml:space=\"asfsa\"> <layer3> <layer4 xml:space=\"default\">\n" + 132 " <l5>5</l5>\n" + 133 " </layer4> </layer3> </layer2> <layer2 xml:space=\"default\">\n" + 134 " <layer3>\n" + 135 " <l4/>\n" + 136 " </layer3>\n" + 137 " <layer3 xml:space=\"preserve\"> <l4> </l4> </layer3>\n" + 138 " </layer2> </layer1>\n" + 139 "</rss>\n" }, 140 { toXmlDocument(MIXED_NODE_TYPES_XML), 141 "<root>\n" + 142 " \n" + 143 " t\n" + 144 " <![CDATA[ ]]>\n" + 145 " \n" + 146 "t \n" + 147 " \n" + 148 " <child1/>\n" + 149 " \n" + 150 " t\n" + 151 " <!-- test comment -->\n" + 152 " <child2/>\n" + 153 " <child5>\n" + 154 " \n" + 155 " t\n" + 156 " <?target1 test?>\n" + 157 " <child51>\n" + 158 " <child511>t</child511>\n" + 159 " </child51>\n" + 160 " <?target1 test?>\n" + 161 " \n" + 162 " t\n" + 163 " \n" + 164 " </child5>\n" + 165 "</root>\n" }, 166 { createDocWithSequentTextNodes(), 167 "<root>\n" + 168 " t\n" + 169 "t \n" + 170 " <child1/>\n" + 171 " t\n" + 172 " <child2/>\n" + 173 " <child3/>\n" + 174 " <child4/>\n" + 175 " <child5>\n" + 176 " t\n" + 177 " <child51>\n" + 178 " <child511>t</child511>\n" + 179 " </child51>\n" + 180 " t\n" + 181 " </child5>\n" + 182 " <!-- test comment -->\n" + 183 " <!-- -->\n" + 184 " <?target1 test?>\n" + 185 "</root>\n" } }; 186 } 187 188 /* 189 * @bug 8087303 190 * Test the whitespace text nodes are serialized with pretty-print by LSSerializer and transformer correctly 191 * 192 */ 193 @Test(dataProvider = "xml-data") 194 public void testXMLPrettyPrint(Node xml, String expected) throws Exception { 195 assertEquals(serializerWrite(xml), expected); 196 assertEquals(transform(xml).replaceAll("\r\n", "\n"), expected); 197 } 198 199 /* 200 * test block element, inline element, text, and mixed elements. 201 */ 202 @DataProvider(name = "html-data") 203 public Object[][] htmlData() throws Exception { 204 return new Object[][] { 205 { "<rss version=\"2.0\"><channel xml:space=\"preserve\"><title>Java Tutorials and Examples 1" + 206 "</title> <language>en-us</language></channel></rss>", 207 "<rss version=\"2.0\">\n" + 208 " <channel xml:space=\"preserve\">\n" + 209 " <title>Java Tutorials and Examples 1</title>\n" + 210 " <language>en-us</language>\n" + 211 " </channel>\n" + 212 "</rss>\n" }, 213 { "<html><code>Java</code><b><sup>TM</sup></b></html>", 214 "<html>\n" + 215 " <code>Java</code><b><sup>TM</sup></b>\n" + 216 "</html>\n" }, 217 { "<p>this is <a href=\"test.html\">a <strong>test</strong></a> page</p>", 218 "<p>\n" + 219 " this is <a href=\"test.html\">a <strong>test</strong></a> page\n" + 220 "</p>\n" }, 221 { "<html><body><h1>A heading</h1><p>new paragraph<form>an empty form</form>" + 222 "<a href=\"test.html\">test</a></p></body></html>", 223 "<html>\n" + 224 " <body>\n" + 225 " <h1>A heading</h1>\n" + 226 " <p>\n" + 227 " new paragraph\n" + 228 " <form>an empty form</form>\n" + 229 " <a href=\"test.html\">test</a>\n" + 230 " </p>\n" + 231 " </body>\n" + 232 "</html>\n" }, 233 { "<html><p>this is a mixed test <a href=\"test.html\">click<p>to the test" + 234 "</p>page</a>link end</p></html>", 235 "<html>\n" + 236 " <p>\n" + 237 " this is a mixed test <a href=\"test.html\">click\n" + 238 " <p>to the test</p>\n" + 239 " page</a>link end\n" + 240 " </p>\n" + 241 "</html>\n" }, 242 { "<p><a href=\"test.html\">text<table/></a> another<form/></p>", 243 "<p>\n" + 244 " <a href=\"test.html\">text\n" + 245 " <table></table>\n" + 246 " </a> another\n" + 247 " <form></form>\n" + 248 "</p>\n" }, 249 { MIXED_NODE_TYPES_XML, 250 "<root>\n" + 251 " \n" + 252 " t\n" + 253 " <![CDATA[ ]]>\n" + 254 " \n" + 255 "t \n" + 256 " \n" + 257 " <child1></child1>\n" + 258 " \n" + 259 " t\n" + 260 " <!-- test comment -->\n" + 261 " <child2></child2>\n" + 262 " <child5>\n" + 263 " \n" + 264 " t\n" + 265 " <?target1 test>\n" + 266 " <child51>\n" + 267 " <child511>t</child511>\n" + 268 " </child51>\n" + 269 " <?target1 test>\n" + 270 " \n" + 271 " t\n" + 272 " \n" + 273 " </child5>\n" + 274 "</root>\n" } }; 275 } 276 277 /* 278 * @bug 8087303 279 * Transform to HTML, test Pretty Print for HTML. 280 * 281 */ 282 @Test(dataProvider = "html-data") 283 public void testTransformToHTML(String source, String expected) throws Exception { 284 Transformer transformer = TransformerFactory.newInstance().newTransformer(); 285 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); 286 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 287 transformer.setOutputProperty(OutputKeys.METHOD, "html"); 288 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 289 290 StringWriter writer = new StringWriter(); 291 transformer.transform(new StreamSource(new StringReader(source)), new StreamResult(writer)); 292 assertEquals(writer.toString().replaceAll("\r\n", "\n"), expected); 293 } 294 295 @Test 296 public void testLSSerializerFormatPrettyPrint() { 297 298 final String XML_DOCUMENT = "<?xml version=\"1.0\" encoding=\"UTF-16\"?>\n" 299 + "<hello>before child element<child><children/><children/></child>after child element</hello>"; 300 /**JDK-8035467 301 * no newline in default output 302 */ 303 final String XML_DOCUMENT_DEFAULT_PRINT = 304 "<?xml version=\"1.0\" encoding=\"UTF-16\"?>" 305 + "<hello>" 306 + "before child element" 307 + "<child><children/><children/></child>" 308 + "after child element</hello>"; 309 310 final String XML_DOCUMENT_PRETTY_PRINT = "<?xml version=\"1.0\" encoding=\"UTF-16\"?><hello>\n" + 311 " before child element\n" + 312 " <child>\n" + 313 " <children/>\n" + 314 " <children/>\n" + 315 " </child>\n" + 316 " after child element\n" + 317 "</hello>\n"; 318 319 // it all begins with a Document 320 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 321 DocumentBuilder documentBuilder = null; 322 try { 323 documentBuilder = documentBuilderFactory.newDocumentBuilder(); 324 } catch (ParserConfigurationException parserConfigurationException) { 325 parserConfigurationException.printStackTrace(); 326 Assert.fail(parserConfigurationException.toString()); 327 } 328 Document document = null; 329 330 StringReader stringReader = new StringReader(XML_DOCUMENT); 331 InputSource inputSource = new InputSource(stringReader); 332 try { 333 document = documentBuilder.parse(inputSource); 334 } catch (SAXException saxException) { 335 saxException.printStackTrace(); 336 Assert.fail(saxException.toString()); 337 } catch (IOException ioException) { 338 ioException.printStackTrace(); 339 Assert.fail(ioException.toString()); 340 } 341 342 // query DOM Interfaces to get to a LSSerializer 343 DOMImplementation domImplementation = documentBuilder.getDOMImplementation(); 344 DOMImplementationLS domImplementationLS = (DOMImplementationLS) domImplementation; 345 LSSerializer lsSerializer = domImplementationLS.createLSSerializer(); 346 347 System.out.println("Serializer is: " + lsSerializer.getClass().getName() + " " + lsSerializer); 348 349 // get configuration 350 DOMConfiguration domConfiguration = lsSerializer.getDomConfig(); 351 352 // query current configuration 353 Boolean defaultFormatPrettyPrint = (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT); 354 Boolean canSetFormatPrettyPrintFalse = (Boolean) domConfiguration.canSetParameter(DOM_FORMAT_PRETTY_PRINT, Boolean.FALSE); 355 Boolean canSetFormatPrettyPrintTrue = (Boolean) domConfiguration.canSetParameter(DOM_FORMAT_PRETTY_PRINT, Boolean.TRUE); 356 357 System.out.println(DOM_FORMAT_PRETTY_PRINT + " default/can set false/can set true = " + defaultFormatPrettyPrint + "/" 358 + canSetFormatPrettyPrintFalse + "/" + canSetFormatPrettyPrintTrue); 359 360 // test values 361 assertEquals(defaultFormatPrettyPrint, Boolean.FALSE, "Default value of " + DOM_FORMAT_PRETTY_PRINT + " should be " + Boolean.FALSE); 362 363 assertEquals(canSetFormatPrettyPrintFalse, Boolean.TRUE, "Can set " + DOM_FORMAT_PRETTY_PRINT + " to " + Boolean.FALSE + " should be " 364 + Boolean.TRUE); 365 366 assertEquals(canSetFormatPrettyPrintTrue, Boolean.TRUE, "Can set " + DOM_FORMAT_PRETTY_PRINT + " to " + Boolean.TRUE + " should be " 367 + Boolean.TRUE); 368 369 // get default serialization 370 String prettyPrintDefault = lsSerializer.writeToString(document); 371 System.out.println("(default) " + DOM_FORMAT_PRETTY_PRINT + "==" + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT) 372 + ": \n\"" + prettyPrintDefault + "\""); 373 374 assertEquals(prettyPrintDefault, XML_DOCUMENT_DEFAULT_PRINT, "Invalid serialization with default value, " + DOM_FORMAT_PRETTY_PRINT + "==" 375 + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT)); 376 377 // configure LSSerializer to not format-pretty-print 378 domConfiguration.setParameter(DOM_FORMAT_PRETTY_PRINT, Boolean.FALSE); 379 String prettyPrintFalse = lsSerializer.writeToString(document); 380 System.out.println("(FALSE) " + DOM_FORMAT_PRETTY_PRINT + "==" + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT) 381 + ": \n\"" + prettyPrintFalse + "\""); 382 383 assertEquals(prettyPrintFalse, XML_DOCUMENT_DEFAULT_PRINT, "Invalid serialization with FALSE value, " + DOM_FORMAT_PRETTY_PRINT + "==" 384 + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT)); 385 386 // configure LSSerializer to format-pretty-print 387 domConfiguration.setParameter(DOM_FORMAT_PRETTY_PRINT, Boolean.TRUE); 388 String prettyPrintTrue = lsSerializer.writeToString(document); 389 System.out.println("(TRUE) " + DOM_FORMAT_PRETTY_PRINT + "==" + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT) 390 + ": \n\"" + prettyPrintTrue + "\""); 391 392 assertEquals(prettyPrintTrue, XML_DOCUMENT_PRETTY_PRINT, "Invalid serialization with TRUE value, " + DOM_FORMAT_PRETTY_PRINT + "==" 393 + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT)); 394 } 395 396 private String serializerWrite(Node xml) throws Exception { 397 DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance(); 398 DOMImplementationLS domImplementation = (DOMImplementationLS) registry.getDOMImplementation("LS"); 399 StringWriter writer = new StringWriter(); 400 LSOutput formattedOutput = domImplementation.createLSOutput(); 401 formattedOutput.setCharacterStream(writer); 402 LSSerializer domSerializer = domImplementation.createLSSerializer(); 403 domSerializer.getDomConfig().setParameter(DOM_FORMAT_PRETTY_PRINT, true); 404 domSerializer.getDomConfig().setParameter("xml-declaration", false); 405 domSerializer.write(xml, formattedOutput); 406 return writer.toString(); 407 } 408 409 private String transform(Node xml) throws Exception { 410 Transformer transformer = TransformerFactory.newInstance().newTransformer(); 411 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); 412 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 413 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 414 StringWriter writer = new StringWriter(); 415 transformer.transform(new DOMSource(xml), new StreamResult(writer)); 416 return writer.toString(); 417 } 418 419 private Document toXmlDocument(String xmlString) throws Exception { 420 InputSource xmlInputSource = new InputSource(new StringReader(xmlString)); 421 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 422 dbf.setValidating(true); 423 DocumentBuilder xmlDocumentBuilder = dbf.newDocumentBuilder(); 424 Document node = xmlDocumentBuilder.parse(xmlInputSource); 425 return node; 426 } 427 428 private Text newTextNode(String text) throws Exception { 429 DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 430 return db.newDocument().createTextNode(text); 431 } 432 433 private Document createDocWithSequentTextNodes() throws Exception { 434 DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 435 Document doc = db.newDocument(); 436 Node root = doc.createElement("root"); 437 doc.appendChild(root); 438 root.appendChild(doc.createTextNode(" ")); 439 root.appendChild(doc.createTextNode("t")); 440 root.appendChild(doc.createTextNode("\n")); 441 root.appendChild(doc.createTextNode("t")); 442 root.appendChild(doc.createTextNode(" ")); 443 Node child1 = doc.createElement("child1"); 444 root.appendChild(child1); 445 child1.appendChild(doc.createTextNode(" ")); 446 child1.appendChild(doc.createTextNode("\n")); 447 root.appendChild(doc.createTextNode("t")); 448 Node child2 = doc.createElement("child2"); 449 root.appendChild(child2); 450 child2.appendChild(doc.createTextNode(" ")); 451 root.appendChild(doc.createTextNode(" ")); 452 Node child3 = doc.createElement("child3"); 453 root.appendChild(child3); 454 child3.appendChild(doc.createTextNode(" ")); 455 root.appendChild(doc.createTextNode(" ")); 456 Node child4 = doc.createElement("child4"); 457 root.appendChild(child4); 458 child4.appendChild(doc.createTextNode(" ")); 459 460 root.appendChild(doc.createTextNode(" ")); 461 Node child5 = doc.createElement("child5"); 462 root.appendChild(child5); 463 child5.appendChild(doc.createTextNode("t")); 464 465 Node child51 = doc.createElement("child51"); 466 child5.appendChild(child51); 467 child51.appendChild(doc.createTextNode(" ")); 468 Node child511 = doc.createElement("child511"); 469 child51.appendChild(child511); 470 child511.appendChild(doc.createTextNode("t")); 471 child51.appendChild(doc.createTextNode(" ")); 472 child5.appendChild(doc.createTextNode("t")); 473 474 root.appendChild(doc.createTextNode(" ")); 475 root.appendChild(doc.createComment(" test comment ")); 476 root.appendChild(doc.createTextNode(" \n")); 477 root.appendChild(doc.createComment(" ")); 478 root.appendChild(doc.createTextNode("\n")); 479 root.appendChild(doc.createProcessingInstruction("target1", "test")); 480 root.appendChild(doc.createTextNode(" ")); 481 root.appendChild(doc.createTextNode(" ")); 482 return doc; 483 } 484 485 private static final String DOM_FORMAT_PRETTY_PRINT = "format-pretty-print"; 486 487 private static final String MIXED_NODE_TYPES_XML = "<root>\n" + 488 " t<![CDATA[ ]]>\n" + 489 "t \n" + 490 " <child1/>\n" + 491 " t<!-- test comment -->\n" + 492 " <child2/>\n" + 493 " <child5>\n" + 494 " t<?target1 test?>\n" + 495 " <child51>\n" + 496 " <child511>t</child511>\n" + 497 " </child51><?target1 test?>\n" + 498 " t\n" + 499 " </child5>\n" + 500 " \n" + 501 "</root> \n"; 502 }