You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/02/20 20:23:43 UTC
[2/2] incubator-freemarker git commit: 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 t
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 <dd...@apache.org>
Authored: Mon Feb 20 21:23:33 2017 +0100
Committer: ddekany <dd...@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));
+ }
+
+}