All Downloads are FREE. Search and download functionalities are using the official Maven repository.

freemarker.ext.xml.NodeListModel Maven / Gradle / Ivy

/*
 * Copyright (c) 2003 The Visigoth Software Society. All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgement:
 *       "This product includes software developed by the
 *        Visigoth Software Society (http://www.visigoths.org/)."
 *    Alternately, this acknowledgement may appear in the software itself,
 *    if and wherever such third-party acknowledgements normally appear.
 *
 * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the 
 *    project contributors may be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [email protected].
 *
 * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
 *    nor may "FreeMarker" or "Visigoth" appear in their names
 *    without prior written permission of the Visigoth Software Society.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Visigoth Software Society. For more
 * information on the Visigoth Software Society, please see
 * http://www.visigoths.org/
 */

package freemarker.ext.xml;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import freemarker.log.Logger;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateMethodModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNodeModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.Collections12;

/**
 * 

A data model adapter for three widespread XML document object model * representations: W3C DOM, dom4j, and JDOM. The adapter automatically * recognizes the used XML object model and provides a unified interface for it * toward the template. The model provides access to all XML InfoSet features * of the XML document and includes XPath support if it has access to the XPath- * evaluator library Jaxen. The model's philosophy (which closely follows that * of XML InfoSet and XPath) is as follows: it always wraps a list of XML nodes * (the "nodelist"). The list can be empty, can have a single element, or can * have multiple elements. Every operation applied to the model is applied to * all nodes in its nodelist. You usually start with a single- element nodelist, * usually the root element node or the document node of the XML tree. * Additionally, the nodes can contain String objects as a result of certain * evaluations (getting the names of elements, values of attributes, etc.)

*

Implementation note: If you are using W3C DOM documents * built by the Crimson XML parser (or you are using the built-in JDK 1.4 XML * parser, which is essentially Crimson), make sure you call * setNamespaceAware(true) on the * javax.xml.parsers.DocumentBuilderFactory instance used for document * building even when your documents don't use XML namespaces. Failing to do so, * you will experience incorrect behavior when using the documents wrapped with * this model.

* * @deprecated Use {@link freemarker.ext.dom.NodeModel} instead. * @version $Id: NodeListModel.java,v 1.15 2004/01/06 17:06:43 szegedia Exp $ * @author Attila Szegedi */ public class NodeListModel implements TemplateHashModel, TemplateMethodModel, TemplateScalarModel, TemplateSequenceModel, TemplateNodeModel { private static final Logger logger = Logger.getLogger("freemarker.xml"); private static final Class DOM_NODE_CLASS = getClass("org.w3c.dom.Node"); private static final Class DOM4J_NODE_CLASS = getClass("org.dom4j.Node"); private static final Navigator DOM_NAVIGATOR = getNavigator("Dom"); private static final Navigator DOM4J_NAVIGATOR = getNavigator("Dom4j"); private static final Navigator JDOM_NAVIGATOR = getNavigator("Jdom"); private static final Namespaces.Factory NS_FACTORY = getNamespacesFactory(); // The navigator object that implements document model-specific behavior. private final Navigator navigator; // The contained nodes private final List nodes; // The namespaces object (potentially shared by multiple models) private Namespaces namespaces; /** * Creates a new NodeListModel, wrapping the passed nodes. * @param nodes you can pass it a single XML node from any supported * document model, or a Java collection containing any number of nodes. * Passing null is prohibited. To create an empty model, pass it an empty * collection. If a collection is passed, all passed nodes must belong to * the same XML object model, i.e. you can't mix JDOM and dom4j in a single * instance of NodeListModel. The model itself doesn't check for this condition, * as it can be time consuming, but will throw spurious * {@link ClassCastException}s when it encounters mixed objects. * @throws IllegalArgumentException if you pass null */ public NodeListModel(Object nodes) { Object node = nodes; if(nodes instanceof Collection) { this.nodes = new ArrayList((Collection)nodes); node = this.nodes.isEmpty() ? null : this.nodes.get(0); } else if(nodes != null) { this.nodes = Collections12.singletonList(nodes); } else { throw new IllegalArgumentException("nodes == null"); } if(DOM_NODE_CLASS != null && DOM_NODE_CLASS.isInstance(node)) { navigator = DOM_NAVIGATOR; } else if(DOM4J_NODE_CLASS != null && DOM4J_NODE_CLASS.isInstance(node)) { navigator = DOM4J_NAVIGATOR; } else { // Assume JDOM navigator = JDOM_NAVIGATOR; } namespaces = NS_FACTORY.create(); } private NodeListModel(Navigator navigator, List nodes, Namespaces namespaces) { this.navigator = navigator; this.nodes = nodes; this.namespaces = namespaces; } private NodeListModel deriveModel(List derivedNodes) { namespaces.markShared(); return new NodeListModel(navigator, derivedNodes, namespaces); } /** * Returns the number of nodes in this model's nodelist. * @see freemarker.template.TemplateSequenceModel#size() */ public int size() { return nodes.size(); } /** * Evaluates an XPath expression on XML nodes in this model. * @param arguments the arguments to the method invocation. Expectes exactly * one argument - the XPath expression. * @return a new NodeListModel with nodes selected by applying the XPath * expression to this model's nodelist. * @see freemarker.template.TemplateMethodModel#exec(List) */ public Object exec(List arguments) throws TemplateModelException { if(arguments.size() != 1) { throw new TemplateModelException( "Expecting exactly one argument - an XPath expression"); } return deriveModel(navigator.applyXPath(nodes, (String)arguments.get(0), namespaces)); } /** * Returns the string representation of the wrapped nodes. String objects in * the nodelist are rendered as-is (with no XML escaping applied). All other * nodes are rendered in the default XML serialization format ("plain XML"). * This makes the model quite suited for use as an XML-transformation tool. * @return the string representation of the wrapped nodes. String objects * in the nodelist are rendered as-is (with no XML escaping applied). All * other nodes are rendered in the default XML serialization format ("plain * XML"). * @see freemarker.template.TemplateScalarModel#getAsString() */ public String getAsString() throws TemplateModelException { StringWriter sw = new StringWriter(size() * 128); for (Iterator iter = nodes.iterator(); iter.hasNext();) { Object o = iter.next(); if(o instanceof String) { sw.write((String)o); } else { navigator.getAsString(o, sw); } } return sw.toString(); } /** * Selects a single node from this model's nodelist by its list index and * returns a new NodeListModel containing that single node. * @param index the ordinal number of the selected node * @see freemarker.template.TemplateSequenceModel#get(int) */ public TemplateModel get(int index) { return deriveModel(Collections12.singletonList(nodes.get(index))); } /** * Returns a new NodeListModel containing the nodes that result from applying * an operator to this model's nodes. * @param key the operator to apply to nodes. Available operators are: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Key nameEvaluates to
* or _childrenall direct element children of current nodes (non-recursive). * Applicable to element and document nodes.
@* or _attributesall attributes of current nodes. Applicable to elements only. *
@attributeNamenamed attributes of current nodes. Applicable to elements, * doctypes and processing instructions. On doctypes it supports * attributes publicId, systemId and * elementName. On processing instructions, it supports * attributes target and data, as well as any * other attribute name specified in data as * name="value" pair on dom4j or JDOM models. * The attribute nodes for doctype and processing instruction are * synthetic, and as such have no parent. Note, however that * @* does NOT operate on doctypes or processing * instructions.
_ancestorall ancestors up to root element (recursive) of current nodes. * Applicable to same node types as _parent.
_ancestorOrSelfall ancestors of current nodes plus current nodes. Applicable * to same node types as _parent.
_cnamethe canonical names of current nodes (namespace URI + local * name), one string per node (non-recursive). Applicable to * elements and attributes
_contentthe complete content of current nodes, including children * elements, text, entity references, and processing instructions * (non-recursive). Applicable to elements and documents.
_descendantall recursive descendant element children of current nodes. * Applicable to document and element nodes.
_descendantOrSelfall recursive descendant element children of current nodes * plus current nodes. Applicable to document and element nodes. *
_documentall documents the current nodes belong to. Applicable to all * nodes except text.
_doctypedoctypes of the current nodes. Applicable to document nodes * only.
_filterTypeis a filter-by-type template method model. When called, it * will yield a node list that contains only those current nodes * whose type matches one of types passed as argument. You can pass * as many string arguments as you want, each representing one of * the types to select: "attribute", "cdata", * "comment", "document", * "documentType", "element", * "entity", "entityReference", * "namespace", "processingInstruction", or * "text".
_namethe names of current nodes, one string per node * (non-recursive). Applicable to elements and attributes * (returns their local names), entity references, processing * instructions (returns its target), doctypes (returns its public * ID)
_nsprefixthe namespace prefixes of current nodes, one string per node * (non-recursive). Applicable to elements and attributes
_nsurithe namespace URIs of current nodes, one string per node * (non-recursive). Applicable to elements and attributes
_parentparent elements of current nodes. Applicable to element, * attribute, comment, entity, processing instruction.
_qnamethe qualified names of current nodes in * [namespacePrefix:]localName form, one string per node * (non-recursive). Applicable to elements and attributes
_registerNamespace(prefix, uri)register a XML namespace with the specified prefix and URI for * the current node list and all node lists that are derived from * the current node list. After registering, you can use the * nodelist["prefix:localname"] or * nodelist["@prefix:localname"] syntaxes to * reach elements and attributes whose names are namespace-scoped. * Note that the namespace prefix need not match the actual prefix * used by the XML document itself since namespaces are compared * solely by their URI.
_textthe text of current nodes, one string per node * (non-recursive). Applicable to elements, attributes, comments, * processing instructions (returns its data) and CDATA sections. * The reserved XML characters ('<' and '&') are NOT * escaped.
_typeReturns a string describing the type of nodes, one * string per node. The returned values are "attribute", * "cdata", "comment", "document", * "documentType", "element", * "entity", "entityReference", * "namespace", "processingInstruction", * "text", or "unknown".
_uniquea copy of the current nodes that keeps only the first * occurrence of every node, eliminating duplicates. Duplicates can * occur in the node list by applying uptree-traversals * _parent, _ancestor, _ancestorOrSelf, * and _document on a node list with multiple elements. * I.e. foo._children._parent will return a node list that * has duplicates of nodes in foo - each node will have the number * of occurrences equal to the number of its children. In these * cases, use foo._children._parent._unique to eliminate * duplicates. Applicable to all node types.
any other keyelement children of current nodes with name matching the key. * This allows for convenience child traversal in * book.chapter.title style syntax. Applicable to document * and element nodes.
* @return a new NodeListModel containing the nodes that result from applying * the operator to this model's nodes. * @see freemarker.template.TemplateHashModel#get(String) */ public TemplateModel get(String key) throws TemplateModelException { // Try a built-in navigator operator NodeOperator op = navigator.getOperator(key); String localName = null; String namespaceUri = ""; // If not a nav op, then check for special keys. if(op == null && key.length() > 0 && key.charAt(0) == '_') { if(key.equals("_unique")) { return deriveModel(removeDuplicates(nodes)); } else if(key.equals("_filterType") || key.equals("_ftype")) { return new FilterByType(); } else if(key.equals("_registerNamespace")) { if(namespaces.isShared()) { namespaces = (Namespaces)namespaces.clone(); } } } // Last, do a named child element or attribute lookup if(op == null) { int colon = key.indexOf(':'); if(colon == -1) { // No namespace prefix specified localName = key; } else { // Namespace prefix specified localName = key.substring(colon + 1); String prefix = key.substring(0, colon); namespaceUri = namespaces.translateNamespacePrefixToUri(prefix); if(namespaceUri == null) { throw new TemplateModelException("Namespace prefix " + prefix + " is not registered."); } } if(localName.charAt(0) == '@') { op = navigator.getAttributeOperator(); localName = localName.substring(1); } else { op = navigator.getChildrenOperator(); } } List result = new ArrayList(); for (Iterator iter = nodes.iterator(); iter.hasNext();) { try { op.process(iter.next(), localName, namespaceUri, result); } catch(RuntimeException e) { throw new TemplateModelException(e); } } return deriveModel(result); } /** * Returns true if this NodeListModel contains no nodes. * @see freemarker.template.TemplateHashModel#isEmpty() */ public boolean isEmpty() { return nodes.isEmpty(); } /** * Registers a namespace prefix-URI pair for subsequent use in {@link * #get(String)} as well as for use in XPath expressions. * @param prefix the namespace prefix to use for the namespace * @param uri the namespace URI that identifies the namespace. */ public void registerNamespace(String prefix, String uri) { if(namespaces.isShared()) { namespaces = (Namespaces)namespaces.clone(); } namespaces.registerNamespace(prefix, uri); } private class FilterByType implements TemplateMethodModel { public Object exec(List arguments) { List filteredNodes = new ArrayList(); for (Iterator iter = arguments.iterator(); iter.hasNext();) { Object node = iter.next(); if(arguments.contains(navigator.getType(node))) { filteredNodes.add(node); } } return deriveModel(filteredNodes); } } private static final List removeDuplicates(List list) { int s = list.size(); ArrayList ulist = new ArrayList(s); Set set = new HashSet(s * 4 / 3, .75f); Iterator it = list.iterator(); while (it.hasNext()) { Object o = it.next(); if (set.add(o)) { ulist.add(o); } } return ulist; } private static Class getClass(String className) { try { return ClassUtil.forName(className); } catch(Exception e) { if(logger.isDebugEnabled()) { logger.debug("Couldn't load class " + className, e); } return null; } } private static Namespaces.Factory getNamespacesFactory() { Namespaces.Factory factory = getNamespacesFactory("JaxenNamespaces"); if(factory == null) { factory = getNamespacesFactory("Namespaces"); } return factory; } private static Namespaces.Factory getNamespacesFactory(String clazz) { try { return (Namespaces.Factory) ClassUtil.forName("freemarker.ext.xml." + clazz) .getDeclaredField("FACTORY").get(null); } catch(Throwable t) { if(logger.isDebugEnabled()) { logger.debug("Could not load " + clazz, t); } return null; } } private static Navigator getNavigator(String navType) { try { Navigator nav = (Navigator) ClassUtil.forName("freemarker.ext.xml." + navType + "Navigator") .getDeclaredConstructor(new Class[] {}).newInstance(new Object[] {}); return nav; } catch(Throwable t) { if(logger.isDebugEnabled()) { logger.debug("Could not load navigator for " + navType, t); } return null; } } public TemplateSequenceModel getChildNodes() throws TemplateModelException { return (TemplateSequenceModel)get("_content"); } public String getNodeName() throws TemplateModelException { return getUniqueText((NodeListModel)get("_name"), "name"); } public String getNodeNamespace() throws TemplateModelException { return getUniqueText((NodeListModel)get("_nsuri"), "namespace"); } public String getNodeType() throws TemplateModelException { return getUniqueText((NodeListModel)get("_type"), "type"); } public TemplateNodeModel getParentNode() throws TemplateModelException { return (TemplateNodeModel)get("_parent"); } private String getUniqueText(NodeListModel model, String property) throws TemplateModelException { String s1 = null; Set s = null; for(Iterator it = model.nodes.iterator(); it.hasNext();) { String s2 = (String)it.next(); if(s2 != null) { // No text yet, make this text the current text if(s1 == null) { s1 = s2; } // else if there's already a text and they differ, start // accumulating them for an error message else if(!s1.equals(s2)) { if(s == null) { s = new HashSet(); s.add(s1); } s.add(s2); } } } // If the set for the error messages is empty, return the retval if(s == null) { return s1; } // Else throw an exception signaling ambiguity throw new TemplateModelException( "Value for node " + property + " is ambiguos: " + s); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy