You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2022/03/08 12:03:57 UTC

[groovy] 01/02: GROOVY-10495: Invalid newlines generated by XmlTemplateEngine

This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 785f7274171a2053388eacf182e6131134c0e8b0
Author: Paul King <pa...@asert.com.au>
AuthorDate: Tue Mar 8 12:26:18 2022 +1000

    GROOVY-10495: Invalid newlines generated by XmlTemplateEngine
---
 .../main/groovy/groovy/text/XmlTemplateEngine.java | 46 +++++++++++++-
 .../groovy/text/GroovyXmlTemplateEngineTest.groovy | 74 ++++++++++++++++++++++
 .../src/main/java/groovy/xml/XmlNodePrinter.java   |  2 +-
 3 files changed, 118 insertions(+), 4 deletions(-)

diff --git a/subprojects/groovy-templates/src/main/groovy/groovy/text/XmlTemplateEngine.java b/subprojects/groovy-templates/src/main/groovy/groovy/text/XmlTemplateEngine.java
index 68f413d..0decca7 100644
--- a/subprojects/groovy-templates/src/main/groovy/groovy/text/XmlTemplateEngine.java
+++ b/subprojects/groovy-templates/src/main/groovy/groovy/text/XmlTemplateEngine.java
@@ -19,6 +19,7 @@
 package groovy.text;
 
 import groovy.lang.Binding;
+import groovy.lang.Closure;
 import groovy.lang.GroovyRuntimeException;
 import groovy.lang.GroovyShell;
 import groovy.lang.Script;
@@ -108,6 +109,19 @@ public class XmlTemplateEngine extends TemplateEngine {
 
     private static AtomicInteger counter = new AtomicInteger(0);
 
+    private Closure configurePrinter = null;
+
+    /**
+     * Closure that can be used to configure the printer.
+     * The printer is passed as a parameter to the closure.
+     * <pre>
+     * new XmlTemplateEngine(configurePrinter: { it.preserveWhitespace = true })
+     * </pre>
+     */
+    public void setConfigurePrinter(Closure configurePrinter) {
+        this.configurePrinter = configurePrinter;
+    }
+
     private static class GspPrinter extends XmlNodePrinter {
 
         public GspPrinter(PrintWriter out, String indent) {
@@ -137,6 +151,27 @@ public class XmlTemplateEngine extends TemplateEngine {
         }
 
         @Override
+        protected void printName(Node node, NamespaceContext ctx, boolean begin, boolean preserve) {
+            if (node == null || node.name() == null) {
+                super.printName(node, ctx, begin, preserve);
+            }
+            printLineBegin();
+            out.print("<");
+            if (!begin) {
+                out.print("/");
+            }
+            out.print(getName(node));
+            if (ctx != null) {
+                printNamespace(node, ctx);
+            }
+            if (begin) {
+                printNameAttributes(node.attributes(), ctx);
+            }
+            out.print(">");
+            printLineEnd();
+        }
+
+        @Override
         protected void printSimpleItem(Object value) {
             this.printLineBegin();
             out.print(escapeSpecialChars(FormatHelper.toString(value)));
@@ -187,12 +222,13 @@ public class XmlTemplateEngine extends TemplateEngine {
         @Override
         protected void printLineBegin() {
             out.print("out.print(\"\"\"");
-            out.printIndent();
+            if (!isPreserveWhitespace()) out.printIndent();
         }
 
         @Override
         protected void printLineEnd(String comment) {
-            out.print("\\n\"\"\");");
+            if (!isPreserveWhitespace()) out.print("\\n");
+            out.print("\"\"\");");
             if (comment != null) {
                 out.print(" // ");
                 out.print(comment);
@@ -316,7 +352,11 @@ public class XmlTemplateEngine extends TemplateEngine {
 
         StringBuilderWriter writer = new StringBuilderWriter(1024);
         writer.write("/* Generated by XmlTemplateEngine */\n");
-        new GspPrinter(new PrintWriter(writer), indentation).print(root);
+        GspPrinter printer = new GspPrinter(new PrintWriter(writer), indentation);
+        if (configurePrinter != null) {
+            configurePrinter.call(printer);
+        }
+        printer.print(root);
 
         Script script;
         try {
diff --git a/subprojects/groovy-templates/src/test/groovy/groovy/text/GroovyXmlTemplateEngineTest.groovy b/subprojects/groovy-templates/src/test/groovy/groovy/text/GroovyXmlTemplateEngineTest.groovy
new file mode 100644
index 0000000..c3c4c27
--- /dev/null
+++ b/subprojects/groovy-templates/src/test/groovy/groovy/text/GroovyXmlTemplateEngineTest.groovy
@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.text
+
+import groovy.xml.XmlParser
+import org.junit.Test
+
+class GroovyXmlTemplateEngineTest {
+    @Test
+    void testFormat1() {
+        String xmlScript =
+'''<?xml version="1.0"?>
+<person>
+   <name>Daniel</name>
+</person>
+'''
+        def template = new XmlTemplateEngine(configurePrinter: { it.preserveWhitespace = true }).createTemplate(xmlScript).make().toString()
+        def name1 = new XmlParser().parseText(xmlScript).name.text()
+        def name2 = new XmlParser().parseText(template).name.text()
+
+        assert name1 == name2
+    }
+
+    @Test
+    void testFormat2() {
+        String xmlScript =
+'''<person>
+   <name>
+       <firstName>Daniel</firstName>
+       <lastName>Sun</lastName>
+   </name>
+   <age>37</age>
+</person>
+'''
+
+        def expected =
+'<person><name><firstName>Daniel</firstName><lastName>Sun</lastName></name><age>37</age></person>'
+        assert expected == new XmlTemplateEngine(configurePrinter: { it.preserveWhitespace = true }).createTemplate(xmlScript).make().toString()
+    }
+
+    @Test
+    void testRoundTrip() {
+        String xmlScript =
+'''<person>
+   <name>
+       <firstName>Daniel</firstName>
+       <lastName>Sun</lastName>
+   </name>
+   <age>37</age>
+</person>'''
+
+        def parser = new XmlParser(keepIgnorableWhitespace: true)
+        def shell = new GroovyShell()
+        def engine = new XmlTemplateEngine(parser, shell)
+        engine.configurePrinter = { it.preserveWhitespace = true }
+        assert engine.createTemplate(xmlScript).make().toString() == xmlScript
+    }
+}
diff --git a/subprojects/groovy-xml/src/main/java/groovy/xml/XmlNodePrinter.java b/subprojects/groovy-xml/src/main/java/groovy/xml/XmlNodePrinter.java
index fdd419f..0a4716d 100644
--- a/subprojects/groovy-xml/src/main/java/groovy/xml/XmlNodePrinter.java
+++ b/subprojects/groovy-xml/src/main/java/groovy/xml/XmlNodePrinter.java
@@ -348,7 +348,7 @@ public class XmlNodePrinter {
         return node.text().length() == 0;
     }
 
-    private String getName(Object object) {
+    protected String getName(Object object) {
         if (object instanceof String) {
             return (String) object;
         } else if (object instanceof QName) {