Removed NodeModel static utility classes dealing with parsing XML to DOM. How 
it's best to do that is environment and application dependent, and it has 
security implications. Since XML loading/parsing it's not the topic of the 
project, these were removed. Static methods that simplify an already loaded DOM 
have remained, because that's a FreeMarker-specific functionality.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/ea3a06b5
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/ea3a06b5
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/ea3a06b5

Branch: refs/heads/3
Commit: ea3a06b5b085b4ce27e78b250d95a9a997d4b626
Parents: 70dff09
Author: ddekany <ddek...@apache.org>
Authored: Mon Feb 20 21:23:33 2017 +0100
Committer: ddekany <ddek...@apache.org>
Committed: Mon Feb 20 21:23:33 2017 +0100

----------------------------------------------------------------------
 .../core/model/impl/dom/NodeModel.java          | 193 +----------------
 src/manual/en_US/FM3-CHANGE-LOG.txt             |   4 +
 .../impl/dom/DOMConvenienceStaticsTest.java     | 216 -------------------
 .../core/model/impl/dom/DOMSiblingTest.java     |   4 +-
 .../core/model/impl/dom/DOMSimplifiersTest.java | 201 +++++++++++++++++
 .../freemarker/core/model/impl/dom/DOMTest.java |   6 +-
 .../test/templatesuite/TemplateTestCase.java    |  13 +-
 .../apache/freemarker/test/util/XMLLoader.java  | 138 ++++++++++++
 8 files changed, 366 insertions(+), 409 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea3a06b5/src/main/java/org/apache/freemarker/core/model/impl/dom/NodeModel.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/model/impl/dom/NodeModel.java 
b/src/main/java/org/apache/freemarker/core/model/impl/dom/NodeModel.java
index a1679d5..765084d 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/dom/NodeModel.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/dom/NodeModel.java
@@ -20,20 +20,12 @@
 package org.apache.freemarker.core.model.impl.dom;
 
 
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
 import java.lang.ref.WeakReference;
-import java.net.MalformedURLException;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.WeakHashMap;
 
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core._CoreLogs;
 import 
