0001: /*
0002: * Copyright (c) 2003 The Visigoth Software Society. All rights
0003: * reserved.
0004: *
0005: * Redistribution and use in source and binary forms, with or without
0006: * modification, are permitted provided that the following conditions
0007: * are met:
0008: *
0009: * 1. Redistributions of source code must retain the above copyright
0010: * notice, this list of conditions and the following disclaimer.
0011: *
0012: * 2. Redistributions in binary form must reproduce the above copyright
0013: * notice, this list of conditions and the following disclaimer in
0014: * the documentation and/or other materials provided with the
0015: * distribution.
0016: *
0017: * 3. The end-user documentation included with the redistribution, if
0018: * any, must include the following acknowledgement:
0019: * "This product includes software developed by the
0020: * Visigoth Software Society (http://www.visigoths.org/)."
0021: * Alternately, this acknowledgement may appear in the software itself,
0022: * if and wherever such third-party acknowledgements normally appear.
0023: *
0024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
0025: * project contributors may be used to endorse or promote products derived
0026: * from this software without prior written permission. For written
0027: * permission, please contact visigoths@visigoths.org.
0028: *
0029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
0030: * nor may "FreeMarker" or "Visigoth" appear in their names
0031: * without prior written permission of the Visigoth Software Society.
0032: *
0033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
0037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0044: * SUCH DAMAGE.
0045: * ====================================================================
0046: *
0047: * This software consists of voluntary contributions made by many
0048: * individuals on behalf of the Visigoth Software Society. For more
0049: * information on the Visigoth Software Society, please see
0050: * http://www.visigoths.org/
0051: */
0052:
0053: package freemarker.ext.jdom;
0054:
0055: import java.io.FileReader;
0056: import java.io.IOException;
0057: import java.io.Writer;
0058: import java.util.ArrayList;
0059: import java.util.Collections;
0060: import java.util.HashMap;
0061: import java.util.HashSet;
0062: import java.util.Iterator;
0063: import java.util.LinkedList;
0064: import java.util.List;
0065: import java.util.Map;
0066: import java.util.Set;
0067: import java.util.WeakHashMap;
0068:
0069: import org.jaxen.Context;
0070: import org.jaxen.JaxenException;
0071: import org.jaxen.NamespaceContext;
0072: import org.jaxen.jdom.JDOMXPath;
0073: import org.jdom.Attribute;
0074: import org.jdom.CDATA;
0075: import org.jdom.Comment;
0076: import org.jdom.DocType;
0077: import org.jdom.Document;
0078: import org.jdom.Element;
0079: import org.jdom.EntityRef;
0080: import org.jdom.Namespace;
0081: import org.jdom.ProcessingInstruction;
0082: import org.jdom.Text;
0083: import org.jdom.output.XMLOutputter;
0084: import freemarker.template.SimpleHash;
0085: import freemarker.template.SimpleScalar;
0086: import freemarker.template.Template;
0087: import freemarker.template.TemplateCollectionModel;
0088: import freemarker.template.TemplateHashModel;
0089: import freemarker.template.TemplateMethodModel;
0090: import freemarker.template.TemplateModel;
0091: import freemarker.template.TemplateModelException;
0092: import freemarker.template.TemplateModelIterator;
0093: import freemarker.template.TemplateScalarModel;
0094: import freemarker.template.TemplateSequenceModel;
0095: import freemarker.template.utility.Collections12;
0096:
0097: /**
0098: * Provides a template for wrapping JDOM objects. It is capable of storing not only
0099: * a single JDOM node, but a list of JDOM nodes at once (hence the name).
0100: * Each node is an instance of any of the core JDOM node classes (except namespaces,
0101: * which are not supported at the moment), or String for representing text.
0102: * See individual method documentation for exact details on how the class works. In
0103: * short:
0104: * <ul>
0105: * <li>{@link #getAsString()} will render all contained nodes as XML fragment,</tt>
0106: * <li>{@link #exec(List)} provides full XPath functionality implemented on top of
0107: * the <a href="http://www.jaxen.org">Jaxen</a> library,</li>
0108: * <li>{@link #get(String)} provides node traversal, copying and filtering - somewhat
0109: * less expressive than XPath, however it does not require the external library and
0110: * it evaluates somewhat faster</li>
0111: * <li>being a {@link TemplateCollectionModel} allows to iterate the contained node list, and</li>
0112: * <li>being a {@link TemplateSequenceModel} allows to access the contained nodes by index and query the node count.</li>
0113: * </ul>
0114: *
0115: * <p><b>Note:</b> There is a JDOM independent re-implementation of this class:
0116: * {@link freemarker.ext.xml.NodeListModel freemarker.ext.xml.NodeListModel}
0117: *
0118: * @deprecated Use {@link freemarker.ext.dom.NodeModel} instead.
0119: * @author Attila Szegedi
0120: * @version $Id: NodeListModel.java,v 1.52.2.2 2006/11/14 10:39:58 szegedia Exp $
0121: */
0122: public class NodeListModel implements TemplateHashModel,
0123: TemplateMethodModel, TemplateCollectionModel,
0124: TemplateSequenceModel, TemplateScalarModel {
0125: private static final AttributeXMLOutputter OUTPUT = new AttributeXMLOutputter();
0126: // A convenience singleton for representing a node list without nodes.
0127: private static final NodeListModel EMPTY = new NodeListModel(null,
0128: false);
0129:
0130: // Cache of already parsed XPath expressions
0131: private static final Map XPATH_CACHE = new WeakHashMap();
0132:
0133: private static final NamedNodeOperator NAMED_CHILDREN_OP = new NamedChildrenOp();
0134: private static final NamedNodeOperator NAMED_ATTRIBUTE_OP = new NamedAttributeOp();
0135: private static final NodeOperator ALL_ATTRIBUTES_OP = new AllAttributesOp();
0136: private static final NodeOperator ALL_CHILDREN_OP = new AllChildrenOp();
0137: private static final Map OPERATIONS = createOperations();
0138: private static final Map SPECIAL_OPERATIONS = createSpecialOperations();
0139: private static final int SPECIAL_OPERATION_COPY = 0;
0140: private static final int SPECIAL_OPERATION_UNIQUE = 1;
0141: private static final int SPECIAL_OPERATION_FILTER_NAME = 2;
0142: private static final int SPECIAL_OPERATION_FILTER_TYPE = 3;
0143: private static final int SPECIAL_OPERATION_QUERY_TYPE = 4;
0144: private static final int SPECIAL_OPERATION_REGISTER_NAMESPACE = 5;
0145: private static final int SPECIAL_OPERATION_PLAINTEXT = 6;
0146:
0147: // The contained nodes
0148: private final List nodes;
0149: private final Map namespaces;
0150:
0151: /**
0152: * Creates a node list that holds a single {@link Document} node.
0153: */
0154: public NodeListModel(Document document) {
0155: nodes = document == null ? Collections.EMPTY_LIST
0156: : Collections12.singletonList(document);
0157: namespaces = new HashMap();
0158: }
0159:
0160: /**
0161: * Creates a node list that holds a single {@link Element} node.
0162: */
0163: public NodeListModel(Element element) {
0164: nodes = element == null ? Collections.EMPTY_LIST
0165: : Collections12.singletonList(element);
0166: namespaces = new HashMap();
0167: }
0168:
0169: private NodeListModel(Object object, Map namespaces) {
0170: nodes = object == null ? Collections.EMPTY_LIST : Collections12
0171: .singletonList(object);
0172: this .namespaces = namespaces;
0173: }
0174:
0175: /**
0176: * Creates a node list that holds a list of nodes.
0177: * @param nodes the list of nodes this template should hold. The created template
0178: * will copy the passed nodes list, so changes to the passed list will not affect
0179: * the model.
0180: */
0181: public NodeListModel(List nodes) {
0182: this (nodes, true);
0183: }
0184:
0185: /**
0186: * Creates a node list that holds a list of nodes.
0187: * @param nodes the list of nodes this template should hold.
0188: * @param copy if true, the created template will copy the passed nodes list,
0189: * so changes to the passed list will not affect the model. If false, the model
0190: * will reference the passed list and will sense changes in it, although no
0191: * operations on the list will be synchronized.
0192: */
0193: public NodeListModel(List nodes, boolean copy) {
0194: this .nodes = copy && nodes != null ? new ArrayList(nodes)
0195: : (nodes == null ? Collections.EMPTY_LIST : nodes);
0196: namespaces = new HashMap();
0197: }
0198:
0199: private NodeListModel(List nodes, Map namespaces) {
0200: this .nodes = nodes == null ? Collections.EMPTY_LIST : nodes;
0201: this .namespaces = namespaces;
0202: }
0203:
0204: private static final NodeListModel createNodeListModel(List list,
0205: Map namespaces) {
0206: if (list == null || list.isEmpty()) {
0207: if (namespaces.isEmpty()) {
0208: return EMPTY;
0209: } else {
0210: return new NodeListModel(Collections.EMPTY_LIST,
0211: namespaces);
0212: }
0213: }
0214: if (list.size() == 1)
0215: return new NodeListModel(list.get(0), namespaces);
0216: return new NodeListModel(list, namespaces);
0217: }
0218:
0219: /**
0220: * Returns true if this model contains no nodes.
0221: */
0222: public boolean isEmpty() {
0223: return nodes.isEmpty();
0224: }
0225:
0226: /**
0227: * This method returns the string resulting from concatenation
0228: * of string representations of its nodes. Each node is rendered using its XML
0229: * serialization format, while text (String) is rendered as itself. This greatly
0230: * simplifies creating XML-transformation templates, as to output a node contained
0231: * in variable x as XML fragment, you simply write ${x} in the template.
0232: */
0233: public String getAsString() throws TemplateModelException {
0234: if (isEmpty())
0235: return "";
0236:
0237: java.io.StringWriter sw = new java.io.StringWriter(
0238: nodes.size() * 128);
0239: try {
0240: for (Iterator i = nodes.iterator(); i.hasNext();) {
0241: Object node = i.next();
0242: if (node instanceof Element)
0243: OUTPUT.output((Element) node, sw);
0244: else if (node instanceof Attribute)
0245: OUTPUT.output((Attribute) node, sw);
0246: else if (node instanceof String)
0247: sw.write(OUTPUT.escapeElementEntities(node
0248: .toString()));
0249: else if (node instanceof Text)
0250: OUTPUT.output((Text) node, sw);
0251: else if (node instanceof Document)
0252: OUTPUT.output((Document) node, sw);
0253: else if (node instanceof ProcessingInstruction)
0254: OUTPUT.output((ProcessingInstruction) node, sw);
0255: else if (node instanceof Comment)
0256: OUTPUT.output((Comment) node, sw);
0257: else if (node instanceof CDATA)
0258: OUTPUT.output((CDATA) node, sw);
0259: else if (node instanceof DocType)
0260: OUTPUT.output((DocType) node, sw);
0261: else if (node instanceof EntityRef)
0262: OUTPUT.output((EntityRef) node, sw);
0263: else
0264: throw new TemplateModelException(node.getClass()
0265: .getName()
0266: + " is not a core JDOM class");
0267: }
0268: } catch (IOException e) {
0269: throw new TemplateModelException(e.getMessage());
0270: }
0271: return sw.toString();
0272: }
0273:
0274: /**
0275: * Provides node list traversal as well as special functions: filtering by name,
0276: * filtering by node type, shallow-copying, and duplicate removal.
0277: * While not as powerful as the full XPath support built into the
0278: * {@link #exec(List)} method, it does not require the external Jaxen
0279: * library to be present at run time. Below are listed the recognized keys.
0280: * In key descriptions, "applicable to this-and-that node type" means that if
0281: * a key is applied to a node list that contains a node of non-applicable type
0282: * a TemplateMethodModel will be thrown. However, you can use <tt>_ftype</tt>
0283: * key to explicitly filter out undesired node types prior to applying the
0284: * restricted-applicability key. Also "current nodes" means nodes contained in this
0285: * set.
0286: * <ul>
0287: * <li><tt>*</tt> or <tt>_children</tt>: all direct element children of current nodes (non-recursive). Applicable
0288: * to element and document nodes.</li>
0289: * <li><tt>@*</tt> or <tt>_attributes</tt>: all attributes of current nodes. Applicable to elements only.</li>
0290: * <li><tt>_content</tt> the complete content of current nodes (non-recursive).
0291: * Applicable to elements and documents.</li>
0292: * <li><tt>_text</tt>: the text of current nodes, one string per node (non-recursive).
0293: * Applicable to elements, attributes, comments, processing instructions (returns its data)
0294: * and CDATA sections. The reserved XML characters ('<' and '&') are escaped.</li>
0295: * <li><tt>_plaintext</tt>: same as <tt>_text</tt>, but does not escape any characters,
0296: * and instead of returning a NodeList returns a SimpleScalar.</li>
0297: * <li><tt>_name</tt>: the names of current nodes, one string per node (non-recursive).
0298: * Applicable to elements and attributes (returns their local name),
0299: * entities, processing instructions (returns its target), doctypes
0300: * (returns its public ID)</li>
0301: * <li><tt>_qname</tt>: the qualified names of current nodes in <tt>[namespacePrefix:]localName</tt>
0302: * form, one string per node (non-recursive). Applicable to elements and attributes</li>
0303: * <li><tt>_cname</tt>: the canonical names of current nodes (namespace URI + local name),
0304: * one string per node (non-recursive). Applicable to elements and attributes</li>
0305: * <li><tt>_nsprefix</tt>: namespace prefixes of current nodes,
0306: * one string per node (non-recursive). Applicable to elements and attributes</li>
0307: * <li><tt>_nsuri</tt>: namespace URIs of current nodes,
0308: * one string per node (non-recursive). Applicable to elements and attributes</li>
0309: * <li><tt>_parent</tt>: parent elements of current nodes. Applicable to element, attribute, comment,
0310: * entity, processing instruction.</li>
0311: * <li><tt>_ancestor</tt>: all ancestors up to root element (recursive) of current nodes. Applicable
0312: * to same node types as <tt>_parent</tt>.</li>
0313: * <li><tt>_ancestorOrSelf</tt>: all ancestors of current nodes plus current nodes. Applicable
0314: * to same node types as <tt>_parent</tt>.</li>
0315: * <li><tt>_descendant</tt>: all recursive descendant element children of current nodes. Applicable to
0316: * document and element nodes.
0317: * <li><tt>_descendantOrSelf</tt>: all recursive descendant element children of current nodes
0318: * plus current nodes. Applicable to document and element nodes.
0319: * <li><tt>_document</tt>: all documents the current nodes belong to.
0320: * Applicable to all nodes except text.
0321: * <li><tt>_doctype</tt>: doctypes of the current nodes.
0322: * Applicable to document nodes only.
0323: * <li><tt>_fname</tt>: is a filter-by-name template method model. When called,
0324: * it will yield a node list that contains only those current nodes whose name
0325: * matches one of names passed as argument. Attribute names should NOT be prefixed with the
0326: * at sign (@). Applicable on all node types, however has no effect on unnamed nodes.</li>
0327: * <li><tt>_ftype</tt>: is a filter-by-type template method model. When called,
0328: * it will yield a node list that contains only those current nodes whose type matches one
0329: * of types passed as argument. You should pass a single string to this method
0330: * containing the characters of all types to keep. Valid characters are:
0331: * e (Element), a (Attribute), n (Entity), d (Document), t (DocType),
0332: * c (Comment), p (ProcessingInstruction), x (text). If the string anywhere contains
0333: * the exclamation mark (!), the filter's effect is inverted.</li>
0334: * <li><tt>_type</tt>: Returns a one-character String SimpleScalar containing
0335: * the typecode of the first node in the node list. Valid characters are:
0336: * e (Element), a (Attribute), n (Entity), d (Document), t (DocType),
0337: * c (Comment), p (ProcessingInstruction), x (text). If the type of the node
0338: * is unknown, returns '?'. If the node list is empty, returns an empty string scalar.</li>
0339: * <li><tt>_unique</tt>: a copy of the current nodes that keeps only the
0340: * first occurrence of every node, eliminating duplicates. Duplicates can
0341: * occur in the node list by applying uptree-traversals <tt>_parent</tt>,
0342: * <tt>_ancestor</tt>, <tt>_ancestorOrSelf</tt>, and <tt>_document</tt>.
0343: * I.e. <tt>foo._children._parent</tt> will return a node list that has
0344: * duplicates of nodes in foo - each node will have the number of occurrences
0345: * equal to the number of its children. In these cases, use
0346: * <tt>foo._children._parent._unique</tt> to eliminate duplicates. Applicable
0347: * to all node types.</li>
0348: * <li><tt>_copy</tt>: a copy of the current node list. It is a shallow copy that
0349: * shares the underlying node list with this node list, however it has a
0350: * separate namespace registry, so it can be used to guarantee that subsequent
0351: * changes to the set of registered namespaces does not affect the node lists
0352: * that were used to create this node list. Applicable to all node types.</li>
0353: * <li><tt>_registerNamespace(prefix, uri)</tt>: register a XML namespace
0354: * with the specified prefix and URI for the current node list and all node
0355: * lists that are derived from the current node list. After registering,
0356: * you can use the <tt>nodelist["prefix:localname"]</tt> or
0357: * <tt>nodelist["@prefix:localname"]</tt> syntaxes to reach elements and
0358: * attributes whose names are namespace-scoped. Note that the namespace
0359: * prefix need not match the actual prefix used by the XML document itself
0360: * since namespaces are compared solely by their URI. You can also register
0361: * namespaces from Java code using the
0362: * {@link #registerNamespace(String, String)} method.
0363: * </li>
0364: * <li><tt>@attributeName</tt>: named attributes of current nodes. Applicable to
0365: * elements, doctypes and processing instructions. On doctypes it supports
0366: * attributes <tt>publicId</tt>, <tt>systemId</tt> and <tt>elementName</tt>. On processing
0367: * instructions, it supports attributes <tt>target</tt> and <tt>data</tt>, as
0368: * well as any other attribute name specified in data as <tt>name="value"</tt> pair.
0369: * The attribute nodes for doctype and processing instruction are synthetic, and
0370: * as such have no parent. Note, however that <tt>@*</tt> does NOT operate on
0371: * doctypes or processing instructions.</li>
0372: * <li>any other key: element children of current nodes with name matching the key.
0373: * This allows for convenience child traversal in <tt>book.chapter.title</tt> style syntax.
0374: * Note that <tt>nodeset.childname</tt> is technically equivalent to
0375: * <tt>nodeset._children._fname("childname")</tt>, but is both shorter to write
0376: * and evaluates faster. Applicable to document and element nodes.</li>
0377: * </ul>
0378: * The order of nodes in the resulting set is the order of evaluation of the key
0379: * on each node in this set from left to right. Evaluation of the key on a single
0380: * node always yields the results in "natural" order (that of the document preorder
0381: * traversal), even for uptree traversals. As a consequence, if this node list's nodes
0382: * are listed in natural order, applying any of the keys will produce a node list that
0383: * is also naturally ordered. As a special case, all node lists that are directly or
0384: * indirectly generated from a single Document or Element node through repeated
0385: * invocations of this method will be naturally ordered.
0386: * @param key a key that identifies a required set of nodes
0387: * @return a new NodeListModel that represents the requested set of nodes.
0388: */
0389: public TemplateModel get(String key) throws TemplateModelException {
0390: if (isEmpty())
0391: return EMPTY;
0392:
0393: if (key == null || key.length() == 0)
0394: throw new TemplateModelException("Invalid key [" + key
0395: + "]");
0396:
0397: NodeOperator op = null;
0398: NamedNodeOperator nop = null;
0399: String name = null;
0400:
0401: switch (key.charAt(0)) {
0402: case '@': {
0403: if (key.length() != 2 || key.charAt(1) != '*') {
0404: // Generic attribute key
0405: nop = NAMED_ATTRIBUTE_OP;
0406: name = key.substring(1);
0407: } else
0408: // It is @*
0409: op = ALL_ATTRIBUTES_OP;
0410:
0411: break;
0412: }
0413: case '*': {
0414: if (key.length() == 1)
0415: op = ALL_CHILDREN_OP;
0416: else
0417: // Explicitly disallow any other identifier starting with asterisk
0418: throw new TemplateModelException("Invalid key [" + key
0419: + "]");
0420:
0421: break;
0422: }
0423: case 'x':
0424: case '_': {
0425: op = (NodeOperator) OPERATIONS.get(key);
0426: if (op == null) {
0427: // Some special operation?
0428: Integer specop = (Integer) SPECIAL_OPERATIONS.get(key);
0429: if (specop != null) {
0430: switch (specop.intValue()) {
0431: case SPECIAL_OPERATION_COPY: {
0432: synchronized (namespaces) {
0433: return new NodeListModel(nodes,
0434: (Map) ((HashMap) namespaces)
0435: .clone());
0436: }
0437: }
0438: case SPECIAL_OPERATION_UNIQUE:
0439: return new NodeListModel(
0440: removeDuplicates(nodes), namespaces);
0441: case SPECIAL_OPERATION_FILTER_NAME:
0442: return new NameFilter();
0443: case SPECIAL_OPERATION_FILTER_TYPE:
0444: return new TypeFilter();
0445: case SPECIAL_OPERATION_QUERY_TYPE:
0446: return getType();
0447: case SPECIAL_OPERATION_REGISTER_NAMESPACE:
0448: return new RegisterNamespace();
0449: case SPECIAL_OPERATION_PLAINTEXT:
0450: return getPlainText();
0451: }
0452: }
0453: }
0454: break;
0455: }
0456: }
0457:
0458: if (op == null && nop == null) {
0459: nop = NAMED_CHILDREN_OP;
0460: name = key;
0461: }
0462:
0463: List list = null;
0464: if (op != null)
0465: list = evaluateElementOperation(op, nodes);
0466: else {
0467: String localName = name;
0468: Namespace namespace = Namespace.NO_NAMESPACE;
0469: int colon = name.indexOf(':');
0470: if (colon != -1) {
0471: localName = name.substring(colon + 1);
0472: String nsPrefix = name.substring(0, colon);
0473: synchronized (namespaces) {
0474: namespace = (Namespace) namespaces.get(nsPrefix);
0475: }
0476: if (namespace == null) {
0477: if (nsPrefix.equals("xml"))
0478: namespace = Namespace.XML_NAMESPACE;
0479: else
0480: throw new TemplateModelException(
0481: "Unregistered namespace prefix '"
0482: + nsPrefix + "'");
0483: }
0484: }
0485:
0486: list = evaluateNamedElementOperation(nop, localName,
0487: namespace, nodes);
0488: }
0489: return createNodeListModel(list, namespaces);
0490: }
0491:
0492: private TemplateModel getType() {
0493: if (nodes.size() == 0)
0494: return new SimpleScalar("");
0495: Object firstNode = nodes.get(0);
0496: char code;
0497: if (firstNode instanceof Element)
0498: code = 'e';
0499: else if (firstNode instanceof Text
0500: || firstNode instanceof String)
0501: code = 'x';
0502: else if (firstNode instanceof Attribute)
0503: code = 'a';
0504: else if (firstNode instanceof EntityRef)
0505: code = 'n';
0506: else if (firstNode instanceof Document)
0507: code = 'd';
0508: else if (firstNode instanceof DocType)
0509: code = 't';
0510: else if (firstNode instanceof Comment)
0511: code = 'c';
0512: else if (firstNode instanceof ProcessingInstruction)
0513: code = 'p';
0514: else
0515: code = '?';
0516: return new SimpleScalar(new String(new char[] { code }));
0517: }
0518:
0519: private SimpleScalar getPlainText() throws TemplateModelException {
0520: List list = evaluateElementOperation((TextOp) OPERATIONS
0521: .get("_text"), nodes);
0522: StringBuffer buf = new StringBuffer();
0523: for (Iterator it = list.iterator(); it.hasNext();) {
0524: buf.append(it.next());
0525: }
0526: return new SimpleScalar(buf.toString());
0527: }
0528:
0529: public TemplateModelIterator iterator() {
0530: return new TemplateModelIterator() {
0531: private final Iterator it = nodes.iterator();
0532:
0533: public TemplateModel next() {
0534: return it.hasNext() ? new NodeListModel(it.next(),
0535: namespaces) : null;
0536: }
0537:
0538: public boolean hasNext() {
0539: return it.hasNext();
0540: }
0541: };
0542: }
0543:
0544: /**
0545: * Retrieves the i-th element of the node list.
0546: */
0547: public TemplateModel get(int i) throws TemplateModelException {
0548: try {
0549: return new NodeListModel(nodes.get(i), namespaces);
0550: } catch (IndexOutOfBoundsException e) {
0551: throw new TemplateModelException("Index out of bounds: "
0552: + e.getMessage());
0553: }
0554: }
0555:
0556: public int size() {
0557: return nodes.size();
0558: }
0559:
0560: /**
0561: * Applies an XPath expression to the node list and returns the resulting node list.
0562: * In order for this method to work, your application must have access
0563: * <a href="http://www.jaxen.org">Jaxen</a> library classes. The
0564: * implementation does cache the parsed format of XPath expressions in a weak hash
0565: * map, keyed by the string representation of the XPath expression. As the string
0566: * object passed as the argument is usually kept in the parsed FreeMarker template,
0567: * this ensures that each XPath expression is parsed only once during the lifetime
0568: * of the FreeMarker template that contains it.
0569: * @param arguments the list of arguments. Must contain exactly one string that is
0570: * the XPath expression you wish to apply. The XPath expression can use any namespace
0571: * prefixes that were defined using the {@link #registerNamespace(String, String)}
0572: * method or the <code>nodelist._registerNamespace(prefix, uri)</code> expression in the
0573: * template.
0574: * @return a NodeListModel representing the nodes that are the result of application
0575: * of the XPath to the current node list.
0576: */
0577: public Object exec(List arguments) throws TemplateModelException {
0578: if (arguments == null || arguments.size() != 1)
0579: throw new TemplateModelException(
0580: "Exactly one argument required for execute() on NodeTemplate");
0581:
0582: String xpathString = (String) arguments.get(0);
0583: JDOMXPathEx xpath = null;
0584: try {
0585: synchronized (XPATH_CACHE) {
0586: xpath = (JDOMXPathEx) XPATH_CACHE.get(xpathString);
0587: if (xpath == null) {
0588: xpath = new JDOMXPathEx(xpathString);
0589: XPATH_CACHE.put(xpathString, xpath);
0590: }
0591: }
0592: return createNodeListModel(xpath.selectNodes(nodes,
0593: namespaces), namespaces);
0594: } catch (Exception e) {
0595: throw new TemplateModelException(
0596: "Could not evaulate XPath expression "
0597: + xpathString, e);
0598: }
0599: }
0600:
0601: /**
0602: * Registers an XML namespace with this node list. Once registered, you can
0603: * refer to the registered namespace using its prefix in the
0604: * {@link #get(String)} method from this node list and all other
0605: * node lists that are derived from this node list. Use the
0606: * <tt>nodelist["prefix:localname"]</tt> or the
0607: * <tt>nodelist["@prefix:localname"]</tt> syntax to reach elements and
0608: * attributes whose names are namespace-scoped. Note that the namespace
0609: * prefix need not match the actual prefix used by the XML document itself
0610: * since namespaces are compared solely by their URI. You can also register
0611: * namespaces during template evaluation using the
0612: * <tt>nodelist._registerNamespace(prefix, uri)</tt> syntax in the template.
0613: * This mechanism is completely independent from the namespace declarations
0614: * in the XML document itself; its purpose is to give you an easy way
0615: * to refer to namespace-scoped elements in {@link #get(String)} and
0616: * in XPath expressions passed to {@link #exec(List)}. Note also that
0617: * the namespace prefix registry is shared among all node lists that
0618: * are created from a single node list - modifying the registry in one
0619: * affects all others as well. If you want to obtain a namespace
0620: * "detached" copy of the node list, use the <code>_copy</code> key on
0621: * it (or call <code>nodeList.get("_copy")</code> directly from your
0622: * Java code. The returned node list has all the namespaces that the
0623: * original node list has, but they can be manipulated independently
0624: * thereon.
0625: */
0626: public void registerNamespace(String prefix, String uri) {
0627: synchronized (namespaces) {
0628: namespaces.put(prefix, Namespace.getNamespace(prefix, uri));
0629: }
0630: }
0631:
0632: private interface NodeOperator {
0633: List operate(Object node) throws TemplateModelException;
0634: }
0635:
0636: private interface NamedNodeOperator {
0637: List operate(Object node, String localName, Namespace namespace)
0638: throws TemplateModelException;
0639: }
0640:
0641: private static final class AllChildrenOp implements NodeOperator {
0642: public List operate(Object node) {
0643: if (node instanceof Element)
0644: return ((Element) node).getChildren();
0645: else if (node instanceof Document) {
0646: Element root = ((Document) node).getRootElement();
0647: return root == null ? Collections.EMPTY_LIST
0648: : Collections12.singletonList(root);
0649: }
0650: // With 2.1 semantics it makes more sense to just return a null and let the core
0651: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0652: return null;
0653: /*
0654: else
0655: throw new TemplateModelException("_allChildren can not be applied on " + node.getClass());
0656: */
0657: }
0658: }
0659:
0660: private static final class NamedChildrenOp implements
0661: NamedNodeOperator {
0662: public List operate(Object node, String localName,
0663: Namespace namespace) {
0664: if (node instanceof Element) {
0665: return ((Element) node).getChildren(localName,
0666: namespace);
0667: } else if (node instanceof Document) {
0668: Element root = ((Document) node).getRootElement();
0669: if (root != null
0670: && root.getName().equals(localName)
0671: && root.getNamespaceURI().equals(
0672: namespace.getURI())) {
0673: return Collections12.singletonList(root);
0674: } else
0675: return Collections.EMPTY_LIST;
0676: }
0677: // With 2.1 semantics it makes more sense to just return a null and let the core
0678: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0679: return null;
0680: /*
0681: else
0682: throw new TemplateModelException("_namedChildren can not be applied on " + node.getClass());
0683: */
0684: }
0685: }
0686:
0687: private static final class AllAttributesOp implements NodeOperator {
0688: public List operate(Object node) {
0689: // With 2.1 semantics it makes more sense to just return a null and let the core
0690: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0691: if (!(node instanceof Element)) {
0692: return null;
0693: }
0694: return ((Element) node).getAttributes();
0695: /*
0696: else
0697: throw new TemplateModelException("_allAttributes can not be applied on " + node.getClass());
0698: */
0699: }
0700: }
0701:
0702: private static final class NamedAttributeOp implements
0703: NamedNodeOperator {
0704: public List operate(Object node, String localName,
0705: Namespace namespace) {
0706: Attribute attr = null;
0707: if (node instanceof Element) {
0708: Element element = (Element) node;
0709: attr = element.getAttribute(localName, namespace);
0710: } else if (node instanceof ProcessingInstruction) {
0711: ProcessingInstruction pi = (ProcessingInstruction) node;
0712: if ("target".equals(localName))
0713: attr = new Attribute("target", pi.getTarget());
0714: else if ("data".equals(localName))
0715: attr = new Attribute("data", pi.getData());
0716: else
0717: attr = new Attribute(localName, pi
0718: .getValue(localName));
0719: } else if (node instanceof DocType) {
0720: DocType doctype = (DocType) node;
0721: if ("publicId".equals(localName))
0722: attr = new Attribute("publicId", doctype
0723: .getPublicID());
0724: else if ("systemId".equals(localName))
0725: attr = new Attribute("systemId", doctype
0726: .getSystemID());
0727: else if ("elementName".equals(localName))
0728: attr = new Attribute("elementName", doctype
0729: .getElementName());
0730: }
0731: // With 2.1 semantics it makes more sense to just return a null and let the core
0732: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0733: else {
0734: return null;
0735: }
0736: /*
0737: else
0738: throw new TemplateModelException("_allAttributes can not be applied on " + node.getClass());
0739: */
0740: return attr == null ? Collections.EMPTY_LIST
0741: : Collections12.singletonList(attr);
0742: }
0743: }
0744:
0745: private static final class NameOp implements NodeOperator {
0746: public List operate(Object node) {
0747: if (node instanceof Element)
0748: return Collections12.singletonList(((Element) node)
0749: .getName());
0750: else if (node instanceof Attribute)
0751: return Collections12.singletonList(((Attribute) node)
0752: .getName());
0753: else if (node instanceof EntityRef)
0754: return Collections12.singletonList(((EntityRef) node)
0755: .getName());
0756: else if (node instanceof ProcessingInstruction)
0757: return Collections12
0758: .singletonList(((ProcessingInstruction) node)
0759: .getTarget());
0760: else if (node instanceof DocType)
0761: return Collections12.singletonList(((DocType) node)
0762: .getPublicID());
0763: else
0764: return null;
0765: // With 2.1 semantics it makes more sense to just return a null and let the core
0766: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0767: // throw new TemplateModelException("_name can not be applied on " + node.getClass());
0768: }
0769: }
0770:
0771: private static final class QNameOp implements NodeOperator {
0772: public List operate(Object node) {
0773: if (node instanceof Element)
0774: return Collections12.singletonList(((Element) node)
0775: .getQualifiedName());
0776: else if (node instanceof Attribute)
0777: return Collections12.singletonList(((Attribute) node)
0778: .getQualifiedName());
0779: // With 2.1 semantics it makes more sense to just return a null and let the core
0780: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0781: return null;
0782: // throw new TemplateModelException("_qname can not be applied on " + node.getClass());
0783: }
0784: }
0785:
0786: private static final class NamespaceUriOp implements NodeOperator {
0787: public List operate(Object node) {
0788: if (node instanceof Element)
0789: return Collections12.singletonList(((Element) node)
0790: .getNamespace().getURI());
0791: else if (node instanceof Attribute)
0792: return Collections12.singletonList(((Attribute) node)
0793: .getNamespace().getURI());
0794: // With 2.1 semantics it makes more sense to just return a null and let the core
0795: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0796: return null;
0797: // throw new TemplateModelException("_nsuri can not be applied on " + node.getClass());
0798: }
0799: }
0800:
0801: private static final class NamespacePrefixOp implements
0802: NodeOperator {
0803: public List operate(Object node) {
0804: if (node instanceof Element)
0805: return Collections12.singletonList(((Element) node)
0806: .getNamespace().getPrefix());
0807: else if (node instanceof Attribute)
0808: return Collections12.singletonList(((Attribute) node)
0809: .getNamespace().getPrefix());
0810: // With 2.1 semantics it makes more sense to just return a null and let the core
0811: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0812: return null;
0813: // throw new TemplateModelException("_nsprefix can not be applied on " + node.getClass());
0814: }
0815: }
0816:
0817: private static final class CanonicalNameOp implements NodeOperator {
0818: public List operate(Object node) {
0819: if (node instanceof Element) {
0820: Element element = (Element) node;
0821: return Collections12.singletonList(element
0822: .getNamespace().getURI()
0823: + element.getName());
0824: } else if (node instanceof Attribute) {
0825: Attribute attribute = (Attribute) node;
0826: return Collections12.singletonList(attribute
0827: .getNamespace().getURI()
0828: + attribute.getName());
0829: }
0830: // With 2.1 semantics it makes more sense to just return a null and let the core
0831: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0832: return null;
0833: // throw new TemplateModelException("_cname can not be applied on " + node.getClass());
0834: }
0835: }
0836:
0837: private static final Element getParent(Object node) {
0838: if (node instanceof Element)
0839: return ((Element) node).getParent();
0840: else if (node instanceof Attribute)
0841: return ((Attribute) node).getParent();
0842: else if (node instanceof Text)
0843: return ((Text) node).getParent();
0844: else if (node instanceof ProcessingInstruction)
0845: return ((ProcessingInstruction) node).getParent();
0846: else if (node instanceof Comment)
0847: return ((Comment) node).getParent();
0848: else if (node instanceof EntityRef)
0849: return ((EntityRef) node).getParent();
0850: else
0851: // With 2.1 semantics it makes more sense to just return a null and let the core
0852: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0853: return null;
0854: // throw new TemplateModelException("_parent can not be applied on " + node.getClass());
0855: }
0856:
0857: private static final class ParentOp implements NodeOperator {
0858: public List operate(Object node) {
0859: Element parent = getParent(node);
0860: return parent == null ? Collections.EMPTY_LIST
0861: : Collections12.singletonList(parent);
0862: }
0863: }
0864:
0865: private static final class AncestorOp implements NodeOperator {
0866: public List operate(Object node) {
0867: Element parent = getParent(node);
0868: if (parent == null)
0869: return Collections.EMPTY_LIST;
0870: LinkedList list = new LinkedList();
0871: do {
0872: list.addFirst(parent);
0873: parent = parent.getParent();
0874: } while (parent != null);
0875: return list;
0876: }
0877: }
0878:
0879: private static final class AncestorOrSelfOp implements NodeOperator {
0880: public List operate(Object node) {
0881: Element parent = getParent(node);
0882: if (parent == null)
0883: return Collections12.singletonList(node);
0884: LinkedList list = new LinkedList();
0885: list.addFirst(node);
0886: do {
0887: list.addFirst(parent);
0888: parent = parent.getParent();
0889: } while (parent != null);
0890: return list;
0891: }
0892: }
0893:
0894: private static class DescendantOp implements NodeOperator {
0895: public List operate(Object node) {
0896: LinkedList list = new LinkedList();
0897: if (node instanceof Element) {
0898: addChildren((Element) node, list);
0899: } else if (node instanceof Document) {
0900: Element root = ((Document) node).getRootElement();
0901: list.add(root);
0902: addChildren(root, list);
0903: } else
0904: // With 2.1 semantics it makes more sense to just return a null and let the core
0905: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0906: return null;
0907: // throw new TemplateModelException("_descendant can not be applied on " + node.getClass());
0908:
0909: return list;
0910: }
0911:
0912: private void addChildren(Element element, List list) {
0913: List children = element.getChildren();
0914: Iterator it = children.iterator();
0915: while (it.hasNext()) {
0916: Element child = (Element) it.next();
0917: list.add(child);
0918: addChildren(child, list);
0919: }
0920: }
0921: }
0922:
0923: private static final class DescendantOrSelfOp extends DescendantOp {
0924: public List operate(Object node) {
0925: LinkedList list = (LinkedList) super .operate(node);
0926: list.addFirst(node);
0927: return list;
0928: }
0929: }
0930:
0931: private static final class DocumentOp implements NodeOperator {
0932: public List operate(Object node) {
0933: Document doc = null;
0934: if (node instanceof Element)
0935: doc = ((Element) node).getDocument();
0936: else if (node instanceof Attribute) {
0937: Element parent = ((Attribute) node).getParent();
0938: doc = parent == null ? null : parent.getDocument();
0939: } else if (node instanceof Text) {
0940: Element parent = ((Text) node).getParent();
0941: doc = parent == null ? null : parent.getDocument();
0942: } else if (node instanceof Document)
0943: doc = (Document) node;
0944: else if (node instanceof ProcessingInstruction)
0945: doc = ((ProcessingInstruction) node).getDocument();
0946: else if (node instanceof EntityRef)
0947: doc = ((EntityRef) node).getDocument();
0948: else if (node instanceof Comment)
0949: doc = ((Comment) node).getDocument();
0950: else
0951: // With 2.1 semantics it makes more sense to just return a null and let the core
0952: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0953: return null;
0954: // throw new TemplateModelException("_document can not be applied on " + node.getClass());
0955:
0956: return doc == null ? Collections.EMPTY_LIST : Collections12
0957: .singletonList(doc);
0958: }
0959: }
0960:
0961: private static final class DocTypeOp implements NodeOperator {
0962: public List operate(Object node) {
0963: if (node instanceof Document) {
0964: DocType doctype = ((Document) node).getDocType();
0965: return doctype == null ? Collections.EMPTY_LIST
0966: : Collections12.singletonList(doctype);
0967: } else
0968: // With 2.1 semantics it makes more sense to just return a null and let the core
0969: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0970: return null;
0971: // throw new TemplateModelException("_doctype can not be applied on " + node.getClass());
0972: }
0973: }
0974:
0975: private static final class ContentOp implements NodeOperator {
0976: public List operate(Object node) {
0977: if (node instanceof Element)
0978: return ((Element) node).getContent();
0979: else if (node instanceof Document)
0980: return ((Document) node).getContent();
0981: // With 2.1 semantics it makes more sense to just return a null and let the core
0982: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
0983: return null;
0984: // throw new TemplateModelException("_content can not be applied on " + node.getClass());
0985: }
0986: }
0987:
0988: private static final class TextOp implements NodeOperator {
0989: public List operate(Object node) {
0990: if (node instanceof Element)
0991: return Collections12.singletonList(((Element) node)
0992: .getTextTrim());
0993: if (node instanceof Attribute)
0994: return Collections12.singletonList(((Attribute) node)
0995: .getValue());
0996: if (node instanceof CDATA)
0997: return Collections12.singletonList(((CDATA) node)
0998: .getText());
0999: if (node instanceof Comment)
1000: return Collections12.singletonList(((Comment) node)
1001: .getText());
1002: if (node instanceof ProcessingInstruction)
1003: return Collections12
1004: .singletonList(((ProcessingInstruction) node)
1005: .getData());
1006: // With 2.1 semantics it makes more sense to just return a null and let the core
1007: // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
1008: return null;
1009: // throw new TemplateModelException("_text can not be applied on " + node.getClass());
1010: }
1011: }
1012:
1013: private static final List evaluateElementOperation(NodeOperator op,
1014: List nodes) throws TemplateModelException {
1015: int s = nodes.size();
1016: List[] lists = new List[s];
1017: int l = 0;
1018: {
1019: int i = 0;
1020: Iterator it = nodes.iterator();
1021: while (it.hasNext()) {
1022: List list = op.operate(it.next());
1023: if (list != null) {
1024: lists[i++] = list;
1025: l += list.size();
1026: }
1027: }
1028: }
1029: List retval = new ArrayList(l);
1030: for (int i = 0; i < s; ++i) {
1031: if (lists[i] != null) {
1032: retval.addAll(lists[i]);
1033: }
1034: }
1035: return retval;
1036: }
1037:
1038: private static final List evaluateNamedElementOperation(
1039: NamedNodeOperator op, String localName,
1040: Namespace namespace, List nodes)
1041: throws TemplateModelException {
1042: int s = nodes.size();
1043: List[] lists = new List[s];
1044: int l = 0;
1045: {
1046: int i = 0;
1047: Iterator it = nodes.iterator();
1048: while (it.hasNext()) {
1049: List list = op.operate(it.next(), localName, namespace);
1050: lists[i++] = list;
1051: l += list.size();
1052: }
1053: }
1054: List retval = new ArrayList(l);
1055: for (int i = 0; i < s; ++i)
1056: retval.addAll(lists[i]);
1057: return retval;
1058: }
1059:
1060: private static final List removeDuplicates(List list) {
1061: int s = list.size();
1062: ArrayList ulist = new ArrayList(s);
1063: Set set = new HashSet(s * 4 / 3, .75f);
1064: Iterator it = list.iterator();
1065: while (it.hasNext()) {
1066: Object o = it.next();
1067: if (set.add(o))
1068: ulist.add(o);
1069: }
1070: ulist.trimToSize();
1071: return ulist;
1072: }
1073:
1074: private static final Map createOperations() {
1075: Map map = new HashMap();
1076:
1077: map.put("_ancestor", new AncestorOp());
1078: map.put("_ancestorOrSelf", new AncestorOrSelfOp());
1079: map.put("_attributes", ALL_ATTRIBUTES_OP);
1080: map.put("_children", ALL_CHILDREN_OP);
1081: map.put("_cname", new CanonicalNameOp());
1082: map.put("_content", new ContentOp());
1083: map.put("_descendant", new DescendantOp());
1084: map.put("_descendantOrSelf", new DescendantOrSelfOp());
1085: map.put("_document", new DocumentOp());
1086: map.put("_doctype", new DocTypeOp());
1087: map.put("_name", new NameOp());
1088: map.put("_nsprefix", new NamespacePrefixOp());
1089: map.put("_nsuri", new NamespaceUriOp());
1090: map.put("_parent", new ParentOp());
1091: map.put("_qname", new QNameOp());
1092: map.put("_text", new TextOp());
1093:
1094: return map;
1095: }
1096:
1097: private static final Map createSpecialOperations() {
1098: Map map = new HashMap();
1099:
1100: Integer copy = new Integer(SPECIAL_OPERATION_COPY);
1101: Integer unique = new Integer(SPECIAL_OPERATION_UNIQUE);
1102: Integer fname = new Integer(SPECIAL_OPERATION_FILTER_NAME);
1103: Integer ftype = new Integer(SPECIAL_OPERATION_FILTER_TYPE);
1104: Integer type = new Integer(SPECIAL_OPERATION_QUERY_TYPE);
1105: Integer regns = new Integer(
1106: SPECIAL_OPERATION_REGISTER_NAMESPACE);
1107: Integer plaintext = new Integer(SPECIAL_OPERATION_PLAINTEXT);
1108:
1109: map.put("_copy", copy);
1110: map.put("_unique", unique);
1111: map.put("_fname", fname);
1112: map.put("_ftype", ftype);
1113: map.put("_type", type);
1114: map.put("_registerNamespace", regns);
1115: map.put("_plaintext", plaintext);
1116:
1117: // These are in for backward compatibility
1118: map.put("x_copy", copy);
1119: map.put("x_unique", unique);
1120: map.put("x_fname", fname);
1121: map.put("x_ftype", ftype);
1122: map.put("x_type", type);
1123:
1124: return map;
1125: }
1126:
1127: private final class RegisterNamespace implements
1128: TemplateMethodModel {
1129: public boolean isEmpty() {
1130: return false;
1131: }
1132:
1133: public Object exec(List arguments)
1134: throws TemplateModelException {
1135: if (arguments.size() != 2)
1136: throw new TemplateModelException(
1137: "_registerNamespace(prefix, uri) requires two arguments");
1138:
1139: registerNamespace((String) arguments.get(0),
1140: (String) arguments.get(1));
1141:
1142: return TemplateScalarModel.EMPTY_STRING;
1143: }
1144: }
1145:
1146: private final class NameFilter implements TemplateMethodModel {
1147: public boolean isEmpty() {
1148: return false;
1149: }
1150:
1151: public Object exec(List arguments) {
1152: Set names = new HashSet(arguments);
1153: List list = new LinkedList(nodes);
1154: Iterator it = list.iterator();
1155: while (it.hasNext()) {
1156: Object node = it.next();
1157: String name = null;
1158: if (node instanceof Element)
1159: name = ((Element) node).getName();
1160: else if (node instanceof Attribute)
1161: name = ((Attribute) node).getName();
1162: else if (node instanceof ProcessingInstruction)
1163: name = ((ProcessingInstruction) node).getTarget();
1164: else if (node instanceof EntityRef)
1165: name = ((EntityRef) node).getName();
1166: else if (node instanceof DocType)
1167: name = ((DocType) node).getPublicID();
1168:
1169: if (name == null || !names.contains(name))
1170: it.remove();
1171: }
1172: return createNodeListModel(list, namespaces);
1173: }
1174: }
1175:
1176: private final class TypeFilter implements TemplateMethodModel {
1177: public boolean isEmpty() {
1178: return false;
1179: }
1180:
1181: public Object exec(List arguments)
1182: throws TemplateModelException {
1183: if (arguments == null || arguments.size() == 0)
1184: throw new TemplateModelException(
1185: "_type expects exactly one argument");
1186: String arg = (String) arguments.get(0);
1187: boolean invert = arg.indexOf('!') != -1;
1188: // NOTE: true in each of these variables means 'remove', not 'keep'
1189: // This is so we don't invert their values in the loop. So,
1190: // a is true <--> (a is not present in the string) xor invert.
1191: boolean a = invert != (arg.indexOf('a') == -1);
1192: boolean c = invert != (arg.indexOf('c') == -1);
1193: boolean d = invert != (arg.indexOf('d') == -1);
1194: boolean e = invert != (arg.indexOf('e') == -1);
1195: boolean n = invert != (arg.indexOf('n') == -1);
1196: boolean p = invert != (arg.indexOf('p') == -1);
1197: boolean t = invert != (arg.indexOf('t') == -1);
1198: boolean x = invert != (arg.indexOf('x') == -1);
1199:
1200: LinkedList list = new LinkedList(nodes);
1201: Iterator it = list.iterator();
1202: while (it.hasNext()) {
1203: Object node = it.next();
1204: if ((node instanceof Element && e)
1205: || (node instanceof Attribute && a)
1206: || (node instanceof String && x)
1207: || (node instanceof Text && x)
1208: || (node instanceof ProcessingInstruction && p)
1209: || (node instanceof Comment && c)
1210: || (node instanceof EntityRef && n)
1211: || (node instanceof Document && d)
1212: || (node instanceof DocType && t))
1213: it.remove();
1214: }
1215: return createNodeListModel(list, namespaces);
1216: }
1217: }
1218:
1219: /**
1220: * Loads a template from a file passed as the first argument, loads an XML
1221: * document from the standard input, passes it to the template as variable
1222: * <tt>document</tt> and writes the result of template processing to
1223: * standard output.
1224: */
1225: public static void main(String[] args) throws Exception {
1226: org.jdom.input.SAXBuilder builder = new org.jdom.input.SAXBuilder();
1227: Document document = builder.build(System.in);
1228: SimpleHash model = new SimpleHash();
1229: model.put("document", new NodeListModel(document));
1230: FileReader fr = new FileReader(args[0]);
1231: Template template = new Template(args[0], fr);
1232: Writer w = new java.io.OutputStreamWriter(System.out);
1233: template.process(model, w);
1234: w.flush();
1235: w.close();
1236: }
1237:
1238: private static final class AttributeXMLOutputter extends
1239: XMLOutputter {
1240: public void output(Attribute attribute, Writer out)
1241: throws IOException {
1242: out.write(" ");
1243: out.write(attribute.getQualifiedName());
1244: out.write("=");
1245:
1246: out.write("\"");
1247: out.write(escapeAttributeEntities(attribute.getValue()));
1248: out.write("\"");
1249: }
1250: }
1251:
1252: private static final class JDOMXPathEx extends JDOMXPath {
1253: JDOMXPathEx(String path) throws JaxenException {
1254: super (path);
1255: }
1256:
1257: public List selectNodes(Object object, Map namespaces)
1258: throws JaxenException {
1259: Context context = getContext(object);
1260: context.getContextSupport().setNamespaceContext(
1261: new NamespaceContextImpl(namespaces));
1262: return selectNodesForContext(context);
1263: }
1264:
1265: private static final class NamespaceContextImpl implements
1266: NamespaceContext {
1267: private final Map namespaces;
1268:
1269: NamespaceContextImpl(Map namespaces) {
1270: this .namespaces = namespaces;
1271: }
1272:
1273: public String translateNamespacePrefixToUri(String prefix) {
1274: // Empty prefix always maps to empty URL in XPath
1275: if (prefix.length() == 0) {
1276: return prefix;
1277: }
1278: synchronized (namespaces) {
1279: Namespace ns = (Namespace) namespaces.get(prefix);
1280: return ns == null ? null : ns.getURI();
1281: }
1282: }
1283: }
1284: }
1285: }
|