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
  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, whitespace and element,
  73      * xml:space property and nested xml:space property, mixed node types.
  74      */
  75     @DataProvider(name = "xml-data")
  76     public Object[][] xmlData() throws Exception {
  77         return new Object[][] {
  78                 { read("xmltest1.xml"), read("xmltest1.out") },
  79                 { read("xmltest2.xml"), read("xmltest2.out") },
  80                 { read("xmltest3.xml"), read("xmltest3.out") },
  81                 { read("xmltest4.xml"), read("xmltest4.out") },
  82                 { read("xmltest5.xml"), read("xmltest5.out") },
  83                 { read("xmltest6.xml"), read("xmltest6.out") },
  84                 { read("xmltest7.xml"), read("xmltest7.out") },
  85                 { read("xmltest8.xml"), read("xmltest8.out") } };
  86     }
  87 
  88     /*
  89      * @bug 8087303
  90      * Test the whitespace text nodes are serialized with pretty-print by LSSerializer and transformer correctly
  91      *
  92      */
  93     @Test(dataProvider = "xml-data")
  94     public void testXMLPrettyPrint(String source, String expected) throws Exception {
  95         // test it's no change if no pretty-print
  96         String result = serializerWrite(toXmlDocument(source), false);
  97         assertTrue(toXmlDocument(source).isEqualNode(toXmlDocument(result)), "The actual is: " + result);
  98         // test pretty-print
  99         assertEquals(serializerWrite(toXmlDocument(source), true), expected);
 100         // test it's no change if no pretty-print
 101         result = transform(toXmlDocument(source), false);
 102         assertTrue(toXmlDocument(source).isEqualNode(toXmlDocument(result)), "The actual is: " + result);
 103         // test pretty-print
 104         assertEquals(transform(toXmlDocument(source), true).replaceAll("\r\n", "\n"), expected);
 105     }
 106     
 107     /*
 108      * test pure text content, and sequent Text nodes.
 109      */
 110     @DataProvider(name = "xml-node-data")
 111     public Object[][] xmlNodeData() throws Exception {
 112         return new Object[][] {
 113                 { newTextNode(read("nodetest1.txt")), read("nodetest1.out") },
 114                 { createDocWithSequentTextNodes(), read("nodetest2.out") } };
 115     }
 116     
 117     /*
 118      * @bug 8087303
 119      * Test the whitespace text nodes are serialized with pretty-print by LSSerializer and transformer correctly,
 120      * doesn't compare with the source because the test data is Node object
 121      *
 122      */
 123     @Test(dataProvider = "xml-node-data")
 124     public void testXMLNodePrettyPrint(Node xml, String expected) throws Exception {
 125         assertEquals(serializerWrite(xml, true), expected);
 126         assertEquals(transform(xml, true).replaceAll("\r\n", "\n"), expected);
 127     }
 128 
 129     /*
 130      * test block element, inline element, text, and mixed elements.
 131      */
 132     @DataProvider(name = "html-data")
 133     public Object[][] htmlData() throws Exception {
 134         return new Object[][] {
 135             { read("htmltest1.xml"), read("htmltest1.out") },
 136             { read("htmltest2.xml"), read("htmltest2.out") },
 137             { read("htmltest3.xml"), read("htmltest3.out") },
 138             { read("htmltest4.xml"), read("htmltest4.out") },
 139             { read("htmltest5.xml"), read("htmltest5.out") },
 140             { read("htmltest6.xml"), read("htmltest6.out") } };
 141     }
 142 
 143     /*
 144      * @bug 8087303
 145      * Transform to HTML, test Pretty Print for HTML.
 146      *
 147      */
 148     @Test(dataProvider = "html-data")
 149     public void testTransformToHTML(String source, String expected) throws Exception {
 150         // test it's no change if no pretty-print
 151         StringWriter writer = new StringWriter();
 152         getTransformer(true, false).transform(new StreamSource(new StringReader(source)), new StreamResult(writer));
 153         assertTrue(toXmlDocument(source).isEqualNode(toXmlDocument(writer.toString())), "The actual is: " + writer.toString());
 154  
 155         // test pretty-print
 156         writer = new StringWriter();
 157         getTransformer(true, true).transform(new StreamSource(new StringReader(source)), new StreamResult(writer));
 158         assertEquals(writer.toString().replaceAll("\r\n", "\n"), expected);
 159     }
 160 
 161     @Test
 162     public void testLSSerializerFormatPrettyPrint() {
 163 
 164         final String XML_DOCUMENT = "<?xml version=\"1.0\" encoding=\"UTF-16\"?>\n"
 165                 + "<hello>before child element<child><children/><children/></child>after child element</hello>";
 166         /**JDK-8035467
 167          * no newline in default output
 168          */
 169         final String XML_DOCUMENT_DEFAULT_PRINT =
 170                 "<?xml version=\"1.0\" encoding=\"UTF-16\"?>"
 171                 + "<hello>"
 172                 + "before child element"
 173                 + "<child><children/><children/></child>"
 174                 + "after child element</hello>";
 175 
 176         final String XML_DOCUMENT_PRETTY_PRINT = "<?xml version=\"1.0\" encoding=\"UTF-16\"?><hello>\n" +
 177                 "    before child element\n" +
 178                 "    <child>\n" +
 179                 "        <children/>\n" +
 180                 "        <children/>\n" +
 181                 "    </child>\n" +
 182                 "    after child element\n" +
 183                 "</hello>\n";
 184 
 185         // it all begins with a Document
 186         DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
 187         DocumentBuilder documentBuilder = null;
 188         try {
 189             documentBuilder = documentBuilderFactory.newDocumentBuilder();
 190         } catch (ParserConfigurationException parserConfigurationException) {
 191             parserConfigurationException.printStackTrace();
 192             Assert.fail(parserConfigurationException.toString());
 193         }
 194         Document document = null;
 195 
 196         StringReader stringReader = new StringReader(XML_DOCUMENT);
 197         InputSource inputSource = new InputSource(stringReader);
 198         try {
 199             document = documentBuilder.parse(inputSource);
 200         } catch (SAXException saxException) {
 201             saxException.printStackTrace();
 202             Assert.fail(saxException.toString());
 203         } catch (IOException ioException) {
 204             ioException.printStackTrace();
 205             Assert.fail(ioException.toString());
 206         }
 207 
 208         // query DOM Interfaces to get to a LSSerializer
 209         DOMImplementation domImplementation = documentBuilder.getDOMImplementation();
 210         DOMImplementationLS domImplementationLS = (DOMImplementationLS) domImplementation;
 211         LSSerializer lsSerializer = domImplementationLS.createLSSerializer();
 212 
 213         System.out.println("Serializer is: " + lsSerializer.getClass().getName() + " " + lsSerializer);
 214 
 215         // get configuration
 216         DOMConfiguration domConfiguration = lsSerializer.getDomConfig();
 217 
 218         // query current configuration
 219         Boolean defaultFormatPrettyPrint = (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT);
 220         Boolean canSetFormatPrettyPrintFalse = (Boolean) domConfiguration.canSetParameter(DOM_FORMAT_PRETTY_PRINT, Boolean.FALSE);
 221         Boolean canSetFormatPrettyPrintTrue = (Boolean) domConfiguration.canSetParameter(DOM_FORMAT_PRETTY_PRINT, Boolean.TRUE);
 222 
 223         System.out.println(DOM_FORMAT_PRETTY_PRINT + " default/can set false/can set true = " + defaultFormatPrettyPrint + "/"
 224                 + canSetFormatPrettyPrintFalse + "/" + canSetFormatPrettyPrintTrue);
 225 
 226         // test values
 227         assertEquals(defaultFormatPrettyPrint, Boolean.FALSE, "Default value of " + DOM_FORMAT_PRETTY_PRINT + " should be " + Boolean.FALSE);
 228 
 229         assertEquals(canSetFormatPrettyPrintFalse, Boolean.TRUE, "Can set " + DOM_FORMAT_PRETTY_PRINT + " to " + Boolean.FALSE + " should be "
 230                 + Boolean.TRUE);
 231 
 232         assertEquals(canSetFormatPrettyPrintTrue, Boolean.TRUE, "Can set " + DOM_FORMAT_PRETTY_PRINT + " to " + Boolean.TRUE + " should be "
 233                 + Boolean.TRUE);
 234 
 235         // get default serialization
 236         String prettyPrintDefault = lsSerializer.writeToString(document);
 237         System.out.println("(default) " + DOM_FORMAT_PRETTY_PRINT + "==" + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT)
 238                 + ": \n\"" + prettyPrintDefault + "\"");
 239 
 240         assertEquals(prettyPrintDefault, XML_DOCUMENT_DEFAULT_PRINT, "Invalid serialization with default value, " + DOM_FORMAT_PRETTY_PRINT + "=="
 241                 + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT));
 242 
 243         // configure LSSerializer to not format-pretty-print
 244         domConfiguration.setParameter(DOM_FORMAT_PRETTY_PRINT, Boolean.FALSE);
 245         String prettyPrintFalse = lsSerializer.writeToString(document);
 246         System.out.println("(FALSE) " + DOM_FORMAT_PRETTY_PRINT + "==" + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT)
 247                 + ": \n\"" + prettyPrintFalse + "\"");
 248 
 249         assertEquals(prettyPrintFalse, XML_DOCUMENT_DEFAULT_PRINT, "Invalid serialization with FALSE value, " + DOM_FORMAT_PRETTY_PRINT + "=="
 250                 + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT));
 251 
 252         // configure LSSerializer to format-pretty-print
 253         domConfiguration.setParameter(DOM_FORMAT_PRETTY_PRINT, Boolean.TRUE);
 254         String prettyPrintTrue = lsSerializer.writeToString(document);
 255         System.out.println("(TRUE) " + DOM_FORMAT_PRETTY_PRINT + "==" + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT)
 256                 + ": \n\"" + prettyPrintTrue + "\"");
 257 
 258         assertEquals(prettyPrintTrue, XML_DOCUMENT_PRETTY_PRINT, "Invalid serialization with TRUE value, " + DOM_FORMAT_PRETTY_PRINT + "=="
 259                 + (Boolean) domConfiguration.getParameter(DOM_FORMAT_PRETTY_PRINT));
 260     }
 261 
 262     private String serializerWrite(Node xml, boolean pretty) throws Exception {
 263         DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
 264         DOMImplementationLS domImplementation = (DOMImplementationLS) registry.getDOMImplementation("LS");
 265         StringWriter writer = new StringWriter();
 266         LSOutput formattedOutput = domImplementation.createLSOutput();
 267         formattedOutput.setCharacterStream(writer);
 268         LSSerializer domSerializer = domImplementation.createLSSerializer();
 269         domSerializer.getDomConfig().setParameter(DOM_FORMAT_PRETTY_PRINT, pretty);
 270         domSerializer.getDomConfig().setParameter("xml-declaration", false);
 271         domSerializer.write(xml, formattedOutput);
 272         return writer.toString();
 273     }
 274 
 275     private String transform(Node xml, boolean pretty) throws Exception {
 276         Transformer transformer = getTransformer(false, pretty);
 277         StringWriter writer = new StringWriter();
 278         transformer.transform(new DOMSource(xml), new StreamResult(writer));
 279         return writer.toString();
 280     }
 281 
 282     private Document toXmlDocument(String xmlString) throws Exception {
 283         InputSource xmlInputSource = new InputSource(new StringReader(xmlString));
 284         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
 285         dbf.setValidating(true);
 286         DocumentBuilder xmlDocumentBuilder = dbf.newDocumentBuilder();
 287         Document node = xmlDocumentBuilder.parse(xmlInputSource);
 288         return node;
 289     }
 290 
 291     private Text newTextNode(String text) throws Exception {
 292         DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 293         return db.newDocument().createTextNode(text);
 294     }
 295 
 296     private Document createDocWithSequentTextNodes() throws Exception {
 297         DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 298         Document doc = db.newDocument();
 299         Node root = doc.createElement("root");
 300         doc.appendChild(root);
 301         root.appendChild(doc.createTextNode(" "));
 302         root.appendChild(doc.createTextNode("t"));
 303         root.appendChild(doc.createTextNode("\n"));
 304         root.appendChild(doc.createTextNode("t"));
 305         root.appendChild(doc.createTextNode("   "));
 306         Node child1 = doc.createElement("child1");
 307         root.appendChild(child1);
 308         child1.appendChild(doc.createTextNode(" "));
 309         child1.appendChild(doc.createTextNode("\n"));
 310         root.appendChild(doc.createTextNode("t"));
 311         Node child2 = doc.createElement("child2");
 312         root.appendChild(child2);
 313         child2.appendChild(doc.createTextNode(" "));
 314         root.appendChild(doc.createTextNode(" "));
 315         Node child3 = doc.createElement("child3");
 316         root.appendChild(child3);
 317         child3.appendChild(doc.createTextNode(" "));
 318         root.appendChild(doc.createTextNode(" "));
 319         Node child4 = doc.createElement("child4");
 320         root.appendChild(child4);
 321         child4.appendChild(doc.createTextNode(" "));
 322 
 323         root.appendChild(doc.createTextNode(" "));
 324         Node child5 = doc.createElement("child5");
 325         root.appendChild(child5);
 326         child5.appendChild(doc.createTextNode("t"));
 327 
 328         Node child51 = doc.createElement("child51");
 329         child5.appendChild(child51);
 330         child51.appendChild(doc.createTextNode(" "));
 331         Node child511 = doc.createElement("child511");
 332         child51.appendChild(child511);
 333         child511.appendChild(doc.createTextNode("t"));
 334         child51.appendChild(doc.createTextNode(" "));
 335         child5.appendChild(doc.createTextNode("t"));
 336 
 337         root.appendChild(doc.createTextNode(" "));
 338         root.appendChild(doc.createComment(" test comment "));
 339         root.appendChild(doc.createTextNode(" \n"));
 340         root.appendChild(doc.createComment(" "));
 341         root.appendChild(doc.createTextNode("\n"));
 342         root.appendChild(doc.createProcessingInstruction("target1", "test"));
 343         root.appendChild(doc.createTextNode(" "));
 344         root.appendChild(doc.createTextNode(" "));
 345         return doc;
 346     }
 347 
 348     private Transformer getTransformer(boolean html, boolean pretty) throws Exception {
 349         Transformer transformer = TransformerFactory.newInstance().newTransformer();
 350         transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
 351         transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
 352         if (html)
 353             transformer.setOutputProperty(OutputKeys.METHOD, "html");
 354         transformer.setOutputProperty(OutputKeys.INDENT, pretty ? "yes" : "no");
 355         return transformer;
 356     }
 357 
 358     
 359     private String read(String filename) throws Exception {
 360         try (InputStream in = PrettyPrintTest.class.getResourceAsStream(filename)) {
 361             return new String(in.readAllBytes());
 362         }
 363     }
 364 
 365     private static final String DOM_FORMAT_PRETTY_PRINT = "format-pretty-print";
 366 }