freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [3/4] incubator-freemarker git commit: Moved o.a.f.core.model.impl.dom to org.apache.freemarker.dom, so that later it can be a separate module (separate jar)
Date Sun, 26 Feb 2017 01:57:07 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/59412b29/src/main/java/org/apache/freemarker/core/model/impl/dom/NodeOutputter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/dom/NodeOutputter.java b/src/main/java/org/apache/freemarker/core/model/impl/dom/NodeOutputter.java
deleted file mode 100644
index a4a6efe..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/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.core.model.impl.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/59412b29/src/main/java/org/apache/freemarker/core/model/impl/dom/PINodeModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/dom/PINodeModel.java b/src/main/java/org/apache/freemarker/core/model/impl/dom/PINodeModel.java
deleted file mode 100644
index f2fe0f7..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/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.core.model.impl.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/59412b29/src/main/java/org/apache/freemarker/core/model/impl/dom/SunInternalXalanXPathSupport.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/dom/SunInternalXalanXPathSupport.java b/src/main/java/org/apache/freemarker/core/model/impl/dom/SunInternalXalanXPathSupport.java
deleted file mode 100644
index b00d2a1..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/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.core.model.impl.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/59412b29/src/main/java/org/apache/freemarker/core/model/impl/dom/XPathSupport.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/dom/XPathSupport.java b/src/main/java/org/apache/freemarker/core/model/impl/dom/XPathSupport.java
deleted file mode 100644
index d0b12d3..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/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.core.model.impl.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/59412b29/src/main/java/org/apache/freemarker/core/model/impl/dom/XalanXPathSupport.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/dom/XalanXPathSupport.java b/src/main/java/org/apache/freemarker/core/model/impl/dom/XalanXPathSupport.java
deleted file mode 100644
index 1dcb994..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/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.core.model.impl.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/59412b29/src/main/java/org/apache/freemarker/core/model/impl/dom/_ExtDomApi.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/dom/_ExtDomApi.java b/src/main/java/org/apache/freemarker/core/model/impl/dom/_ExtDomApi.java
deleted file mode 100644
index d4663c5..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/dom/_ExtDomApi.java
+++ /dev/null
@@ -1,43 +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 org.apache.freemarker.core.Environment;
-
-/**
- * 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. 
- */
-public final class _ExtDomApi {
-
-    private _ExtDomApi() {
-        // Not meant to be called
-    }
-    
-    static public boolean isXMLNameLike(String name) {
-        return DomStringUtil.isXMLNameLike(name);
-    }
-    
-    static public boolean matchesName(String qname, String nodeName, String nsURI, Environment env) {
-        return DomStringUtil.matchesName(qname, nodeName, nsURI, env);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/59412b29/src/main/java/org/apache/freemarker/core/model/impl/dom/package.html
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/dom/package.html b/src/main/java/org/apache/freemarker/core/model/impl/dom/package.html
deleted file mode 100644
index a3518ff..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/dom/package.html
+++ /dev/null
@@ -1,31 +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 {@link freemarker.template.DefaultObjectWrapper default object wrapper} of FreeMarker
-automatically wraps W3C nodes with this.
-
-</body>
-</html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/59412b29/src/main/java/org/apache/freemarker/core/util/_StringUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/_StringUtil.java b/src/main/java/org/apache/freemarker/core/util/_StringUtil.java
index 9674727..69f1d0e 100644
--- a/src/main/java/org/apache/freemarker/core/util/_StringUtil.java
+++ b/src/main/java/org/apache/freemarker/core/util/_StringUtil.java
@@ -30,8 +30,8 @@ import java.util.regex.Pattern;
 
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.Template;
 import org.apache.freemarker.core.Version;
-import org.apache.freemarker.core.model.impl.dom._ExtDomApi;
 
 /** Don't use this; used internally by FreeMarker, might changes without notice. */
 public class _StringUtil {
@@ -1098,8 +1098,24 @@ public class _StringUtil {
     /**
      * @return whether the qname matches the combination of nodeName, nsURI, and environment prefix settings.
      */
-    static public boolean matchesName(String qname, String nodeName, String nsURI, Environment env) {
-        return _ExtDomApi.matchesName(qname, nodeName, nsURI, env);
+    static public boolean matchesQName(String qname, String nodeName, String nsURI, Environment env) {
+        String defaultNS = env.getDefaultNS();
+        if ((defaultNS != null) && defaultNS.equals(nsURI)) {
+            return qname.equals(nodeName)
+                    || qname.equals(Template.DEFAULT_NAMESPACE_PREFIX + ":" + nodeName);
+        }
+        if ("".equals(nsURI)) {
+            if (defaultNS != null) {
+                return qname.equals(Template.NO_NS_PREFIX + ":" + nodeName);
+            } else {
+                return qname.equals(nodeName) || qname.equals(Template.NO_NS_PREFIX + ":" + nodeName);
+            }
+        }
+        String prefix = env.getPrefixForNamespace(nsURI);
+        if (prefix == null) {
+            return false; // Is this the right thing here???
+        }
+        return qname.equals(prefix + ":" + nodeName);
     }
     
     /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/59412b29/src/main/java/org/apache/freemarker/dom/AtAtKey.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/dom/AtAtKey.java b/src/main/java/org/apache/freemarker/dom/AtAtKey.java
new file mode 100644
index 0000000..9c105a2
--- /dev/null
+++ b/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/59412b29/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java b/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java
new file mode 100644
index 0000000..31e00eb
--- /dev/null
+++ b/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/59412b29/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java b/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java
new file mode 100644
index 0000000..c08e711
--- /dev/null
+++ b/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/59412b29/src/main/java/org/apache/freemarker/dom/DocumentModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/dom/DocumentModel.java b/src/main/java/org/apache/freemarker/dom/DocumentModel.java
new file mode 100644
index 0000000..ee84635
--- /dev/null
+++ b/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/59412b29/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java b/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java
new file mode 100644
index 0000000..b911e2c
--- /dev/null
+++ b/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/59412b29/src/main/java/org/apache/freemarker/dom/DomLog.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/dom/DomLog.java b/src/main/java/org/apache/freemarker/dom/DomLog.java
new file mode 100644
index 0000000..21632cf
--- /dev/null
+++ b/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/59412b29/src/main/java/org/apache/freemarker/dom/DomStringUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/dom/DomStringUtil.java b/src/main/java/org/apache/freemarker/dom/DomStringUtil.java
new file mode 100644
index 0000000..58d9d32
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/dom/DomStringUtil.java
@@ -0,0 +1,70 @@
+/*
+ * 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.Template;
+
+/**
+ * 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;
+    }    
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/59412b29/src/main/java/org/apache/freemarker/dom/ElementModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/dom/ElementModel.java b/src/main/java/org/apache/freemarker/dom/ElementModel.java
new file mode 100644
index 0000000..420fef5
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/dom/ElementModel.java
@@ -0,0 +1,234 @@
+/*
+ * 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.Collections;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.Template;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.apache.freemarker.core.util._StringUtil;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+class ElementModel extends NodeModel implements TemplateScalarModel {
+
+    public ElementModel(Element element) {
+        super(element);
+    }
+    
+    @Override
+    public boolean isEmpty() {
+        return false;
+    }
+    
+    /**
+     * An Element node supports various hash keys.
+     * Any key that corresponds to the tag name of any child elements
+     * returns a sequence of those elements. The special key "*" returns 
+     * all the element's direct children.
+     * The "**" key return all the element's descendants in the order they
+     * occur in the document.
+     * Any key starting with '@' is taken to be the name of an element attribute.
+     * The special key "@@" returns a hash of all the element's attributes.
+     * The special key "/" returns the root document node associated with this element.
+     */
+    @Override
+    public TemplateModel get(String key) throws TemplateModelException {
+        if (key.equals("*")) {
+            NodeListModel ns = new NodeListModel(this);
+            TemplateSequenceModel children = getChildNodes();
+            for (int i = 0; i < children.size(); i++) {
+                NodeModel child = (NodeModel) children.get(i);
+                if (child.node.getNodeType() == Node.ELEMENT_NODE) {
+                    ns.add(child);
+                }
+            }
+            return ns;
+        } else if (key.equals("**")) {
+            return new NodeListModel(((Element) node).getElementsByTagName("*"), this);    
+        } else if (key.startsWith("@")) {
+            if (key.startsWith("@@")) {
+                if (key.equals(AtAtKey.ATTRIBUTES.getKey())) {
+                    return new NodeListModel(node.getAttributes(), this);
+                } else if (key.equals(AtAtKey.START_TAG.getKey())) {
+                    NodeOutputter nodeOutputter = new NodeOutputter(node);
+                    return new SimpleScalar(nodeOutputter.getOpeningTag((Element) node));
+                } else if (key.equals(AtAtKey.END_TAG.getKey())) {
+                    NodeOutputter nodeOutputter = new NodeOutputter(node);
+                    return new SimpleScalar(nodeOutputter.getClosingTag((Element) node));
+                } else if (key.equals(AtAtKey.ATTRIBUTES_MARKUP.getKey())) {
+                    StringBuilder buf = new StringBuilder();
+                    NodeOutputter nu = new NodeOutputter(node);
+                    nu.outputContent(node.getAttributes(), buf);
+                    return new SimpleScalar(buf.toString().trim());
+                } else if (key.equals(AtAtKey.PREVIOUS_SIBLING_ELEMENT.getKey())) {
+                    Node previousSibling = node.getPreviousSibling();
+                    while (previousSibling != null && !isSignificantNode(previousSibling)) {
+                        previousSibling = previousSibling.getPreviousSibling();
+                    }
+                    return previousSibling != null && previousSibling.getNodeType() == Node.ELEMENT_NODE
+                            ? wrap(previousSibling) : new NodeListModel(Collections.emptyList(), null);  
+                } else if (key.equals(AtAtKey.NEXT_SIBLING_ELEMENT.getKey())) {
+                    Node nextSibling = node.getNextSibling();
+                    while (nextSibling != null && !isSignificantNode(nextSibling)) {
+                        nextSibling = nextSibling.getNextSibling();
+                    }
+                    return nextSibling != null && nextSibling.getNodeType() == Node.ELEMENT_NODE
+                            ? wrap(nextSibling) : new NodeListModel(Collections.emptyList(), null);  
+                } else {
+                    // We don't know anything like this that's element-specific; fall back 
+                    return super.get(key);
+                }
+            } else { // Starts with "@", but not with "@@"
+                if (DomStringUtil.isXMLNameLike(key, 1)) {
+                    Attr att = getAttribute(key.substring(1));
+                    if (att == null) { 
+                        return new NodeListModel(this);
+                    }
+                    return wrap(att);
+                } else if (key.equals("@*")) {
+                    return new NodeListModel(node.getAttributes(), this);
+                } else {
+                    // We don't know anything like this that's element-specific; fall back 
+                    return super.get(key);
+                }
+            }
+        } else if (DomStringUtil.isXMLNameLike(key)) {
+            // We interpret key as an element name
+            NodeListModel result = ((NodeListModel) getChildNodes()).filterByName(key);
+            return result.size() != 1 ? result : result.get(0);
+        } else {
+            // We don't anything like this that's element-specific; fall back 
+            return super.get(key);
+        }
+    }
+
+    @Override
+    public String getAsString() throws TemplateModelException {
+        NodeList nl = node.getChildNodes();
+        String result = "";
+        for (int i = 0; i < nl.getLength(); i++) {
+            Node child = nl.item(i);
+            int nodeType = child.getNodeType();
+            if (nodeType == Node.ELEMENT_NODE) {
+                String msg = "Only elements with no child elements can be processed as text."
+                             + "\nThis element with name \""
+                             + node.getNodeName()
+                             + "\" has a child element named: " + child.getNodeName();
+                throw new TemplateModelException(msg);
+            } else if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) {
+                result += child.getNodeValue();
+            }
+        }
+        return result;
+    }
+    
+    @Override
+    public String getNodeName() {
+        String result = node.getLocalName();
+        if (result == null || result.equals("")) {
+            result = node.getNodeName();
+        }
+        return result;
+    }
+    
+    @Override
+    String getQualifiedName() {
+        String nodeName = getNodeName();
+        String nsURI = getNodeNamespace();
+        if (nsURI == null || nsURI.length() == 0) {
+            return nodeName;
+        }
+        Environment env = Environment.getCurrentEnvironment();
+        String defaultNS = env.getDefaultNS();
+        String prefix;
+        if (defaultNS != null && defaultNS.equals(nsURI)) {
+            prefix = "";
+        } else {
+            prefix = env.getPrefixForNamespace(nsURI);
+            
+        }
+        if (prefix == null) {
+            return null; // We have no qualified name, because there is no prefix mapping
+        }
+        if (prefix.length() > 0) {
+            prefix += ":";
+        }
+        return prefix + nodeName;
+    }
+    
+    private Attr getAttribute(String qname) {
+        Element element = (Element) node;
+        Attr result = element.getAttributeNode(qname);
+        if (result != null)
+            return result;
+        int colonIndex = qname.indexOf(':');
+        if (colonIndex > 0) {
+            String prefix = qname.substring(0, colonIndex);
+            String uri;
+            if (prefix.equals(Template.DEFAULT_NAMESPACE_PREFIX)) {
+                uri = Environment.getCurrentEnvironment().getDefaultNS();
+            } else {
+                uri = Environment.getCurrentEnvironment().getNamespaceForPrefix(prefix);
+            }
+            String localName = qname.substring(1 + colonIndex);
+            if (uri != null) {
+                result = element.getAttributeNodeNS(uri, localName);
+            }
+        }
+        return result;
+    }
+    
+    private boolean isSignificantNode(Node node) throws TemplateModelException {
+        return (node.getNodeType() == Node.TEXT_NODE || node.getNodeType() == Node.CDATA_SECTION_NODE)
+                ? !isBlankXMLText(node.getTextContent())
+                : node.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE && node.getNodeType() != Node.COMMENT_NODE;
+    }
+    
+    private boolean isBlankXMLText(String s) {
+        if (s == null) {
+            return true;
+        }
+        for (int i = 0; i < s.length(); i++) {
+            if (!isXMLWhiteSpace(s.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * White space according the XML spec. 
+     */
+    private boolean isXMLWhiteSpace(char c) {
+        return c == ' ' || c == '\t' || c == '\n' | c == '\r';
+    }
+
+    boolean matchesName(String name, Environment env) {
+        return _StringUtil.matchesQName(name, getNodeName(), getNodeNamespace(), env);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/59412b29/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java b/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java
new file mode 100644
index 0000000..44e6f8b
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java
@@ -0,0 +1,238 @@
+/*
+ * 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.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.freemarker.core.CustomAttribute;
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.Template;
+import org.apache.freemarker.core.TemplateException;
+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.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.impl._StaticObjectWrappers;
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+import org.jaxen.BaseXPath;
+import org.jaxen.Function;
+import org.jaxen.FunctionCallException;
+import org.jaxen.FunctionContext;
+import org.jaxen.JaxenException;
+import org.jaxen.NamespaceContext;
+import org.jaxen.Navigator;
+import org.jaxen.UnresolvableException;
+import org.jaxen.VariableContext;
+import org.jaxen.XPathFunctionContext;
+import org.jaxen.dom.DocumentNavigator;
+import org.w3c.dom.Document;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+
+/**
+ */
+class JaxenXPathSupport implements XPathSupport {
+    
+    private static final CustomAttribute XPATH_CACHE_ATTR = 
+        new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE) {
+            @Override
+            protected Object create() {
+                return new HashMap<String, BaseXPath>();
+            }
+        };
+
+        // [2.4] Can't we just use Collections.emptyList()? 
+    private final static ArrayList EMPTY_ARRAYLIST = new ArrayList();
+
+    @Override
+    public TemplateModel executeQuery(Object context, String xpathQuery) throws TemplateModelException {
+        try {
+            BaseXPath xpath;
+            Map<String, BaseXPath> xpathCache = (Map<String, BaseXPath>) XPATH_CACHE_ATTR.get();
+            synchronized (xpathCache) {
+                xpath = xpathCache.get(xpathQuery);
+                if (xpath == null) {
+                    xpath = new BaseXPath(xpathQuery, FM_DOM_NAVIGATOR);
+                    xpath.setNamespaceContext(customNamespaceContext);
+                    xpath.setFunctionContext(FM_FUNCTION_CONTEXT);
+                    xpath.setVariableContext(FM_VARIABLE_CONTEXT);
+                    xpathCache.put(xpathQuery, xpath);
+                }
+            }
+            List result = xpath.selectNodes(context != null ? context : EMPTY_ARRAYLIST);
+            if (result.size() == 1) {
+                // [2.4] Use the proper object wrapper (argument in 2.4) 
+                return _StaticObjectWrappers.DEFAULT_OBJECT_WRAPPER.wrap(result.get(0));
+            }
+            NodeListModel nlm = new NodeListModel(result, null);
+            nlm.xpathSupport = this;
+            return nlm;
+        } catch (UndeclaredThrowableException e) {
+            Throwable t  = e.getUndeclaredThrowable();
+            if (t instanceof TemplateModelException) {
+                throw (TemplateModelException) t;
+            }
+            throw e;
+        } catch (JaxenException je) {
+            throw new TemplateModelException(je);
+        }
+    }
+
+    static private final NamespaceContext customNamespaceContext = new NamespaceContext() {
+        
+        @Override
+        public String translateNamespacePrefixToUri(String prefix) {
+            if (prefix.equals(Template.DEFAULT_NAMESPACE_PREFIX)) {
+                return Environment.getCurrentEnvironment().getDefaultNS();
+            }
+            return Environment.getCurrentEnvironment().getNamespaceForPrefix(prefix);
+        }
+    };
+
+    private static final VariableContext FM_VARIABLE_CONTEXT = new VariableContext() {
+        @Override
+        public Object getVariableValue(String namespaceURI, String prefix, String localName)
+        throws UnresolvableException {
+            try {
+                TemplateModel model = Environment.getCurrentEnvironment().getVariable(localName);
+                if (model == null) {
+                    throw new UnresolvableException("Variable \"" + localName + "\" not found.");
+                }
+                if (model instanceof TemplateScalarModel) {
+                    return ((TemplateScalarModel) model).getAsString();
+                }
+                if (model instanceof TemplateNumberModel) {
+                    return ((TemplateNumberModel) model).getAsNumber();
+                }
+                if (model instanceof TemplateDateModel) {
+                    return ((TemplateDateModel) model).getAsDate();
+                }
+                if (model instanceof TemplateBooleanModel) {
+                    return Boolean.valueOf(((TemplateBooleanModel) model).getAsBoolean());
+                }
+            } catch (TemplateModelException e) {
+                throw new UndeclaredThrowableException(e);
+            }
+            throw new UnresolvableException(
+                    "Variable \"" + localName + "\" exists, but it's not a string, number, date, or boolean");
+        }
+    };
+     
+    private static final FunctionContext FM_FUNCTION_CONTEXT = new XPathFunctionContext() {
+        @Override
+        public Function getFunction(String namespaceURI, String prefix, String localName)
+        throws UnresolvableException {
+            try {
+                return super.getFunction(namespaceURI, prefix, localName);
+            } catch (UnresolvableException e) {
+                return super.getFunction(null, null, localName);
+            }
+        }
+    };
+    
+    /**
+     * Stores the the template parsed as {@link Document} in the template itself.
+     */
+    private static final CustomAttribute FM_DOM_NAVIAGOTOR_CACHED_DOM
+            = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE);
+     
+    private static final Navigator FM_DOM_NAVIGATOR = new DocumentNavigator() {
+        @Override
+        public Object getDocument(String uri) throws FunctionCallException {
+            try {
+                Template raw = getTemplate(uri);
+                Document doc = (Document) FM_DOM_NAVIAGOTOR_CACHED_DOM.get(raw);
+                if (doc == null) {
+                    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+                    factory.setNamespaceAware(true);
+                    DocumentBuilder builder = factory.newDocumentBuilder();
+                    FmEntityResolver er = new FmEntityResolver();
+                    builder.setEntityResolver(er);
+                    doc = builder.parse(createInputSource(null, raw));
+                    // If the entity resolver got called 0 times, the document
+                    // is standalone, so we can safely cache it
+                    if (er.getCallCount() == 0) {
+                        FM_DOM_NAVIAGOTOR_CACHED_DOM.set(doc, raw);
+                    }
+                }
+                return doc;
+            } catch (Exception e) {
+                throw new FunctionCallException("Failed to parse document for URI: " + uri, e);
+            }
+        }
+    };
+
+    // [FM3] Look into this "hidden" feature
+    static Template getTemplate(String systemId) throws IOException {
+        Environment env = Environment.getCurrentEnvironment();
+        String encoding = env.getCurrentTemplate().getEncoding(); // [FM3] Encoding shouldn't be inherited anymore
+        if (encoding == null) {
+            encoding = env.getConfiguration().getEncoding(env.getLocale());
+        }
+        String templatePath = env.getCurrentTemplate().getName();
+        int lastSlash = templatePath.lastIndexOf('/');
+        templatePath = lastSlash == -1 ? "" : templatePath.substring(0, lastSlash + 1);
+        systemId = env.toFullTemplateName(templatePath, systemId);
+        return env.getConfiguration().getTemplate(systemId, env.getLocale(), encoding, false);
+    }
+
+    private static InputSource createInputSource(String publicId, Template raw) throws IOException, SAXException {
+        StringWriter sw = new StringWriter();
+        try {
+            raw.process(Collections.EMPTY_MAP, sw);
+        } catch (TemplateException e) {
+            throw new SAXException(e);
+        }
+        InputSource is = new InputSource();
+        is.setPublicId(publicId);
+        is.setSystemId(raw.getName());
+        is.setCharacterStream(new StringReader(sw.toString()));
+        return is;
+    }
+
+    private static class FmEntityResolver implements EntityResolver {
+        private int callCount = 0;
+        
+        @Override
+        public InputSource resolveEntity(String publicId, String systemId)
+        throws SAXException, IOException {
+            ++callCount;
+            return createInputSource(publicId, getTemplate(systemId));
+        }
+        
+        int getCallCount() {
+            return callCount;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/59412b29/src/main/java/org/apache/freemarker/dom/NodeListModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/dom/NodeListModel.java b/src/main/java/org/apache/freemarker/dom/NodeListModel.java
new file mode 100644
index 0000000..7505930
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/dom/NodeListModel.java
@@ -0,0 +1,231 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel;
+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.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.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.apache.freemarker.core.model.impl.SimpleSequence;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Used when the result set contains 0 or multiple nodes; shouldn't be used when you have exactly 1 node. For exactly 1
+ * node, use {@link NodeModel#wrap(Node)}, because {@link NodeModel} subclasses can have extra features building on that
+ * restriction, like single elements with text content can be used as FTL string-s.
+ * 
+ * <p>
+ * This class is not guaranteed to be thread safe, so instances of this shouldn't be used as shared variable (
+ * {@link Configuration#setSharedVariable(String, Object)}).
+ */
+class NodeListModel extends SimpleSequence implements TemplateHashModel, _UnexpectedTypeErrorExplainerTemplateModel {
+    
+    // [2.4] make these private
+    NodeModel contextNode;
+    XPathSupport xpathSupport;
+    
+    private static ObjectWrapper nodeWrapper = new ObjectWrapper() {
+        @Override
+        public TemplateModel wrap(Object obj) {
+            if (obj instanceof NodeModel) {
+                return (NodeModel) obj;
+            }
+            return NodeModel.wrap((Node) obj);
+        }
+    };
+    
+    NodeListModel(Node contextNode) {
+        this(NodeModel.wrap(contextNode));
+    }
+    
+    NodeListModel(NodeModel contextNode) {
+        super(nodeWrapper);
+        this.contextNode = contextNode;
+    }
+    
+    NodeListModel(NodeList nodeList, NodeModel contextNode) {
+        super(nodeWrapper);
+        for (int i = 0; i < nodeList.getLength(); i++) {
+            list.add(nodeList.item(i));
+        }
+        this.contextNode = contextNode;
+    }
+    
+    NodeListModel(NamedNodeMap nodeList, NodeModel contextNode) {
+        super(nodeWrapper);
+        for (int i = 0; i < nodeList.getLength(); i++) {
+            list.add(nodeList.item(i));
+        }
+        this.contextNode = contextNode;
+    }
+    
+    NodeListModel(List list, NodeModel contextNode) {
+        super(list, nodeWrapper);
+        this.contextNode = contextNode;
+    }
+    
+    NodeListModel filterByName(String name) throws TemplateModelException {
+        NodeListModel result = new NodeListModel(contextNode);
+        int size = size();
+        if (size == 0) {
+            return result;
+        }
+        Environment env = Environment.getCurrentEnvironment();
+        for (int i = 0; i < size; i++) {
+            NodeModel nm = (NodeModel) get(i);
+            if (nm instanceof ElementModel) {
+                if (((ElementModel) nm).matchesName(name, env)) {
+                    result.add(nm);
+                }
+            }
+        }
+        return result;
+    }
+    
+    @Override
+    public boolean isEmpty() {
+        return size() == 0;
+    }
+    
+    @Override
+    public TemplateModel get(String key) throws TemplateModelException {
+        if (size() == 1) {
+            NodeModel nm = (NodeModel) get(0);
+            return nm.get(key);
+        }
+        if (key.startsWith("@@")) {
+            if (key.equals(AtAtKey.MARKUP.getKey()) 
+                    || key.equals(AtAtKey.NESTED_MARKUP.getKey()) 
+                    || key.equals(AtAtKey.TEXT.getKey())) {
+                StringBuilder result = new StringBuilder();
+                for (int i = 0; i < size(); i++) {
+                    NodeModel nm = (NodeModel) get(i);
+                    TemplateScalarModel textModel = (TemplateScalarModel) nm.get(key);
+                    result.append(textModel.getAsString());
+                }
+                return new SimpleScalar(result.toString());
+            } else if (key.length() != 2 /* to allow "@@" to fall through */) {
+                // As @@... would cause exception in the XPath engine, we throw a nicer exception now. 
+                if (AtAtKey.containsKey(key)) {
+                    throw new TemplateModelException(
+                            "\"" + key + "\" is only applicable to a single XML node, but it was applied on "
+                            + (size() != 0
+                                    ? size() + " XML nodes (multiple matches)."
+                                    : "an empty list of XML nodes (no matches)."));
+                } else {
+                    throw new TemplateModelException("Unsupported @@ key: " + key);
+                }
+            }
+        }
+        if (DomStringUtil.isXMLNameLike(key) 
+                || ((key.startsWith("@")
+                        && (DomStringUtil.isXMLNameLike(key, 1)  || key.equals("@@") || key.equals("@*"))))
+                || key.equals("*") || key.equals("**")) {
+            NodeListModel result = new NodeListModel(contextNode);
+            for (int i = 0; i < size(); i++) {
+                NodeModel nm = (NodeModel) get(i);
+                if (nm instanceof ElementModel) {
+                    TemplateSequenceModel tsm = (TemplateSequenceModel) nm.get(key);
+                    if (tsm != null) {
+                        int size = tsm.size();
+                        for (int j = 0; j < size; j++) {
+                            result.add(tsm.get(j));
+                        }
+                    }
+                }
+            }
+            if (result.size() == 1) {
+                return result.get(0);
+            }
+            return result;
+        }
+        XPathSupport xps = getXPathSupport();
+        if (xps != null) {
+            Object context = (size() == 0) ? null : rawNodeList(); 
+            return xps.executeQuery(context, 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);
+        }
+    }
+    
+    private List rawNodeList() throws TemplateModelException {
+        int size = size();
+        ArrayList al = new ArrayList(size);
+        for (int i = 0; i < size; i++) {
+            al.add(((NodeModel) get(i)).node);
+        }
+        return al;
+    }
+    
+    XPathSupport getXPathSupport() throws TemplateModelException {
+        if (xpathSupport == null) {
+            if (contextNode != null) {
+                xpathSupport = contextNode.getXPathSupport();
+            } else if (size() > 0) {
+                xpathSupport = ((NodeModel) get(0)).getXPathSupport();
+            }
+        }
+        return xpathSupport;
+    }
+
+    @Override
+    public Object[] explainTypeError(Class[] expectedClasses) {
+        for (Class expectedClass : expectedClasses) {
+            if (TemplateScalarModel.class.isAssignableFrom(expectedClass)
+                    || TemplateDateModel.class.isAssignableFrom(expectedClass)
+                    || TemplateNumberModel.class.isAssignableFrom(expectedClass)
+                    || TemplateBooleanModel.class.isAssignableFrom(expectedClass)) {
+                return newTypeErrorExplanation("string");
+            } else if (TemplateNodeModel.class.isAssignableFrom(expectedClass)) {
+                return newTypeErrorExplanation("node");
+            }
+        }
+        return null;
+    }
+
+    private Object[] newTypeErrorExplanation(String type) {
+        return new Object[] {
+                "This XML query result can't be used as ", type, " because for that it had to contain exactly "
+                + "1 XML node, but it contains ", Integer.valueOf(size()), " nodes. "
+                + "That is, the constructing XML query has found ",
+                isEmpty()
+                    ? "no matches."
+                    : "multiple matches."
+                };
+    }
+    
+}
\ No newline at end of file


Mime
View raw message