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
