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 ('<' and '&') 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 }