org.apache.freemarker.core.ast._UnexpectedTypeErrorExplainerTemplateModel;
@@ -61,9 +53,6 @@ import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.w3c.dom.ProcessingInstruction;
 import org.w3c.dom.Text;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
 
 /**
  * A base class for wrapping a single W3C DOM_WRAPPER Node as a FreeMarker 
template model.
@@ -90,14 +79,10 @@ implements TemplateNodeModelEx, TemplateHashModel, 
TemplateSequenceModel,
 
     private static final Object STATIC_LOCK = new Object();
     
-    static private DocumentBuilderFactory docBuilderFactory;
-    
     static private final Map xpathSupportMap = Collections.synchronizedMap(new 
WeakHashMap());
     
     static private XPathSupport jaxenXPathSupport;
     
-    static private ErrorHandler errorHandler;
-    
     static Class xpathSupportClass;
     
     static {
@@ -118,173 +103,6 @@ implements TemplateNodeModelEx, TemplateHashModel, 
TemplateSequenceModel,
     private TemplateSequenceModel children;
     private NodeModel parent;
     
-    /**
-     * Sets the DOM_WRAPPER parser implementation to be used when building 
{@link NodeModel} objects from XML files or from
-     * {@link InputStream} with the static convenience methods of {@link 
NodeModel}. Otherwise FreeMarker itself doesn't
-     * use this.
-     * 
-     * @see #getDocumentBuilderFactory()
-     * 
-     * @deprecated It's a bad practice to change static fields, as if multiple 
independent components do that in the
-     *             same JVM, they unintentionally affect each other. Therefore 
it's recommended to leave this static
-     *             value at its default.
-     */
-    @Deprecated
-    static public void setDocumentBuilderFactory(DocumentBuilderFactory 
docBuilderFactory) {
-        synchronized (STATIC_LOCK) {
-            NodeModel.docBuilderFactory = docBuilderFactory;
-        }
-    }
-    
-    /**
-     * Returns the DOM_WRAPPER parser implementation that is used when 
building {@link NodeModel} objects from XML files or from
-     * {@link InputStream} with the static convenience methods of {@link 
NodeModel}. Otherwise FreeMarker itself doesn't
-     * use this.
-     * 
-     * @see #setDocumentBuilderFactory(DocumentBuilderFactory)
-     */
-    static public DocumentBuilderFactory getDocumentBuilderFactory() {
-        synchronized (STATIC_LOCK) {
-            if (docBuilderFactory == null) {
-                DocumentBuilderFactory newFactory = 
DocumentBuilderFactory.newInstance();
-                newFactory.setNamespaceAware(true);
-                newFactory.setIgnoringElementContentWhitespace(true);
-                docBuilderFactory = newFactory;  // We only write it out when 
the initialization was full 
-            }
-            return docBuilderFactory;
-        }
-    }
-    
-    /**
-     * Sets the error handler to use when parsing the document with the static 
convenience methods of {@link NodeModel}.
-     * 
-     * @deprecated It's a bad practice to change static fields, as if multiple 
independent components do that in the
-     *             same JVM, they unintentionally affect each other. Therefore 
it's recommended to leave this static
-     *             value at its default.
-     *             
-     * @see #getErrorHandler()
-     */
-    @Deprecated
-    static public void setErrorHandler(ErrorHandler errorHandler) {
-        synchronized (STATIC_LOCK) {
-            NodeModel.errorHandler = errorHandler;
-        }
-    }
-
-    /**
-     * @since 2.3.20
-     * 
-     * @see #setErrorHandler(ErrorHandler)
-     */
-    static public ErrorHandler getErrorHandler() {
-        synchronized (STATIC_LOCK) {
-            return NodeModel.errorHandler;
-        }
-    }
-    
-    /**
-     * Convenience method to create a {@link NodeModel} from a SAX {@link 
InputSource}; please see the security warning
-     * further down. Adjacent text nodes will be merged (and CDATA sections 
are considered as text nodes) as with
-     * {@link #mergeAdjacentText(Node)}. Further simplifications are applied 
depending on the parameters. If all
-     * simplifications are turned on, then it applies {@link #simplify(Node)} 
on the loaded DOM_WRAPPER.
-     * 
-     * <p>
-     * Note that {@code parse(...)} is only a convenience method, and 
FreeMarker itself doesn't use it (except when you
-     * call the other similar static convenience methods, as they may build on 
each other). In particular, if you want
-     * full control over the {@link DocumentBuilderFactory} used, create the 
{@link Node} with your own
-     * {@link DocumentBuilderFactory}, apply {@link #simplify(Node)} (or such) 
on it, then call
-     * {@link NodeModel#wrap(Node)}.
-     * 
-     * <p>
-     * <b>Security warning:</b> If the XML to load is coming from a source 
that you can't fully trust, you shouldn't use
-     * this method, as the {@link DocumentBuilderFactory} it uses by default 
supports external entities, and so it
-     * doesn't prevent XML External Entity (XXE) attacks. Note that XXE 
attacks are not specific to FreeMarker, they
-     * affect all XML parsers in general. If that's a problem in your 
application, OWASP has a cheat sheet to set up a
-     * {@link DocumentBuilderFactory} that has limited functionality but is 
immune to XXE attacks. Because it's just a
-     * convenience method, you can just use your own {@link 
DocumentBuilderFactory} and do a few extra steps instead
-     * (see earlier).
-     * 
-     * @param removeComments
-     *            Whether to remove all comment nodes (recursively); this is 
like calling {@link #removeComments(Node)}
-     * @param removePIs
-     *            Whether to remove all processing instruction nodes 
(recursively); this is like calling
-     *            {@link #removePIs(Node)}
-     */
-    static public NodeModel parse(InputSource is, boolean removeComments, 
boolean removePIs)
-        throws SAXException, IOException, ParserConfigurationException {
-        DocumentBuilder builder = 
getDocumentBuilderFactory().newDocumentBuilder();
-        ErrorHandler errorHandler = getErrorHandler();
-        if (errorHandler != null) builder.setErrorHandler(errorHandler);
-        final Document doc;
-        try {
-               doc = builder.parse(is);
-        } catch (MalformedURLException e) {
-               // This typical error has an error message that is hard to 
understand, so let's translate it:
-               if (is.getSystemId() == null && is.getCharacterStream() == null 
&& is.getByteStream() == null) {
-                       throw new MalformedURLException(
-                                       "The SAX InputSource has systemId == 
null && characterStream == null && byteStream == null. "
-                                       + "This is often because it was created 
with a null InputStream or Reader, which is often because "
-                                       + "the XML file it should point to was 
not found. "
-                                       + "(The original exception was: " + e + 
")");
-               } else {
-                       throw e;
-               }
-        }
-        if (removeComments && removePIs) {
-            simplify(doc);
-        } else {
-            if (removeComments) {
-                removeComments(doc);
-            }
-            if (removePIs) {
-                removePIs(doc);
-            }
-            mergeAdjacentText(doc);
-        }
-        return wrap(doc);
-    }
-    
-    /**
-     * Same as {@link #parse(InputSource, boolean, boolean) parse(is, true, 
true)}; don't miss the security warnings
-     * documented there.
-     */
-    static public NodeModel parse(InputSource is) throws SAXException, 
IOException, ParserConfigurationException {
-        return parse(is, true, true);
-    }
-    
-    
-    /**
-     * Same as {@link #parse(InputSource, boolean, boolean)}, but loads from a 
{@link File}; don't miss the security
-     * warnings documented there.
-     */
-    static public NodeModel parse(File f, boolean removeComments, boolean 
removePIs) 
-    throws SAXException, IOException, ParserConfigurationException {
-        DocumentBuilder builder = 
getDocumentBuilderFactory().newDocumentBuilder();
-        ErrorHandler errorHandler = getErrorHandler();
-        if (errorHandler != null) builder.setErrorHandler(errorHandler);
-        Document doc = builder.parse(f);
-        if (removeComments && removePIs) {
-            simplify(doc);
-        } else {
-            if (removeComments) {
-                removeComments(doc);
-            }
-            if (removePIs) {
-                removePIs(doc);
-            }
-            mergeAdjacentText(doc);
-        }
-        return wrap(doc);
-    }
-    
-    /**
-     * Same as {@link #parse(InputSource, boolean, boolean) parse(source, 
true, true)}, but loads from a {@link File};
-     * don't miss the security warnings documented there.
-     */
-    static public NodeModel parse(File f) throws SAXException, IOException, 
ParserConfigurationException {
-        return parse(f, true, true);
-    }
-    
     protected NodeModel(Node node) {
         this.node = node;
     }
