Save This Page
Home » freemarker-2.3.13 » freemarker.ext.jdom » [javadoc | source]
    1   /*
    2    * Copyright (c) 2003 The Visigoth Software Society. All rights
    3    * reserved.
    4    *
    5    * Redistribution and use in source and binary forms, with or without
    6    * modification, are permitted provided that the following conditions
    7    * are met:
    8    *
    9    * 1. Redistributions of source code must retain the above copyright
   10    *    notice, this list of conditions and the following disclaimer.
   11    *
   12    * 2. Redistributions in binary form must reproduce the above copyright
   13    *    notice, this list of conditions and the following disclaimer in
   14    *    the documentation and/or other materials provided with the
   15    *    distribution.
   16    *
   17    * 3. The end-user documentation included with the redistribution, if
   18    *    any, must include the following acknowledgement:
   19    *       "This product includes software developed by the
   20    *        Visigoth Software Society (http://www.visigoths.org/)."
   21    *    Alternately, this acknowledgement may appear in the software itself,
   22    *    if and wherever such third-party acknowledgements normally appear.
   23    *
   24    * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the 
   25    *    project contributors may be used to endorse or promote products derived
   26    *    from this software without prior written permission. For written
   27    *    permission, please contact visigoths@visigoths.org.
   28    *
   29    * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
   30    *    nor may "FreeMarker" or "Visigoth" appear in their names
   31    *    without prior written permission of the Visigoth Software Society.
   32    *
   33    * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   34    * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   35    * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   36    * DISCLAIMED.  IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
   37    * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   38    * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   39    * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   40    * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   41    * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   42    * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   43    * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   44    * SUCH DAMAGE.
   45    * ====================================================================
   46    *
   47    * This software consists of voluntary contributions made by many
   48    * individuals on behalf of the Visigoth Software Society. For more
   49    * information on the Visigoth Software Society, please see
   50    * http://www.visigoths.org/
   51    */
   52   
   53   package freemarker.ext.jdom;
   54   
   55   import java.io.FileReader;
   56   import java.io.IOException;
   57   import java.io.Writer;
   58   import java.util.ArrayList;
   59   import java.util.Collections;
   60   import java.util.HashMap;
   61   import java.util.HashSet;
   62   import java.util.Iterator;
   63   import java.util.LinkedList;
   64   import java.util.List;
   65   import java.util.Map;
   66   import java.util.Set;
   67   import java.util.WeakHashMap;
   68   
   69   import org.jaxen.Context;
   70   import org.jaxen.JaxenException;
   71   import org.jaxen.NamespaceContext;
   72   import org.jaxen.jdom.JDOMXPath;
   73   import org.jdom.Attribute;
   74   import org.jdom.CDATA;
   75   import org.jdom.Comment;
   76   import org.jdom.DocType;
   77   import org.jdom.Document;
   78   import org.jdom.Element;
   79   import org.jdom.EntityRef;
   80   import org.jdom.Namespace;
   81   import org.jdom.ProcessingInstruction;
   82   import org.jdom.Text;
   83   import org.jdom.output.XMLOutputter;
   84   import freemarker.template.SimpleHash;
   85   import freemarker.template.SimpleScalar;
   86   import freemarker.template.Template;
   87   import freemarker.template.TemplateCollectionModel;
   88   import freemarker.template.TemplateHashModel;
   89   import freemarker.template.TemplateMethodModel;
   90   import freemarker.template.TemplateModel;
   91   import freemarker.template.TemplateModelException;
   92   import freemarker.template.TemplateModelIterator;
   93   import freemarker.template.TemplateScalarModel;
   94   import freemarker.template.TemplateSequenceModel;
   95   import freemarker.template.utility.Collections12;
   96   
   97   /**
   98    * Provides a template for wrapping JDOM objects. It is capable of storing not only
   99    * a single JDOM node, but a list of JDOM nodes at once (hence the name).
  100    * Each node is an instance of any of the core JDOM node classes (except namespaces,
  101    * which are not supported at the moment), or String for representing text.
  102    * See individual method documentation for exact details on how the class works. In
  103    * short:
  104    * <ul>
  105    * <li>{@link #getAsString()} will render all contained nodes as XML fragment,</tt>
  106    * <li>{@link #exec(List)} provides full XPath functionality implemented on top of
  107    * the <a href="http://www.jaxen.org">Jaxen</a> library,</li>
  108    * <li>{@link #get(String)} provides node traversal, copying and filtering - somewhat
  109    * less expressive than XPath, however it does not require the external library and
  110    * it evaluates somewhat faster</li>
  111    * <li>being a {@link TemplateCollectionModel} allows to iterate the contained node list, and</li>
  112    * <li>being a {@link TemplateSequenceModel} allows to access the contained nodes by index and query the node count.</li>
  113    * </ul>
  114    * 
  115    * <p><b>Note:</b> There is a JDOM independent re-implementation of this class:
  116    *   {@link freemarker.ext.xml.NodeListModel freemarker.ext.xml.NodeListModel}
  117    * 
  118    * @deprecated Use {@link freemarker.ext.dom.NodeModel} instead.
  119    * @author Attila Szegedi
  120    * @version $Id: NodeListModel.java,v 1.52.2.2 2006/11/14 10:39:58 szegedia Exp $
  121    */
  122   public class NodeListModel
  123   implements
  124       TemplateHashModel,
  125       TemplateMethodModel,
  126       TemplateCollectionModel,
  127       TemplateSequenceModel,
  128       TemplateScalarModel
  129   {
  130       private static final AttributeXMLOutputter OUTPUT = new AttributeXMLOutputter();
  131       // A convenience singleton for representing a node list without nodes.
  132       private static final NodeListModel EMPTY = new NodeListModel(null, false);
  133   
  134       // Cache of already parsed XPath expressions
  135       private static final Map XPATH_CACHE = new WeakHashMap();
  136   
  137       private static final NamedNodeOperator NAMED_CHILDREN_OP = new NamedChildrenOp();
  138       private static final NamedNodeOperator NAMED_ATTRIBUTE_OP = new NamedAttributeOp();
  139       private static final NodeOperator ALL_ATTRIBUTES_OP = new AllAttributesOp();
  140       private static final NodeOperator ALL_CHILDREN_OP = new AllChildrenOp();
  141       private static final Map OPERATIONS = createOperations();
  142       private static final Map SPECIAL_OPERATIONS = createSpecialOperations();
  143       private static final int SPECIAL_OPERATION_COPY = 0;
  144       private static final int SPECIAL_OPERATION_UNIQUE = 1;
  145       private static final int SPECIAL_OPERATION_FILTER_NAME = 2;
  146       private static final int SPECIAL_OPERATION_FILTER_TYPE = 3;
  147       private static final int SPECIAL_OPERATION_QUERY_TYPE = 4;
  148       private static final int SPECIAL_OPERATION_REGISTER_NAMESPACE = 5;
  149       private static final int SPECIAL_OPERATION_PLAINTEXT = 6;
  150   
  151       // The contained nodes
  152       private final List nodes;
  153       private final Map namespaces;
  154   
  155       /**
  156        * Creates a node list that holds a single {@link Document} node.
  157        */
  158       public NodeListModel(Document document)
  159       {
  160           nodes = document == null ? Collections.EMPTY_LIST : Collections12.singletonList(document);
  161           namespaces = new HashMap();
  162       }
  163   
  164       /**
  165        * Creates a node list that holds a single {@link Element} node.
  166        */
  167       public NodeListModel(Element element)
  168       {
  169           nodes = element == null ? Collections.EMPTY_LIST : Collections12.singletonList(element);
  170           namespaces = new HashMap();
  171       }
  172   
  173       private NodeListModel(Object object, Map namespaces)
  174       {
  175           nodes = object == null ? Collections.EMPTY_LIST : Collections12.singletonList(object);
  176           this.namespaces = namespaces;
  177       }
  178   
  179       /**
  180        * Creates a node list that holds a list of nodes.
  181        * @param nodes the list of nodes this template should hold. The created template
  182        * will copy the passed nodes list, so changes to the passed list will not affect
  183        * the model.
  184        */
  185       public NodeListModel(List nodes)
  186       {
  187           this(nodes, true);
  188       }
  189   
  190       /**
  191        * Creates a node list that holds a list of nodes.
  192        * @param nodes the list of nodes this template should hold.
  193        * @param copy if true, the created template will copy the passed nodes list,
  194        * so changes to the passed list will not affect the model. If false, the model
  195        * will reference the passed list and will sense changes in it, although no
  196        * operations on the list will be synchronized.
  197        */
  198       public NodeListModel(List nodes, boolean copy)
  199       {
  200           this.nodes = copy && nodes != null ? new ArrayList(nodes) : (nodes == null ? Collections.EMPTY_LIST : nodes);
  201           namespaces = new HashMap();
  202       }
  203   
  204       private NodeListModel(List nodes, Map namespaces)
  205       {
  206           this.nodes = nodes == null ? Collections.EMPTY_LIST : nodes;
  207           this.namespaces = namespaces;
  208       }
  209   
  210       private static final NodeListModel createNodeListModel(List list, Map namespaces)
  211       {
  212           if (list == null || list.isEmpty()) {
  213               if (namespaces.isEmpty()) {
  214                   return EMPTY;
  215               } else {
  216                   return new NodeListModel(Collections.EMPTY_LIST, namespaces);
  217               }
  218           }
  219           if (list.size() == 1) return new NodeListModel(list.get(0), namespaces);
  220           return new NodeListModel(list, namespaces);
  221       }
  222   
  223       /**
  224        * Returns true if this model contains no nodes.
  225        */
  226       public boolean isEmpty()
  227       {
  228           return nodes.isEmpty();
  229       }
  230   
  231       /**
  232        * This method returns the string resulting from concatenation
  233        * of string representations of its nodes. Each node is rendered using its XML
  234        * serialization format, while text (String) is rendered as itself. This greatly
  235        * simplifies creating XML-transformation templates, as to output a node contained
  236        * in variable x as XML fragment, you simply write ${x} in the template.
  237        */
  238       public String getAsString()
  239       throws
  240       TemplateModelException
  241       {
  242           if (isEmpty())
  243               return "";
  244   
  245           java.io.StringWriter sw = new java.io.StringWriter(nodes.size() * 128);
  246           try {
  247               for (Iterator i = nodes.iterator(); i.hasNext();) {
  248                   Object node = i.next();
  249                   if (node instanceof Element)
  250                       OUTPUT.output((Element)node, sw);
  251                   else if (node instanceof Attribute)
  252                       OUTPUT.output((Attribute)node, sw);
  253                   else if (node instanceof String)
  254                       sw.write(OUTPUT.escapeElementEntities(node.toString()));
  255                   else if (node instanceof Text)
  256                       OUTPUT.output((Text)node, sw);
  257                   else if (node instanceof Document)
  258                       OUTPUT.output((Document)node, sw);
  259                   else if (node instanceof ProcessingInstruction)
  260                       OUTPUT.output((ProcessingInstruction)node, sw);
  261                   else if (node instanceof Comment)
  262                       OUTPUT.output((Comment)node, sw);
  263                   else if (node instanceof CDATA)
  264                       OUTPUT.output((CDATA)node, sw);
  265                   else if (node instanceof DocType)
  266                       OUTPUT.output((DocType)node, sw);
  267                   else if (node instanceof EntityRef)
  268                       OUTPUT.output((EntityRef)node, sw);
  269                   else
  270                       throw new TemplateModelException(node.getClass().getName() + " is not a core JDOM class");
  271               }
  272           } catch (IOException e) {
  273               throw new TemplateModelException(e.getMessage());
  274           }
  275           return sw.toString();
  276       }
  277   
  278   
  279       /**
  280        * Provides node list traversal as well as special functions: filtering by name,
  281        * filtering by node type, shallow-copying, and duplicate removal.
  282        * While not as powerful as the full XPath support built into the
  283        * {@link #exec(List)} method, it does not require the external Jaxen
  284        * library to be present at run time. Below are listed the recognized keys.
  285        * In key descriptions, "applicable to this-and-that node type" means that if
  286        * a key is applied to a node list that contains a node of non-applicable type
  287        * a TemplateMethodModel will be thrown. However, you can use <tt>_ftype</tt>
  288        * key to explicitly filter out undesired node types prior to applying the
  289        * restricted-applicability key. Also "current nodes" means nodes contained in this
  290        * set.
  291        * <ul>
  292        *    <li><tt>*</tt> or <tt>_children</tt>: all direct element children of current nodes (non-recursive). Applicable
  293        *  to element and document nodes.</li>
  294        *    <li><tt>@*</tt> or <tt>_attributes</tt>: all attributes of current nodes. Applicable to elements only.</li>
  295        *    <li><tt>_content</tt> the complete content of current nodes (non-recursive).
  296        *  Applicable to elements and documents.</li>
  297        *    <li><tt>_text</tt>: the text of current nodes, one string per node (non-recursive).
  298        *  Applicable to elements, attributes, comments, processing instructions (returns its data)
  299        *  and CDATA sections. The reserved XML characters ('&lt;' and '&amp;') are escaped.</li>
  300        *    <li><tt>_plaintext</tt>: same as <tt>_text</tt>, but does not escape any characters,
  301        *  and instead of returning a NodeList returns a SimpleScalar.</li>
  302        *    <li><tt>_name</tt>: the names of current nodes, one string per node (non-recursive).
  303        *  Applicable to elements and attributes (returns their local name), 
  304        *  entities, processing instructions (returns its target), doctypes 
  305        * (returns its public ID)</li>
  306        *    <li><tt>_qname</tt>: the qualified names of current nodes in <tt>[namespacePrefix:]localName</tt>
  307        * form, one string per node (non-recursive). Applicable to elements and attributes</li>
  308        *    <li><tt>_cname</tt>: the canonical names of current nodes (namespace URI + local name),
  309        * one string per node (non-recursive). Applicable to elements and attributes</li>
  310        *    <li><tt>_nsprefix</tt>: namespace prefixes of current nodes,
  311        * one string per node (non-recursive). Applicable to elements and attributes</li>
  312        *    <li><tt>_nsuri</tt>: namespace URIs of current nodes,
  313        * one string per node (non-recursive). Applicable to elements and attributes</li>
  314        *    <li><tt>_parent</tt>: parent elements of current nodes. Applicable to element, attribute, comment,
  315        *  entity, processing instruction.</li>
  316        *    <li><tt>_ancestor</tt>: all ancestors up to root element (recursive) of current nodes. Applicable
  317        *  to same node types as <tt>_parent</tt>.</li>
  318        *    <li><tt>_ancestorOrSelf</tt>: all ancestors of current nodes plus current nodes. Applicable
  319        *  to same node types as <tt>_parent</tt>.</li>
  320        *    <li><tt>_descendant</tt>: all recursive descendant element children of current nodes. Applicable to
  321        *  document and element nodes.
  322        *    <li><tt>_descendantOrSelf</tt>: all recursive descendant element children of current nodes
  323        *  plus current nodes. Applicable to document and element nodes.
  324        *    <li><tt>_document</tt>: all documents the current nodes belong to.
  325        *  Applicable to all nodes except text.
  326        *    <li><tt>_doctype</tt>: doctypes of the current nodes.
  327        *  Applicable to document nodes only.
  328        *    <li><tt>_fname</tt>: is a filter-by-name template method model. When called,
  329        *  it will yield a node list that contains only those current nodes whose name
  330        *  matches one of names passed as argument. Attribute names should NOT be prefixed with the
  331        *  at sign (@). Applicable on all node types, however has no effect on unnamed nodes.</li>
  332        *    <li><tt>_ftype</tt>: is a filter-by-type template method model. When called,
  333        *  it will yield a node list that contains only those current nodes whose type matches one
  334        *  of types passed as argument. You should pass a single string to this method
  335        *  containing the characters of all types to keep. Valid characters are:
  336        *  e (Element), a (Attribute), n (Entity), d (Document), t (DocType),
  337        *  c (Comment), p (ProcessingInstruction), x (text). If the string anywhere contains
  338        *  the exclamation mark (!), the filter's effect is inverted.</li>
  339        *    <li><tt>_type</tt>: Returns a one-character String SimpleScalar containing
  340        *    the typecode of the first node in the node list. Valid characters are:
  341        *  e (Element), a (Attribute), n (Entity), d (Document), t (DocType),
  342        *  c (Comment), p (ProcessingInstruction), x (text). If the type of the node
  343        *  is unknown, returns '?'. If the node list is empty, returns an empty string scalar.</li>
  344        *    <li><tt>_unique</tt>: a copy of the current nodes that keeps only the
  345        *  first occurrence of every node, eliminating duplicates. Duplicates can
  346        *  occur in the node list by applying uptree-traversals <tt>_parent</tt>,
  347        *  <tt>_ancestor</tt>, <tt>_ancestorOrSelf</tt>, and <tt>_document</tt>.
  348        *  I.e. <tt>foo._children._parent</tt> will return a node list that has
  349        *  duplicates of nodes in foo - each node will have the number of occurrences
  350        *  equal to the number of its children. In these cases, use
  351        *  <tt>foo._children._parent._unique</tt> to eliminate duplicates. Applicable
  352        *  to all node types.</li>
  353        *    <li><tt>_copy</tt>: a copy of the current node list. It is a shallow copy that
  354        *  shares the underlying node list with this node list, however it has a
  355        *  separate namespace registry, so it can be used to guarantee that subsequent
  356        *  changes to the set of registered namespaces does not affect the node lists
  357        *  that were used to create this node list. Applicable to all node types.</li>
  358        *    <li><tt>_registerNamespace(prefix, uri)</tt>: register a XML namespace
  359        *  with the specified prefix and URI for the current node list and all node
  360        *  lists that are derived from the current node list. After registering,
  361        *  you can use the <tt>nodelist["prefix:localname"]</tt> or
  362        *  <tt>nodelist["@prefix:localname"]</tt> syntaxes to reach elements and
  363        *  attributes whose names are namespace-scoped. Note that the namespace
  364        *  prefix need not match the actual prefix used by the XML document itself
  365        *  since namespaces are compared solely by their URI. You can also register
  366        *  namespaces from Java code using the
  367        *  {@link #registerNamespace(String, String)} method.
  368        * </li>
  369        *    <li><tt>@attributeName</tt>: named attributes of current nodes. Applicable to
  370        *  elements, doctypes and processing instructions. On doctypes it supports
  371        *  attributes <tt>publicId</tt>, <tt>systemId</tt> and <tt>elementName</tt>. On processing
  372        *  instructions, it supports attributes <tt>target</tt> and <tt>data</tt>, as
  373        *  well as any other attribute name specified in data as <tt>name="value"</tt> pair.
  374        *  The attribute nodes for doctype and processing instruction are synthetic, and
  375        *  as such have no parent. Note, however that <tt>@*</tt> does NOT operate on
  376        *  doctypes or processing instructions.</li>
  377        *    <li>any other key: element children of current nodes with name matching the key.
  378        *  This allows for convenience child traversal in <tt>book.chapter.title</tt> style syntax.
  379        *  Note that <tt>nodeset.childname</tt> is technically equivalent to
  380        *  <tt>nodeset._children._fname("childname")</tt>, but is both shorter to write
  381        *  and evaluates faster. Applicable to document and element nodes.</li>
  382        * </ul>
  383        * The order of nodes in the resulting set is the order of evaluation of the key
  384        * on each node in this set from left to right. Evaluation of the key on a single
  385        * node always yields the results in "natural" order (that of the document preorder
  386        * traversal), even for uptree traversals. As a consequence, if this node list's nodes
  387        * are listed in natural order, applying any of the keys will produce a node list that
  388        * is also naturally ordered. As a special case, all node lists that are directly or
  389        * indirectly generated from a single Document or Element node through repeated
  390        * invocations of this method will be naturally ordered.
  391        * @param key a key that identifies a required set of nodes
  392        * @return a new NodeListModel that represents the requested set of nodes.
  393        */
  394       public TemplateModel get(String key)
  395       throws
  396       TemplateModelException
  397       {
  398           if (isEmpty())
  399               return EMPTY;
  400   
  401           if (key == null || key.length() == 0)
  402               throw new TemplateModelException("Invalid key [" + key + "]");
  403   
  404           NodeOperator op = null;
  405           NamedNodeOperator nop = null;
  406           String name = null;
  407   
  408           switch (key.charAt(0)) {
  409               case '@':
  410                   {
  411                       if (key.length() != 2 || key.charAt(1) != '*') {
  412                           // Generic attribute key
  413                           nop = NAMED_ATTRIBUTE_OP;
  414                           name = key.substring(1);
  415                       } else
  416                           // It is @*
  417                           op = ALL_ATTRIBUTES_OP;
  418   
  419                       break;
  420                   }
  421               case '*':
  422                   {
  423                       if (key.length() == 1)
  424                           op = ALL_CHILDREN_OP;
  425                       else
  426                           // Explicitly disallow any other identifier starting with asterisk
  427                           throw new TemplateModelException("Invalid key [" + key + "]");
  428   
  429                       break;
  430                   }
  431               case 'x':
  432               case '_':
  433                   {
  434                       op = (NodeOperator)OPERATIONS.get(key);
  435                       if (op == null) {
  436                           // Some special operation?
  437                           Integer specop = (Integer)SPECIAL_OPERATIONS.get(key);
  438                           if (specop != null) {
  439                               switch (specop.intValue()) {
  440                                   case SPECIAL_OPERATION_COPY:
  441                                   {
  442                                       synchronized(namespaces)
  443                                       {
  444                                           return new NodeListModel(nodes, (Map)((HashMap)namespaces).clone());
  445                                       }
  446                                   }
  447                                   case SPECIAL_OPERATION_UNIQUE:
  448                                       return new NodeListModel(removeDuplicates(nodes), namespaces);
  449                                   case SPECIAL_OPERATION_FILTER_NAME:
  450                                       return new NameFilter();
  451                                   case SPECIAL_OPERATION_FILTER_TYPE:
  452                                       return new TypeFilter();
  453                                   case SPECIAL_OPERATION_QUERY_TYPE:
  454                                       return getType();
  455                                   case SPECIAL_OPERATION_REGISTER_NAMESPACE:
  456                                       return new RegisterNamespace();
  457                                   case SPECIAL_OPERATION_PLAINTEXT:
  458                                       return getPlainText();
  459                               }
  460                           }
  461                       }
  462                       break;
  463                   }
  464           }
  465   
  466           if (op == null && nop == null) {
  467               nop = NAMED_CHILDREN_OP;
  468               name = key;
  469           }
  470   
  471           List list = null;
  472           if (op != null)
  473               list = evaluateElementOperation(op, nodes);
  474           else {
  475               String localName = name;
  476               Namespace namespace = Namespace.NO_NAMESPACE;
  477               int colon = name.indexOf(':');
  478               if (colon != -1) {
  479                   localName = name.substring(colon + 1);
  480                   String nsPrefix = name.substring(0, colon);
  481                   synchronized(namespaces)
  482                   {
  483                       namespace = (Namespace)namespaces.get(nsPrefix);
  484                   }
  485                   if (namespace == null) {
  486                       if (nsPrefix.equals("xml"))
  487                           namespace = Namespace.XML_NAMESPACE;
  488                       else
  489                           throw new TemplateModelException("Unregistered namespace prefix '" + nsPrefix + "'");
  490                   }
  491               }
  492   
  493               list = evaluateNamedElementOperation(nop, localName, namespace, nodes);
  494           }
  495           return createNodeListModel(list, namespaces);
  496       }
  497   
  498       private TemplateModel getType()
  499       {
  500           if (nodes.size() == 0)
  501               return new SimpleScalar("");
  502           Object firstNode = nodes.get(0);
  503           char code;
  504           if (firstNode instanceof Element)
  505               code = 'e';
  506           else if (firstNode instanceof Text || firstNode instanceof String)
  507               code = 'x';
  508           else if (firstNode instanceof Attribute)
  509               code = 'a';
  510           else if (firstNode instanceof EntityRef)
  511               code = 'n';
  512           else if (firstNode instanceof Document)
  513               code = 'd';
  514           else if (firstNode instanceof DocType)
  515               code = 't';
  516           else if (firstNode instanceof Comment)
  517               code = 'c';
  518           else if (firstNode instanceof ProcessingInstruction)
  519               code = 'p';
  520           else
  521               code = '?';
  522           return new SimpleScalar(new String(new char[] { code}));
  523       }
  524   
  525       private SimpleScalar getPlainText()
  526       throws
  527       TemplateModelException
  528       {
  529           List list = evaluateElementOperation((TextOp)OPERATIONS.get("_text"), nodes);
  530           StringBuffer buf = new StringBuffer();
  531           for (Iterator it = list.iterator(); it.hasNext();) {
  532               buf.append(it.next());
  533           }
  534           return new SimpleScalar(buf.toString());
  535       }
  536   
  537       public TemplateModelIterator iterator()
  538       {
  539           return new TemplateModelIterator()
  540           {
  541               private final Iterator it = nodes.iterator();
  542   
  543               public TemplateModel next()
  544               {
  545                   return it.hasNext() ? new NodeListModel(it.next(), namespaces) : null;
  546               }
  547   
  548               public boolean hasNext() 
  549               {
  550                   return it.hasNext();
  551               }
  552           };
  553       }
  554   
  555       /**
  556        * Retrieves the i-th element of the node list.
  557        */
  558       public TemplateModel get(int i)
  559       throws
  560       TemplateModelException
  561       {
  562           try {
  563               return new NodeListModel(nodes.get(i), namespaces);
  564           } catch (IndexOutOfBoundsException e) {
  565               throw new TemplateModelException("Index out of bounds: " + e.getMessage());
  566           }
  567       }
  568       
  569       public int size()
  570       {
  571           return nodes.size();
  572       }
  573   
  574       /**
  575        * Applies an XPath expression to the node list and returns the resulting node list.
  576        * In order for this method to work, your application must have access
  577        * <a href="http://www.jaxen.org">Jaxen</a> library classes. The
  578        * implementation does cache the parsed format of XPath expressions in a weak hash
  579        * map, keyed by the string representation of the XPath expression. As the string
  580        * object passed as the argument is usually kept in the parsed FreeMarker template,
  581        * this ensures that each XPath expression is parsed only once during the lifetime
  582        * of the FreeMarker template that contains it.
  583        * @param arguments the list of arguments. Must contain exactly one string that is
  584        * the XPath expression you wish to apply. The XPath expression can use any namespace
  585        * prefixes that were defined using the {@link #registerNamespace(String, String)}
  586        * method or the <code>nodelist._registerNamespace(prefix, uri)</code> expression in the
  587        * template.
  588        * @return a NodeListModel representing the nodes that are the result of application
  589        * of the XPath to the current node list.
  590        */
  591       public Object exec(List arguments)
  592       throws
  593       TemplateModelException
  594       {
  595           if (arguments == null || arguments.size() != 1)
  596               throw new TemplateModelException("Exactly one argument required for execute() on NodeTemplate");
  597   
  598           String xpathString = (String)arguments.get(0);
  599           JDOMXPathEx xpath = null;
  600           try
  601           {
  602               synchronized(XPATH_CACHE)
  603               {
  604                   xpath = (JDOMXPathEx)XPATH_CACHE.get(xpathString);
  605                   if (xpath == null)
  606                   {
  607                       xpath = new JDOMXPathEx(xpathString);
  608                       XPATH_CACHE.put(xpathString, xpath);
  609                   }
  610               }
  611               return createNodeListModel(xpath.selectNodes(nodes, namespaces), namespaces);
  612           }
  613           catch(Exception e)
  614           {
  615               throw new TemplateModelException("Could not evaulate XPath expression " + xpathString, e);
  616           }
  617       }
  618   
  619       /**
  620        * Registers an XML namespace with this node list. Once registered, you can
  621        * refer to the registered namespace using its prefix in the
  622        * {@link #get(String)} method from this node list and all other
  623        * node lists that are derived from this node list. Use the
  624        * <tt>nodelist["prefix:localname"]</tt> or the
  625        * <tt>nodelist["@prefix:localname"]</tt> syntax to reach elements and
  626        *  attributes whose names are namespace-scoped. Note that the namespace
  627        * prefix need not match the actual prefix used by the XML document itself
  628        * since namespaces are compared solely by their URI. You can also register
  629        * namespaces during template evaluation using the
  630        * <tt>nodelist._registerNamespace(prefix, uri)</tt> syntax in the template.
  631        * This mechanism is completely independent from the namespace declarations
  632        * in the XML document itself; its purpose is to give you an easy way
  633        * to refer to namespace-scoped elements in {@link #get(String)} and
  634        * in XPath expressions passed to {@link #exec(List)}. Note also that
  635        * the namespace prefix registry is shared among all node lists that
  636        * are created from a single node list - modifying the registry in one
  637        * affects all others as well. If you want to obtain a namespace 
  638        * "detached" copy of the node list, use the <code>_copy</code> key on
  639        * it (or call <code>nodeList.get("_copy")</code> directly from your
  640        * Java code. The returned node list has all the namespaces that the
  641        * original node list has, but they can be manipulated independently
  642        * thereon.
  643        */
  644       public void registerNamespace(String prefix, String uri)
  645       {
  646           synchronized(namespaces)
  647           {
  648               namespaces.put(prefix, Namespace.getNamespace(prefix, uri));
  649           }
  650       }
  651   
  652       private interface NodeOperator {
  653           List operate(Object node)
  654           throws
  655           TemplateModelException;
  656       }
  657   
  658       private interface NamedNodeOperator {
  659           List operate(Object node, String localName, Namespace namespace)
  660           throws
  661           TemplateModelException;
  662       }
  663   
  664       private static final class AllChildrenOp implements NodeOperator {
  665           public List operate(Object node)
  666           {
  667               if (node instanceof Element)
  668                   return((Element)node).getChildren();
  669               else if (node instanceof Document) {
  670                   Element root = ((Document)node).getRootElement();
  671                   return root == null ? Collections.EMPTY_LIST : Collections12.singletonList(root);
  672               } 
  673    // With 2.1 semantics it  makes more sense to just return a null and let the core 
  674    // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  675               return null;
  676   /*            
  677               else
  678                   throw new TemplateModelException("_allChildren can not be applied on " + node.getClass());
  679   */                
  680           }
  681       }
  682   
  683       private static final class NamedChildrenOp implements NamedNodeOperator {
  684           public List operate(Object node, String localName, Namespace namespace)
  685           {
  686               if (node instanceof Element) {
  687                   return((Element)node).getChildren(localName, namespace);
  688               } else if (node instanceof Document) {
  689                   Element root = ((Document)node).getRootElement();
  690                   if (root != null &&
  691                       root.getName().equals(localName) &&
  692                       root.getNamespaceURI().equals(namespace.getURI())) {
  693                       return Collections12.singletonList(root);
  694                   } else
  695                       return Collections.EMPTY_LIST;
  696               } 
  697    // With 2.1 semantics it  makes more sense to just return a null and let the core 
  698    // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  699               return null;
  700    /*           
  701               else
  702                   throw new TemplateModelException("_namedChildren can not be applied on " + node.getClass());
  703   */                
  704           }
  705       }
  706   
  707       private static final class AllAttributesOp implements NodeOperator {
  708           public List operate(Object node)
  709           {
  710    // With 2.1 semantics it  makes more sense to just return a null and let the core 
  711    // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  712               if (!(node instanceof Element)) {
  713                   return null;
  714               }
  715               return ((Element)node).getAttributes();
  716   /*
  717               else
  718                   throw new TemplateModelException("_allAttributes can not be applied on " + node.getClass());
  719   */                
  720           }
  721       }
  722   
  723       private static final class NamedAttributeOp implements NamedNodeOperator {
  724           public List operate(Object node, String localName, Namespace namespace)
  725           {
  726               Attribute attr = null;
  727               if (node instanceof Element) {
  728                   Element element = (Element)node;
  729                   attr = element.getAttribute(localName, namespace);
  730               } else if (node instanceof ProcessingInstruction) {
  731                   ProcessingInstruction pi = (ProcessingInstruction)node;
  732                   if ("target".equals(localName))
  733                       attr = new Attribute("target", pi.getTarget());
  734                   else if ("data".equals(localName))
  735                       attr = new Attribute("data", pi.getData());
  736                   else
  737                       attr = new Attribute(localName, pi.getValue(localName));
  738               } else if (node instanceof DocType) {
  739                   DocType doctype = (DocType)node;
  740                   if ("publicId".equals(localName))
  741                       attr = new Attribute("publicId", doctype.getPublicID());
  742                   else if ("systemId".equals(localName))
  743                       attr = new Attribute("systemId", doctype.getSystemID());
  744                   else if ("elementName".equals(localName))
  745                       attr = new Attribute("elementName", doctype.getElementName());
  746               } 
  747               // With 2.1 semantics it  makes more sense to just return a null and let the core 
  748               // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  749               else {
  750                   return null;
  751               }
  752   /*            
  753               else
  754                   throw new TemplateModelException("_allAttributes can not be applied on " + node.getClass());
  755   */
  756               return attr == null ? Collections.EMPTY_LIST : Collections12.singletonList(attr);
  757           }
  758       }
  759   
  760       private static final class NameOp implements NodeOperator {
  761           public List operate(Object node)
  762           {
  763               if (node instanceof Element)
  764                   return Collections12.singletonList(((Element)node).getName());
  765               else if (node instanceof Attribute)
  766                   return Collections12.singletonList(((Attribute)node).getName());
  767               else if (node instanceof EntityRef)
  768                   return Collections12.singletonList(((EntityRef)node).getName());
  769               else if (node instanceof ProcessingInstruction)
  770                   return Collections12.singletonList(((ProcessingInstruction)node).getTarget());
  771               else if (node instanceof DocType)
  772                   return Collections12.singletonList(((DocType)node).getPublicID());
  773               else
  774                   return null;
  775               // With 2.1 semantics it  makes more sense to just return a null and let the core 
  776               // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  777   //                throw new TemplateModelException("_name can not be applied on " + node.getClass());
  778           }
  779       }
  780   
  781       private static final class QNameOp implements NodeOperator {
  782           public List operate(Object node)
  783           {
  784               if (node instanceof Element)
  785                   return Collections12.singletonList(((Element)node).getQualifiedName());
  786               else if (node instanceof Attribute)
  787                   return Collections12.singletonList(((Attribute)node).getQualifiedName());
  788               // With 2.1 semantics it  makes more sense to just return a null and let the core 
  789               // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  790               return null;
  791   //            throw new TemplateModelException("_qname can not be applied on " + node.getClass());
  792           }
  793       }
  794   
  795       private static final class NamespaceUriOp implements NodeOperator {
  796           public List operate(Object node)
  797           {
  798               if (node instanceof Element)
  799                   return Collections12.singletonList(((Element)node).getNamespace().getURI());
  800               else if (node instanceof Attribute)
  801                   return Collections12.singletonList(((Attribute)node).getNamespace().getURI());
  802               // With 2.1 semantics it  makes more sense to just return a null and let the core 
  803               // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  804               return null;
  805   //            throw new TemplateModelException("_nsuri can not be applied on " + node.getClass());
  806           }
  807       }
  808   
  809       private static final class NamespacePrefixOp implements NodeOperator {
  810           public List operate(Object node)
  811           {
  812               if (node instanceof Element)
  813                   return Collections12.singletonList(((Element)node).getNamespace().getPrefix());
  814               else if (node instanceof Attribute)
  815                   return Collections12.singletonList(((Attribute)node).getNamespace().getPrefix());
  816               // With 2.1 semantics it  makes more sense to just return a null and let the core 
  817               // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  818               return null;
  819   //            throw new TemplateModelException("_nsprefix can not be applied on " + node.getClass());
  820           }
  821       }
  822   
  823       private static final class CanonicalNameOp implements NodeOperator {
  824           public List operate(Object node)
  825           {
  826               if (node instanceof Element)
  827               {
  828                   Element element = (Element)node;
  829                   return Collections12.singletonList(element.getNamespace().getURI() + element.getName());
  830               }
  831               else if (node instanceof Attribute)
  832               {
  833                   Attribute attribute = (Attribute)node;
  834                   return Collections12.singletonList(attribute.getNamespace().getURI() + attribute.getName());
  835               }
  836               // With 2.1 semantics it  makes more sense to just return a null and let the core 
  837               // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  838               return null;
  839   //            throw new TemplateModelException("_cname can not be applied on " + node.getClass());
  840           }
  841       }
  842   
  843   
  844       private static final Element getParent(Object node)
  845       {
  846           if (node instanceof Element)
  847               return((Element)node).getParent();
  848           else if (node instanceof Attribute)
  849               return((Attribute)node).getParent();
  850           else if (node instanceof Text)
  851               return((Text)node).getParent();
  852           else if (node instanceof ProcessingInstruction)
  853               return((ProcessingInstruction)node).getParent();
  854           else if (node instanceof Comment)
  855               return((Comment)node).getParent();
  856           else if (node instanceof EntityRef)
  857               return((EntityRef)node).getParent();
  858           else
  859               // With 2.1 semantics it  makes more sense to just return a null and let the core 
  860               // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  861               return null;
  862   //            throw new TemplateModelException("_parent can not be applied on " + node.getClass());
  863       }
  864   
  865       private static final class ParentOp implements NodeOperator {
  866           public List operate(Object node)
  867           {
  868               Element parent = getParent(node);
  869               return parent == null ? Collections.EMPTY_LIST : Collections12.singletonList(parent);
  870           }
  871       }
  872   
  873       private static final class AncestorOp implements NodeOperator {
  874           public List operate(Object node)
  875           {
  876               Element parent = getParent(node);
  877               if (parent == null) return Collections.EMPTY_LIST;
  878               LinkedList list = new LinkedList();
  879               do {
  880                   list.addFirst(parent);
  881                   parent = parent.getParent();
  882               }
  883               while (parent != null);
  884               return list;
  885           }
  886       }
  887   
  888       private static final class AncestorOrSelfOp implements NodeOperator {
  889           public List operate(Object node)
  890           {
  891               Element parent = getParent(node);
  892               if (parent == null) return Collections12.singletonList(node);
  893               LinkedList list = new LinkedList();
  894               list.addFirst(node);
  895               do {
  896                   list.addFirst(parent);
  897                   parent = parent.getParent();
  898               }
  899               while (parent != null);
  900               return list;
  901           }
  902       }
  903   
  904       private static class DescendantOp implements NodeOperator {
  905           public List operate(Object node)
  906           {
  907               LinkedList list = new LinkedList();
  908               if (node instanceof Element) {
  909                   addChildren((Element)node, list);
  910               }
  911               else if (node instanceof Document) {
  912                   Element root = ((Document)node).getRootElement();
  913                   list.add(root);
  914                   addChildren(root, list);
  915               }
  916               else
  917                   // With 2.1 semantics it  makes more sense to just return a null and let the core 
  918                   // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  919                   return null;
  920   //                throw new TemplateModelException("_descendant can not be applied on " + node.getClass());
  921   
  922               return list;
  923           }
  924   
  925           private void addChildren(Element element, List list)
  926           {
  927               List children = element.getChildren();
  928               Iterator it = children.iterator();
  929               while (it.hasNext()) {
  930                   Element child = (Element)it.next();
  931                   list.add(child);
  932                   addChildren(child, list);
  933               }
  934           }
  935       }
  936   
  937       private static final class DescendantOrSelfOp extends DescendantOp {
  938           public List operate(Object node)
  939           {
  940               LinkedList list = (LinkedList)super.operate(node);
  941               list.addFirst(node);
  942               return list;
  943           }
  944       }
  945   
  946       private static final class DocumentOp implements NodeOperator {
  947           public List operate(Object node)
  948           {
  949               Document doc = null;
  950               if (node instanceof Element)
  951                   doc = ((Element)node).getDocument();
  952               else if (node instanceof Attribute) {
  953                   Element parent = ((Attribute)node).getParent();
  954                   doc = parent == null ? null : parent.getDocument();
  955               } else if (node instanceof Text) {
  956                   Element parent = ((Text)node).getParent();
  957                   doc = parent == null ? null : parent.getDocument();
  958               } else if (node instanceof Document)
  959                   doc = (Document)node;
  960               else if (node instanceof ProcessingInstruction)
  961                   doc = ((ProcessingInstruction)node).getDocument();
  962               else if (node instanceof EntityRef)
  963                   doc = ((EntityRef)node).getDocument();
  964               else if (node instanceof Comment)
  965                   doc = ((Comment)node).getDocument();
  966               else
  967                   // With 2.1 semantics it  makes more sense to just return a null and let the core 
  968                   // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  969                   return null;
  970   //                throw new TemplateModelException("_document can not be applied on " + node.getClass());
  971   
  972               return doc == null ? Collections.EMPTY_LIST : Collections12.singletonList(doc);
  973           }
  974       }
  975   
  976       private static final class DocTypeOp implements NodeOperator {
  977           public List operate(Object node)
  978           {
  979               if (node instanceof Document) {
  980                   DocType doctype = ((Document)node).getDocType();
  981                   return doctype == null ? Collections.EMPTY_LIST : Collections12.singletonList(doctype);
  982               } else
  983                   // With 2.1 semantics it  makes more sense to just return a null and let the core 
  984                   // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  985                   return null;
  986   //                throw new TemplateModelException("_doctype can not be applied on " + node.getClass());
  987           }
  988       }
  989   
  990       private static final class ContentOp implements NodeOperator {
  991           public List operate(Object node)
  992           {
  993               if (node instanceof Element)
  994                   return((Element)node).getContent();
  995               else if (node instanceof Document)
  996                   return((Document)node).getContent();
  997               // With 2.1 semantics it  makes more sense to just return a null and let the core 
  998               // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
  999               return null;
 1000   //            throw new TemplateModelException("_content can not be applied on " + node.getClass());
 1001           }
 1002       }
 1003   
 1004       private static final class TextOp implements NodeOperator {
 1005           public List operate(Object node)
 1006           {
 1007               if (node instanceof Element)
 1008                   return Collections12.singletonList(((Element)node).getTextTrim());
 1009               if (node instanceof Attribute)
 1010                   return Collections12.singletonList(((Attribute)node).getValue());
 1011               if (node instanceof CDATA)
 1012                   return Collections12.singletonList(((CDATA)node).getText());
 1013               if (node instanceof Comment)
 1014                   return Collections12.singletonList(((Comment)node).getText());
 1015               if (node instanceof ProcessingInstruction)
 1016                   return Collections12.singletonList(((ProcessingInstruction)node).getData());
 1017               // With 2.1 semantics it  makes more sense to just return a null and let the core 
 1018               // throw an InvalidReferenceException and the template writer can use ?exists etcetera. (JR)
 1019               return null;
 1020   //            throw new TemplateModelException("_text can not be applied on " + node.getClass());
 1021           }
 1022       }
 1023   
 1024       private static final List evaluateElementOperation(NodeOperator op, List nodes)
 1025       throws
 1026       TemplateModelException
 1027       {
 1028           int s = nodes.size();
 1029           List[] lists = new List[s];
 1030           int l = 0;
 1031           {
 1032               int i = 0;
 1033               Iterator it = nodes.iterator();
 1034               while (it.hasNext()) {
 1035                   List list = op.operate(it.next());
 1036                   if (list != null) {
 1037                       lists[i++] = list;
 1038                       l += list.size();
 1039                   }
 1040               }
 1041           }
 1042           List retval = new ArrayList(l);
 1043           for (int i = 0; i < s; ++i) {
 1044               if (lists[i] != null) {
 1045                   retval.addAll(lists[i]);
 1046               }
 1047           }
 1048           return retval;
 1049       }
 1050   
 1051       private static final List evaluateNamedElementOperation(NamedNodeOperator op, String localName, Namespace namespace, List nodes)
 1052       throws
 1053       TemplateModelException
 1054       {
 1055           int s = nodes.size();
 1056           List[] lists = new List[s];
 1057           int l = 0;
 1058           {
 1059               int i = 0;
 1060               Iterator it = nodes.iterator();
 1061               while (it.hasNext()) {
 1062                   List list = op.operate(it.next(), localName, namespace);
 1063                   lists[i++] = list;
 1064                   l += list.size();
 1065               }
 1066           }
 1067           List retval = new ArrayList(l);
 1068           for (int i = 0; i < s; ++i)
 1069               retval.addAll(lists[i]);
 1070           return retval;
 1071       }
 1072   
 1073       private static final List removeDuplicates(List list)
 1074       {
 1075           int s = list.size();
 1076           ArrayList ulist = new ArrayList(s);
 1077           Set set = new HashSet(s * 4 / 3, .75f);
 1078           Iterator it = list.iterator();
 1079           while (it.hasNext()) {
 1080               Object o = it.next();
 1081               if (set.add(o))
 1082                   ulist.add(o);
 1083           }
 1084           ulist.trimToSize();
 1085           return ulist;
 1086       }
 1087   
 1088       private static final Map createOperations()
 1089       {
 1090           Map map = new HashMap();
 1091   
 1092           map.put("_ancestor", new AncestorOp());
 1093           map.put("_ancestorOrSelf", new AncestorOrSelfOp());
 1094           map.put("_attributes", ALL_ATTRIBUTES_OP);
 1095           map.put("_children", ALL_CHILDREN_OP);
 1096           map.put("_cname", new CanonicalNameOp());
 1097           map.put("_content", new ContentOp());
 1098           map.put("_descendant", new DescendantOp());
 1099           map.put("_descendantOrSelf", new DescendantOrSelfOp());
 1100           map.put("_document", new DocumentOp());
 1101           map.put("_doctype", new DocTypeOp());
 1102           map.put("_name", new NameOp());
 1103           map.put("_nsprefix", new NamespacePrefixOp());
 1104           map.put("_nsuri", new NamespaceUriOp());
 1105           map.put("_parent", new ParentOp());
 1106           map.put("_qname", new QNameOp());
 1107           map.put("_text", new TextOp());
 1108   
 1109           return map;
 1110       }
 1111   
 1112       private static final Map createSpecialOperations()
 1113       {
 1114           Map map = new HashMap();
 1115   
 1116           Integer copy = new Integer(SPECIAL_OPERATION_COPY);
 1117           Integer unique = new Integer(SPECIAL_OPERATION_UNIQUE);
 1118           Integer fname = new Integer(SPECIAL_OPERATION_FILTER_NAME);
 1119           Integer ftype = new Integer(SPECIAL_OPERATION_FILTER_TYPE);
 1120           Integer type = new Integer(SPECIAL_OPERATION_QUERY_TYPE);
 1121           Integer regns = new Integer(SPECIAL_OPERATION_REGISTER_NAMESPACE);
 1122           Integer plaintext = new Integer(SPECIAL_OPERATION_PLAINTEXT);
 1123   
 1124           map.put("_copy", copy);
 1125           map.put("_unique", unique);
 1126           map.put("_fname", fname);
 1127           map.put("_ftype", ftype);
 1128           map.put("_type", type);
 1129           map.put("_registerNamespace", regns);
 1130           map.put("_plaintext", plaintext);
 1131   
 1132           // These are in for backward compatibility
 1133           map.put("x_copy", copy);
 1134           map.put("x_unique", unique);
 1135           map.put("x_fname", fname);
 1136           map.put("x_ftype", ftype);
 1137           map.put("x_type", type);
 1138   
 1139           return map;
 1140       }
 1141   
 1142       private final class RegisterNamespace implements TemplateMethodModel {
 1143           public boolean isEmpty()
 1144           {
 1145               return false;
 1146           }
 1147   
 1148           public Object exec(List arguments)
 1149           throws
 1150           TemplateModelException
 1151           {
 1152               if (arguments.size() != 2)
 1153                   throw new TemplateModelException("_registerNamespace(prefix, uri) requires two arguments");
 1154   
 1155               registerNamespace((String)arguments.get(0), (String)arguments.get(1));
 1156   
 1157               return TemplateScalarModel.EMPTY_STRING;
 1158           }
 1159       }
 1160   
 1161       private final class NameFilter implements TemplateMethodModel {
 1162           public boolean isEmpty()
 1163           {
 1164               return false;
 1165           }
 1166   
 1167           public Object exec(List arguments)
 1168           {
 1169               Set names = new HashSet(arguments);
 1170               List list = new LinkedList(nodes);
 1171               Iterator it = list.iterator();
 1172               while (it.hasNext()) {
 1173                   Object node = it.next();
 1174                   String name = null;
 1175                   if (node instanceof Element)
 1176                       name = ((Element)node).getName();
 1177                   else if (node instanceof Attribute)
 1178                       name = ((Attribute)node).getName();
 1179                   else if (node instanceof ProcessingInstruction)
 1180                       name = ((ProcessingInstruction)node).getTarget();
 1181                   else if (node instanceof EntityRef)
 1182                       name = ((EntityRef)node).getName();
 1183                   else if (node instanceof DocType)
 1184                       name = ((DocType)node).getPublicID();
 1185   
 1186                   if (name == null || !names.contains(name))
 1187                       it.remove();
 1188               }
 1189               return createNodeListModel(list, namespaces);
 1190           }
 1191       }
 1192   
 1193       private final class TypeFilter implements TemplateMethodModel {
 1194           public boolean isEmpty()
 1195           {
 1196               return false;
 1197           }
 1198   
 1199           public Object exec(List arguments)
 1200           throws
 1201           TemplateModelException
 1202           {
 1203               if (arguments == null || arguments.size() == 0)
 1204                   throw new TemplateModelException("_type expects exactly one argument");
 1205               String arg = (String)arguments.get(0);
 1206               boolean invert = arg.indexOf('!') != -1;
 1207               // NOTE: true in each of these variables means 'remove', not 'keep'
 1208               // This is so we don't invert their values in the loop. So,
 1209               // a is true <--> (a is not present in the string) xor invert.
 1210               boolean a = invert != (arg.indexOf('a') == -1);
 1211               boolean c = invert != (arg.indexOf('c') == -1);
 1212               boolean d = invert != (arg.indexOf('d') == -1);
 1213               boolean e = invert != (arg.indexOf('e') == -1);
 1214               boolean n = invert != (arg.indexOf('n') == -1);
 1215               boolean p = invert != (arg.indexOf('p') == -1);
 1216               boolean t = invert != (arg.indexOf('t') == -1);
 1217               boolean x = invert != (arg.indexOf('x') == -1);
 1218   
 1219               LinkedList list = new LinkedList(nodes);
 1220               Iterator it = list.iterator();
 1221               while (it.hasNext()) {
 1222                   Object node = it.next();
 1223                   if ((node instanceof Element && e)
 1224                       || (node instanceof Attribute && a)
 1225                       || (node instanceof String && x)
 1226                       || (node instanceof Text && x)
 1227                       || (node instanceof ProcessingInstruction && p)
 1228                       || (node instanceof Comment && c)
 1229                       || (node instanceof EntityRef && n)
 1230                       || (node instanceof Document && d)
 1231                       || (node instanceof DocType && t))
 1232                       it.remove();
 1233               }
 1234               return createNodeListModel(list, namespaces);
 1235           }
 1236       }
 1237   
 1238       /**
 1239        * Loads a template from a file passed as the first argument, loads an XML
 1240        * document from the standard input, passes it to the template as variable
 1241        * <tt>document</tt> and writes the result of template processing to
 1242        * standard output.
 1243        */
 1244       public static void main(String[] args)
 1245       throws
 1246       Exception
 1247       {
 1248           org.jdom.input.SAXBuilder builder = new org.jdom.input.SAXBuilder();
 1249           Document document = builder.build(System.in);
 1250           SimpleHash model = new SimpleHash();
 1251           model.put("document", new NodeListModel(document));
 1252           FileReader fr = new FileReader(args[0]);
 1253           Template template = new Template(args[0], fr);
 1254           Writer w = new java.io.OutputStreamWriter(System.out);
 1255           template.process(model, w);
 1256           w.flush();
 1257           w.close();
 1258       }
 1259   
 1260       private static final class AttributeXMLOutputter extends XMLOutputter {
 1261           public void output(Attribute attribute, Writer out)
 1262           throws
 1263           IOException
 1264           {
 1265               out.write(" ");
 1266               out.write(attribute.getQualifiedName());
 1267               out.write("=");
 1268   
 1269               out.write("\"");
 1270               out.write(escapeAttributeEntities(attribute.getValue()));
 1271               out.write("\"");
 1272           }
 1273       }
 1274   
 1275       private static final class JDOMXPathEx
 1276       extends
 1277           JDOMXPath
 1278       {
 1279           JDOMXPathEx(String path)
 1280           throws 
 1281               JaxenException
 1282           {
 1283               super(path);
 1284           }
 1285   
 1286           public List selectNodes(Object object, Map namespaces)
 1287           throws
 1288               JaxenException
 1289           {
 1290               Context context = getContext(object);
 1291               context.getContextSupport().setNamespaceContext(new NamespaceContextImpl(namespaces));
 1292               return selectNodesForContext(context);
 1293           } 
 1294   
 1295           private static final class NamespaceContextImpl
 1296           implements
 1297               NamespaceContext
 1298           {
 1299               private final Map namespaces;
 1300               
 1301               NamespaceContextImpl(Map namespaces)
 1302               {
 1303                   this.namespaces = namespaces;
 1304               }
 1305               
 1306               public String translateNamespacePrefixToUri(String prefix)
 1307               {
 1308                   // Empty prefix always maps to empty URL in XPath
 1309                   if(prefix.length() == 0)
 1310                   {
 1311                       return prefix;
 1312                   }
 1313                   synchronized(namespaces)
 1314                   {
 1315                       Namespace ns = (Namespace)namespaces.get(prefix);
 1316                       return ns == null ? null : ns.getURI();
 1317                   }   
 1318               }
 1319           }
 1320       }
 1321   }

Save This Page
Home » freemarker-2.3.13 » freemarker.ext.jdom » [javadoc | source]