/*
 * 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 freemarker.ext.dom;


import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import freemarker.core._UnexpectedTypeErrorExplainerTemplateModel;
import freemarker.ext.util.WrapperTemplateModel;
import freemarker.log.Logger;
import freemarker.template.AdapterTemplateModel;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateDateModel;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNodeModel;
import freemarker.template.TemplateNodeModelEx;
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateSequenceModel;

A base class for wrapping a single W3C DOM Node as a FreeMarker template model.

Note that DefaultObjectWrapper automatically wraps W3C DOM Node-s into this, so you may need do that with this class manually. However, before dropping the Node-s into the data-model, you certainly want to apply simplify(Node) on them.

This class is not guaranteed to be thread safe, so instances of this shouldn't be used as shared variable ( Configuration.setSharedVariable(String, Object)).

To represent a node sequence (such as a query result) of exactly 1 nodes, this class should be used instead of NodeListModel, as it adds extra capabilities by utilizing that we have exactly 1 node. If you need to wrap a node sequence of 0 or multiple nodes, you must use NodeListModel.

/** * A base class for wrapping a single W3C DOM Node as a FreeMarker template model. * * <p> * Note that {@link DefaultObjectWrapper} automatically wraps W3C DOM {@link Node}-s into this, so you may need do that * with this class manually. However, before dropping the {@link Node}-s into the data-model, you certainly want to * apply {@link NodeModel#simplify(Node)} on them. * * <p> * This class is not guaranteed to be thread safe, so instances of this shouldn't be used as shared variable ( * {@link Configuration#setSharedVariable(String, Object)}). * * <p> * To represent a node sequence (such as a query result) of exactly 1 nodes, this class should be used instead of * {@link NodeListModel}, as it adds extra capabilities by utilizing that we have exactly 1 node. If you need to wrap a * node sequence of 0 or multiple nodes, you must use {@link NodeListModel}. */
abstract public class NodeModel implements TemplateNodeModelEx, TemplateHashModel, TemplateSequenceModel, AdapterTemplateModel, WrapperTemplateModel, _UnexpectedTypeErrorExplainerTemplateModel { static private final Logger LOG = Logger.getLogger("freemarker.dom"); private static final Object STATIC_LOCK = new Object(); static private DocumentBuilderFactory docBuilderFactory; static private final Map xpathSupportMap = Collections.synchronizedMap(new WeakHashMap()); static private XPathSupport jaxenXPathSupport; static private ErrorHandler errorHandler; static Class xpathSupportClass; static { try { useDefaultXPathSupport(); } catch (Exception e) { // do nothing } if (xpathSupportClass == null && LOG.isWarnEnabled()) { LOG.warn("No XPath support is available."); } }
The W3C DOM Node being wrapped.
/** * The W3C DOM Node being wrapped. */
final Node node; private TemplateSequenceModel children; private NodeModel parent;
Sets the DOM parser implementation to be used when building NodeModel objects from XML files or from InputStream with the static convenience methods of NodeModel. Otherwise FreeMarker itself doesn't use this.
See Also:
Deprecated:It's a bad practice to change static fields, as if multiple independent components do that in the same JVM, they unintentionally affect each other. Therefore it's recommended to leave this static value at its default.
/** * Sets the DOM parser implementation to be used when building {@link NodeModel} objects from XML files or from * {@link InputStream} with the static convenience methods of {@link NodeModel}. Otherwise FreeMarker itself doesn't * use this. * * @see #getDocumentBuilderFactory() * * @deprecated It's a bad practice to change static fields, as if multiple independent components do that in the * same JVM, they unintentionally affect each other. Therefore it's recommended to leave this static * value at its default. */
@Deprecated static public void setDocumentBuilderFactory(DocumentBuilderFactory docBuilderFactory) { synchronized (STATIC_LOCK) { NodeModel.docBuilderFactory = docBuilderFactory; } }
Returns the DOM parser implementation that is used when building NodeModel objects from XML files or from InputStream with the static convenience methods of NodeModel. Otherwise FreeMarker itself doesn't use this.
See Also:
/** * Returns the DOM parser implementation that is used when building {@link NodeModel} objects from XML files or from * {@link InputStream} with the static convenience methods of {@link NodeModel}. Otherwise FreeMarker itself doesn't * use this. * * @see #setDocumentBuilderFactory(DocumentBuilderFactory) */
static public DocumentBuilderFactory getDocumentBuilderFactory() { synchronized (STATIC_LOCK) { if (docBuilderFactory == null) { DocumentBuilderFactory newFactory = DocumentBuilderFactory.newInstance(); newFactory.setNamespaceAware(true); newFactory.setIgnoringElementContentWhitespace(true); docBuilderFactory = newFactory; // We only write it out when the initialization was full } return docBuilderFactory; } }
Sets the error handler to use when parsing the document with the static convenience methods of NodeModel.
See Also:
Deprecated:It's a bad practice to change static fields, as if multiple independent components do that in the same JVM, they unintentionally affect each other. Therefore it's recommended to leave this static value at its default.
/** * Sets the error handler to use when parsing the document with the static convenience methods of {@link NodeModel}. * * @deprecated It's a bad practice to change static fields, as if multiple independent components do that in the * same JVM, they unintentionally affect each other. Therefore it's recommended to leave this static * value at its default. * * @see #getErrorHandler() */
@Deprecated static public void setErrorHandler(ErrorHandler errorHandler) { synchronized (STATIC_LOCK) { NodeModel.errorHandler = errorHandler; } }
See Also:
Since:2.3.20
/** * @since 2.3.20 * * @see #setErrorHandler(ErrorHandler) */
static public ErrorHandler getErrorHandler() { synchronized (STATIC_LOCK) { return NodeModel.errorHandler; } }
Convenience method to create a NodeModel from a SAX InputSource; please see the security warning further down. Adjacent text nodes will be merged (and CDATA sections are considered as text nodes) as with mergeAdjacentText(Node). Further simplifications are applied depending on the parameters. If all simplifications are turned on, then it applies simplify(Node) on the loaded DOM.

Note that parse(...) is only a convenience method, and FreeMarker itself doesn't use it (except when you call the other similar static convenience methods, as they may build on each other). In particular, if you want full control over the DocumentBuilderFactory used, create the Node with your own DocumentBuilderFactory, apply simplify(Node) (or such) on it, then call wrap(Node).

Security warning: If the XML to load is coming from a source that you can't fully trust, you shouldn't use this method, as the DocumentBuilderFactory it uses by default supports external entities, and so it doesn't prevent XML External Entity (XXE) attacks. Note that XXE attacks are not specific to FreeMarker, they affect all XML parsers in general. If that's a problem in your application, OWASP has a cheat sheet to set up a DocumentBuilderFactory that has limited functionality but is immune to XXE attacks. Because it's just a convenience method, you can just use your own DocumentBuilderFactory and do a few extra steps instead (see earlier).

Params:
  • removeComments – Whether to remove all comment nodes (recursively); this is like calling removeComments(Node)
  • removePIs – Whether to remove all processing instruction nodes (recursively); this is like calling removePIs(Node)
/** * Convenience method to create a {@link NodeModel} from a SAX {@link InputSource}; please see the security warning * further down. Adjacent text nodes will be merged (and CDATA sections are considered as text nodes) as with * {@link #mergeAdjacentText(Node)}. Further simplifications are applied depending on the parameters. If all * simplifications are turned on, then it applies {@link #simplify(Node)} on the loaded DOM. * * <p> * Note that {@code parse(...)} is only a convenience method, and FreeMarker itself doesn't use it (except when you * call the other similar static convenience methods, as they may build on each other). In particular, if you want * full control over the {@link DocumentBuilderFactory} used, create the {@link Node} with your own * {@link DocumentBuilderFactory}, apply {@link #simplify(Node)} (or such) on it, then call * {@link NodeModel#wrap(Node)}. * * <p> * <b>Security warning:</b> If the XML to load is coming from a source that you can't fully trust, you shouldn't use * this method, as the {@link DocumentBuilderFactory} it uses by default supports external entities, and so it * doesn't prevent XML External Entity (XXE) attacks. Note that XXE attacks are not specific to FreeMarker, they * affect all XML parsers in general. If that's a problem in your application, OWASP has a cheat sheet to set up a * {@link DocumentBuilderFactory} that has limited functionality but is immune to XXE attacks. Because it's just a * convenience method, you can just use your own {@link DocumentBuilderFactory} and do a few extra steps instead * (see earlier). * * @param removeComments * Whether to remove all comment nodes (recursively); this is like calling {@link #removeComments(Node)} * @param removePIs * Whether to remove all processing instruction nodes (recursively); this is like calling * {@link #removePIs(Node)} */
static public NodeModel parse(InputSource is, boolean removeComments, boolean removePIs) throws SAXException, IOException, ParserConfigurationException { DocumentBuilder builder = getDocumentBuilderFactory().newDocumentBuilder(); ErrorHandler errorHandler = getErrorHandler(); if (errorHandler != null) builder.setErrorHandler(errorHandler); final Document doc; try { doc = builder.parse(is); } catch (MalformedURLException e) { // This typical error has an error message that is hard to understand, so let's translate it: if (is.getSystemId() == null && is.getCharacterStream() == null && is.getByteStream() == null) { throw new MalformedURLException( "The SAX InputSource has systemId == null && characterStream == null && byteStream == null. " + "This is often because it was created with a null InputStream or Reader, which is often because " + "the XML file it should point to was not found. " + "(The original exception was: " + e + ")"); } else { throw e; } } if (removeComments && removePIs) { simplify(doc); } else { if (removeComments) { removeComments(doc); } if (removePIs) { removePIs(doc); } mergeAdjacentText(doc); } return wrap(doc); }
Same as parse(is, true, true); don't miss the security warnings documented there.
/** * Same as {@link #parse(InputSource, boolean, boolean) parse(is, true, true)}; don't miss the security warnings * documented there. */
static public NodeModel parse(InputSource is) throws SAXException, IOException, ParserConfigurationException { return parse(is, true, true); }
Same as parse(InputSource, boolean, boolean), but loads from a File; don't miss the security warnings documented there.
/** * Same as {@link #parse(InputSource, boolean, boolean)}, but loads from a {@link File}; don't miss the security * warnings documented there. */
static public NodeModel parse(File f, boolean removeComments, boolean removePIs) throws SAXException, IOException, ParserConfigurationException { DocumentBuilder builder = getDocumentBuilderFactory().newDocumentBuilder(); ErrorHandler errorHandler = getErrorHandler(); if (errorHandler != null) builder.setErrorHandler(errorHandler); Document doc = builder.parse(f); if (removeComments && removePIs) { simplify(doc); } else { if (removeComments) { removeComments(doc); } if (removePIs) { removePIs(doc); } mergeAdjacentText(doc); } return wrap(doc); }
Same as parse(source, true, true), but loads from a File; don't miss the security warnings documented there.
/** * Same as {@link #parse(InputSource, boolean, boolean) parse(source, true, true)}, but loads from a {@link File}; * don't miss the security warnings documented there. */
static public NodeModel parse(File f) throws SAXException, IOException, ParserConfigurationException { return parse(f, true, true); } protected NodeModel(Node node) { this.node = node; }
Returns:the underling W3C DOM Node object that this TemplateNodeModel is wrapping.
/** * @return the underling W3C DOM Node object that this TemplateNodeModel * is wrapping. */
public Node getNode() { return node; } public TemplateModel get(String key) throws TemplateModelException { if (key.startsWith("@@")) { if (key.equals(AtAtKey.TEXT.getKey())) { return new SimpleScalar(getText(node)); } else if (key.equals(AtAtKey.NAMESPACE.getKey())) { String nsURI = node.getNamespaceURI(); return nsURI == null ? null : new SimpleScalar(nsURI); } else if (key.equals(AtAtKey.LOCAL_NAME.getKey())) { String localName = node.getLocalName(); if (localName == null) { localName = getNodeName(); } return new SimpleScalar(localName); } else if (key.equals(AtAtKey.MARKUP.getKey())) { StringBuilder buf = new StringBuilder(); NodeOutputter nu = new NodeOutputter(node); nu.outputContent(node, buf); return new SimpleScalar(buf.toString()); } else if (key.equals(AtAtKey.NESTED_MARKUP.getKey())) { StringBuilder buf = new StringBuilder(); NodeOutputter nu = new NodeOutputter(node); nu.outputContent(node.getChildNodes(), buf); return new SimpleScalar(buf.toString()); } else if (key.equals(AtAtKey.QNAME.getKey())) { String qname = getQualifiedName(); return qname != null ? new SimpleScalar(qname) : null; } else { // As @@... would cause exception in the XPath engine, we throw a nicer exception now. if (AtAtKey.containsKey(key)) { throw new TemplateModelException( "\"" + key + "\" is not supported for an XML node of type \"" + getNodeType() + "\"."); } else { throw new TemplateModelException("Unsupported @@ key: " + key); } } } else { XPathSupport xps = getXPathSupport(); if (xps != null) { return xps.executeQuery(node, key); } else { throw new TemplateModelException( "Can't try to resolve the XML query key, because no XPath support is available. " + "This is either malformed or an XPath expression: " + key); } } } public TemplateNodeModel getParentNode() { if (parent == null) { Node parentNode = node.getParentNode(); if (parentNode == null) { if (node instanceof Attr) { parentNode = ((Attr) node).getOwnerElement(); } } parent = wrap(parentNode); } return parent; } public TemplateNodeModelEx getPreviousSibling() throws TemplateModelException { return wrap(node.getPreviousSibling()); } public TemplateNodeModelEx getNextSibling() throws TemplateModelException { return wrap(node.getNextSibling()); } public TemplateSequenceModel getChildNodes() { if (children == null) { children = new NodeListModel(node.getChildNodes(), this); } return children; } public final String getNodeType() throws TemplateModelException { short nodeType = node.getNodeType(); switch (nodeType) { case Node.ATTRIBUTE_NODE : return "attribute"; case Node.CDATA_SECTION_NODE : return "text"; case Node.COMMENT_NODE : return "comment"; case Node.DOCUMENT_FRAGMENT_NODE : return "document_fragment"; case Node.DOCUMENT_NODE : return "document"; case Node.DOCUMENT_TYPE_NODE : return "document_type"; case Node.ELEMENT_NODE : return "element"; case Node.ENTITY_NODE : return "entity"; case Node.ENTITY_REFERENCE_NODE : return "entity_reference"; case Node.NOTATION_NODE : return "notation"; case Node.PROCESSING_INSTRUCTION_NODE : return "pi"; case Node.TEXT_NODE : return "text"; } throw new TemplateModelException("Unknown node type: " + nodeType + ". This should be impossible!"); } public TemplateModel exec(List args) throws TemplateModelException { if (args.size() != 1) { throw new TemplateModelException("Expecting exactly one arguments"); } String query = (String) args.get(0); // Now, we try to behave as if this is an XPath expression XPathSupport xps = getXPathSupport(); if (xps == null) { throw new TemplateModelException("No XPath support available"); } return xps.executeQuery(node, query); }
Always returns 1.
/** * Always returns 1. */
public final int size() { return 1; } public final TemplateModel get(int i) { return i == 0 ? this : null; } public String getNodeNamespace() { int nodeType = node.getNodeType(); if (nodeType != Node.ATTRIBUTE_NODE && nodeType != Node.ELEMENT_NODE) { return null; } String result = node.getNamespaceURI(); if (result == null && nodeType == Node.ELEMENT_NODE) { result = ""; } else if ("".equals(result) && nodeType == Node.ATTRIBUTE_NODE) { result = null; } return result; } @Override public final int hashCode() { return node.hashCode(); } @Override public boolean equals(Object other) { if (other == null) return false; return other.getClass() == this.getClass() && ((NodeModel) other).node.equals(this.node); } static public NodeModel wrap(Node node) { if (node == null) { return null; } NodeModel result = null; switch (node.getNodeType()) { case Node.DOCUMENT_NODE : result = new DocumentModel((Document) node); break; case Node.ELEMENT_NODE : result = new ElementModel((Element) node); break; case Node.ATTRIBUTE_NODE : result = new AttributeNodeModel((Attr) node); break; case Node.CDATA_SECTION_NODE : case Node.COMMENT_NODE : case Node.TEXT_NODE : result = new CharacterDataNodeModel((org.w3c.dom.CharacterData) node); break; case Node.PROCESSING_INSTRUCTION_NODE : result = new PINodeModel((ProcessingInstruction) node); break; case Node.DOCUMENT_TYPE_NODE : result = new DocumentTypeModel((DocumentType) node); break; } return result; }
Recursively removes all comment nodes from the subtree.
See Also:
  • simplify
/** * Recursively removes all comment nodes from the subtree. * * @see #simplify */
static public void removeComments(Node parent) { Node child = parent.getFirstChild(); while (child != null) { Node nextSibling = child.getNextSibling(); if (child.getNodeType() == Node.COMMENT_NODE) { parent.removeChild(child); } else if (child.hasChildNodes()) { removeComments(child); } child = nextSibling; } }
Recursively removes all processing instruction nodes from the subtree.
See Also:
  • simplify
/** * Recursively removes all processing instruction nodes from the subtree. * * @see #simplify */
static public void removePIs(Node parent) { Node child = parent.getFirstChild(); while (child != null) { Node nextSibling = child.getNextSibling(); if (child.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) { parent.removeChild(child); } else if (child.hasChildNodes()) { removePIs(child); } child = nextSibling; } }
Merges adjacent text nodes (where CDATA counts as text node too). Operates recursively on the entire subtree. The merged node will have the type of the first node of the adjacent merged nodes.

Because XPath assumes that there are no adjacent text nodes in the tree, not doing this can have undesirable side effects. Xalan queries like text() will only return the first of a list of matching adjacent text nodes instead of all of them, while Jaxen will return all of them as intuitively expected.

See Also:
/** * Merges adjacent text nodes (where CDATA counts as text node too). Operates recursively on the entire subtree. * The merged node will have the type of the first node of the adjacent merged nodes. * * <p>Because XPath assumes that there are no adjacent text nodes in the tree, not doing this can have * undesirable side effects. Xalan queries like {@code text()} will only return the first of a list of matching * adjacent text nodes instead of all of them, while Jaxen will return all of them as intuitively expected. * * @see #simplify */
static public void mergeAdjacentText(Node parent) { mergeAdjacentText(parent, new StringBuilder(0)); } static private void mergeAdjacentText(Node parent, StringBuilder collectorBuf) { Node child = parent.getFirstChild(); while (child != null) { Node next = child.getNextSibling(); if (child instanceof Text) { boolean atFirstText = true; while (next instanceof Text) { // if (atFirstText) { collectorBuf.setLength(0); collectorBuf.ensureCapacity(child.getNodeValue().length() + next.getNodeValue().length()); collectorBuf.append(child.getNodeValue()); atFirstText = false; } collectorBuf.append(next.getNodeValue()); parent.removeChild(next); next = child.getNextSibling(); } if (!atFirstText && collectorBuf.length() != 0) { ((CharacterData) child).setData(collectorBuf.toString()); } } else { mergeAdjacentText(child, collectorBuf); } child = next; } }
Removes all comments and processing instruction, and unites adjacent text nodes (here CDATA counts as text as well). This is similar to applying removeComments(Node), removePIs(Node), and finally mergeAdjacentText(Node), but it does all that somewhat faster.
/** * Removes all comments and processing instruction, and unites adjacent text nodes (here CDATA counts as text as * well). This is similar to applying {@link #removeComments(Node)}, {@link #removePIs(Node)}, and finally * {@link #mergeAdjacentText(Node)}, but it does all that somewhat faster. */
static public void simplify(Node parent) { simplify(parent, new StringBuilder(0)); } static private void simplify(Node parent, StringBuilder collectorTextChildBuff) { Node collectorTextChild = null; Node child = parent.getFirstChild(); while (child != null) { Node next = child.getNextSibling(); if (child.hasChildNodes()) { if (collectorTextChild != null) { // Commit pending text node merge: if (collectorTextChildBuff.length() != 0) { ((CharacterData) collectorTextChild).setData(collectorTextChildBuff.toString()); collectorTextChildBuff.setLength(0); } collectorTextChild = null; } simplify(child, collectorTextChildBuff); } else { int type = child.getNodeType(); if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE ) { if (collectorTextChild != null) { if (collectorTextChildBuff.length() == 0) { collectorTextChildBuff.ensureCapacity( collectorTextChild.getNodeValue().length() + child.getNodeValue().length()); collectorTextChildBuff.append(collectorTextChild.getNodeValue()); } collectorTextChildBuff.append(child.getNodeValue()); parent.removeChild(child); } else { collectorTextChild = child; collectorTextChildBuff.setLength(0); } } else if (type == Node.COMMENT_NODE) { parent.removeChild(child); } else if (type == Node.PROCESSING_INSTRUCTION_NODE) { parent.removeChild(child); } else if (collectorTextChild != null) { // Commit pending text node merge: if (collectorTextChildBuff.length() != 0) { ((CharacterData) collectorTextChild).setData(collectorTextChildBuff.toString()); collectorTextChildBuff.setLength(0); } collectorTextChild = null; } } child = next; } if (collectorTextChild != null) { // Commit pending text node merge: if (collectorTextChildBuff.length() != 0) { ((CharacterData) collectorTextChild).setData(collectorTextChildBuff.toString()); collectorTextChildBuff.setLength(0); } } } NodeModel getDocumentNodeModel() { if (node instanceof Document) { return this; } else { return wrap(node.getOwnerDocument()); } }
Tells the system to use (restore) the default (initial) XPath system used by this FreeMarker version on this system.
/** * Tells the system to use (restore) the default (initial) XPath system used by * this FreeMarker version on this system. */
static public void useDefaultXPathSupport() { synchronized (STATIC_LOCK) { xpathSupportClass = null; jaxenXPathSupport = null; try { useXalanXPathSupport(); } catch (ClassNotFoundException e) { // Expected } catch (Exception e) { LOG.debug("Failed to use Xalan XPath support.", e); } catch (IllegalAccessError e) { LOG.debug("Failed to use Xalan internal XPath support.", e); } if (xpathSupportClass == null) try { useSunInternalXPathSupport(); } catch (Exception e) { LOG.debug("Failed to use Sun internal XPath support.", e); } catch (IllegalAccessError e) { // Happens on OpenJDK 9 (but not on Oracle Java 9) LOG.debug("Failed to use Sun internal XPath support. " + "Tip: On Java 9+, you may need Xalan or Jaxen+Saxpath.", e); } if (xpathSupportClass == null) try { useJaxenXPathSupport(); } catch (ClassNotFoundException e) { // Expected } catch (Exception e) { LOG.debug("Failed to use Jaxen XPath support.", e); } catch (IllegalAccessError e) { LOG.debug("Failed to use Jaxen XPath support.", e); } } }
Convenience method. Tells the system to use Jaxen for XPath queries.
Throws:
  • Exception – if the Jaxen classes are not present.
/** * Convenience method. Tells the system to use Jaxen for XPath queries. * @throws Exception if the Jaxen classes are not present. */
static public void useJaxenXPathSupport() throws Exception { Class.forName("org.jaxen.dom.DOMXPath"); Class c = Class.forName("freemarker.ext.dom.JaxenXPathSupport"); jaxenXPathSupport = (XPathSupport) c.newInstance(); synchronized (STATIC_LOCK) { xpathSupportClass = c; } LOG.debug("Using Jaxen classes for XPath support"); }
Convenience method. Tells the system to use Xalan for XPath queries.
Throws:
  • Exception – if the Xalan XPath classes are not present.
/** * Convenience method. Tells the system to use Xalan for XPath queries. * @throws Exception if the Xalan XPath classes are not present. */
static public void useXalanXPathSupport() throws Exception { Class.forName("org.apache.xpath.XPath"); Class c = Class.forName("freemarker.ext.dom.XalanXPathSupport"); synchronized (STATIC_LOCK) { xpathSupportClass = c; } LOG.debug("Using Xalan classes for XPath support"); } static public void useSunInternalXPathSupport() throws Exception { Class.forName("com.sun.org.apache.xpath.internal.XPath"); Class c = Class.forName("freemarker.ext.dom.SunInternalXalanXPathSupport"); synchronized (STATIC_LOCK) { xpathSupportClass = c; } LOG.debug("Using Sun's internal Xalan classes for XPath support"); }
Set an alternative implementation of freemarker.ext.dom.XPathSupport to use as the XPath engine.
Params:
  • cl – the class, or null to disable XPath support.
/** * Set an alternative implementation of freemarker.ext.dom.XPathSupport to use * as the XPath engine. * @param cl the class, or <code>null</code> to disable XPath support. */
static public void setXPathSupportClass(Class cl) { if (cl != null && !XPathSupport.class.isAssignableFrom(cl)) { throw new RuntimeException("Class " + cl.getName() + " does not implement freemarker.ext.dom.XPathSupport"); } synchronized (STATIC_LOCK) { xpathSupportClass = cl; } }
Get the currently used freemarker.ext.dom.XPathSupport used as the XPath engine. Returns null if XPath support is disabled.
/** * Get the currently used freemarker.ext.dom.XPathSupport used as the XPath engine. * Returns <code>null</code> if XPath support is disabled. */
static public Class getXPathSupportClass() { synchronized (STATIC_LOCK) { return xpathSupportClass; } } static private String getText(Node node) { String result = ""; if (node instanceof Text || node instanceof CDATASection) { result = ((org.w3c.dom.CharacterData) node).getData(); } else if (node instanceof Element) { NodeList children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { result += getText(children.item(i)); } } else if (node instanceof Document) { result = getText(((Document) node).getDocumentElement()); } return result; } XPathSupport getXPathSupport() { if (jaxenXPathSupport != null) { return jaxenXPathSupport; } XPathSupport xps = null; Document doc = node.getOwnerDocument(); if (doc == null) { doc = (Document) node; } synchronized (doc) { WeakReference ref = (WeakReference) xpathSupportMap.get(doc); if (ref != null) { xps = (XPathSupport) ref.get(); } if (xps == null) { try { xps = (XPathSupport) xpathSupportClass.newInstance(); xpathSupportMap.put(doc, new WeakReference(xps)); } catch (Exception e) { LOG.error("Error instantiating xpathSupport class", e); } } } return xps; } String getQualifiedName() throws TemplateModelException { return getNodeName(); } public Object getAdaptedObject(Class hint) { return node; } public Object getWrappedObject() { return node; } public Object[] explainTypeError(Class[] expectedClasses) { for (int i = 0; i < expectedClasses.length; i++) { Class expectedClass = expectedClasses[i]; if (TemplateDateModel.class.isAssignableFrom(expectedClass) || TemplateNumberModel.class.isAssignableFrom(expectedClass) || TemplateBooleanModel.class.isAssignableFrom(expectedClass)) { return new Object[] { "XML node values are always strings (text), that is, they can't be used as number, " + "date/time/datetime or boolean without explicit conversion (such as " + "someNode?number, someNode?datetime.xs, someNode?date.xs, someNode?time.xs, " + "someNode?boolean).", }; } } return null; } }