@@ -450,6 +268,14 @@ implements TemplateNodeModelEx, TemplateHashModel, 
TemplateSequenceModel,
                 && ((NodeModel) other).node.equals(node);
     }
     
+    /**
+     * Creates a {@link NodeModel} from a DOM {@link Node}. It's strongly 
recommended modify the {@link Node} with
+     * {@link #simplify(Node)}, so the DOM will be easier to process in 
templates.
+     * 
+     * @param node
+     *            The DOM node to wrap. This is typically an {@link Element} 
or a {@link Document}, but all kind of node
+     *            types are supported. If {@code null}, {@code null} will be 
returned.
+     */
     static public NodeModel wrap(Node node) {
         if (node == null) {
             return null;
@@ -464,6 +290,9 @@ implements TemplateNodeModelEx, TemplateHashModel, 
TemplateSequenceModel,
             case Node.TEXT_NODE : result = new 
CharacterDataNodeModel((org.w3c.dom.CharacterData) node); break;
             case Node.PROCESSING_INSTRUCTION_NODE : result = new 
PINodeModel((ProcessingInstruction) node); break;
             case Node.DOCUMENT_TYPE_NODE : result = new 
DocumentTypeModel((DocumentType) node); break;
+            default: throw new IllegalArgumentException(
+                    "Unsupported node type: " + node.getNodeType() + " ("
+                    + node.getClass().getName() + ")");
         }
         return result;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea3a06b5/src/manual/en_US/FM3-CHANGE-LOG.txt
----------------------------------------------------------------------
diff --git a/src/manual/en_US/FM3-CHANGE-LOG.txt 
b/src/manual/en_US/FM3-CHANGE-LOG.txt
index 891b5a8..68899fb 100644
--- a/src/manual/en_US/FM3-CHANGE-LOG.txt
+++ b/src/manual/en_US/FM3-CHANGE-LOG.txt
@@ -102,3 +102,7 @@ the FreeMarer 3 changelog here:
   - Removed the deprecated BeansWrapper.nullModel setting. So null is always 
wrapped to null now.
   - Removed the overridable BeansWrapper.finetuneMethodAppearance method, 
which was deprecated by the
     finetuneMethodAppearance setting 
(BeansWrapper.setFinetuneMethodAppearance).
+  - Removed NodeModel static utility classes dealing with parsing XML to DOM. 
How it's best to do that is environment
+    and application dependent, and it has security implications. Since XML 
loading/parsing it's not the topic of the
+    project, these were removed. Static methods that simplify an already 
loaded DOM have remained, because that's
+    FreeMarker-specific functionality.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea3a06b5/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMConvenienceStaticsTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMConvenienceStaticsTest.java
 
b/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMConvenienceStaticsTest.java
deleted file mode 100644
index 14e293b..0000000
--- 
a/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMConvenienceStaticsTest.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * 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 org.apache.freemarker.core.model.impl.dom;
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-import java.io.StringReader;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.ParserConfigurationException;
-
-import org.apache.freemarker.core.model.impl.dom.NodeModel;
-import org.junit.Test;
-import org.w3c.dom.CDATASection;
-import org.w3c.dom.Comment;
-import org.w3c.dom.Document;
-import org.w3c.dom.DocumentType;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.ProcessingInstruction;
-import org.w3c.dom.Text;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
-public class DOMConvenienceStaticsTest {
-
-    private static final String COMMON_TEST_XML
-            = "<!DOCTYPE a 
[]><?p?><a>x<![CDATA[y]]><!--c--><?p?>z<?p?><b><!--c--></b><c></c>"
-              + "<d>a<e>c</e>b<!--c--><!--c--><!--c--><?p?><?p?><?p?></d>"
-              + "<f><![CDATA[1]]>2</f></a><!--c-->";
-
-    private static final String TEXT_MERGE_CONTENT =
-            "<a>"
-            + "a<!--c--><s/>"
-            + "<!--c-->a<s/>"
-            + "a<!--c-->b<s/>"
-            + "<!--c-->a<!--c-->b<!--c--><s/>"
-            + "a<b>b</b>c<s/>"
-            + "a<b>b</b><!--c-->c<s/>"
-            + "a<!--c-->1<b>b<!--c--></b>c<!--c-->1<s/>"
-            + "a<!--c-->1<b>b<!--c-->c</b>d<!--c-->1<s/>"
-            + "a<!--c-->1<b>b<!--c-->c</b>d<!--c-->1<s/>"
-            + 
"a<!--c-->1<b>b<!--c-->1<e>c<!--c-->1</e>d<!--c-->1</b>e<!--c-->1<s/>"
-            + "</a>";
-    private static final String TEXT_MERGE_EXPECTED =
-            "<a>"
-            + "%a<s/>"
-            + "%a<s/>"
-            + "%ab<s/>"
-            + "%ab<s/>"
-            + "%a<b>%b</b>%c<s/>"
-            + "%a<b>%b</b>%c<s/>"
-            + "%a1<b>%b</b>%c1<s/>"
-            + "%a1<b>%bc</b>%d1<s/>"
-            + "%a1<b>%bc</b>%d1<s/>"
-            + "%a1<b>%b1<e>%c1</e>%d1</b>%e1<s/>"
-            + "</a>";
-    
-    @Test
-    public void testTest() throws Exception {
-        String expected = "<!DOCTYPE 
...><?p?><a>%x<![CDATA[y]]><!--c--><?p?>%z<?p?><b><!--c--></b><c/>"
-                   + 
"<d>%a<e>%c</e>%b<!--c--><!--c--><!--c--><?p?><?p?><?p?></d>"
-                   + "<f><![CDATA[1]]>%2</f></a><!--c-->";
-        assertEquals(expected, toString(toDOM(COMMON_TEST_XML)));
-    }
-
-    @Test
-    public void testMergeAdjacentText() throws Exception {
-        Document dom = toDOM(COMMON_TEST_XML);
-        NodeModel.mergeAdjacentText(dom);
-        assertEquals(
-                "<!DOCTYPE 
...><?p?><a>%xy<!--c--><?p?>%z<?p?><b><!--c--></b><c/>"
-                + "<d>%a<e>%c</e>%b<!--c--><!--c--><!--c--><?p?><?p?><?p?></d>"
-                + "<f><![CDATA[12]]></f></a><!--c-->",
-                toString(dom));
-    }
-
-    @Test
-    public void testRemoveComments() throws Exception {
-        Document dom = toDOM(COMMON_TEST_XML);
-        NodeModel.removeComments(dom);
-        assertEquals(
-                "<!DOCTYPE ...><?p?><a>%x<![CDATA[y]]><?p?>%z<?p?><b/><c/>"
-                + "<d>%a<e>%c</e>%b<?p?><?p?><?p?></d>"
-                + "<f><![CDATA[1]]>%2</f></a>",
-                toString(dom));
-    }
-
-    @Test
-    public void testRemovePIs() throws Exception {
-        Document dom = toDOM(COMMON_TEST_XML);
-        NodeModel.removePIs(dom);
-        assertEquals(
-                "<!DOCTYPE ...><a>%x<![CDATA[y]]><!--c-->%z<b><!--c--></b><c/>"
-                + "<d>%a<e>%c</e>%b<!--c--><!--c--><!--c--></d>"
-                + "<f><![CDATA[1]]>%2</f></a><!--c-->",
-                toString(dom));
-    }
-    
-    @Test
-    public void testSimplify() throws Exception {
-        testSimplify(
-                "<!DOCTYPE ...><a>%xyz<b/><c/>"
-                + "<d>%a<e>%c</e>%b</d><f><![CDATA[12]]></f></a>",
-                COMMON_TEST_XML);
-    }
-
-    @Test
-    public void testSimplify2() throws Exception {
-        testSimplify(TEXT_MERGE_EXPECTED, TEXT_MERGE_CONTENT);
-    }
-
-    @Test
-    public void testSimplify3() throws Exception {
-        testSimplify("<a/>", "<a/>");
-    }
-    
-    private void testSimplify(String expected, String content)
-            throws SAXException, IOException, ParserConfigurationException {
-        {
-            Document dom = toDOM(content);
-            NodeModel.simplify(dom);
-            assertEquals(expected, toString(dom));
-        }
-        
-        // Must be equivalent:
-        {
-            Document dom = toDOM(content);
-            NodeModel.removeComments(dom);
-            NodeModel.removePIs(dom);
-            NodeModel.mergeAdjacentText(dom);
-            assertEquals(expected, toString(dom));
-        }
-        
-        // Must be equivalent:
-        {
-            Document dom = toDOM(content);
-            NodeModel.removeComments(dom);
-            NodeModel.removePIs(dom);
-            NodeModel.simplify(dom);
-            assertEquals(expected, toString(dom));
-        }
-    }
-
-    private Document toDOM(String content) throws SAXException, IOException, 
ParserConfigurationException {
-        DocumentBuilder builder =  
NodeModel.getDocumentBuilderFactory().newDocumentBuilder();
-        ErrorHandler errorHandler =  NodeModel.getErrorHandler();
-        if (errorHandler != null) builder.setErrorHandler(errorHandler);
-        return builder.parse(toInputSource(content));
-    }
-
-    private InputSource toInputSource(String content) {
-        return new InputSource(new StringReader(content));
-    }
-
-    private String toString(Document doc) {
-        StringBuilder sb = new StringBuilder();
-        toString(doc, sb);
-        return sb.toString();
-    }
-
-    private void toString(Node node, StringBuilder sb) {
-        if (node instanceof Document) {
-            childrenToString(node, sb);
-        } else if (node instanceof Element) {
-            if (node.hasChildNodes()) {
-                sb.append("<").append(node.getNodeName()).append(">");
-                childrenToString(node, sb);
-                sb.append("</").append(node.getNodeName()).append(">");
-            } else {
-                sb.append("<").append(node.getNodeName()).append("/>");
-            }
-        } else if (node instanceof Text) {
-            if (node instanceof CDATASection) {
-                
sb.append("<![CDATA[").append(node.getNodeValue()).append("]]>");
-            } else {
-                sb.append("%").append(node.getNodeValue());
-            }
-        } else if (node instanceof Comment) {
-            sb.append("<!--").append(node.getNodeValue()).append("-->");
-        } else if (node instanceof ProcessingInstruction) {
-            sb.append("<?").append(node.getNodeName()).append("?>");
-        } else if (node instanceof DocumentType) {
-            sb.append("<!DOCTYPE ...>");
-        } else {
-            throw new IllegalStateException("Unhandled node type: " + 
node.getClass().getName());
-        }
-    }
-
-    private void childrenToString(Node node, StringBuilder sb) {
-        Node child = node.getFirstChild();
-        while (child != null) {
-            toString(child, sb);
-            child = child.getNextSibling();
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea3a06b5/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMSiblingTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMSiblingTest.java 
b/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMSiblingTest.java
index a4b85fa..435fb4e 100644
--- 
a/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMSiblingTest.java
+++ 
b/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMSiblingTest.java
@@ -24,8 +24,8 @@ import java.io.IOException;
 import javax.xml.parsers.ParserConfigurationException;
 
 import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.impl.dom.NodeModel;
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.util.XMLLoader;
 import org.junit.Before;
 import org.junit.Test;
 import org.xml.sax.InputSource;
@@ -36,7 +36,7 @@ public class DOMSiblingTest extends TemplateTest {
     @Before
     public void setUp() throws SAXException, IOException, 
ParserConfigurationException {
         InputSource is = new 
InputSource(getClass().getResourceAsStream("DOMSiblingTest.xml"));
-        addToDataModel("doc", NodeModel.parse(is));
+        addToDataModel("doc", XMLLoader.toModel(is));
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea3a06b5/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMSimplifiersTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMSimplifiersTest.java
 
b/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMSimplifiersTest.java
new file mode 100644
index 0000000..91d0b89
--- /dev/null
+++ 
b/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMSimplifiersTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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 org.apache.freemarker.core.model.impl.dom;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.freemarker.test.util.XMLLoader;
+import org.junit.Test;
+import org.w3c.dom.CDATASection;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.ProcessingInstruction;
+import org.w3c.dom.Text;
+import org.xml.sax.SAXException;
+
+public class DOMSimplifiersTest {
+
+    private static final String COMMON_TEST_XML
+            = "<!DOCTYPE a 
[]><?p?><a>x<![CDATA[y]]><!--c--><?p?>z<?p?><b><!--c--></b><c></c>"
+              + "<d>a<e>c</e>b<!--c--><!--c--><!--c--><?p?><?p?><?p?></d>"
+              + "<f><![CDATA[1]]>2</f></a><!--c-->";
+
+    private static final String TEXT_MERGE_CONTENT =
+            "<a>"
+            + "a<!--c--><s/>"
+            + "<!--c-->a<s/>"
+            + "a<!--c-->b<s/>"
+            + "<!--c-->a<!--c-->b<!--c--><s/>"
+            + "a<b>b</b>c<s/>"
+            + "a<b>b</b><!--c-->c<s/>"
+            + "a<!--c-->1<b>b<!--c--></b>c<!--c-->1<s/>"
+            + "a<!--c-->1<b>b<!--c-->c</b>d<!--c-->1<s/>"
+            + "a<!--c-->1<b>b<!--c-->c</b>d<!--c-->1<s/>"
+            + 
"a<!--c-->1<b>b<!--c-->1<e>c<!--c-->1</e>d<!--c-->1</b>e<!--c-->1<s/>"
+            + "</a>";
+    private static final String TEXT_MERGE_EXPECTED =
+            "<a>"
+            + "%a<s/>"
+            + "%a<s/>"
+            + "%ab<s/>"
+            + "%ab<s/>"
+            + "%a<b>%b</b>%c<s/>"
+            + "%a<b>%b</b>%c<s/>"
+            + "%a1<b>%b</b>%c1<s/>"
+            + "%a1<b>%bc</b>%d1<s/>"
+            + "%a1<b>%bc</b>%d1<s/>"
+            + "%a1<b>%b1<e>%c1</e>%d1</b>%e1<s/>"
+            + "</a>";
+    
+    @Test
+    public void testTest() throws Exception {
+        String expected = "<!DOCTYPE 
...><?p?><a>%x<![CDATA[y]]><!--c--><?p?>%z<?p?><b><!--c--></b><c/>"
+                   + 
"<d>%a<e>%c</e>%b<!--c--><!--c--><!--c--><?p?><?p?><?p?></d>"
+                   + "<f><![CDATA[1]]>%2</f></a><!--c-->";
+        assertEquals(expected, toString(XMLLoader.toDOM(COMMON_TEST_XML)));
+    }
+
+    @Test
+    public void testMergeAdjacentText() throws Exception {
+        Document dom = XMLLoader.toDOM(COMMON_TEST_XML);
+        NodeModel.mergeAdjacentText(dom);
+        assertEquals(
+                "<!DOCTYPE 
...><?p?><a>%xy<!--c--><?p?>%z<?p?><b><!--c--></b><c/>"
+                + "<d>%a<e>%c</e>%b<!--c--><!--c--><!--c--><?p?><?p?><?p?></d>"
+                + "<f><![CDATA[12]]></f></a><!--c-->",
+                toString(dom));
+    }
+
+    @Test
+    public void testRemoveComments() throws Exception {
+        Document dom = XMLLoader.toDOM(COMMON_TEST_XML);
+        NodeModel.removeComments(dom);
+        assertEquals(
+                "<!DOCTYPE ...><?p?><a>%x<![CDATA[y]]><?p?>%z<?p?><b/><c/>"
+                + "<d>%a<e>%c</e>%b<?p?><?p?><?p?></d>"
+                + "<f><![CDATA[1]]>%2</f></a>",
+                toString(dom));
+    }
+
+    @Test
+    public void testRemovePIs() throws Exception {
+        Document dom = XMLLoader.toDOM(COMMON_TEST_XML);
+        NodeModel.removePIs(dom);
+        assertEquals(
+                "<!DOCTYPE ...><a>%x<![CDATA[y]]><!--c-->%z<b><!--c--></b><c/>"
+                + "<d>%a<e>%c</e>%b<!--c--><!--c--><!--c--></d>"
+                + "<f><![CDATA[1]]>%2</f></a><!--c-->",
+                toString(dom));
+    }
+    
+    @Test
+    public void testSimplify() throws Exception {
+        testSimplify(
+                "<!DOCTYPE ...><a>%xyz<b/><c/>"
+                + "<d>%a<e>%c</e>%b</d><f><![CDATA[12]]></f></a>",
+                COMMON_TEST_XML);
+    }
+
+    @Test
+    public void testSimplify2() throws Exception {
+        testSimplify(TEXT_MERGE_EXPECTED, TEXT_MERGE_CONTENT);
+    }
+
+    @Test
+    public void testSimplify3() throws Exception {
+        testSimplify("<a/>", "<a/>");
+    }
+    
+    private void testSimplify(String expected, String content)
+            throws SAXException, IOException, ParserConfigurationException {
+        {
+            Document dom = XMLLoader.toDOM(content);
+            NodeModel.simplify(dom);
+            assertEquals(expected, toString(dom));
+        }
+        
+        // Must be equivalent:
+        {
+            Document dom = XMLLoader.toDOM(content);
+            NodeModel.removeComments(dom);
+            NodeModel.removePIs(dom);
+            NodeModel.mergeAdjacentText(dom);
+            assertEquals(expected, toString(dom));
+        }
+        
+        // Must be equivalent:
+        {
+            Document dom = XMLLoader.toDOM(content);
+            NodeModel.removeComments(dom);
+            NodeModel.removePIs(dom);
+            NodeModel.simplify(dom);
+            assertEquals(expected, toString(dom));
+        }
+    }
+
+    private String toString(Document doc) {
+        StringBuilder sb = new StringBuilder();
+        toString(doc, sb);
+        return sb.toString();
+    }
+
+    private void toString(Node node, StringBuilder sb) {
+        if (node instanceof Document) {
+            childrenToString(node, sb);
+        } else if (node instanceof Element) {
+            if (node.hasChildNodes()) {
+                sb.append("<").append(node.getNodeName()).append(">");
+                childrenToString(node, sb);
+                sb.append("</").append(node.getNodeName()).append(">");
+            } else {
+                sb.append("<").append(node.getNodeName()).append("/>");
+            }
+        } else if (node instanceof Text) {
+            if (node instanceof CDATASection) {
+                
sb.append("<![CDATA[").append(node.getNodeValue()).append("]]>");
+            } else {
+                sb.append("%").append(node.getNodeValue());
+            }
+        } else if (node instanceof Comment) {
+            sb.append("<!--").append(node.getNodeValue()).append("-->");
+        } else if (node instanceof ProcessingInstruction) {
+            sb.append("<?").append(node.getNodeName()).append("?>");
+        } else if (node instanceof DocumentType) {
+            sb.append("<!DOCTYPE ...>");
+        } else {
+            throw new IllegalStateException("Unhandled node type: " + 
node.getClass().getName());
+        }
+    }
+
+    private void childrenToString(Node node, StringBuilder sb) {
+        Node child = node.getFirstChild();
+        while (child != null) {
+            toString(child, sb);
+            child = child.getNextSibling();
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea3a06b5/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMTest.java 
b/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMTest.java
index eee0eb1..387b863 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/dom/DOMTest.java
@@ -31,8 +31,8 @@ import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 
 import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.impl.dom.NodeModel;
 import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.util.XMLLoader;
 import org.junit.Test;
 import org.w3c.dom.Document;
 import org.xml.sax.InputSource;
@@ -98,11 +98,11 @@ public class DOMTest extends TemplateTest {
     }
     
     private void addDocToDataModel(String xml) throws SAXException, 
IOException, ParserConfigurationException {
-        addToDataModel("doc", NodeModel.parse(new InputSource(new 
StringReader(xml))));
+        addToDataModel("doc", XMLLoader.toModel(new InputSource(new 
StringReader(xml))));
     }
 
     private void addDocToDataModelNoSimplification(String xml) throws 
SAXException, IOException, ParserConfigurationException {
-        addToDataModel("doc", NodeModel.parse(new InputSource(new 
StringReader(xml)), false, false));
+        addToDataModel("doc", XMLLoader.toModel(new InputSource(new 
StringReader(xml)), false));
     }
     
     private void addNSUnawareDocToDataModel(String xml) throws 
ParserConfigurationException, SAXException, IOException {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea3a06b5/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java 
b/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
index c210728..2f28dab 100644
--- 
a/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
+++ 
b/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
@@ -86,6 +86,7 @@ import org.apache.freemarker.test.util.AssertEqualsDirective;
 import org.apache.freemarker.test.util.AssertFailsDirective;
 import org.apache.freemarker.test.util.FileTestCase;
 import org.apache.freemarker.test.util.NoOutputDirective;
+import org.apache.freemarker.test.util.XMLLoader;
 import org.junit.Ignore;
 import org.xml.sax.InputSource;
 
@@ -319,7 +320,7 @@ public class TemplateTestCase extends FileTestCase {
             dataModel.put("mMixed", mMixed);
         } else if (simpleTestName.equals("default-xmlns")) {
             InputSource is = new 
InputSource(getClass().getResourceAsStream("models/defaultxmlns1.xml"));
-            NodeModel nm = NodeModel.parse(is);
+            NodeModel nm = XMLLoader.toModel(is);
             dataModel.put("doc", nm);
         } else if (simpleTestName.equals("multimodels")) {
             dataModel.put("test", "selftest");
@@ -355,23 +356,23 @@ public class TemplateTestCase extends FileTestCase {
             dataModel.put("node", 
NodeModel.wrap(doc.getDocumentElement().getFirstChild().getFirstChild()));
         } else if (simpleTestName.equals("xmlns1")) {
             InputSource is = new 
InputSource(getClass().getResourceAsStream("models/xmlns.xml"));
-            NodeModel nm = NodeModel.parse(is);
+            NodeModel nm = XMLLoader.toModel(is);
             dataModel.put("doc", nm);
         } else if (simpleTestName.equals("xmlns2")) {
             InputSource is = new 
InputSource(getClass().getResourceAsStream("models/xmlns2.xml"));
-            NodeModel nm = NodeModel.parse(is);
+            NodeModel nm = XMLLoader.toModel(is);
             dataModel.put("doc", nm);
         } else if (simpleTestName.equals("xmlns3") || 
simpleTestName.equals("xmlns4")) {
             InputSource is = new 
InputSource(getClass().getResourceAsStream("models/xmlns3.xml"));
-            NodeModel nm = NodeModel.parse(is);
+            NodeModel nm = XMLLoader.toModel(is);
             dataModel.put("doc", nm);
         } else if (simpleTestName.equals("xmlns5")) {
             InputSource is = new 
InputSource(getClass().getResourceAsStream("models/defaultxmlns1.xml"));
-            NodeModel nm = NodeModel.parse(is);
+            NodeModel nm = XMLLoader.toModel(is);
             dataModel.put("doc", nm);
         } else if (simpleTestName.equals("xml-ns_prefix-scope")) {
             InputSource is = new 
InputSource(getClass().getResourceAsStream("models/xml-ns_prefix-scope.xml"));
-            NodeModel nm = NodeModel.parse(is);
+            NodeModel nm = XMLLoader.toModel(is);
             dataModel.put("doc", nm);
         } else if (simpleTestName.startsWith("sequence-builtins")) {
             Set<String> abcSet = new TreeSet<>();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ea3a06b5/src/test/java/org/apache/freemarker/test/util/XMLLoader.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/util/XMLLoader.java 
b/src/test/java/org/apache/freemarker/test/util/XMLLoader.java
new file mode 100644
index 0000000..7226f97
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/test/util/XMLLoader.java
@@ -0,0 +1,138 @@
+/*
+ * 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 org.apache.freemarker.test.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.freemarker.core.model.impl.dom.NodeModel;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public final class XMLLoader {
+
+    private static final Object STATIC_LOCK = new Object();
+    
+    static private DocumentBuilderFactory docBuilderFactory;
+    
+    private XMLLoader() {
+        //
+    }
+    
+    /**
+     * Convenience method to create a {@link NodeModel} from a SAX {@link 
InputSource}.
+     */
+    static public NodeModel toModel(InputSource is, boolean simplify)
+        throws SAXException, IOException, ParserConfigurationException {
+        DocumentBuilder builder = 
getDocumentBuilderFactory().newDocumentBuilder();
+        final Document doc;
+        try {
+            doc = builder.parse(is);
+        } catch (MalformedURLException e) {
+            // This typical error has an error message that is hard to 
understand, so let's translate it:
+            if (is.getSystemId() == null && is.getCharacterStream() == null && 
is.getByteStream() == null) {
+                throw new MalformedURLException(
+                        "The SAX InputSource has systemId == null && 
characterStream == null && byteStream == null. "
+                        + "This is often because it was created with a null 
InputStream or Reader, which is often because "
+                        + "the XML file it should point to was not found. "
+                        + "(The original exception was: " + e + ")");
+            } else {
+                throw e;
+            }
+        }
+        if (simplify) {
+            NodeModel.simplify(doc);
+        }
+        return NodeModel.wrap(doc);
+    }
+    
+    /**
+     * Same as {@link #toModel(InputSource, boolean) parse(is, true)}.
+     */
+    static public NodeModel toModel(InputSource is) throws SAXException, 
IOException, ParserConfigurationException {
+        return toModel(is, true);
+    }
+    
+    /**
+     * Same as {@link #toModel(InputSource, boolean)}, but loads from a {@link 
File}; don't miss the security
+     * warnings documented there.
+     */
+    static public NodeModel toModel(File f, boolean simplify) 
+    throws SAXException, IOException, ParserConfigurationException {
+        DocumentBuilder builder = 
getDocumentBuilderFactory().newDocumentBuilder();
+        Document doc = builder.parse(f);
+        if (simplify) {
+            NodeModel.simplify(doc);
+        }
+        return NodeModel.wrap(doc);
+    }
+    
+    /**
+     * Same as {@link #toModel(InputSource, boolean) parse(source, true)}, but 
loads from a {@link String}.
+     */
+    static public NodeModel toModel(File f) throws SAXException, IOException, 
ParserConfigurationException {
+        return toModel(f, true);
+    }
+    
+    /**
+     * Same as {@link #toModel(InputSource, boolean)}, but loads from a {@link 
File}; don't miss the security
+     * warnings documented there.
+     */
+    static public NodeModel toModel(String content, boolean simplify) 
+    throws SAXException, IOException, ParserConfigurationException {
+        return toModel(toInputSource(content));
+    }
+    
+    /**
+     * Same as {@link #toModel(InputSource, boolean) parse(source, true)}, but 
loads from a {@link String}.
+     */
+    static public NodeModel toModel(String content) throws SAXException, 
IOException, ParserConfigurationException {
+        return toModel(content, true);
+    }
+    
+    public static Document toDOM(String content) throws SAXException, 
IOException, ParserConfigurationException {
+        DocumentBuilder builder =  
getDocumentBuilderFactory().newDocumentBuilder();
+        return builder.parse(toInputSource(content));
+    }
+    
+    static private DocumentBuilderFactory getDocumentBuilderFactory() {
+        synchronized (STATIC_LOCK) {
+            if (docBuilderFactory == null) {
+                DocumentBuilderFactory newFactory = 
DocumentBuilderFactory.newInstance();
+                newFactory.setNamespaceAware(true);
+                newFactory.setIgnoringElementContentWhitespace(true);
+                docBuilderFactory = newFactory;  // We only write it out when 
the initialization was full 
+            }
+            return docBuilderFactory;
+        }
+    }
+
+    private static InputSource toInputSource(String content) {
+        return new InputSource(new StringReader(content));
+    }
+    
+}


Reply via email to