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/05/21 21:34:59 UTC

[4/6] incubator-freemarker git commit: Factored out freemarker-dom from freemarker-core. Also added mechanism to "inject" DOM wrapping capability into DefaultObjectWrapper on configuration time. See details below.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeModel.java
deleted file mode 100644
index 37a5c7d..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeModel.java
+++ /dev/null
@@ -1,613 +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.dom;
-
-
-import java.lang.ref.WeakReference;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel;
-import org.apache.freemarker.core.model.AdapterTemplateModel;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateNodeModel;
-import org.apache.freemarker.core.model.TemplateNodeModelEx;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.WrapperTemplateModel;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.model.impl.SimpleScalar;
-import org.slf4j.Logger;
-import org.w3c.dom.Attr;
-import org.w3c.dom.CDATASection;
-import org.w3c.dom.CharacterData;
-import org.w3c.dom.Document;
-import org.w3c.dom.DocumentType;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.w3c.dom.ProcessingInstruction;
-import org.w3c.dom.Text;
-
-/**
- * A base class for wrapping a single W3C DOM_WRAPPER Node as a FreeMarker template model.
- * <p>
- * Note that {@link DefaultObjectWrapper} automatically wraps W3C DOM_WRAPPER {@link Node}-s into this, so you may need do that
- * with this class manually. However, before dropping the {@link Node}-s into the data-model, you certainly want to
- * apply {@link NodeModel#simplify(Node)} on them.
- * <p>
- * This class is not guaranteed to be thread safe, so instances of this shouldn't be used as
- * {@linkplain Configuration#getSharedVariables() shared variable}.
- * <p>
- * To represent a node sequence (such as a query result) of exactly 1 nodes, this class should be used instead of
- * {@link NodeListModel}, as it adds extra capabilities by utilizing that we have exactly 1 node. If you need to wrap a
- * node sequence of 0 or multiple nodes, you must use {@link NodeListModel}.
- */
-abstract public class NodeModel implements TemplateNodeModelEx, TemplateHashModel, TemplateSequenceModel,
-    AdapterTemplateModel, WrapperTemplateModel, _UnexpectedTypeErrorExplainerTemplateModel {
-
-    static private final Logger LOG = DomLog.LOG;
-
-    private static final Object STATIC_LOCK = new Object();
-    
-    static private final Map xpathSupportMap = Collections.synchronizedMap(new WeakHashMap());
-    
-    static private XPathSupport jaxenXPathSupport;
-    
-    static Class xpathSupportClass;
-    
-    static {
-        try {
-            useDefaultXPathSupport();
-        } catch (Exception e) {
-            // do nothing
-        }
-        if (xpathSupportClass == null && LOG.isWarnEnabled()) {
-            LOG.warn("No XPath support is available.");
-        }
-    }
-    
-    /**
-     * The W3C DOM_WRAPPER Node being wrapped.
-     */
-    final Node node;
-    private TemplateSequenceModel children;
-    private NodeModel parent;
-    
-    protected NodeModel(Node node) {
-        this.node = node;
-    }
-    
-    /**
-     * @return the underling W3C DOM_WRAPPER Node object that this TemplateNodeModel
-     * is wrapping.
-     */
-    public Node getNode() {
-        return node;
-    }
-    
-    @Override
-    public TemplateModel get(String key) throws TemplateModelException {
-        if (key.startsWith("@@")) {
-            if (key.equals(AtAtKey.TEXT.getKey())) {
-                return new SimpleScalar(getText(node));
-            } else if (key.equals(AtAtKey.NAMESPACE.getKey())) {
-                String nsURI = node.getNamespaceURI();
-                return nsURI == null ? null : new SimpleScalar(nsURI);
-            } else if (key.equals(AtAtKey.LOCAL_NAME.getKey())) {
-                String localName = node.getLocalName();
-                if (localName == null) {
-                    localName = getNodeName();
-                }
-                return new SimpleScalar(localName);
-            } else if (key.equals(AtAtKey.MARKUP.getKey())) {
-                StringBuilder buf = new StringBuilder();
-                NodeOutputter nu = new NodeOutputter(node);
-                nu.outputContent(node, buf);
-                return new SimpleScalar(buf.toString());
-            } else if (key.equals(AtAtKey.NESTED_MARKUP.getKey())) {
-                StringBuilder buf = new StringBuilder();
-                NodeOutputter nu = new NodeOutputter(node);
-                nu.outputContent(node.getChildNodes(), buf);
-                return new SimpleScalar(buf.toString());
-            } else if (key.equals(AtAtKey.QNAME.getKey())) {
-                String qname = getQualifiedName();
-                return qname != null ? new SimpleScalar(qname) : null;
-            } else {
-                // As @@... would cause exception in the XPath engine, we throw a nicer exception now. 
-                if (AtAtKey.containsKey(key)) {
-                    throw new TemplateModelException(
-                            "\"" + key + "\" is not supported for an XML node of type \"" + getNodeType() + "\".");
-                } else {
-                    throw new TemplateModelException("Unsupported @@ key: " + key);
-                }
-            }
-        } else {
-            XPathSupport xps = getXPathSupport();
-            if (xps != null) {
-                return xps.executeQuery(node, key);
-            } else {
-                throw new TemplateModelException(
-                        "Can't try to resolve the XML query key, because no XPath support is available. "
-                        + "This is either malformed or an XPath expression: " + key);
-            }
-        }
-    }
-    
-    @Override
-    public TemplateNodeModel getParentNode() {
-        if (parent == null) {
-            Node parentNode = node.getParentNode();
-            if (parentNode == null) {
-                if (node instanceof Attr) {
-                    parentNode = ((Attr) node).getOwnerElement();
-                }
-            }
-            parent = wrap(parentNode);
-        }
-        return parent;
-    }
-
-    @Override
-    public TemplateNodeModelEx getPreviousSibling() throws TemplateModelException {
-        return wrap(node.getPreviousSibling());
-    }
-
-    @Override
-    public TemplateNodeModelEx getNextSibling() throws TemplateModelException {
-        return wrap(node.getNextSibling());
-    }
-
-    @Override
-    public TemplateSequenceModel getChildNodes() {
-        if (children == null) {
-            children = new NodeListModel(node.getChildNodes(), this);
-        }
-        return children;
-    }
-    
-    @Override
-    public final String getNodeType() throws TemplateModelException {
-        short nodeType = node.getNodeType();
-        switch (nodeType) {
-            case Node.ATTRIBUTE_NODE : return "attribute";
-            case Node.CDATA_SECTION_NODE : return "text";
-            case Node.COMMENT_NODE : return "comment";
-            case Node.DOCUMENT_FRAGMENT_NODE : return "document_fragment";
-            case Node.DOCUMENT_NODE : return "document";
-            case Node.DOCUMENT_TYPE_NODE : return "document_type";
-            case Node.ELEMENT_NODE : return "element";
-            case Node.ENTITY_NODE : return "entity";
-            case Node.ENTITY_REFERENCE_NODE : return "entity_reference";
-            case Node.NOTATION_NODE : return "notation";
-            case Node.PROCESSING_INSTRUCTION_NODE : return "pi";
-            case Node.TEXT_NODE : return "text";
-        }
-        throw new TemplateModelException("Unknown node type: " + nodeType + ". This should be impossible!");
-    }
-    
-    public TemplateModel exec(List args) throws TemplateModelException {
-        if (args.size() != 1) {
-            throw new TemplateModelException("Expecting exactly one arguments");
-        }
-        String query = (String) args.get(0);
-        // Now, we try to behave as if this is an XPath expression
-        XPathSupport xps = getXPathSupport();
-        if (xps == null) {
-            throw new TemplateModelException("No XPath support available");
-        }
-        return xps.executeQuery(node, query);
-    }
-    
-    /**
-     * Always returns 1.
-     */
-    @Override
-    public final int size() {
-        return 1;
-    }
-    
-    @Override
-    public final TemplateModel get(int i) {
-        return i == 0 ? this : null;
-    }
-    
-    @Override
-    public String getNodeNamespace() {
-        int nodeType = node.getNodeType();
-        if (nodeType != Node.ATTRIBUTE_NODE && nodeType != Node.ELEMENT_NODE) { 
-            return null;
-        }
-        String result = node.getNamespaceURI();
-        if (result == null && nodeType == Node.ELEMENT_NODE) {
-            result = "";
-        } else if ("".equals(result) && nodeType == Node.ATTRIBUTE_NODE) {
-            result = null;
-        }
-        return result;
-    }
-    
-    @Override
-    public final int hashCode() {
-        return node.hashCode();
-    }
-    
-    @Override
-    public boolean equals(Object other) {
-        if (other == null) return false;
-        return other.getClass() == getClass()
-                && ((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;
-        }
-        NodeModel result = null;
-        switch (node.getNodeType()) {
-            case Node.DOCUMENT_NODE : result = new DocumentModel((Document) node); break;
-            case Node.ELEMENT_NODE : result = new ElementModel((Element) node); break;
-            case Node.ATTRIBUTE_NODE : result = new AttributeNodeModel((Attr) node); break;
-            case Node.CDATA_SECTION_NODE : 
-            case Node.COMMENT_NODE :
-            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;
-    }
-    
-    /**
-     * Recursively removes all comment nodes from the subtree.
-     *
-     * @see #simplify
-     */
-    static public void removeComments(Node parent) {
-        Node child = parent.getFirstChild();
-        while (child != null) {
-            Node nextSibling = child.getNextSibling();
-            if (child.getNodeType() == Node.COMMENT_NODE) {
-                parent.removeChild(child);
-            } else if (child.hasChildNodes()) {
-                removeComments(child);
-            }
-            child = nextSibling;
-        }
-    }
-    
-    /**
-     * Recursively removes all processing instruction nodes from the subtree.
-     *
-     * @see #simplify
-     */
-    static public void removePIs(Node parent) {
-        Node child = parent.getFirstChild();
-        while (child != null) {
-            Node nextSibling = child.getNextSibling();
-            if (child.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
-                parent.removeChild(child);
-            } else if (child.hasChildNodes()) {
-                removePIs(child);
-            }
-            child = nextSibling;
-        }
-    }
-    
-    /**
-     * Merges adjacent text nodes (where CDATA counts as text node too). Operates recursively on the entire subtree.
-     * The merged node will have the type of the first node of the adjacent merged nodes.
-     * 
-     * <p>Because XPath assumes that there are no adjacent text nodes in the tree, not doing this can have
-     * undesirable side effects. Xalan queries like {@code text()} will only return the first of a list of matching
-     * adjacent text nodes instead of all of them, while Jaxen will return all of them as intuitively expected. 
-     *
-     * @see #simplify
-     */
-    static public void mergeAdjacentText(Node parent) {
-        mergeAdjacentText(parent, new StringBuilder(0));
-    }
-    
-    static private void mergeAdjacentText(Node parent, StringBuilder collectorBuf) {
-        Node child = parent.getFirstChild();
-        while (child != null) {
-            Node next = child.getNextSibling();
-            if (child instanceof Text) {
-                boolean atFirstText = true;
-                while (next instanceof Text) { //
-                    if (atFirstText) {
-                        collectorBuf.setLength(0);
-                        collectorBuf.ensureCapacity(child.getNodeValue().length() + next.getNodeValue().length());
-                        collectorBuf.append(child.getNodeValue());
-                        atFirstText = false;
-                    }
-                    collectorBuf.append(next.getNodeValue());
-                    
-                    parent.removeChild(next);
-                    
-                    next = child.getNextSibling();
-                }
-                if (!atFirstText && collectorBuf.length() != 0) {
-                    ((CharacterData) child).setData(collectorBuf.toString());
-                }
-            } else {
-                mergeAdjacentText(child, collectorBuf);
-            }
-            child = next;
-        }
-    }
-    
-    /**
-     * Removes all comments and processing instruction, and unites adjacent text nodes (here CDATA counts as text as
-     * well). This is similar to applying {@link #removeComments(Node)}, {@link #removePIs(Node)}, and finally
-     * {@link #mergeAdjacentText(Node)}, but it does all that somewhat faster.
-     */    
-    static public void simplify(Node parent) {
-        simplify(parent, new StringBuilder(0));
-    }
-    
-    static private void simplify(Node parent, StringBuilder collectorTextChildBuff) {
-        Node collectorTextChild = null;
-        Node child = parent.getFirstChild();
-        while (child != null) {
-            Node next = child.getNextSibling();
-            if (child.hasChildNodes()) {
-                if (collectorTextChild != null) {
-                    // Commit pending text node merge:
-                    if (collectorTextChildBuff.length() != 0) {
-                        ((CharacterData) collectorTextChild).setData(collectorTextChildBuff.toString());
-                        collectorTextChildBuff.setLength(0);
-                    }
-                    collectorTextChild = null;
-                }
-                
-                simplify(child, collectorTextChildBuff);
-            } else {
-                int type = child.getNodeType();
-                if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE ) {
-                    if (collectorTextChild != null) {
-                        if (collectorTextChildBuff.length() == 0) {
-                            collectorTextChildBuff.ensureCapacity(
-                                    collectorTextChild.getNodeValue().length() + child.getNodeValue().length());
-                            collectorTextChildBuff.append(collectorTextChild.getNodeValue());
-                        }
-                        collectorTextChildBuff.append(child.getNodeValue());
-                        parent.removeChild(child);
-                    } else {
-                        collectorTextChild = child;
-                        collectorTextChildBuff.setLength(0);
-                    }
-                } else if (type == Node.COMMENT_NODE) {
-                    parent.removeChild(child);
-                } else if (type == Node.PROCESSING_INSTRUCTION_NODE) {
-                    parent.removeChild(child);
-                } else if (collectorTextChild != null) {
-                    // Commit pending text node merge:
-                    if (collectorTextChildBuff.length() != 0) {
-                        ((CharacterData) collectorTextChild).setData(collectorTextChildBuff.toString());
-                        collectorTextChildBuff.setLength(0);
-                    }
-                    collectorTextChild = null;
-                }
-            }
-            child = next;
-        }
-        
-        if (collectorTextChild != null) {
-            // Commit pending text node merge:
-            if (collectorTextChildBuff.length() != 0) {
-                ((CharacterData) collectorTextChild).setData(collectorTextChildBuff.toString());
-                collectorTextChildBuff.setLength(0);
-            }
-        }
-    }
-    
-    NodeModel getDocumentNodeModel() {
-        if (node instanceof Document) {
-            return this;
-        } else {
-            return wrap(node.getOwnerDocument());
-        }
-    }
-
-    /**
-     * Tells the system to use (restore) the default (initial) XPath system used by
-     * this FreeMarker version on this system.
-     */
-    static public void useDefaultXPathSupport() {
-        synchronized (STATIC_LOCK) {
-            xpathSupportClass = null;
-            jaxenXPathSupport = null;
-            try {
-                useXalanXPathSupport();
-            } catch (Exception e) {
-                // ignore
-            }
-            if (xpathSupportClass == null) try {
-            	useSunInternalXPathSupport();
-            } catch (Exception e) {
-                // ignore
-            }
-            if (xpathSupportClass == null) try {
-                useJaxenXPathSupport();
-            } catch (Exception e) {
-                // ignore
-            }
-        }
-    }
-    
-    /**
-     * Convenience method. Tells the system to use Jaxen for XPath queries.
-     * @throws Exception if the Jaxen classes are not present.
-     */
-    static public void useJaxenXPathSupport() throws Exception {
-        Class.forName("org.jaxen.dom.DOMXPath");
-        Class c = Class.forName("org.apache.freemarker.dom.JaxenXPathSupport");
-        jaxenXPathSupport = (XPathSupport) c.newInstance();
-        synchronized (STATIC_LOCK) {
-            xpathSupportClass = c;
-        }
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Using Jaxen classes for XPath support");
-        }
-    }
-    
-    /**
-     * Convenience method. Tells the system to use Xalan for XPath queries.
-     * @throws Exception if the Xalan XPath classes are not present.
-     */
-    static public void useXalanXPathSupport() throws Exception {
-        Class.forName("org.apache.xpath.XPath");
-        Class c = Class.forName("org.apache.freemarker.dom.XalanXPathSupport");
-        synchronized (STATIC_LOCK) {
-            xpathSupportClass = c;
-        }
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Using Xalan classes for XPath support");
-        }
-    }
-    
-    static public void useSunInternalXPathSupport() throws Exception {
-        Class.forName("com.sun.org.apache.xpath.internal.XPath");
-        Class c = Class.forName("org.apache.freemarker.dom.SunInternalXalanXPathSupport");
-        synchronized (STATIC_LOCK) {
-            xpathSupportClass = c;
-        }
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Using Sun's internal Xalan classes for XPath support");
-        }
-    }
-    
-    /**
-     * Set an alternative implementation of org.apache.freemarker.dom.XPathSupport to use
-     * as the XPath engine.
-     * @param cl the class, or <code>null</code> to disable XPath support.
-     */
-    static public void setXPathSupportClass(Class cl) {
-        if (cl != null && !XPathSupport.class.isAssignableFrom(cl)) {
-            throw new RuntimeException("Class " + cl.getName()
-                    + " does not implement org.apache.freemarker.dom.XPathSupport");
-        }
-        synchronized (STATIC_LOCK) {
-            xpathSupportClass = cl;
-        }
-    }
-
-    /**
-     * Get the currently used org.apache.freemarker.dom.XPathSupport used as the XPath engine.
-     * Returns <code>null</code> if XPath support is disabled.
-     */
-    static public Class getXPathSupportClass() {
-        synchronized (STATIC_LOCK) {
-            return xpathSupportClass;
-        }
-    }
-
-    static private String getText(Node node) {
-        String result = "";
-        if (node instanceof Text || node instanceof CDATASection) {
-            result = ((org.w3c.dom.CharacterData) node).getData();
-        } else if (node instanceof Element) {
-            NodeList children = node.getChildNodes();
-            for (int i = 0; i < children.getLength(); i++) {
-                result += getText(children.item(i));
-            }
-        } else if (node instanceof Document) {
-            result = getText(((Document) node).getDocumentElement());
-        }
-        return result;
-    }
-    
-    XPathSupport getXPathSupport() {
-        if (jaxenXPathSupport != null) {
-            return jaxenXPathSupport;
-        }
-        XPathSupport xps = null;
-        Document doc = node.getOwnerDocument();
-        if (doc == null) {
-            doc = (Document) node;
-        }
-        synchronized (doc) {
-            WeakReference ref = (WeakReference) xpathSupportMap.get(doc);
-            if (ref != null) {
-                xps = (XPathSupport) ref.get();
-            }
-            if (xps == null) {
-                try {
-                    xps = (XPathSupport) xpathSupportClass.newInstance();
-                    xpathSupportMap.put(doc, new WeakReference(xps));
-                } catch (Exception e) {
-                    LOG.error("Error instantiating xpathSupport class", e);
-                }                
-            }
-        }
-        return xps;
-    }
-    
-    
-    String getQualifiedName() throws TemplateModelException {
-        return getNodeName();
-    }
-    
-    @Override
-    public Object getAdaptedObject(Class hint) {
-        return node;
-    }
-    
-    @Override
-    public Object getWrappedObject() {
-        return node;
-    }
-    
-    @Override
-    public Object[] explainTypeError(Class[] expectedClasses) {
-        for (Class expectedClass : expectedClasses) {
-            if (TemplateDateModel.class.isAssignableFrom(expectedClass)
-                    || TemplateNumberModel.class.isAssignableFrom(expectedClass)
-                    || TemplateBooleanModel.class.isAssignableFrom(expectedClass)) {
-                return new Object[]{
-                        "XML node values are always strings (text), that is, they can't be used as number, "
-                                + "date/time/datetime or boolean without explicit conversion (such as "
-                                + "someNode?number, someNode?datetime.xs, someNode?date.xs, someNode?time.xs, "
-                                + "someNode?boolean).",
-                };
-            }
-        }
-        return null;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeOutputter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeOutputter.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeOutputter.java
deleted file mode 100644
index bda38ac..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeOutputter.java
+++ /dev/null
@@ -1,258 +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.dom;
-
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-
-import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.util._StringUtil;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.DocumentType;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-class NodeOutputter {
-    
-    private Element contextNode;
-    private Environment env;
-    private String defaultNS;
-    private boolean hasDefaultNS;
-    private boolean explicitDefaultNSPrefix;
-    private LinkedHashMap<String, String> namespacesToPrefixLookup = new LinkedHashMap<>();
-    private String namespaceDecl;
-    int nextGeneratedPrefixNumber = 1;
-    
-    NodeOutputter(Node node) {
-        if (node instanceof Element) {
-            setContext((Element) node);
-        } else if (node instanceof Attr) {
-            setContext(((Attr) node).getOwnerElement());
-        } else if (node instanceof Document) {
-            setContext(((Document) node).getDocumentElement());
-        }
-    }
-    
-    private void setContext(Element contextNode) {
-        this.contextNode = contextNode;
-        env = Environment.getCurrentEnvironment();
-        defaultNS = env.getDefaultNS();
-        hasDefaultNS = defaultNS != null && defaultNS.length() > 0;
-        namespacesToPrefixLookup.put(null, "");
-        namespacesToPrefixLookup.put("", "");
-        buildPrefixLookup(contextNode);
-        if (!explicitDefaultNSPrefix && hasDefaultNS) {
-            namespacesToPrefixLookup.put(defaultNS, "");
-        }
-        constructNamespaceDecl();
-    }
-    
-    private void buildPrefixLookup(Node n) {
-        String nsURI = n.getNamespaceURI();
-        if (nsURI != null && nsURI.length() > 0) {
-            String prefix = env.getPrefixForNamespace(nsURI);
-            if (prefix == null) {
-                prefix = namespacesToPrefixLookup.get(nsURI);
-                if (prefix == null) {
-                    // Assign a generated prefix:
-                    do {
-                        prefix = _StringUtil.toLowerABC(nextGeneratedPrefixNumber++);
-                    } while (env.getNamespaceForPrefix(prefix) != null);
-                }
-            }
-            namespacesToPrefixLookup.put(nsURI, prefix);
-        } else if (hasDefaultNS && n.getNodeType() == Node.ELEMENT_NODE) {
-            namespacesToPrefixLookup.put(defaultNS, Template.DEFAULT_NAMESPACE_PREFIX); 
-            explicitDefaultNSPrefix = true;
-        } else if (n.getNodeType() == Node.ATTRIBUTE_NODE && hasDefaultNS && defaultNS.equals(nsURI)) {
-            namespacesToPrefixLookup.put(defaultNS, Template.DEFAULT_NAMESPACE_PREFIX); 
-            explicitDefaultNSPrefix = true;
-        }
-        NodeList childNodes = n.getChildNodes();
-        for (int i = 0; i < childNodes.getLength(); i++) {
-            buildPrefixLookup(childNodes.item(i));
-        }
-    }
-    
-    private void constructNamespaceDecl() {
-        StringBuilder buf = new StringBuilder();
-        if (explicitDefaultNSPrefix) {
-            buf.append(" xmlns=\"");
-            buf.append(defaultNS);
-            buf.append("\"");
-        }
-        for (Iterator<String> it = namespacesToPrefixLookup.keySet().iterator(); it.hasNext(); ) {
-            String nsURI = it.next();
-            if (nsURI == null || nsURI.length() == 0) {
-                continue;
-            }
-            String prefix = namespacesToPrefixLookup.get(nsURI);
-            if (prefix == null) {
-                throw new BugException("No xmlns prefix was associated to URI: " + nsURI);
-            }
-            buf.append(" xmlns");
-            if (prefix.length() > 0) {
-                buf.append(":");
-                buf.append(prefix);
-            }
-            buf.append("=\"");
-            buf.append(nsURI);
-            buf.append("\"");
-        }
-        namespaceDecl = buf.toString();
-    }
-    
-    private void outputQualifiedName(Node n, StringBuilder buf) {
-        String nsURI = n.getNamespaceURI();
-        if (nsURI == null || nsURI.length() == 0) {
-            buf.append(n.getNodeName());
-        } else {
-            String prefix = namespacesToPrefixLookup.get(nsURI);
-            if (prefix == null) {
-                //REVISIT!
-                buf.append(n.getNodeName());
-            } else {
-                if (prefix.length() > 0) {
-                    buf.append(prefix);
-                    buf.append(':');
-                }
-                buf.append(n.getLocalName());
-            }
-        }
-    }
-    
-    void outputContent(Node n, StringBuilder buf) {
-        switch(n.getNodeType()) {
-            case Node.ATTRIBUTE_NODE: {
-                if (((Attr) n).getSpecified()) {
-                    buf.append(' ');
-                    outputQualifiedName(n, buf);
-                    buf.append("=\"")
-                       .append(_StringUtil.XMLEncQAttr(n.getNodeValue()))
-                       .append('"');
-                }
-                break;
-            }
-            case Node.COMMENT_NODE: {
-                buf.append("<!--").append(n.getNodeValue()).append("-->");
-                break;
-            }
-            case Node.DOCUMENT_NODE: {
-                outputContent(n.getChildNodes(), buf);
-                break;
-            }
-            case Node.DOCUMENT_TYPE_NODE: {
-                buf.append("<!DOCTYPE ").append(n.getNodeName());
-                DocumentType dt = (DocumentType) n;
-                if (dt.getPublicId() != null) {
-                    buf.append(" PUBLIC \"").append(dt.getPublicId()).append('"');
-                }
-                if (dt.getSystemId() != null) {
-                    buf.append(" \"").append(dt.getSystemId()).append('"');
-                }
-                if (dt.getInternalSubset() != null) {
-                    buf.append(" [").append(dt.getInternalSubset()).append(']');
-                }
-                buf.append('>');
-                break;
-            }
-            case Node.ELEMENT_NODE: {
-                buf.append('<');
-                outputQualifiedName(n, buf);
-                if (n == contextNode) {
-                    buf.append(namespaceDecl);
-                }
-                outputContent(n.getAttributes(), buf);
-                NodeList children = n.getChildNodes();
-                if (children.getLength() == 0) {
-                    buf.append(" />");
-                } else {
-                    buf.append('>');
-                    outputContent(n.getChildNodes(), buf);
-                    buf.append("</");
-                    outputQualifiedName(n, buf);
-                    buf.append('>');
-                }
-                break;
-            }
-            case Node.ENTITY_NODE: {
-                outputContent(n.getChildNodes(), buf);
-                break;
-            }
-            case Node.ENTITY_REFERENCE_NODE: {
-                buf.append('&').append(n.getNodeName()).append(';');
-                break;
-            }
-            case Node.PROCESSING_INSTRUCTION_NODE: {
-                buf.append("<?").append(n.getNodeName()).append(' ').append(n.getNodeValue()).append("?>");
-                break;
-            }
-            /*            
-                        case Node.CDATA_SECTION_NODE: {
-                            buf.append("<![CDATA[").append(n.getNodeValue()).append("]]>");
-                            break;
-                        }*/
-            case Node.CDATA_SECTION_NODE:
-            case Node.TEXT_NODE: {
-                buf.append(_StringUtil.XMLEncNQG(n.getNodeValue()));
-                break;
-            }
-        }
-    }
-
-    void outputContent(NodeList nodes, StringBuilder buf) {
-        for (int i = 0; i < nodes.getLength(); ++i) {
-            outputContent(nodes.item(i), buf);
-        }
-    }
-    
-    void outputContent(NamedNodeMap nodes, StringBuilder buf) {
-        for (int i = 0; i < nodes.getLength(); ++i) {
-            Node n = nodes.item(i);
-            if (n.getNodeType() != Node.ATTRIBUTE_NODE 
-                || (!n.getNodeName().startsWith("xmlns:") && !n.getNodeName().equals("xmlns"))) { 
-                outputContent(n, buf);
-            }
-        }
-    }
-    
-    String getOpeningTag(Element element) {
-        StringBuilder buf = new StringBuilder();
-        buf.append('<');
-        outputQualifiedName(element, buf);
-        buf.append(namespaceDecl);
-        outputContent(element.getAttributes(), buf);
-        buf.append('>');
-        return buf.toString();
-    }
-    
-    String getClosingTag(Element element) {
-        StringBuilder buf = new StringBuilder();
-        buf.append("</");
-        outputQualifiedName(element, buf);
-        buf.append('>');
-        return buf.toString();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeQueryResultItemObjectWrapper.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeQueryResultItemObjectWrapper.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeQueryResultItemObjectWrapper.java
deleted file mode 100644
index e84e977..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeQueryResultItemObjectWrapper.java
+++ /dev/null
@@ -1,92 +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.dom;
-
-import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelAdapter;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.WrappingTemplateModel;
-import org.apache.freemarker.core.model.impl.SimpleDate;
-import org.apache.freemarker.core.model.impl.SimpleNumber;
-import org.apache.freemarker.core.model.impl.SimpleScalar;
-import org.w3c.dom.Node;
-
-/**
- * Used for wrapping query result items (such as XPath query result items). Because {@link NodeModel} and such aren't
- * {@link WrappingTemplateModel}-s, we can't use the actual {@link ObjectWrapper} from the {@link Environment}, also,
- * even if we could, it might not be the right thing to do, because that  {@link ObjectWrapper} might not even wrap
- * {@link Node}-s via {@link NodeModel}.
- */
-class NodeQueryResultItemObjectWrapper implements ObjectWrapper {
-
-    static final NodeQueryResultItemObjectWrapper INSTANCE = new NodeQueryResultItemObjectWrapper();
-
-    private NodeQueryResultItemObjectWrapper() {
-        //
-    }
-
-    @Override
-    public TemplateModel wrap(Object obj) throws TemplateModelException {
-        if (obj instanceof NodeModel) {
-            return (NodeModel) obj;
-        }
-        if (obj instanceof Node) {
-            return NodeModel.wrap((Node) obj);
-        } else {
-            if (obj == null) {
-                return null;
-            }
-            if (obj instanceof TemplateModel) {
-                return (TemplateModel) obj;
-            }
-            if (obj instanceof TemplateModelAdapter) {
-                return ((TemplateModelAdapter) obj).getTemplateModel();
-            }
-
-            if (obj instanceof String) {
-                return new SimpleScalar((String) obj);
-            }
-            if (obj instanceof Number) {
-                return new SimpleNumber((Number) obj);
-            }
-            if (obj instanceof Boolean) {
-                return obj.equals(Boolean.TRUE) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
-            }
-            if (obj instanceof java.util.Date) {
-                if (obj instanceof java.sql.Date) {
-                    return new SimpleDate((java.sql.Date) obj);
-                }
-                if (obj instanceof java.sql.Time) {
-                    return new SimpleDate((java.sql.Time) obj);
-                }
-                if (obj instanceof java.sql.Timestamp) {
-                    return new SimpleDate((java.sql.Timestamp) obj);
-                }
-                return new SimpleDate((java.util.Date) obj, TemplateDateModel.UNKNOWN);
-            }
-            throw new TemplateModelException("Don't know how to wrap a W3C DOM query result item of this type: "
-                    + obj.getClass().getName());
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/PINodeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/PINodeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/PINodeModel.java
deleted file mode 100644
index 381d4d6..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/PINodeModel.java
+++ /dev/null
@@ -1,45 +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.dom;
-
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.w3c.dom.ProcessingInstruction;
-
-class PINodeModel extends NodeModel implements TemplateScalarModel {
-    
-    public PINodeModel(ProcessingInstruction pi) {
-        super(pi);
-    }
-    
-    @Override
-    public String getAsString() {
-        return ((ProcessingInstruction) node).getData();
-    }
-    
-    @Override
-    public String getNodeName() {
-        return "@pi$" + ((ProcessingInstruction) node).getTarget();
-    }
-    
-    @Override
-    public boolean isEmpty() {
-        return true;
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/SunInternalXalanXPathSupport.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/SunInternalXalanXPathSupport.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/SunInternalXalanXPathSupport.java
deleted file mode 100644
index 991c93f..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/SunInternalXalanXPathSupport.java
+++ /dev/null
@@ -1,163 +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.dom;
-
-import java.util.List;
-
-import javax.xml.transform.TransformerException;
-
-import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.impl.SimpleNumber;
-import org.apache.freemarker.core.model.impl.SimpleScalar;
-import org.w3c.dom.Node;
-import org.w3c.dom.traversal.NodeIterator;
-
-import com.sun.org.apache.xml.internal.utils.PrefixResolver;
-import com.sun.org.apache.xpath.internal.XPath;
-import com.sun.org.apache.xpath.internal.XPathContext;
-import com.sun.org.apache.xpath.internal.objects.XBoolean;
-import com.sun.org.apache.xpath.internal.objects.XNodeSet;
-import com.sun.org.apache.xpath.internal.objects.XNull;
-import com.sun.org.apache.xpath.internal.objects.XNumber;
-import com.sun.org.apache.xpath.internal.objects.XObject;
-import com.sun.org.apache.xpath.internal.objects.XString;
-
-/**
- * This is just the XalanXPathSupport class using the sun internal
- * package names
- */
-
-class SunInternalXalanXPathSupport implements XPathSupport {
-    
-    private XPathContext xpathContext = new XPathContext();
-        
-    private static final String ERRMSG_RECOMMEND_JAXEN
-            = "(Note that there is no such restriction if you "
-                    + "configure FreeMarker to use Jaxen instead of Xalan.)";
-
-    private static final String ERRMSG_EMPTY_NODE_SET
-            = "Cannot perform an XPath query against an empty node set." + ERRMSG_RECOMMEND_JAXEN;
-    
-    @Override
-    synchronized public TemplateModel executeQuery(Object context, String xpathQuery) throws TemplateModelException {
-        if (!(context instanceof Node)) {
-            if (context != null) {
-                if (isNodeList(context)) {
-                    int cnt = ((List) context).size();
-                    if (cnt != 0) {
-                        throw new TemplateModelException(
-                                "Cannot perform an XPath query against a node set of " + cnt
-                                + " nodes. Expecting a single node." + ERRMSG_RECOMMEND_JAXEN);
-                    } else {
-                        throw new TemplateModelException(ERRMSG_EMPTY_NODE_SET);
-                    }
-                } else {
-                    throw new TemplateModelException(
-                            "Cannot perform an XPath query against a " + context.getClass().getName()
-                            + ". Expecting a single org.w3c.dom.Node.");
-                }
-            } else {
-                throw new TemplateModelException(ERRMSG_EMPTY_NODE_SET);
-            }
-        }
-        Node node = (Node) context;
-        try {
-            XPath xpath = new XPath(xpathQuery, null, customPrefixResolver, XPath.SELECT, null);
-            int ctxtNode = xpathContext.getDTMHandleFromNode(node);
-            XObject xresult = xpath.execute(xpathContext, ctxtNode, customPrefixResolver);
-            if (xresult instanceof XNodeSet) {
-                NodeListModel result = new NodeListModel(node);
-                result.xpathSupport = this;
-                NodeIterator nodeIterator = xresult.nodeset();
-                Node n;
-                do {
-                    n = nodeIterator.nextNode();
-                    if (n != null) {
-                        result.add(n);
-                    }
-                } while (n != null);
-                return result.size() == 1 ? result.get(0) : result;
-            }
-            if (xresult instanceof XBoolean) {
-                return ((XBoolean) xresult).bool() ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
-            }
-            if (xresult instanceof XNull) {
-                return null;
-            }
-            if (xresult instanceof XString) {
-                return new SimpleScalar(xresult.toString());
-            }
-            if (xresult instanceof XNumber) {
-                return new SimpleNumber(Double.valueOf(((XNumber) xresult).num()));
-            }
-            throw new TemplateModelException("Cannot deal with type: " + xresult.getClass().getName());
-        } catch (TransformerException te) {
-            throw new TemplateModelException(te);
-        }
-    }
-    
-    private static PrefixResolver customPrefixResolver = new PrefixResolver() {
-        
-        @Override
-        public String getNamespaceForPrefix(String prefix, Node node) {
-            return getNamespaceForPrefix(prefix);
-        }
-        
-        @Override
-        public String getNamespaceForPrefix(String prefix) {
-            if (prefix.equals(Template.DEFAULT_NAMESPACE_PREFIX)) {
-                return Environment.getCurrentEnvironment().getDefaultNS();
-            }
-            return Environment.getCurrentEnvironment().getNamespaceForPrefix(prefix);
-        }
-        
-        @Override
-        public String getBaseIdentifier() {
-            return null;
-        }
-        
-        @Override
-        public boolean handlesNullPrefixes() {
-            return false;
-        }
-    };
-    
-    /**
-     * Used for generating more intelligent error messages.
-     */
-    private static boolean isNodeList(Object context) {
-        if (context instanceof List) {
-            List ls = (List) context;
-            int ln = ls.size();
-            for (int i = 0; i < ln; i++) {
-                if (!(ls.get(i) instanceof Node)) {
-                    return false;
-                }
-            }
-            return true;
-        } else {
-            return false;
-        }
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/XPathSupport.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/XPathSupport.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/XPathSupport.java
deleted file mode 100644
index e94d391..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/XPathSupport.java
+++ /dev/null
@@ -1,30 +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.dom;
-
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-
-public interface XPathSupport {
-    
-    // [2.4] Add argument to pass down the ObjectWrapper to use 
-    TemplateModel executeQuery(Object context, String xpathQuery) throws TemplateModelException;
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/XalanXPathSupport.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/XalanXPathSupport.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/XalanXPathSupport.java
deleted file mode 100644
index 99a4249..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/XalanXPathSupport.java
+++ /dev/null
@@ -1,163 +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.dom;
-
-import java.util.List;
-
-import javax.xml.transform.TransformerException;
-
-import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.impl.SimpleNumber;
-import org.apache.freemarker.core.model.impl.SimpleScalar;
-import org.apache.xml.utils.PrefixResolver;
-import org.apache.xpath.XPath;
-import org.apache.xpath.XPathContext;
-import org.apache.xpath.objects.XBoolean;
-import org.apache.xpath.objects.XNodeSet;
-import org.apache.xpath.objects.XNull;
-import org.apache.xpath.objects.XNumber;
-import org.apache.xpath.objects.XObject;
-import org.apache.xpath.objects.XString;
-import org.w3c.dom.Node;
-import org.w3c.dom.traversal.NodeIterator;
-
-/**
- * Some glue code that bridges the Xalan XPath stuff (that is built into the JDK 1.4.x)
- * with FreeMarker TemplateModel semantics
- */
-
-class XalanXPathSupport implements XPathSupport {
-    
-    private XPathContext xpathContext = new XPathContext();
-        
-    /* I don't recommend Jaxen...
-    private static final String ERRMSG_RECOMMEND_JAXEN
-            = "(Note that there is no such restriction if you "
-                    + "configure FreeMarker to use Jaxen instead of Xalan.)";
-    */
-    private static final String ERRMSG_EMPTY_NODE_SET
-            = "Cannot perform an XPath query against an empty node set."; /* " + ERRMSG_RECOMMEND_JAXEN;*/
-    
-    @Override
-    synchronized public TemplateModel executeQuery(Object context, String xpathQuery) throws TemplateModelException {
-        if (!(context instanceof Node)) {
-            if (context != null) {
-                if (isNodeList(context)) {
-                    int cnt = ((List) context).size();
-                    if (cnt != 0) {
-                        throw new TemplateModelException(
-                                "Cannot perform an XPath query against a node set of " + cnt
-                                + " nodes. Expecting a single node."/* " + ERRMSG_RECOMMEND_JAXEN*/);
-                    } else {
-                        throw new TemplateModelException(ERRMSG_EMPTY_NODE_SET);
-                    }
-                } else {
-                    throw new TemplateModelException(
-                            "Cannot perform an XPath query against a " + context.getClass().getName()
-                            + ". Expecting a single org.w3c.dom.Node.");
-                }
-            } else {
-                throw new TemplateModelException(ERRMSG_EMPTY_NODE_SET);
-            }
-        }
-        Node node = (Node) context;
-        try {
-            XPath xpath = new XPath(xpathQuery, null, customPrefixResolver, XPath.SELECT, null);
-            int ctxtNode = xpathContext.getDTMHandleFromNode(node);
-            XObject xresult = xpath.execute(xpathContext, ctxtNode, customPrefixResolver);
-            if (xresult instanceof XNodeSet) {
-                NodeListModel result = new NodeListModel(node);
-                result.xpathSupport = this;
-                NodeIterator nodeIterator = xresult.nodeset();
-                Node n;
-                do {
-                    n = nodeIterator.nextNode();
-                    if (n != null) {
-                        result.add(n);
-                    }
-                } while (n != null);
-                return result.size() == 1 ? result.get(0) : result;
-            }
-            if (xresult instanceof XBoolean) {
-                return ((XBoolean) xresult).bool() ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
-            }
-            if (xresult instanceof XNull) {
-                return null;
-            }
-            if (xresult instanceof XString) {
-                return new SimpleScalar(xresult.toString());
-            }
-            if (xresult instanceof XNumber) {
-                return new SimpleNumber(Double.valueOf(((XNumber) xresult).num()));
-            }
-            throw new TemplateModelException("Cannot deal with type: " + xresult.getClass().getName());
-        } catch (TransformerException te) {
-            throw new TemplateModelException(te);
-        }
-    }
-    
-    private static PrefixResolver customPrefixResolver = new PrefixResolver() {
-        
-        @Override
-        public String getNamespaceForPrefix(String prefix, Node node) {
-            return getNamespaceForPrefix(prefix);
-        }
-        
-        @Override
-        public String getNamespaceForPrefix(String prefix) {
-            if (prefix.equals(Template.DEFAULT_NAMESPACE_PREFIX)) {
-                return Environment.getCurrentEnvironment().getDefaultNS();
-            }
-            return Environment.getCurrentEnvironment().getNamespaceForPrefix(prefix);
-        }
-        
-        @Override
-        public String getBaseIdentifier() {
-            return null;
-        }
-        
-        @Override
-        public boolean handlesNullPrefixes() {
-            return false;
-        }
-    };
-    
-    /**
-     * Used for generating more intelligent error messages.
-     */
-    private static boolean isNodeList(Object context) {
-        if (context instanceof List) {
-            List ls = (List) context;
-            int ln = ls.size();
-            for (int i = 0; i < ln; i++) {
-                if (!(ls.get(i) instanceof Node)) {
-                    return false;
-                }
-            }
-            return true;
-        } else {
-            return false;
-        }
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/package.html
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/package.html b/freemarker-core/src/main/java/org/apache/freemarker/dom/package.html
deleted file mode 100644
index 61b1737..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/package.html
+++ /dev/null
@@ -1,30 +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.
--->
-<html>
-<head>
-<title></title>
-</head>
-<body>
-
-<p>Exposes DOM XML nodes to templates as easily traversable trees;
-see <a href="http://freemarker.org/docs/xgui.html" target="_blank">in the Manual</a>.
-The default object wrapper of FreeMarker can automatically wraps W3C nodes with this.
-
-</body>
-</html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-dom/build.gradle
----------------------------------------------------------------------
diff --git a/freemarker-dom/build.gradle b/freemarker-dom/build.gradle
new file mode 100644
index 0000000..0fe800f
--- /dev/null
+++ b/freemarker-dom/build.gradle
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+title = "Apache FreeMarker DOM support"
+description = """\
+FreeMarker template engine, W3C DOM (XML) wrapping support. \
+This is an optional module, useful when the data-model can contain variables that are XML nodes."""
+
+dependencies {
+    compile project(":freemarker-core")
+
+    compileOnly "jaxen:jaxen:1.0-FCS"
+    compileOnly "saxpath:saxpath:1.0-FCS"
+    compileOnly("xalan:xalan:2.7.0") {
+        // xml-apis is part of Java SE since version 1.4:
+        exclude group: "xml-apis", module: "xml-apis"
+    }
+
+    testCompile project(":freemarker-test-utils")
+    testRuntime "jaxen:jaxen:1.0-FCS"
+    testRuntime "saxpath:saxpath:1.0-FCS"
+    testRuntime("xalan:xalan:2.7.0") {
+        // xml-apis is part of Java SE since version 1.4:
+        exclude group: "xml-apis", module: "xml-apis"
+    }
+}
+
+jar {
+    manifest {
+        instructionReplace 'Bundle-RequiredExecutionEnvironment', 'JavaSE-1.7'
+
+        attributes(
+                "Extension-name": "${project.group}:${project.name}",
+                "Specification-Title": project.title,
+                "Implementation-Title": project.title
+        )
+    }
+}
+
+javadoc {
+    title "${project.title} ${versionCanonical} API"
+}
+
+// The identical parts of Maven "deployer" and "installer" configurations:
+def mavenCommons = { callerDelegate ->
+    delegate = callerDelegate
+
+    pom.project {
+        description project.description
+    }
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            mavenCommons(delegate)
+        }
+    }
+}
+
+install {
+    repositories {
+        mavenInstaller {
+            mavenCommons(delegate)
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-dom/src/main/java/org/apache/freemarker/dom/AtAtKey.java
----------------------------------------------------------------------
diff --git a/freemarker-dom/src/main/java/org/apache/freemarker/dom/AtAtKey.java b/freemarker-dom/src/main/java/org/apache/freemarker/dom/AtAtKey.java
new file mode 100644
index 0000000..ca6ac6b
--- /dev/null
+++ b/freemarker-dom/src/main/java/org/apache/freemarker/dom/AtAtKey.java
@@ -0,0 +1,58 @@
+/*
+ * 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.dom;
+
+/**
+ * The special hash keys that start with "@@".
+ */
+enum AtAtKey {
+    
+    MARKUP("@@markup"),
+    NESTED_MARKUP("@@nested_markup"),
+    ATTRIBUTES_MARKUP("@@attributes_markup"),
+    TEXT("@@text"),
+    START_TAG("@@start_tag"),
+    END_TAG("@@end_tag"),
+    QNAME("@@qname"),
+    NAMESPACE("@@namespace"),
+    LOCAL_NAME("@@local_name"),
+    ATTRIBUTES("@@"),
+    PREVIOUS_SIBLING_ELEMENT("@@previous_sibling_element"),
+    NEXT_SIBLING_ELEMENT("@@next_sibling_element");
+
+    private final String key;
+
+    public String getKey() {
+        return key;
+    }
+
+    AtAtKey(String key) {
+        this.key = key;
+    }
+    
+    public static boolean containsKey(String key) {
+        for (AtAtKey item : AtAtKey.values()) {
+            if (item.getKey().equals(key)) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-dom/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-dom/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java b/freemarker-dom/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java
new file mode 100644
index 0000000..cc510c4
--- /dev/null
+++ b/freemarker-dom/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java
@@ -0,0 +1,69 @@
+/*
+ * 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.dom;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.w3c.dom.Attr;
+
+class AttributeNodeModel extends NodeModel implements TemplateScalarModel {
+    
+    public AttributeNodeModel(Attr att) {
+        super(att);
+    }
+    
+    @Override
+    public String getAsString() {
+        return ((Attr) node).getValue();
+    }
+    
+    @Override
+    public String getNodeName() {
+        String result = node.getLocalName();
+        if (result == null || result.equals("")) {
+            result = node.getNodeName();
+        }
+        return result;
+    }
+    
+    @Override
+    public boolean isEmpty() {
+        return true;
+    }
+    
+    @Override
+    String getQualifiedName() {
+        String nsURI = node.getNamespaceURI();
+        if (nsURI == null || nsURI.equals(""))
+            return node.getNodeName();
+        Environment env = Environment.getCurrentEnvironment();
+        String defaultNS = env.getDefaultNS();
+        String prefix = null;
+        if (nsURI.equals(defaultNS)) {
+            prefix = "D";
+        } else {
+            prefix = env.getPrefixForNamespace(nsURI);
+        }
+        if (prefix == null) {
+            return null;
+        }
+        return prefix + ":" + node.getLocalName();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-dom/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-dom/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java b/freemarker-dom/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java
new file mode 100644
index 0000000..264c0db
--- /dev/null
+++ b/freemarker-dom/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java
@@ -0,0 +1,46 @@
+/*
+ * 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.dom;
+
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.w3c.dom.CharacterData;
+import org.w3c.dom.Comment;
+
+class CharacterDataNodeModel extends NodeModel implements TemplateScalarModel {
+    
+    public CharacterDataNodeModel(CharacterData text) {
+        super(text);
+    }
+    
+    @Override
+    public String getAsString() {
+        return ((org.w3c.dom.CharacterData) node).getData();
+    }
+    
+    @Override
+    public String getNodeName() {
+        return (node instanceof Comment) ? "@comment" : "@text";
+    }
+    
+    @Override
+    public boolean isEmpty() {
+        return true;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-dom/src/main/java/org/apache/freemarker/dom/DOMDefaultObjectWrapperExtension.java
----------------------------------------------------------------------
diff --git a/freemarker-dom/src/main/java/org/apache/freemarker/dom/DOMDefaultObjectWrapperExtension.java b/freemarker-dom/src/main/java/org/apache/freemarker/dom/DOMDefaultObjectWrapperExtension.java
new file mode 100644
index 0000000..90c50b7
--- /dev/null
+++ b/freemarker-dom/src/main/java/org/apache/freemarker/dom/DOMDefaultObjectWrapperExtension.java
@@ -0,0 +1,48 @@
+/*
+ * 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.dom;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapperExtension;
+import org.w3c.dom.Node;
+
+/**
+ * Add this extension to {@link DefaultObjectWrapper} if you want {@link Node}-s to be wrapped into {@link NodeModel}-s.
+ */
+public class DOMDefaultObjectWrapperExtension extends DefaultObjectWrapperExtension {
+
+    /**
+     * The singleton instance of this class.
+     */
+    public static final DOMDefaultObjectWrapperExtension INSTANCE = new DOMDefaultObjectWrapperExtension();
+
+    private DOMDefaultObjectWrapperExtension() {
+        // private to hide it from outside
+    }
+
+    @Override
+    public TemplateModel wrap(Object obj) {
+        if (obj instanceof Node) {
+            return NodeModel.wrap((Node) obj);
+        }
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-dom/src/main/java/org/apache/freemarker/dom/DocumentModel.java
----------------------------------------------------------------------
diff --git a/freemarker-dom/src/main/java/org/apache/freemarker/dom/DocumentModel.java b/freemarker-dom/src/main/java/org/apache/freemarker/dom/DocumentModel.java
new file mode 100644
index 0000000..876b3cf
--- /dev/null
+++ b/freemarker-dom/src/main/java/org/apache/freemarker/dom/DocumentModel.java
@@ -0,0 +1,76 @@
+/*
+ * 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.dom;
+ 
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+
+/**
+ * A class that wraps the root node of a parsed XML document, using
+ * the W3C DOM_WRAPPER API.
+ */
+
+class DocumentModel extends NodeModel implements TemplateHashModel {
+    
+    private ElementModel rootElement;
+    
+    DocumentModel(Document doc) {
+        super(doc);
+    }
+    
+    @Override
+    public String getNodeName() {
+        return "@document";
+    }
+    
+    @Override
+    public TemplateModel get(String key) throws TemplateModelException {
+        if (key.equals("*")) {
+            return getRootElement();
+        } else if (key.equals("**")) {
+            NodeList nl = ((Document) node).getElementsByTagName("*");
+            return new NodeListModel(nl, this);
+        } else if (DomStringUtil.isXMLNameLike(key)) {
+            ElementModel em = (ElementModel) NodeModel.wrap(((Document) node).getDocumentElement());
+            if (em.matchesName(key, Environment.getCurrentEnvironment())) {
+                return em;
+            } else {
+                return new NodeListModel(this);
+            }
+        }
+        return super.get(key);
+    }
+    
+    ElementModel getRootElement() {
+        if (rootElement == null) {
+            rootElement = (ElementModel) wrap(((Document) node).getDocumentElement());
+        }
+        return rootElement;
+    }
+    
+    @Override
+    public boolean isEmpty() {
+        return false;
+    }
+} 
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-dom/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-dom/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java b/freemarker-dom/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java
new file mode 100644
index 0000000..3448f77
--- /dev/null
+++ b/freemarker-dom/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java
@@ -0,0 +1,56 @@
+/*
+ * 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.dom;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.ProcessingInstruction;
+
+class DocumentTypeModel extends NodeModel {
+    
+    public DocumentTypeModel(DocumentType docType) {
+        super(docType);
+    }
+    
+    public String getAsString() {
+        return ((ProcessingInstruction) node).getData();
+    }
+    
+    public TemplateSequenceModel getChildren() throws TemplateModelException {
+        throw new TemplateModelException("entering the child nodes of a DTD node is not currently supported");
+    }
+    
+    @Override
+    public TemplateModel get(String key) throws TemplateModelException {
+        throw new TemplateModelException("accessing properties of a DTD is not currently supported");
+    }
+    
+    @Override
+    public String getNodeName() {
+        return "@document_type$" + node.getNodeName();
+    }
+    
+    @Override
+    public boolean isEmpty() {
+        return true;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-dom/src/main/java/org/apache/freemarker/dom/DomLog.java
----------------------------------------------------------------------
diff --git a/freemarker-dom/src/main/java/org/apache/freemarker/dom/DomLog.java b/freemarker-dom/src/main/java/org/apache/freemarker/dom/DomLog.java
new file mode 100644
index 0000000..a1f6f0c
--- /dev/null
+++ b/freemarker-dom/src/main/java/org/apache/freemarker/dom/DomLog.java
@@ -0,0 +1,32 @@
+/*
+ * 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.dom;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class DomLog {
+
+    private DomLog() {
+        //
+    }
+
+    public static final Logger LOG = LoggerFactory.getLogger("org.apache.freemarker.dom");
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-dom/src/main/java/org/apache/freemarker/dom/DomStringUtil.java
----------------------------------------------------------------------
diff --git a/freemarker-dom/src/main/java/org/apache/freemarker/dom/DomStringUtil.java b/freemarker-dom/src/main/java/org/apache/freemarker/dom/DomStringUtil.java
new file mode 100644
index 0000000..f5b58f8
--- /dev/null
+++ b/freemarker-dom/src/main/java/org/apache/freemarker/dom/DomStringUtil.java
@@ -0,0 +1,67 @@
+/*
+ * 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.dom;
+
+/**
+ * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
+ * This class is to work around the lack of module system in Java, i.e., so that other FreeMarker packages can
+ * access things inside this package that users shouldn't. 
+ */
+final class DomStringUtil {
+
+    private DomStringUtil() {
+        // Not meant to be instantiated
+    }
+
+    static boolean isXMLNameLike(String name) {
+        return isXMLNameLike(name, 0);
+    }
+    
+    /**
+     * Check if the name looks like an XML element name.
+     * 
+     * @param firstCharIdx The index of the character in the string parameter that we treat as the beginning of the
+     *      string to check. This is to spare substringing that has become more expensive in Java 7.  
+     * 
+     * @return whether the name is a valid XML element name. (This routine might only be 99% accurate. REVISIT)
+     */
+    static boolean isXMLNameLike(String name, int firstCharIdx) {
+        int ln = name.length();
+        for (int i = firstCharIdx; i < ln; i++) {
+            char c = name.charAt(i);
+            if (i == firstCharIdx && (c == '-' || c == '.' || Character.isDigit(c))) {
+                return false;
+            }
+            if (!Character.isLetterOrDigit(c) && c != '_' && c != '-' && c != '.') {
+                if (c == ':') {
+                    if (i + 1 < ln && name.charAt(i + 1) == ':') {
+                        // "::" is used in XPath
+                        return false;
+                    }
+                    // We don't return here, as a lonely ":" is allowed.
+                } else {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }    
+
+}