/*
* 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;
/**
* 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.
*/
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.
*/
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.
*/
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());
}
public
TemplateModelIterator iterator() {
return new
TemplateModelIterator()
{
private final
Iterator it =
nodes.
iterator();
public
TemplateModel next() {
return
it.
hasNext() ? new
NodeListModel(
it.
next(),
namespaces) : null;
}
public boolean
hasNext() {
return
it.
hasNext();
}
};
}
/**
* Retrieves the i-th element of the node list.
*/
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());
}
}
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.
*/
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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;
}
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;
}
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;
}
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();
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;
}
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();
}
}
}
}
}