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.prettyprint;
  25 
  26 import static org.testng.Assert.assertEquals;
  27 import static org.testng.Assert.assertTrue;
  28 
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.io.StringReader;
  32 import java.io.StringWriter;
  33 
  34 import javax.xml.parsers.DocumentBuilder;
  35 import javax.xml.parsers.DocumentBuilderFactory;
  36 import javax.xml.parsers.ParserConfigurationException;
  37 import javax.xml.transform.OutputKeys;
  38 import javax.xml.transform.Transformer;
  39 import javax.xml.transform.TransformerFactory;
  40 import javax.xml.transform.dom.DOMSource;
  41 import javax.xml.transform.stream.StreamResult;
  42 import javax.xml.transform.stream.StreamSource;
  43 
  44 import org.testng.Assert;
  45 import org.testng.annotations.DataProvider;
  46 import org.testng.annotations.Listeners;
  47 import org.testng.annotations.Test;
  48 import org.w3c.dom.DOMConfiguration;
  49 import org.w3c.dom.DOMImplementation;
  50 import org.w3c.dom.Document;
  51 import org.w3c.dom.Node;
  52 import org.w3c.dom.Text;
  53 import org.w3c.dom.bootstrap.DOMImplementationRegistry;
  54 import org.w3c.dom.ls.DOMImplementationLS;
  55 import org.w3c.dom.ls.LSOutput;
  56 import org.w3c.dom.ls.LSSerializer;
  57 import org.xml.sax.InputSource;
  58 import org.xml.sax.SAXException;
  59 
  60 
  61 /*
  62  * @test
  63  * @bug 6439439 8087303 8174025
  64  * @library /javax/xml/jaxp/libs /javax/xml/jaxp/unittest
  65  * @run testng/othervm -DrunSecMngr=true common.prettyprint.PrettyPrintTest
  66  * @run testng/othervm common.prettyprint.PrettyPrintTest
  67  * @summary Test serializing xml and html with indentation.
  68  */
  69 @Listeners({jaxp.library.FilePolicy.class})
  70 public class PrettyPrintTest {
  71     /*
  72      * test CDATA, elements only, text and element, xml:space property, mixed
  73      * node types.
  74      */
  75     @DataProvider(name = "xml-data")
  76     public Object[][] xmlData() throws Exception {
  77         return new Object[][] {
  78                 { "xmltest1.xml", "xmltest1.out" },
  79                 { "xmltest2.xml", "xmltest2.out" },
  80                 { "xmltest3.xml", "xmltest3.out" },
  81                 { "xmltest4.xml", "xmltest4.out" },
  82                 { "xmltest6.xml", "xmltest6.out" },
  83                 { "xmltest8.xml", "xmltest8.out" } };
  84     }
  85 
  86     /*
  87      * @bug 8087303
  88      * Test the xml document are serialized with pretty-print by
  89      * LSSerializer and transformer correctly
  90      *
  91      */
  92     @Test(dataProvider = "xml-data")
  93     public void testXMLPrettyPrint(String sourceFile, String expectedFile) throws Exception {
  94         String source = read(sourceFile);
  95         String expected = read(expectedFile);
  96         // test it's no change if no pretty-print
  97         String result = serializerWrite(toXmlDocument(source), false);
  98         assertTrue(toXmlDocument(source).isEqualNode(toXmlDocument(result)), "The actual is: " + result);
  99         // test pretty-print
 100         assertEquals(serializerWrite(toXmlDocument(source), true), expected);
 101         // test it's no change if no pretty-print
 102         result = transform(toXmlDocument(source), false);
 103         assertTrue(toXmlDocument(source).isEqualNode(toXmlDocument(result)), "The actual is: " + result);
 104         // test pretty-print
 105         assertEquals(transform(toXmlDocument(source), true).replaceAll("\r\n", "\n"), expected);
 106     }
 107 
 108 
 109     /*
 110      * @bug 8087303
 111      * Test a single text node is serialized with pretty-print by
 112      * LSSerializer and transformer correctly
 113      *
 114      */
 115     @Test
 116     public void testSingleTextNode() throws Exception {
 117         Node xml = newTextNode(read("nodetest1.txt"));
 118         String expected = read("nodetest1.out");
 119         assertEquals(serializerWrite(xml, true), expected);
 120         assertEquals(transform(xml, true).replaceAll("\r\n", "\n"), expected);
 121     }
 122 
 123     /*
 124      * @bug 8087303
 125      * Test the transformer shall keep all whitespace text node in
 126      * sequent text nodes
 127      *
 128      */
 129     @Test
 130     public void testSequentTextNodesWithTransformer() throws Exception {
 131         Node xml = createDocWithSequentTextNodes();
 132         String expected = read("nodetest2.out");
 133         assertEquals(transform(xml, true).replaceAll("\r\n", "\n"), expected);
 134     }
 135 
 136     /*
 137      * @bug 8087303
 138      * Test LSSerializer shall eliminate the whitespace text node
 139      * in sequent text nodes
 140      *
 141      */
 142     @Test
 143     public void testSequentTextNodesWithLSSerializer() throws Exception {
 144         Node xml = createDocWithSequentTextNodes();
 145         String expected = read("nodetest2ls.out");
 146         assertEquals(serializerWrite(xml, true), expected);
 147     }
 148 
 149 
 150     /*
 151      * test whitespace and element, nested xml:space property.
 152      */
 153     @DataProvider(name = "xml-data-whitespace-ls")
 154     public Object[][] whitespaceLS() throws Exception {
 155         return new Object[][] {
 156                 { "xmltest5.xml", "xmltest5ls.out" },
 157                 { "xmltest7.xml", "xmltest7ls.out" } };
 158     }
 159 
 160     /*
 161      * @bug 8087303
 162      * Test LSSerializer shall eliminate the whitespace text node
 163      * unless xml:space="preserve"
 164      *
 165      */
 166     @Test(dataProvider = "xml-data-whitespace-ls")
 167     public void testWhitespaceWithLSSerializer(String sourceFile, String expectedFile) throws Exception {
 168         String source = read(sourceFile);
 169         String expected = read(expectedFile);
 170         // test it's no change if no pretty-print
 171         String result = serializerWrite(toXmlDocument(source), false);
 172         assertTrue(toXmlDocument(source).isEqualNode(toXmlDocument(result)), "The actual is: " + result);
 173         // test pretty-print
 174         assertEquals(serializerWrite(toXmlDocument(source), true), expected);
 175     }
 176 
 177     /*
 178      * test whitespace and element, nested xml:space property.
 179      */
 180     @DataProvider(name = "xml-data-whitespace-xslt")
 181     public Object[][] whitespaceXSLT() throws Exception {
 182         return new Object[][] {
 183                 { "xmltest5.xml", "xmltest5xslt.out" },
 184                 { "xmltest7.xml", "xmltest7xslt.out" } };
 185     }
 186 
 187     /*
 188      * @bug 8087303
 189      * Test the transformer shall format the output but keep all
 190      * whitespace text node even if xml:space="preserve"
 191      *
 192      */
 193     @Test(dataProvider = "xml-data-whitespace-xslt")
 194     public void testWhitespaceWithTransformer(String sourceFile, String expectedFile) throws Exception {
 195         String source = read(sourceFile);
 196         String expected = read(expectedFile);
 197         // test it's no change if no pretty-print
 198         String result = transform(toXmlDocument(source), false);
 199         assertTrue(toXmlDocument(source).isEqualNode(toXmlDocument(result)), "The actual is: " + result);
 200         // test pretty-print
 201         assertEquals(transform(toXmlDocument(source), true).replaceAll("\r\n", "\n"), expected);
 202     }
 203 
 204     /*
 205      * test block element, inline element, text, and mixed elements.
 206      */
 207     @DataProvider(name = "html-data")
 208     public Object[][] htmlData() throws Exception {
 209         return new Object[][] {
 210             { "htmltest1.xml", "htmltest1.out" },
 211             { "htmltest2.xml", "htmltest2.out" },
 212             { "htmltest3.xml", "htmltest3.out" },
 213             { "htmltest4.xml", "htmltest4.out" },
 214             { "htmltest5.xml", "htmltest5.out" },
 215             { "htmltest6.xml", "htmltest6.out" },
 216             /* @bug 8174025, test whitespace between inline elements */
 217             { "htmltest7.xml", "htmltest7.out" } };
 218     }
 219 
 220     /*
 221      * @bug 8087303
 222      * Transform to HTML, test Pretty Print for HTML.
 223      *
 224      */
 225     @Test(dataProvider = "html-data")
 226     public void testTransformToHTML(String sourceFile, String expectedFile) throws Exception {
 227         String source = read(sourceFile);
 228         String expected = read(expectedFile);
 229         // test it's no change if no pretty-print
 230         StringWriter writer = new StringWriter();
 231         getTransformer(true, false).transform(new StreamSource(new StringReader(source)), new StreamResult(writer));
 232         assertTrue(toXmlDocument(source).isEqualNode(toXmlDocument(writer.toString())), "The actual is: " + writer.toString());
 233 
 234         // test pretty-print
 235         writer = new StringWriter();
 236         getTransformer(true, true).transform(new StreamSource(new StringReader(source)), new StreamResult(writer));
 237         assertEquals(writer.toString().replaceAll("\r\n", "\n"), expected);
 238     }
 239 
 240     /*
 241      * @bug 8174025
 242      * Test the serializer can handle <xsl:text disable-output-escaping="yes"> correctly.
 243      *
 244      */
 245     @Test
 246     public void testDisableOutputEscaping() throws Exception {
 247         final String xsl ="generate-catalog.xsl";
 248         final String xml ="simple-entity-resolver-config.xml";
 249         final String expectedOutput ="simple-entity-resolver-config-transformed.xml";
 250         TransformerFactory factory = TransformerFactory.newInstance();
 251         Transformer transformer = factory.newTemplates(new StreamSource(new StringReader(read(xsl)))).newTransformer();
 252 
 253         String key = "schemaBase";
 254         String value = "schemas";
 255         transformer.setParameter(key, value);
 256         StringWriter writer = new StringWriter();
 257         transformer.transform(new StreamSource(new StringReader(read(xml))), new StreamResult(writer));
 258         assertEquals(writer.toString().replaceAll("\r\n", "\n"), read(expectedOutput));
 259     }
 260 
 261     @Test
 262     public void testLSSerializerFormatPrettyPrint() {
 263 
 264         final String XML_DOCUMENT = "<?xml version=\"1.0\" encoding=\"UTF-16\"?>\n"
 265                 + "<hello>before child element<child><children/><children/></child>after child element</hello>";
 266         /**JDK-8035467
 267          * no newline in default output
 268          */
 269         final String XML_DOCUMENT_DEFAULT_PRINT =
 270                 "<?xml version=\"1.0\" encoding=\"UTF-16\"?>"
 271                 + "<hello>"
 272                 + "before child element"
 273                 + "<child><children/><children/></child>"
 274                 + "after child element</hello>";
 275 
 276         final String XML_DOCUMENT_PRETTY_PRINT = "<?xml version=\"1.0\" encoding=\"UTF-16\"?><hello>\n" +
 277                 "    before child element\n" +
 278                 "    <child>\n" +
 279                 "        <children/>\n" +
 280                 "        <children/>\n" +
 281                 "    </child>\n" +
 282                 "    after child element\n" +
 283                 "</hello>\n";
 284 
 285         // it all begins with a Document
 286         DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
 287         DocumentBuilder documentBuilder = null;
 288         try {
 289             documentBuilder = documentBuilderFactory.newDocumentBuilder();
 290         } catch (ParserConfigurationException parserConfigurationException) {
 291             parserConfigurationException.printStackTrace();
 292             Assert.fail(parserConfigurationException.toString());
 293         }
 294         Document document = null;
 295 
 296         StringReader stringReader = new StringReader(XML_DOCUMENT);
 297         InputSource inputSource = new InputSource(stringReader);
 298         try {
 299             document = documentBuilder.parse(inputSource);
 300         } catch (SAXException saxException) {
 301             saxException.printStackTrace();
 302             Assert.fail(saxException.toString());
 303         } catch (IOException ioException) {
 304             ioException.printStackTrace();
 305             Assert.fail(ioException.toString());
 306         }
 307 
 308         // query DOM Interfaces to get to a LSSerializer
 309         DOMImplementation domImplementation = documentBuilder.getDOMImplementation();
 310         DOMImplementationLS domImplementationLS = (DOMImplementationLS) domImplementation;
 311         LSSerializer lsSerializer = domImplementationLS.createLSSerializer();
 312 
 313         System.out.println("Serializer is: " + lsSerializer.getClass().getName() + " " + lsSerializer);
 314 
 315         // get configuration
 316         DOMConfiguration domConfiguration = lsSerializer.getDomConfig();
 317 
 318         // query current configuration
 319         Boolean defaultFormatPrettyPrint = (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT);
 320         Boolean canSetFormatPrettyPrintFalse = (Boolean) domConfiguration.canSetParameter(DOM_FORMAT_PRETTY_PRINT, Boolean.FALSE);
 321         Boolean canSetFormatPrettyPrintTrue = (Boolean) domConfiguration.canSetParameter(DOM_FORMAT_PRETTY_PRINT, Boolean.TRUE);
 322 
 323         System.out.println(DOM_FORMAT_PRETTY_PRINT + " default/can set false/can set true = " + defaultFormatPrettyPrint + "/"
 324                 + canSetFormatPrettyPrintFalse + "/" + canSetFormatPrettyPrintTrue);
 325 
 326         // test values
 327         assertEquals(defaultFormatPrettyPrint, Boolean.FALSE, "Default value of " + DOM_FORMAT_PRETTY_PRINT + " should be " + Boolean.FALSE);
 328 
 329         assertEquals(canSetFormatPrettyPrintFalse, Boolean.TRUE, "Can set " + DOM_FORMAT_PRETTY_PRINT + " to " + Boolean.FALSE + " should be "
 330                 + Boolean.TRUE);
 331 
 332         assertEquals(canSetFormatPrettyPrintTrue, Boolean.TRUE, "Can set " + DOM_FORMAT_PRETTY_PRINT + " to " + Boolean.TRUE + " should be "
 333                 + Boolean.TRUE);
 334 
 335         // get default serialization
 336         String prettyPrintDefault = lsSerializer.writeToString(document);
 337         System.out.println("(default) " + DOM_FORMAT_PRETTY_PRINT + "==" + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT)
 338                 + ": \n\"" + prettyPrintDefault + "\"");
 339 
 340         assertEquals(prettyPrintDefault, XML_DOCUMENT_DEFAULT_PRINT, "Invalid serialization with default value, " + DOM_FORMAT_PRETTY_PRINT + "=="
 341                 + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT));
 342 
 343         // configure LSSerializer to not format-pretty-print
 344         domConfiguration.setParameter(DOM_FORMAT_PRETTY_PRINT, Boolean.FALSE);
 345         String prettyPrintFalse = lsSerializer.writeToString(document);
 346         System.out.println("(FALSE) " + DOM_FORMAT_PRETTY_PRINT + "==" + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT)
 347                 + ": \n\"" + prettyPrintFalse + "\"");
 348 
 349         assertEquals(prettyPrintFalse, XML_DOCUMENT_DEFAULT_PRINT, "Invalid serialization with FALSE value, " + DOM_FORMAT_PRETTY_PRINT + "=="
 350                 + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT));
 351 
 352         // configure LSSerializer to format-pretty-print
 353         domConfiguration.setParameter(DOM_FORMAT_PRETTY_PRINT, Boolean.TRUE);
 354         String prettyPrintTrue = lsSerializer.writeToString(document);
 355         System.out.println("(TRUE) " + DOM_FORMAT_PRETTY_PRINT + "==" + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT)
 356                 + ": \n\"" + prettyPrintTrue + "\"");
 357 
 358         assertEquals(prettyPrintTrue, XML_DOCUMENT_PRETTY_PRINT, "Invalid serialization with TRUE value, " + DOM_FORMAT_PRETTY_PRINT + "=="
 359                 + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT));
 360     }
 361 
 362     private String serializerWrite(Node xml, boolean pretty) throws Exception {
 363         DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
 364         DOMImplementationLS domImplementation = (DOMImplementationLS) registry.getDOMImplementation("LS");
 365         StringWriter writer = new StringWriter();
 366         LSOutput formattedOutput = domImplementation.createLSOutput();
 367         formattedOutput.setCharacterStream(writer);
 368         LSSerializer domSerializer = domImplementation.createLSSerializer();
 369         domSerializer.getDomConfig().setParameter(DOM_FORMAT_PRETTY_PRINT, pretty);
 370         domSerializer.getDomConfig().setParameter("xml-declaration", false);
 371         domSerializer.write(xml, formattedOutput);
 372         return writer.toString();
 373     }
 374 
 375     private String transform(Node xml, boolean pretty) throws Exception {
 376         Transformer transformer = getTransformer(false, pretty);
 377         StringWriter writer = new StringWriter();
 378         transformer.transform(new DOMSource(xml), new StreamResult(writer));
 379         return writer.toString();
 380     }
 381 
 382     private Document toXmlDocument(String xmlString) throws Exception {
 383         InputSource xmlInputSource = new InputSource(new StringReader(xmlString));
 384         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
 385         dbf.setValidating(true);
 386         DocumentBuilder xmlDocumentBuilder = dbf.newDocumentBuilder();
 387         Document node = xmlDocumentBuilder.parse(xmlInputSource);
 388         return node;
 389     }
 390 
 391     private Text newTextNode(String text) throws Exception {
 392         DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 393         return db.newDocument().createTextNode(text);
 394     }
 395 
 396     private Document createDocWithSequentTextNodes() throws Exception {
 397         DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 398         Document doc = db.newDocument();
 399         Node root = doc.createElement("root");
 400         doc.appendChild(root);
 401         root.appendChild(doc.createTextNode("\n"));
 402         root.appendChild(doc.createTextNode("\n"));
 403         root.appendChild(doc.createTextNode("\n"));
 404         root.appendChild(doc.createTextNode(" "));
 405         root.appendChild(doc.createTextNode("t"));
 406         root.appendChild(doc.createTextNode("\n"));
 407         root.appendChild(doc.createTextNode("t"));
 408         root.appendChild(doc.createTextNode("   "));
 409         Node child1 = doc.createElement("child1");
 410         root.appendChild(child1);
 411         child1.appendChild(doc.createTextNode(" "));
 412         child1.appendChild(doc.createTextNode("\n"));
 413         root.appendChild(doc.createTextNode("t"));
 414         Node child2 = doc.createElement("child2");
 415         root.appendChild(child2);
 416         child2.appendChild(doc.createTextNode(" "));
 417         root.appendChild(doc.createTextNode(" "));
 418         Node child3 = doc.createElement("child3");
 419         root.appendChild(child3);
 420         child3.appendChild(doc.createTextNode(" "));
 421         root.appendChild(doc.createTextNode(" "));
 422         Node child4 = doc.createElement("child4");
 423         root.appendChild(child4);
 424         child4.appendChild(doc.createTextNode(" "));
 425 
 426         root.appendChild(doc.createTextNode(" "));
 427         Node child5 = doc.createElement("child5");
 428         root.appendChild(child5);
 429         child5.appendChild(doc.createTextNode("t"));
 430 
 431         Node child51 = doc.createElement("child51");
 432         child5.appendChild(child51);
 433         child51.appendChild(doc.createTextNode(" "));
 434         Node child511 = doc.createElement("child511");
 435         child51.appendChild(child511);
 436         child511.appendChild(doc.createTextNode("t"));
 437         child51.appendChild(doc.createTextNode(" "));
 438         child5.appendChild(doc.createTextNode("t"));
 439 
 440         root.appendChild(doc.createTextNode(" "));
 441         root.appendChild(doc.createComment(" test comment "));
 442         root.appendChild(doc.createTextNode(" \n"));
 443         root.appendChild(doc.createComment(" "));
 444         root.appendChild(doc.createTextNode("\n"));
 445         root.appendChild(doc.createProcessingInstruction("target1", "test"));
 446         root.appendChild(doc.createTextNode(" "));
 447         root.appendChild(doc.createTextNode(" "));
 448         return doc;
 449     }
 450 
 451     private Transformer getTransformer(boolean html, boolean pretty) throws Exception {
 452         Transformer transformer = TransformerFactory.newInstance().newTransformer();
 453         transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
 454         transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
 455         if (html)
 456             transformer.setOutputProperty(OutputKeys.METHOD, "html");
 457         transformer.setOutputProperty(OutputKeys.INDENT, pretty ? "yes" : "no");
 458         return transformer;
 459     }
 460 
 461 
 462     private String read(String filename) throws Exception {
 463         try (InputStream in = PrettyPrintTest.class.getResourceAsStream(filename)) {
 464             return new String(in.readAllBytes());
 465         }
 466     }
 467 
 468     private static final String DOM_FORMAT_PRETTY_PRINT = "format-pretty-print";
 469 }