| /* |
| * 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.jdom; |
| |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.WeakHashMap; |
| |
| import org.jaxen.Context; |
| import org.jaxen.JaxenException; |
| import org.jaxen.NamespaceContext; |
| import org.jaxen.jdom.JDOMXPath; |
| import org.jdom.Attribute; |
| import org.jdom.CDATA; |
| import org.jdom.Comment; |
| import org.jdom.DocType; |
| import org.jdom.Document; |
| import org.jdom.Element; |
| import org.jdom.EntityRef; |
| import org.jdom.Namespace; |
| import org.jdom.ProcessingInstruction; |
| import org.jdom.Text; |
| import org.jdom.output.XMLOutputter; |
| |
| import freemarker.template.SimpleHash; |
| import freemarker.template.SimpleScalar; |
| import freemarker.template.Template; |
| import freemarker.template.TemplateCollectionModel; |
| import freemarker.template.TemplateHashModel; |
| import freemarker.template.TemplateMethodModel; |
| import freemarker.template.TemplateModel; |
| import freemarker.template.TemplateModelException; |
| import freemarker.template.TemplateModelIterator; |
| import freemarker.template.TemplateScalarModel; |
| import freemarker.template.TemplateSequenceModel; |
| import freemarker.template._ObjectWrappers; |
| |
| /** |
| * Provides a template for wrapping JDOM objects. It is capable of storing not only |
| * a single JDOM node, but a list of JDOM nodes at once (hence the name). |
| * Each node is an instance of any of the core JDOM node classes (except namespaces, |
| * which are not supported at the moment), or String for representing text. |
| * See individual method documentation for exact details on how the class works. In |
| * short: |
| * <ul> |
| * <li>{@link #getAsString()} will render all contained nodes as XML fragment, |
| * <li>{@link #exec(List)} provides full XPath functionality implemented on top of |
| * the <a href="http://www.jaxen.org">Jaxen</a> library,</li> |
| * <li>{@link #get(String)} provides node traversal, copying and filtering - somewhat |
| * less expressive than XPath, however it does not require the external library and |
| * it evaluates somewhat faster</li> |
| * <li>being a {@link TemplateCollectionModel} allows to iterate the contained node list, and</li> |
| * <li>being a {@link TemplateSequenceModel} allows to access the contained nodes by index and query the node count.</li> |
| * </ul> |
| * |
| * <p><b>Note:</b> There is a JDOM independent re-implementation of this class: |
| * {@link freemarker.ext.xml.NodeListModel freemarker.ext.xml.NodeListModel} |
| * |
| * @deprecated Use {@link freemarker.ext.dom.NodeModel} instead. |
| */ |
| @Deprecated |
| public class NodeListModel |
| implements |
| TemplateHashModel, |
| TemplateMethodModel, |
| TemplateCollectionModel, |
| TemplateSequenceModel, |
| TemplateScalarModel { |
| private static final AttributeXMLOutputter OUTPUT = new AttributeXMLOutputter(); |
| // A convenience singleton for representing a node list without nodes. |
| private static final NodeListModel EMPTY = new NodeListModel(null, false); |
| |
| // Cache of already parsed XPath expressions |
| private static final Map XPATH_CACHE = new WeakHashMap(); |
| |
| private static final NamedNodeOperator NAMED_CHILDREN_OP = new NamedChildrenOp(); |
| private static final NamedNodeOperator NAMED_ATTRIBUTE_OP = new NamedAttributeOp(); |
| private static final NodeOperator ALL_ATTRIBUTES_OP = new AllAttributesOp(); |
| private static final NodeOperator ALL_CHILDREN_OP = new AllChildrenOp(); |
| private static final Map OPERATIONS = createOperations(); |
| private static final Map SPECIAL_OPERATIONS = createSpecialOperations(); |
| private static final int SPECIAL_OPERATION_COPY = 0; |
| private static final int SPECIAL_OPERATION_UNIQUE = 1; |
| private static final int SPECIAL_OPERATION_FILTER_NAME = 2; |
| private static final int SPECIAL_OPERATION_FILTER_TYPE = 3; |
| private static final int SPECIAL_OPERATION_QUERY_TYPE = 4; |
| private static final int SPECIAL_OPERATION_REGISTER_NAMESPACE = 5; |
| private static final int SPECIAL_OPERATION_PLAINTEXT = 6; |
| |
| // The contained nodes |
| private final List nodes; |
| private final Map namespaces; |
| |
| /** |
| * Creates a node list that holds a single {@link Document} node. |
| */ |
| public NodeListModel(Document document) { |
| nodes = document == null ? Collections.EMPTY_LIST : Collections.singletonList(document); |
| namespaces = new HashMap(); |
| } |
| |
| /** |
| * Creates a node list that holds a single {@link Element} node. |
| */ |
| public NodeListModel(Element element) { |
| nodes = element == null ? Collections.EMPTY_LIST : Collections.singletonList(element); |
| namespaces = new HashMap(); |
| } |
| |
| private NodeListModel(Object object, Map namespaces) { |
| nodes = object == null ? Collections.EMPTY_LIST : Collections.singletonList(object); |
| this.namespaces = namespaces; |
| } |
| |
| /** |
| * Creates a node list that holds a list of nodes. |
| * @param nodes the list of nodes this template should hold. The created template |
| * will copy the passed nodes list, so changes to the passed list will not affect |
| * the model. |
| */ |
| public NodeListModel(List nodes) { |
| this(nodes, true); |
| } |
| |
| /** |
| * Creates a node list that holds a list of nodes. |
| * @param nodes the list of nodes this template should hold. |
| * @param copy if true, the created template will copy the passed nodes list, |
| * so changes to the passed list will not affect the model. If false, the model |
| * will reference the passed list and will sense changes in it, although no |
| * operations on the list will be synchronized. |
| */ |
| public NodeListModel(List nodes, boolean copy) { |
| this.nodes = copy && nodes != null ? new ArrayList(nodes) : (nodes == null ? Collections.EMPTY_LIST : nodes); |
| namespaces = new HashMap(); |
| } |
| |
| private NodeListModel(List nodes, Map namespaces) { |
| this.nodes = nodes == null ? Collections.EMPTY_LIST : nodes; |
| this.namespaces = namespaces; |
| } |
| |
| private static final NodeListModel createNodeListModel(List list, Map namespaces) { |
| if (list == null || list.isEmpty()) { |
| if (namespaces.isEmpty()) { |
| return EMPTY; |
| } else { |
| return new NodeListModel(Collections.EMPTY_LIST, namespaces); |
| } |
| } |
| if (list.size() == 1) return new NodeListModel(list.get(0), namespaces); |
| return new NodeListModel(list, namespaces); |
| } |
| |
| /** |
| * Returns true if this model contains no nodes. |
| */ |
| @Override |
| public boolean isEmpty() { |
| return nodes.isEmpty(); |
| } |
| |
| /** |
| * This method returns the string resulting from concatenation |
| * of string representations of its nodes. Each node is rendered using its XML |
| * serialization format, while text (String) is rendered as itself. This greatly |
| * simplifies creating XML-transformation templates, as to output a node contained |
| * in variable x as XML fragment, you simply write ${x} in the template. |
| */ |
| @Override |
| public String getAsString() |
| throws TemplateModelException { |
| if (isEmpty()) |
| return ""; |
| |
| java.io.StringWriter sw = new java.io.StringWriter(nodes.size() * 128); |
| try { |
| for (Iterator i = nodes.iterator(); i.hasNext(); ) { |
| Object node = i.next(); |
| if (node instanceof Element) |
| OUTPUT.output((Element) node, sw); |
| else if (node instanceof Attribute) |
| OUTPUT.output((Attribute) node, sw); |
| else if (node instanceof String) |
| sw.write(OUTPUT.escapeElementEntities(node.toString())); |
| else if (node instanceof Text) |
| OUTPUT.output((Text) node, sw); |
| else if (node instanceof Document) |
| OUTPUT.output((Document) node, sw); |
| else if (node instanceof ProcessingInstruction) |
| OUTPUT.output((ProcessingInstruction) node, sw); |
| else if (node instanceof Comment) |
| OUTPUT.output((Comment) node, sw); |
| else if (node instanceof CDATA) |
| OUTPUT.output((CDATA) node, sw); |
| else if (node instanceof DocType) |
| OUTPUT.output((DocType) node, sw); |
| else if (node instanceof EntityRef) |
| OUTPUT.output((EntityRef) node, sw); |
| else |
| throw new TemplateModelException(node.getClass().getName() + " is not a core JDOM class"); |
| } |
| } catch (IOException e) { |
| throw new TemplateModelException(e.getMessage()); |
| } |
| return sw.toString(); |
| } |
| |
| |
| /** |
| * Provides node list traversal as well as special functions: filtering by name, |
| * filtering by node type, shallow-copying, and duplicate removal. |
| * While not as powerful as the full XPath support built into the |
| * {@link #exec(List)} method, it does not require the external Jaxen |
| * library to be present at run time. Below are listed the recognized keys. |
| * In key descriptions, "applicable to this-and-that node type" means that if |
| * a key is applied to a node list that contains a node of non-applicable type |
| * a TemplateMethodModel will be thrown. However, you can use <tt>_ftype</tt> |
| * key to explicitly filter out undesired node types prior to applying the |
| * restricted-applicability key. Also "current nodes" means nodes contained in this |
| * set. |
| * <ul> |
| * <li><tt>*</tt> or <tt>_children</tt>: all direct element children of current nodes (non-recursive). Applicable |
| * to element and document nodes.</li> |
| * <li><tt>@*</tt> or <tt>_attributes</tt>: all attributes of current nodes. Applicable to elements only.</li> |
| * <li><tt>_content</tt> the complete content of current nodes (non-recursive). |
| * Applicable to elements and documents.</li> |
| * <li><tt>_text</tt>: the 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 escaped.</li> |
| * <li><tt>_plaintext</tt>: same as <tt>_text</tt>, but does not escape any characters, |
| * and instead of returning a NodeList returns a SimpleScalar.</li> |
| * <li><tt>_name</tt>: the names of current nodes, one string per node (non-recursive). |
| * Applicable to elements and attributes (returns their local name), |
| * entities, processing instructions (returns its target), doctypes |
| * (returns its public ID)</li> |
| * <li><tt>_qname</tt>: the qualified names of current nodes in <tt>[namespacePrefix:]localName</tt> |
| * form, one string per node (non-recursive). Applicable to elements and attributes</li> |
| * <li><tt>_cname</tt>: the canonical names of current nodes (namespace URI + local name), |
| * one string per node (non-recursive). Applicable to elements and attributes</li> |
| * <li><tt>_nsprefix</tt>: namespace prefixes of current nodes, |
| * one string per node (non-recursive). Applicable to elements and attributes</li> |
| * <li><tt>_nsuri</tt>: namespace URIs of current nodes, |
| * one string per node (non-recursive). Applicable to elements and attributes</li> |
| * <li><tt>_parent</tt>: parent elements of current nodes. Applicable to element, attribute, comment, |
| * entity, processing instruction.</li> |
| * <li><tt>_ancestor</tt>: all ancestors up to root element (recursive) of current nodes. Applicable |
| * to same node types as <tt>_parent</tt>.</li> |
| * <li><tt>_ancestorOrSelf</tt>: all ancestors of current nodes plus current nodes. Applicable |
| * to same node types as <tt>_parent</tt>.</li> |
| * <li><tt>_descendant</tt>: all recursive descendant element children of current nodes. Applicable to |
| * document and element nodes. |
| * <li><tt>_descendantOrSelf</tt>: all recursive descendant element children of current nodes |
| * plus current nodes. Applicable to document and element nodes. |
| * <li><tt>_document</tt>: all documents the current nodes belong to. |
| * Applicable to all nodes except text. |
| * <li><tt>_doctype</tt>: doctypes of the current nodes. |
| * Applicable to document nodes only. |
| * <li><tt>_fname</tt>: is a filter-by-name template method model. When called, |
| * it will yield a node list that contains only those current nodes whose name |
| * matches one of names passed as argument. Attribute names should NOT be prefixed with the |
| * at sign (@). Applicable on all node types, however has no effect on unnamed nodes.</li> |
| * <li><tt>_ftype</tt>: is 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 should pass a single string to this method |
| * containing the characters of all types to keep. Valid characters are: |
| * e (Element), a (Attribute), n (Entity), d (Document), t (DocType), |
| * c (Comment), p (ProcessingInstruction), x (text). If the string anywhere contains |
| * the exclamation mark (!), the filter's effect is inverted.</li> |
| * <li><tt>_type</tt>: Returns a one-character String SimpleScalar containing |
| * the typecode of the first node in the node list. Valid characters are: |
| * e (Element), a (Attribute), n (Entity), d (Document), t (DocType), |
| * c (Comment), p (ProcessingInstruction), x (text). If the type of the node |
| * is unknown, returns '?'. If the node list is empty, returns an empty string scalar.</li> |
| * <li><tt>_unique</tt>: a 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 <tt>_parent</tt>, |
| * <tt>_ancestor</tt>, <tt>_ancestorOrSelf</tt>, and <tt>_document</tt>. |
| * I.e. <tt>foo._children._parent</tt> 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 |
| * <tt>foo._children._parent._unique</tt> to eliminate duplicates. Applicable |
| * to all node types.</li> |
| * <li><tt>_copy</tt>: a copy of the current node list. It is a shallow copy that |
| * shares the underlying node list with this node list, however it has a |
| * separate namespace registry, so it can be used to guarantee that subsequent |
| * changes to the set of registered namespaces does not affect the node lists |
| * that were used to create this node list. Applicable to all node types.</li> |
| * <li><tt>_registerNamespace(prefix, uri)</tt>: 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 <tt>nodelist["prefix:localname"]</tt> or |
| * <tt>nodelist["@prefix:localname"]</tt> 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. You can also register |
| * namespaces from Java code using the |
| * {@link #registerNamespace(String, String)} method. |
| * </li> |
| * <li><tt>@attributeName</tt>: named attributes of current nodes. Applicable to |
| * elements, doctypes and processing instructions. On doctypes it supports |
| * attributes <tt>publicId</tt>, <tt>systemId</tt> and <tt>elementName</tt>. On processing |
| * instructions, it supports attributes <tt>target</tt> and <tt>data</tt>, as |
| * well as any other attribute name specified in data as <tt>name="value"</tt> pair. |
| * The attribute nodes for doctype and processing instruction are synthetic, and |
| * as such have no parent. Note, however that <tt>@*</tt> does NOT operate on |
| * doctypes or processing instructions.</li> |
| * <li>any other key: element children of current nodes with name matching the key. |
| * This allows for convenience child traversal in <tt>book.chapter.title</tt> style syntax. |
| * Note that <tt>nodeset.childname</tt> is technically equivalent to |
| * <tt>nodeset._children._fname("childname")</tt>, but is both shorter to write |
| * and evaluates faster. Applicable to document and element nodes.</li> |
| * </ul> |
| * The order of nodes in the resulting set is the order of evaluation of the key |
| * on each node in this set from left to right. Evaluation of the key on a single |
| * node always yields the results in "natural" order (that of the document preorder |
| * traversal), even for uptree traversals. As a consequence, if this node list's nodes |
| * are listed in natural order, applying any of the keys will produce a node list that |
| * is also naturally ordered. As a special case, all node lists that are directly or |
| * indirectly generated from a single Document or Element node through repeated |
| * invocations of this method will be naturally ordered. |
| * @param key a key that identifies a required set of nodes |
| * @return a new NodeListModel that represents the requested set of nodes. |
| */ |
| @Override |
| public TemplateModel get(String key) |
| throws TemplateModelException { |
| if (isEmpty()) |
| return EMPTY; |
| |
| if (key == null || key.length() == 0) |
| throw new TemplateModelException("Invalid key [" + key + "]"); |
| |
| NodeOperator op = null; |
| NamedNodeOperator nop = null; |
| String name = null; |
| |
| switch (key.charAt(0)) { |
| case '@': |
| { |
| if (key.length() != 2 || key.charAt(1) != '*') { |
| // Generic attribute key |
| nop = NAMED_ATTRIBUTE_OP; |
| name = key.substring(1); |
| } else |
| // It is @* |
| op = ALL_ATTRIBUTES_OP; |
| |
| break; |
| } |
| case '*': |
| { |
| if (key.length() == 1) |
| op = ALL_CHILDREN_OP; |
| else |
| // Explicitly disallow any other identifier starting with asterisk |
| throw new TemplateModelException("Invalid key [" + key + "]"); |
| |
| break; |
| } |
| case 'x': |
| case '_': |
| { |
| op = (NodeOperator) OPERATIONS.get(key); |
| if (op == null) { |
| // Some special operation? |
| Integer specop = (Integer) SPECIAL_OPERATIONS.get(key); |
| if (specop != null) { |
| switch (specop.intValue()) { |
| case SPECIAL_OPERATION_COPY: |
| { |
| synchronized (namespaces) { |
| return new NodeListModel(nodes, (Map) ((HashMap) namespaces).clone()); |
| } |
| } |
| case SPECIAL_OPERATION_UNIQUE: |
| return new NodeListModel(removeDuplicates(nodes), namespaces); |
| case SPECIAL_OPERATION_FILTER_NAME: |
| return new NameFilter(); |
| case SPECIAL_OPERATION_FILTER_TYPE: |
| return new TypeFilter(); |
| case SPECIAL_OPERATION_QUERY_TYPE: |
| return getType(); |
| case SPECIAL_OPERATION_REGISTER_NAMESPACE: |
| return new RegisterNamespace(); |
| case SPECIAL_OPERATION_PLAINTEXT: |
| return getPlainText(); |
| } |
| } |
| } |
| break; |
| } |
| } |
| |
| if (op == null && nop == null) { |
| nop = NAMED_CHILDREN_OP; |
| name = key; |
| } |
| |
| List list = null; |
| if (op != null) |
| list = evaluateElementOperation(op, nodes); |
| else { |
| String localName = name; |
| Namespace namespace = Namespace.NO_NAMESPACE; |
| int colon = name.indexOf(':'); |
| if (colon != -1) { |
| localName = name.substring(colon + 1); |
| String nsPrefix = name.substring(0, colon); |
| synchronized (namespaces) { |
| namespace = (Namespace) namespaces.get(nsPrefix); |
| } |
| if (namespace == null) { |
| if (nsPrefix.equals("xml")) |
| namespace = Namespace.XML_NAMESPACE; |
| else |
| throw new TemplateModelException("Unregistered namespace prefix '" + nsPrefix + "'"); |
| } |
| } |
| |
| list = evaluateNamedElementOperation(nop, localName, namespace, nodes); |
| } |
| return createNodeListModel(list, namespaces); |
| } |
| |
| private TemplateModel getType() { |
| if (nodes.size() == 0) |
| return new SimpleScalar(""); |
| Object firstNode = nodes.get(0); |
| char code; |
| if (firstNode instanceof Element) |
| code = 'e'; |
| else if (firstNode instanceof Text || firstNode instanceof String) |
| code = 'x'; |
| else if (firstNode instanceof Attribute) |
| code = 'a'; |
| else if (firstNode instanceof EntityRef) |
| code = 'n'; |
| else if (firstNode instanceof Document) |
| code = 'd'; |
| else if (firstNode instanceof DocType) |
| code = 't'; |
| else if (firstNode instanceof Comment) |
| code = 'c'; |
| else if (firstNode instanceof ProcessingInstruction) |
| code = 'p'; |
| else |
| code = '?'; |
| return new SimpleScalar(new String(new char[] { code})); |
| } |
| |
| private SimpleScalar getPlainText() |
| throws TemplateModelException { |
| List list = evaluateElementOperation((TextOp) OPERATIONS.get("_text"), nodes); |
| StringBuilder buf = new StringBuilder(); |
| for (Iterator it = list.iterator(); it.hasNext(); ) { |
| buf.append(it.next()); |
| } |
| return new SimpleScalar(buf.toString()); |
| } |
| |
| @Override |
| public TemplateModelIterator iterator() { |
| return new TemplateModelIterator() |
| { |
| private final Iterator it = nodes.iterator(); |
| |
| @Override |
| public TemplateModel next() { |
| return it.hasNext() ? new NodeListModel(it.next(), namespaces) : null; |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return it.hasNext(); |
| } |
| }; |
| } |
| |
| /** |
| * Retrieves the i-th element of the node list. |
| */ |
| @Override |
| public TemplateModel get(int i) |
| throws TemplateModelException { |
| try { |
| return new NodeListModel(nodes.get(i), namespaces); |
| } catch (IndexOutOfBoundsException e) { |
| throw new TemplateModelException("Index out of bounds: " + e.getMessage()); |
| } |
| } |
| |
| @Override |
| public int size() { |
| return nodes.size(); |
| } |
| |
| /** |
| * Applies an XPath expression to the node list and returns the resulting node list. |
| * In order for this method to work, your application must have access |
| * <a href="http://www.jaxen.org">Jaxen</a> library classes. The |
| * implementation does cache the parsed format of XPath expressions in a weak hash |
| * map, keyed by the string representation of the XPath expression. As the string |
| * object passed as the argument is usually kept in the parsed FreeMarker template, |
| * this ensures that each XPath expression is parsed only once during the lifetime |
| * of the FreeMarker template that contains it. |
| * @param arguments the list of arguments. Must contain exactly one string that is |
| * the XPath expression you wish to apply. The XPath expression can use any namespace |
| * prefixes that were defined using the {@link #registerNamespace(String, String)} |
| * method or the <code>nodelist._registerNamespace(prefix, uri)</code> expression in the |
| * template. |
| * @return a NodeListModel representing the nodes that are the result of application |
| * of the XPath to the current node list. |
| */ |
| @Override |
| public Object exec(List arguments) |
| throws TemplateModelException { |
| if (arguments == null || arguments.size() != 1) |
| throw new TemplateModelException("Exactly one argument required for execute() on NodeTemplate"); |
| |
| String xpathString = (String) arguments.get(0); |
| JDOMXPathEx xpath = null; |
| try { |
| synchronized (XPATH_CACHE) { |
| xpath = (JDOMXPathEx) XPATH_CACHE.get(xpathString); |
| if (xpath == null) { |
| xpath = new JDOMXPathEx(xpathString); |
| XPATH_CACHE.put(xpathString, xpath); |
| } |
| } |
| return createNodeListModel(xpath.selectNodes(nodes, namespaces), namespaces); |
| } catch (Exception e) { |
| throw new TemplateModelException("Could not evaulate XPath expression " + xpathString, e); |
| } |
| } |
| |
| /** |
| * Registers an XML namespace with this node list. Once registered, you can |
| * refer to the registered namespace using its prefix in the |
| * {@link #get(String)} method from this node list and all other |
| * node lists that are derived from this node list. Use the |
| * <tt>nodelist["prefix:localname"]</tt> or the |
| * <tt>nodelist["@prefix:localname"]</tt> syntax 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. You can also register |
| * namespaces during template evaluation using the |
| * <tt>nodelist._registerNamespace(prefix, uri)</tt> syntax in the template. |
| * This mechanism is completely independent from the namespace declarations |
| * in the XML document itself; its purpose is to give you an easy way |
| * to refer to namespace-scoped elements in {@link #get(String)} and |
| * in XPath expressions passed to {@link #exec(List)}. Note also that |
| * the namespace prefix registry is shared among all node lists that |
| * are created from a single node list - modifying the registry in one |
| * affects all others as well. If you want to obtain a namespace |
| * "detached" copy of the node list, use the <code>_copy</code> key on |
| * it (or call <code>nodeList.get("_copy")</code> directly from your |
| * Java code. The returned node list has all the namespaces that the |
| * original node list has, but they can be manipulated independently |
| * thereon. |
| */ |
| public void registerNamespace(String prefix, String uri) { |
| synchronized (namespaces) { |
| namespaces.put(prefix, Namespace.getNamespace(prefix, uri)); |
| } |
| } |
| |
| private interface NodeOperator { |
| List operate(Object node) |
| throws TemplateModelException; |
| } |
| |
| private interface NamedNodeOperator { |
| List operate(Object node, String localName, Namespace namespace) |
| throws TemplateModelException; |
| } |
| |
| private static final class AllChildrenOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| if (node instanceof Element) |
| return((Element) node).getChildren(); |
| else if (node instanceof Document) { |
| Element root = ((Document) node).getRootElement(); |
| return root == null ? Collections.EMPTY_LIST : Collections.singletonList(root); |
| } |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| return null; |
| /* |
| else |
| throw new TemplateModelException("_allChildren can not be applied on " + node.getClass()); |
| */ |
| } |
| } |
| |
| private static final class NamedChildrenOp implements NamedNodeOperator { |
| @Override |
| public List operate(Object node, String localName, Namespace namespace) { |
| if (node instanceof Element) { |
| return((Element) node).getChildren(localName, namespace); |
| } else if (node instanceof Document) { |
| Element root = ((Document) node).getRootElement(); |
| if (root != null && |
| root.getName().equals(localName) && |
| root.getNamespaceURI().equals(namespace.getURI())) { |
| return Collections.singletonList(root); |
| } else |
| return Collections.EMPTY_LIST; |
| } |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| return null; |
| /* |
| else |
| throw new TemplateModelException("_namedChildren can not be applied on " + node.getClass()); |
| */ |
| } |
| } |
| |
| private static final class AllAttributesOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| if (!(node instanceof Element)) { |
| return null; |
| } |
| return ((Element) node).getAttributes(); |
| /* |
| else |
| throw new TemplateModelException("_allAttributes can not be applied on " + node.getClass()); |
| */ |
| } |
| } |
| |
| private static final class NamedAttributeOp implements NamedNodeOperator { |
| @Override |
| public List operate(Object node, String localName, Namespace namespace) { |
| Attribute attr = null; |
| if (node instanceof Element) { |
| Element element = (Element) node; |
| attr = element.getAttribute(localName, namespace); |
| } else if (node instanceof ProcessingInstruction) { |
| ProcessingInstruction pi = (ProcessingInstruction) node; |
| if ("target".equals(localName)) |
| attr = new Attribute("target", pi.getTarget()); |
| else if ("data".equals(localName)) |
| attr = new Attribute("data", pi.getData()); |
| else |
| attr = new Attribute(localName, pi.getValue(localName)); |
| } else if (node instanceof DocType) { |
| DocType doctype = (DocType) node; |
| if ("publicId".equals(localName)) |
| attr = new Attribute("publicId", doctype.getPublicID()); |
| else if ("systemId".equals(localName)) |
| attr = new Attribute("systemId", doctype.getSystemID()); |
| else if ("elementName".equals(localName)) |
| attr = new Attribute("elementName", doctype.getElementName()); |
| } |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| else { |
| return null; |
| } |
| /* |
| else |
| throw new TemplateModelException("_allAttributes can not be applied on " + node.getClass()); |
| */ |
| return attr == null ? Collections.EMPTY_LIST : Collections.singletonList(attr); |
| } |
| } |
| |
| private static final class NameOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| if (node instanceof Element) |
| return Collections.singletonList(((Element) node).getName()); |
| else if (node instanceof Attribute) |
| return Collections.singletonList(((Attribute) node).getName()); |
| else if (node instanceof EntityRef) |
| return Collections.singletonList(((EntityRef) node).getName()); |
| else if (node instanceof ProcessingInstruction) |
| return Collections.singletonList(((ProcessingInstruction) node).getTarget()); |
| else if (node instanceof DocType) |
| return Collections.singletonList(((DocType) node).getPublicID()); |
| else |
| return null; |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| // throw new TemplateModelException("_name can not be applied on " + node.getClass()); |
| } |
| } |
| |
| private static final class QNameOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| if (node instanceof Element) |
| return Collections.singletonList(((Element) node).getQualifiedName()); |
| else if (node instanceof Attribute) |
| return Collections.singletonList(((Attribute) node).getQualifiedName()); |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| return null; |
| // throw new TemplateModelException("_qname can not be applied on " + node.getClass()); |
| } |
| } |
| |
| private static final class NamespaceUriOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| if (node instanceof Element) |
| return Collections.singletonList(((Element) node).getNamespace().getURI()); |
| else if (node instanceof Attribute) |
| return Collections.singletonList(((Attribute) node).getNamespace().getURI()); |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| return null; |
| // throw new TemplateModelException("_nsuri can not be applied on " + node.getClass()); |
| } |
| } |
| |
| private static final class NamespacePrefixOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| if (node instanceof Element) |
| return Collections.singletonList(((Element) node).getNamespace().getPrefix()); |
| else if (node instanceof Attribute) |
| return Collections.singletonList(((Attribute) node).getNamespace().getPrefix()); |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| return null; |
| // throw new TemplateModelException("_nsprefix can not be applied on " + node.getClass()); |
| } |
| } |
| |
| private static final class CanonicalNameOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| if (node instanceof Element) { |
| Element element = (Element) node; |
| return Collections.singletonList(element.getNamespace().getURI() + element.getName()); |
| } else if (node instanceof Attribute) { |
| Attribute attribute = (Attribute) node; |
| return Collections.singletonList(attribute.getNamespace().getURI() + attribute.getName()); |
| } |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| return null; |
| // throw new TemplateModelException("_cname can not be applied on " + node.getClass()); |
| } |
| } |
| |
| |
| private static final Element getParent(Object node) { |
| if (node instanceof Element) |
| return((Element) node).getParent(); |
| else if (node instanceof Attribute) |
| return((Attribute) node).getParent(); |
| else if (node instanceof Text) |
| return((Text) node).getParent(); |
| else if (node instanceof ProcessingInstruction) |
| return((ProcessingInstruction) node).getParent(); |
| else if (node instanceof Comment) |
| return((Comment) node).getParent(); |
| else if (node instanceof EntityRef) |
| return((EntityRef) node).getParent(); |
| else |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| return null; |
| // throw new TemplateModelException("_parent can not be applied on " + node.getClass()); |
| } |
| |
| private static final class ParentOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| Element parent = getParent(node); |
| return parent == null ? Collections.EMPTY_LIST : Collections.singletonList(parent); |
| } |
| } |
| |
| private static final class AncestorOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| Element parent = getParent(node); |
| if (parent == null) return Collections.EMPTY_LIST; |
| LinkedList list = new LinkedList(); |
| do { |
| list.addFirst(parent); |
| parent = parent.getParent(); |
| } while (parent != null); |
| return list; |
| } |
| } |
| |
| private static final class AncestorOrSelfOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| Element parent = getParent(node); |
| if (parent == null) return Collections.singletonList(node); |
| LinkedList list = new LinkedList(); |
| list.addFirst(node); |
| do { |
| list.addFirst(parent); |
| parent = parent.getParent(); |
| } while (parent != null); |
| return list; |
| } |
| } |
| |
| private static class DescendantOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| LinkedList list = new LinkedList(); |
| if (node instanceof Element) { |
| addChildren((Element) node, list); |
| } else if (node instanceof Document) { |
| Element root = ((Document) node).getRootElement(); |
| list.add(root); |
| addChildren(root, list); |
| } else |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| return null; |
| // throw new TemplateModelException("_descendant can not be applied on " + node.getClass()); |
| |
| return list; |
| } |
| |
| private void addChildren(Element element, List list) { |
| List children = element.getChildren(); |
| Iterator it = children.iterator(); |
| while (it.hasNext()) { |
| Element child = (Element) it.next(); |
| list.add(child); |
| addChildren(child, list); |
| } |
| } |
| } |
| |
| private static final class DescendantOrSelfOp extends DescendantOp { |
| @Override |
| public List operate(Object node) { |
| LinkedList list = (LinkedList) super.operate(node); |
| list.addFirst(node); |
| return list; |
| } |
| } |
| |
| private static final class DocumentOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| Document doc = null; |
| if (node instanceof Element) |
| doc = ((Element) node).getDocument(); |
| else if (node instanceof Attribute) { |
| Element parent = ((Attribute) node).getParent(); |
| doc = parent == null ? null : parent.getDocument(); |
| } else if (node instanceof Text) { |
| Element parent = ((Text) node).getParent(); |
| doc = parent == null ? null : parent.getDocument(); |
| } else if (node instanceof Document) |
| doc = (Document) node; |
| else if (node instanceof ProcessingInstruction) |
| doc = ((ProcessingInstruction) node).getDocument(); |
| else if (node instanceof EntityRef) |
| doc = ((EntityRef) node).getDocument(); |
| else if (node instanceof Comment) |
| doc = ((Comment) node).getDocument(); |
| else |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| return null; |
| // throw new TemplateModelException("_document can not be applied on " + node.getClass()); |
| |
| return doc == null ? Collections.EMPTY_LIST : Collections.singletonList(doc); |
| } |
| } |
| |
| private static final class DocTypeOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| if (node instanceof Document) { |
| DocType doctype = ((Document) node).getDocType(); |
| return doctype == null ? Collections.EMPTY_LIST : Collections.singletonList(doctype); |
| } else |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| return null; |
| // throw new TemplateModelException("_doctype can not be applied on " + node.getClass()); |
| } |
| } |
| |
| private static final class ContentOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| if (node instanceof Element) |
| return((Element) node).getContent(); |
| else if (node instanceof Document) |
| return((Document) node).getContent(); |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| return null; |
| // throw new TemplateModelException("_content can not be applied on " + node.getClass()); |
| } |
| } |
| |
| private static final class TextOp implements NodeOperator { |
| @Override |
| public List operate(Object node) { |
| if (node instanceof Element) |
| return Collections.singletonList(((Element) node).getTextTrim()); |
| if (node instanceof Attribute) |
| return Collections.singletonList(((Attribute) node).getValue()); |
| if (node instanceof CDATA) |
| return Collections.singletonList(((CDATA) node).getText()); |
| if (node instanceof Comment) |
| return Collections.singletonList(((Comment) node).getText()); |
| if (node instanceof ProcessingInstruction) |
| return Collections.singletonList(((ProcessingInstruction) node).getData()); |
| // With 2.1 semantics it makes more sense to just return a null and let the core |
| // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR) |
| return null; |
| // throw new TemplateModelException("_text can not be applied on " + node.getClass()); |
| } |
| } |
| |
| private static final List evaluateElementOperation(NodeOperator op, List nodes) |
| throws TemplateModelException { |
| int s = nodes.size(); |
| List[] lists = new List[s]; |
| int l = 0; |
| { |
| int i = 0; |
| Iterator it = nodes.iterator(); |
| while (it.hasNext()) { |
| List list = op.operate(it.next()); |
| if (list != null) { |
| lists[i++] = list; |
| l += list.size(); |
| } |
| } |
| } |
| List retval = new ArrayList(l); |
| for (int i = 0; i < s; ++i) { |
| if (lists[i] != null) { |
| retval.addAll(lists[i]); |
| } |
| } |
| return retval; |
| } |
| |
| private static final List evaluateNamedElementOperation(NamedNodeOperator op, String localName, Namespace namespace, List nodes) |
| throws TemplateModelException { |
| int s = nodes.size(); |
| List[] lists = new List[s]; |
| int l = 0; |
| { |
| int i = 0; |
| Iterator it = nodes.iterator(); |
| while (it.hasNext()) { |
| List list = op.operate(it.next(), localName, namespace); |
| lists[i++] = list; |
| l += list.size(); |
| } |
| } |
| List retval = new ArrayList(l); |
| for (int i = 0; i < s; ++i) |
| retval.addAll(lists[i]); |
| return retval; |
| } |
| |
| 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); |
| } |
| ulist.trimToSize(); |
| return ulist; |
| } |
| |
| private static final Map createOperations() { |
| Map map = new HashMap(); |
| |
| map.put("_ancestor", new AncestorOp()); |
| map.put("_ancestorOrSelf", new AncestorOrSelfOp()); |
| map.put("_attributes", ALL_ATTRIBUTES_OP); |
| map.put("_children", ALL_CHILDREN_OP); |
| map.put("_cname", new CanonicalNameOp()); |
| map.put("_content", new ContentOp()); |
| map.put("_descendant", new DescendantOp()); |
| map.put("_descendantOrSelf", new DescendantOrSelfOp()); |
| map.put("_document", new DocumentOp()); |
| map.put("_doctype", new DocTypeOp()); |
| map.put("_name", new NameOp()); |
| map.put("_nsprefix", new NamespacePrefixOp()); |
| map.put("_nsuri", new NamespaceUriOp()); |
| map.put("_parent", new ParentOp()); |
| map.put("_qname", new QNameOp()); |
| map.put("_text", new TextOp()); |
| |
| return map; |
| } |
| |
| private static final Map createSpecialOperations() { |
| Map map = new HashMap(); |
| |
| Integer copy = Integer.valueOf(SPECIAL_OPERATION_COPY); |
| Integer unique = Integer.valueOf(SPECIAL_OPERATION_UNIQUE); |
| Integer fname = Integer.valueOf(SPECIAL_OPERATION_FILTER_NAME); |
| Integer ftype = Integer.valueOf(SPECIAL_OPERATION_FILTER_TYPE); |
| Integer type = Integer.valueOf(SPECIAL_OPERATION_QUERY_TYPE); |
| Integer regns = Integer.valueOf(SPECIAL_OPERATION_REGISTER_NAMESPACE); |
| Integer plaintext = Integer.valueOf(SPECIAL_OPERATION_PLAINTEXT); |
| |
| map.put("_copy", copy); |
| map.put("_unique", unique); |
| map.put("_fname", fname); |
| map.put("_ftype", ftype); |
| map.put("_type", type); |
| map.put("_registerNamespace", regns); |
| map.put("_plaintext", plaintext); |
| |
| // These are in for backward compatibility |
| map.put("x_copy", copy); |
| map.put("x_unique", unique); |
| map.put("x_fname", fname); |
| map.put("x_ftype", ftype); |
| map.put("x_type", type); |
| |
| return map; |
| } |
| |
| private final class RegisterNamespace implements TemplateMethodModel { |
| public boolean isEmpty() { |
| return false; |
| } |
| |
| @Override |
| public Object exec(List arguments) |
| throws TemplateModelException { |
| if (arguments.size() != 2) |
| throw new TemplateModelException("_registerNamespace(prefix, uri) requires two arguments"); |
| |
| registerNamespace((String) arguments.get(0), (String) arguments.get(1)); |
| |
| return TemplateScalarModel.EMPTY_STRING; |
| } |
| } |
| |
| private final class NameFilter implements TemplateMethodModel { |
| public boolean isEmpty() { |
| return false; |
| } |
| |
| @Override |
| public Object exec(List arguments) { |
| Set names = new HashSet(arguments); |
| List list = new LinkedList(nodes); |
| Iterator it = list.iterator(); |
| while (it.hasNext()) { |
| Object node = it.next(); |
| String name = null; |
| if (node instanceof Element) |
| name = ((Element) node).getName(); |
| else if (node instanceof Attribute) |
| name = ((Attribute) node).getName(); |
| else if (node instanceof ProcessingInstruction) |
| name = ((ProcessingInstruction) node).getTarget(); |
| else if (node instanceof EntityRef) |
| name = ((EntityRef) node).getName(); |
| else if (node instanceof DocType) |
| name = ((DocType) node).getPublicID(); |
| |
| if (name == null || !names.contains(name)) |
| it.remove(); |
| } |
| return createNodeListModel(list, namespaces); |
| } |
| } |
| |
| private final class TypeFilter implements TemplateMethodModel { |
| public boolean isEmpty() { |
| return false; |
| } |
| |
| @Override |
| public Object exec(List arguments) |
| throws TemplateModelException { |
| if (arguments == null || arguments.size() == 0) |
| throw new TemplateModelException("_type expects exactly one argument"); |
| String arg = (String) arguments.get(0); |
| boolean invert = arg.indexOf('!') != -1; |
| // NOTE: true in each of these variables means 'remove', not 'keep' |
| // This is so we don't invert their values in the loop. So, |
| // a is true <--> (a is not present in the string) xor invert. |
| boolean a = invert != (arg.indexOf('a') == -1); |
| boolean c = invert != (arg.indexOf('c') == -1); |
| boolean d = invert != (arg.indexOf('d') == -1); |
| boolean e = invert != (arg.indexOf('e') == -1); |
| boolean n = invert != (arg.indexOf('n') == -1); |
| boolean p = invert != (arg.indexOf('p') == -1); |
| boolean t = invert != (arg.indexOf('t') == -1); |
| boolean x = invert != (arg.indexOf('x') == -1); |
| |
| LinkedList list = new LinkedList(nodes); |
| Iterator it = list.iterator(); |
| while (it.hasNext()) { |
| Object node = it.next(); |
| if ((node instanceof Element && e) |
| || (node instanceof Attribute && a) |
| || (node instanceof String && x) |
| || (node instanceof Text && x) |
| || (node instanceof ProcessingInstruction && p) |
| || (node instanceof Comment && c) |
| || (node instanceof EntityRef && n) |
| || (node instanceof Document && d) |
| || (node instanceof DocType && t)) |
| it.remove(); |
| } |
| return createNodeListModel(list, namespaces); |
| } |
| } |
| |
| /** |
| * Loads a template from a file passed as the first argument, loads an XML |
| * document from the standard input, passes it to the template as variable |
| * <tt>document</tt> and writes the result of template processing to |
| * standard output. |
| * |
| * @deprecated Will be removed (main method in a library, often classified as CWE-489 "Leftover Debug Code"). |
| */ |
| @Deprecated |
| public static void main(String[] args) |
| throws Exception { |
| org.jdom.input.SAXBuilder builder = new org.jdom.input.SAXBuilder(); |
| Document document = builder.build(System.in); |
| SimpleHash model = new SimpleHash(_ObjectWrappers.SAFE_OBJECT_WRAPPER); |
| model.put("document", new NodeListModel(document)); |
| FileReader fr = new FileReader(args[0]); |
| Template template = new Template(args[0], fr); |
| Writer w = new java.io.OutputStreamWriter(System.out); |
| template.process(model, w); |
| w.flush(); |
| w.close(); |
| } |
| |
| private static final class AttributeXMLOutputter extends XMLOutputter { |
| public void output(Attribute attribute, Writer out) |
| throws IOException { |
| out.write(" "); |
| out.write(attribute.getQualifiedName()); |
| out.write("="); |
| |
| out.write("\""); |
| out.write(escapeAttributeEntities(attribute.getValue())); |
| out.write("\""); |
| } |
| } |
| |
| private static final class JDOMXPathEx |
| extends |
| JDOMXPath { |
| JDOMXPathEx(String path) |
| throws JaxenException { |
| super(path); |
| } |
| |
| public List selectNodes(Object object, Map namespaces) |
| throws JaxenException { |
| Context context = getContext(object); |
| context.getContextSupport().setNamespaceContext(new NamespaceContextImpl(namespaces)); |
| return selectNodesForContext(context); |
| } |
| |
| private static final class NamespaceContextImpl |
| implements |
| NamespaceContext { |
| private final Map namespaces; |
| |
| NamespaceContextImpl(Map namespaces) { |
| this.namespaces = namespaces; |
| } |
| |
| @Override |
| public String translateNamespacePrefixToUri(String prefix) { |
| // Empty prefix always maps to empty URL in XPath |
| if (prefix.length() == 0) { |
| return prefix; |
| } |
| synchronized (namespaces) { |
| Namespace ns = (Namespace) namespaces.get(prefix); |
| return ns == null ? null : ns.getURI(); |
| } |
| } |
| } |
| } |
| } |