1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 package org.jaxen;
50
51 import java.io.Serializable;
52 import java.util.List;
53
54 import org.jaxen.expr.Expr;
55 import org.jaxen.expr.XPathExpr;
56 import org.jaxen.function.BooleanFunction;
57 import org.jaxen.function.NumberFunction;
58 import org.jaxen.function.StringFunction;
59 import org.jaxen.saxpath.SAXPathException;
60 import org.jaxen.saxpath.XPathReader;
61 import org.jaxen.saxpath.helpers.XPathReaderFactory;
62 import org.jaxen.util.SingletonList;
63
64 /** Base functionality for all concrete, implementation-specific XPaths.
65 *
66 * <p>
67 * This class provides generic functionality for further-defined
68 * implementation-specific XPaths.
69 * </p>
70 *
71 * <p>
72 * If you want to adapt the Jaxen engine so that it can traverse your own
73 * object model, then this is a good base class to derive from.
74 * Typically you only really need to provide your own
75 * {@link org.jaxen.Navigator} implementation.
76 * </p>
77 *
78 * @see org.jaxen.dom4j.Dom4jXPath XPath for dom4j
79 * @see org.jaxen.jdom.JDOMXPath XPath for JDOM
80 * @see org.jaxen.dom.DOMXPath XPath for W3C DOM
81 *
82 * @author <a href="mailto:bob@werken.com">bob mcwhirter</a>
83 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
84 */
85 public class BaseXPath implements XPath, Serializable
86 {
87
88 private static final long serialVersionUID = -1993731281300293168L;
89
90 /** Original expression text. */
91 private final String exprText;
92
93 /** the parsed form of the XPath expression */
94 private final XPathExpr xpath;
95
96 /** the support information and function, namespace and variable contexts */
97 private ContextSupport support;
98
99 /** the implementation-specific Navigator for retrieving XML nodes **/
100 private Navigator navigator;
101
102 /** Construct given an XPath expression string.
103 *
104 * @param xpathExpr the XPath expression
105 *
106 * @throws JaxenException if there is a syntax error while
107 * parsing the expression
108 */
109 protected BaseXPath(String xpathExpr) throws JaxenException
110 {
111 try
112 {
113 XPathReader reader = XPathReaderFactory.createReader();
114 JaxenHandler handler = new JaxenHandler();
115 reader.setXPathHandler( handler );
116 reader.parse( xpathExpr );
117 this.xpath = handler.getXPathExpr();
118 }
119 catch (org.jaxen.saxpath.XPathSyntaxException e)
120 {
121 throw new org.jaxen.XPathSyntaxException( e );
122 }
123 catch (SAXPathException e)
124 {
125 throw new JaxenException( e );
126 }
127
128 this.exprText = xpathExpr;
129 }
130
131 /** Construct given an XPath expression string.
132 *
133 * @param xpathExpr the XPath expression
134 *
135 * @param navigator the XML navigator to use
136 *
137 * @throws JaxenException if there is a syntax error while
138 * parsing the expression
139 */
140 public BaseXPath(String xpathExpr, Navigator navigator) throws JaxenException
141 {
142 this( xpathExpr );
143 this.navigator = navigator;
144 }
145
146 /** Evaluate this XPath against a given context.
147 * The context of evaluation may be any object type
148 * the navigator recognizes as a node.
149 * The return value is either a <code>String</code>,
150 * <code>Double</code>, <code>Boolean</code>, or <code>List</code>
151 * of nodes.
152 *
153 * <p>
154 * When using this method, one must be careful to
155 * test the class of the returned object. If the returned
156 * object is a list, then the items in this
157 * list will be the actual <code>Document</code>,
158 * <code>Element</code>, <code>Attribute</code>, etc. objects
159 * as defined by the concrete XML object-model implementation,
160 * directly from the context document. This method <strong>does
161 * not return <em>copies</em> of anything</strong>, but merely
162 * returns references to objects within the source document.
163 * </p>
164 *
165 * @param context the node, node-set or Context object for evaluation.
166 * This value can be null.
167 *
168 * @return the result of evaluating the XPath expression
169 * against the supplied context
170 * @throws JaxenException if an XPath error occurs during expression evaluation
171 * @throws ClassCastException if the context is not a node
172 */
173 public Object evaluate(Object context) throws JaxenException
174 {
175 List answer = selectNodes(context);
176
177 if ( answer != null
178 &&
179 answer.size() == 1 )
180 {
181 Object first = answer.get(0);
182
183 if ( first instanceof String
184 ||
185 first instanceof Number
186 ||
187 first instanceof Boolean )
188 {
189 return first;
190 }
191 }
192 return answer;
193 }
194
195 /** Select all nodes that are selected by this XPath
196 * expression. If multiple nodes match, multiple nodes
197 * will be returned. Nodes will be returned
198 * in document-order, as defined by the XPath
199 * specification. If the expression selects a non-node-set
200 * (i.e. a number, boolean, or string) then a List
201 * containing just that one object is returned.
202 * </p>
203 *
204 * @param node the node, node-set or Context object for evaluation.
205 * This value can be null.
206 *
207 * @return the node-set of all items selected
208 * by this XPath expression
209 * @throws JaxenException if an XPath error occurs during expression evaluation
210 *
211 * @see #selectNodesForContext
212 */
213 public List selectNodes(Object node) throws JaxenException
214 {
215 Context context = getContext( node );
216 return selectNodesForContext( context );
217 }
218
219 /** Select only the first node selected by this XPath
220 * expression. If multiple nodes match, only one node will be
221 * returned. The selected node will be the first
222 * selected node in document-order, as defined by the XPath
223 * specification.
224 * </p>
225 *
226 * @param node the node, node-set or Context object for evaluation.
227 * This value can be null.
228 *
229 * @return the node-set of all items selected
230 * by this XPath expression
231 * @throws JaxenException if an XPath error occurs during expression evaluation
232 *
233 * @see #selectNodes
234 */
235 public Object selectSingleNode(Object node) throws JaxenException
236 {
237 List results = selectNodes( node );
238
239 if ( results.isEmpty() )
240 {
241 return null;
242 }
243
244 return results.get( 0 );
245 }
246
247 /**
248 * Returns the XPath string-value of the argument node.
249 *
250 * @param node the node whose value to take
251 * @return the XPath string value of this node
252 * @throws JaxenException if an XPath error occurs during expression evaluation
253 * @deprecated replaced by {@link #stringValueOf}
254 */
255 public String valueOf(Object node) throws JaxenException
256 {
257 return stringValueOf( node );
258 }
259
260 /** Retrieves the string-value of the result of
261 * evaluating this XPath expression when evaluated
262 * against the specified context.
263 *
264 * <p>
265 * The string-value of the expression is determined per
266 * the <code>string(..)</code> core function defined
267 * in the XPath specification. This means that an expression
268 * that selects zero nodes will return the empty string,
269 * while an expression that selects one-or-more nodes will
270 * return the string-value of the first node.
271 * </p>
272 *
273 * @param node the node, node-set or Context object for evaluation. This value can be null.
274 *
275 * @return the string-value of the result of evaluating this expression with the specified context node
276 * @throws JaxenException if an XPath error occurs during expression evaluation
277 */
278 public String stringValueOf(Object node) throws JaxenException
279 {
280 Context context = getContext( node );
281
282 Object result = selectSingleNodeForContext( context );
283
284 if ( result == null )
285 {
286 return "";
287 }
288
289 return StringFunction.evaluate( result,
290 context.getNavigator() );
291 }
292
293 /** Retrieve a boolean-value interpretation of this XPath
294 * expression when evaluated against a given context.
295 *
296 * <p>
297 * The boolean-value of the expression is determined per
298 * the <code>boolean(..)</code> function defined
299 * in the XPath specification. This means that an expression
300 * that selects zero nodes will return <code>false</code>,
301 * while an expression that selects one or more nodes will
302 * return <code>true</code>.
303 * </p>
304 *
305 * @param node the node, node-set or Context object for evaluation. This value can be null.
306 *
307 * @return the boolean-value of the result of evaluating this expression with the specified context node
308 * @throws JaxenException if an XPath error occurs during expression evaluation
309 */
310 public boolean booleanValueOf(Object node) throws JaxenException
311 {
312 Context context = getContext( node );
313 List result = selectNodesForContext( context );
314 if ( result == null ) return false;
315 return BooleanFunction.evaluate( result, context.getNavigator() ).booleanValue();
316 }
317
318 /** Retrieve a number-value interpretation of this XPath
319 * expression when evaluated against a given context.
320 *
321 * <p>
322 * The number-value of the expression is determined per
323 * the <code>number(..)</code> core function as defined
324 * in the XPath specification. This means that if this
325 * expression selects multiple nodes, the number-value
326 * of the first node is returned.
327 * </p>
328 *
329 * @param node the node, node-set or Context object for evaluation. This value can be null.
330 *
331 * @return a <code>Double</code> indicating the numeric value of
332 * evaluating this expression against the specified context
333 * @throws JaxenException if an XPath error occurs during expression evaluation
334 */
335 public Number numberValueOf(Object node) throws JaxenException
336 {
337 Context context = getContext( node );
338 Object result = selectSingleNodeForContext( context );
339 return NumberFunction.evaluate( result,
340 context.getNavigator() );
341 }
342
343
344
345 /** Add a namespace prefix-to-URI mapping for this XPath
346 * expression.
347 *
348 * <p>
349 * Namespace prefix-to-URI mappings in an XPath are independent
350 * of those used within any document. Only the mapping explicitly
351 * added to this XPath will be available for resolving the
352 * XPath expression.
353 * </p>
354 *
355 * <p>
356 * This is a convenience method for adding mappings to the
357 * default {@link NamespaceContext} in place for this XPath.
358 * If you have installed a custom <code>NamespaceContext</code>
359 * that is not a <code>SimpleNamespaceContext</code>,
360 * then this method will throw a <code>JaxenException</code>.
361 * </p>
362 *
363 * @param prefix the namespace prefix
364 * @param uri the namespace URI
365 *
366 * @throws JaxenException if the <code>NamespaceContext</code>
367 * used by this XPath is not a <code>SimpleNamespaceContext</code>
368 */
369 public void addNamespace(String prefix,
370 String uri) throws JaxenException
371 {
372 NamespaceContext nsContext = getNamespaceContext();
373 if ( nsContext instanceof SimpleNamespaceContext )
374 {
375 ((SimpleNamespaceContext)nsContext).addNamespace( prefix,
376 uri );
377 return;
378 }
379
380 throw new JaxenException("Operation not permitted while using a non-simple namespace context.");
381 }
382
383
384
385
386
387
388
389
390
391 /** Set a <code>NamespaceContext</code> for use with this
392 * XPath expression.
393 *
394 * <p>
395 * A <code>NamespaceContext</code> is responsible for translating
396 * namespace prefixes within the expression into namespace URIs.
397 * </p>
398 *
399 * @param namespaceContext the <code>NamespaceContext</code> to
400 * install for this expression
401 *
402 * @see NamespaceContext
403 * @see NamespaceContext#translateNamespacePrefixToUri
404 */
405 public void setNamespaceContext(NamespaceContext namespaceContext)
406 {
407 getContextSupport().setNamespaceContext(namespaceContext);
408 }
409
410 /** Set a <code>FunctionContext</code> for use with this XPath
411 * expression.
412 *
413 * <p>
414 * A <code>FunctionContext</code> is responsible for resolving
415 * all function calls used within the expression.
416 * </p>
417 *
418 * @param functionContext the <code>FunctionContext</code> to
419 * install for this expression
420 *
421 * @see FunctionContext
422 * @see FunctionContext#getFunction
423 */
424 public void setFunctionContext(FunctionContext functionContext)
425 {
426 getContextSupport().setFunctionContext(functionContext);
427 }
428
429 /** Set a <code>VariableContext</code> for use with this XPath
430 * expression.
431 *
432 * <p>
433 * A <code>VariableContext</code> is responsible for resolving
434 * all variables referenced within the expression.
435 * </p>
436 *
437 * @param variableContext The <code>VariableContext</code> to
438 * install for this expression
439 *
440 * @see VariableContext
441 * @see VariableContext#getVariableValue
442 */
443 public void setVariableContext(VariableContext variableContext)
444 {
445 getContextSupport().setVariableContext(variableContext);
446 }
447
448 /** Retrieve the <code>NamespaceContext</code> used by this XPath
449 * expression.
450 *
451 * <p>
452 * A <code>NamespaceContext</code> is responsible for mapping
453 * prefixes used within the expression to namespace URIs.
454 * </p>
455 *
456 * <p>
457 * If this XPath expression has not previously had a <code>NamespaceContext</code>
458 * installed, a new default <code>NamespaceContext</code> will be created,
459 * installed and returned.
460 * </p>
461 *
462 * @return the <code>NamespaceContext</code> used by this expression
463 *
464 * @see NamespaceContext
465 */
466 public NamespaceContext getNamespaceContext()
467 {
468 return getContextSupport().getNamespaceContext();
469 }
470
471 /** Retrieve the <code>FunctionContext</code> used by this XPath
472 * expression.
473 *
474 * <p>
475 * A <code>FunctionContext</code> is responsible for resolving
476 * all function calls used within the expression.
477 * </p>
478 *
479 * <p>
480 * If this XPath expression has not previously had a <code>FunctionContext</code>
481 * installed, a new default <code>FunctionContext</code> will be created,
482 * installed and returned.
483 * </p>
484 *
485 * @return the <code>FunctionContext</code> used by this expression
486 *
487 * @see FunctionContext
488 */
489 public FunctionContext getFunctionContext()
490 {
491 return getContextSupport().getFunctionContext();
492 }
493
494 /** Retrieve the <code>VariableContext</code> used by this XPath
495 * expression.
496 *
497 * <p>
498 * A <code>VariableContext</code> is responsible for resolving
499 * all variables referenced within the expression.
500 * </p>
501 *
502 * <p>
503 * If this XPath expression has not previously had a <code>VariableContext</code>
504 * installed, a new default <code>VariableContext</code> will be created,
505 * installed and returned.
506 * </p>
507 *
508 * @return the <code>VariableContext</code> used by this expression
509 *
510 * @see VariableContext
511 */
512 public VariableContext getVariableContext()
513 {
514 return getContextSupport().getVariableContext();
515 }
516
517
518 /** Retrieve the root expression of the internal
519 * compiled form of this XPath expression.
520 *
521 * <p>
522 * Internally, Jaxen maintains a form of Abstract Syntax
523 * Tree (AST) to represent the structure of the XPath expression.
524 * This is normally not required during normal consumer-grade
525 * usage of Jaxen. This method is provided for hard-core users
526 * who wish to manipulate or inspect a tree-based version of
527 * the expression.
528 * </p>
529 *
530 * @return the root of the AST of this expression
531 */
532 public Expr getRootExpr()
533 {
534 return xpath.getRootExpr();
535 }
536
537 /** Return the original expression text.
538 *
539 * @return the normalized XPath expression string
540 */
541 public String toString()
542 {
543 return this.exprText;
544 }
545
546 /** Returns a string representation of the parse tree.
547 *
548 * @return a string representation of the parse tree.
549 */
550 public String debug()
551 {
552 return this.xpath.toString();
553 }
554
555
556
557
558
559
560
561
562 /** Create a {@link Context} wrapper for the provided
563 * implementation-specific object.
564 *
565 * @param node the implementation-specific object
566 * to be used as the context
567 *
568 * @return a <code>Context</code> wrapper around the object
569 */
570 protected Context getContext(Object node)
571 {
572 if ( node instanceof Context )
573 {
574 return (Context) node;
575 }
576
577 Context fullContext = new Context( getContextSupport() );
578
579 if ( node instanceof List )
580 {
581 fullContext.setNodeSet( (List) node );
582 }
583 else
584 {
585 List list = new SingletonList(node);
586 fullContext.setNodeSet( list );
587 }
588
589 return fullContext;
590 }
591
592 /** Retrieve the {@link ContextSupport} aggregation of
593 * <code>NamespaceContext</code>, <code>FunctionContext</code>,
594 * <code>VariableContext</code>, and {@link Navigator}.
595 *
596 * @return aggregate <code>ContextSupport</code> for this
597 * XPath expression
598 */
599 protected ContextSupport getContextSupport()
600 {
601 if ( support == null )
602 {
603 support = new ContextSupport(
604 createNamespaceContext(),
605 createFunctionContext(),
606 createVariableContext(),
607 getNavigator()
608 );
609 }
610
611 return support;
612 }
613
614 /** Retrieve the XML object-model-specific {@link Navigator}
615 * for us in evaluating this XPath expression.
616 *
617 * @return the implementation-specific <code>Navigator</code>
618 */
619 public Navigator getNavigator()
620 {
621 return navigator;
622 }
623
624
625
626
627
628
629
630
631
632 /** Create a default <code>FunctionContext</code>.
633 *
634 * @return a default <code>FunctionContext</code>
635 */
636 protected FunctionContext createFunctionContext()
637 {
638 return XPathFunctionContext.getInstance();
639 }
640
641 /** Create a default <code>NamespaceContext</code>.
642 *
643 * @return a default <code>NamespaceContext</code> instance
644 */
645 protected NamespaceContext createNamespaceContext()
646 {
647 return new SimpleNamespaceContext();
648 }
649
650 /** Create a default <code>VariableContext</code>.
651 *
652 * @return a default <code>VariableContext</code> instance
653 */
654 protected VariableContext createVariableContext()
655 {
656 return new SimpleVariableContext();
657 }
658
659 /** Select all nodes that match this XPath
660 * expression on the given Context object.
661 * If multiple nodes match, multiple nodes
662 * will be returned in document-order, as defined by the XPath
663 * specification. If the expression selects a non-node-set
664 * (i.e. a number, boolean, or string) then a List
665 * containing just that one object is returned.
666 * </p>
667 *
668 * @param context the Context which gets evaluated
669 *
670 * @return the node-set of all items selected
671 * by this XPath expression
672 * @throws JaxenException if an XPath error occurs during expression evaluation
673 *
674 */
675 protected List selectNodesForContext(Context context) throws JaxenException
676 {
677 List list = this.xpath.asList( context );
678 return list;
679
680 }
681
682
683 /** Return only the first node that is selected by this XPath
684 * expression. If multiple nodes match, only one node will be
685 * returned. The selected node will be the first
686 * selected node in document-order, as defined by the XPath
687 * specification. If the XPath expression selects a double,
688 * String, or boolean, then that object is returned.
689 * </p>
690 *
691 * @param context the Context against which this expression is evaluated
692 *
693 * @return the first node in document order of all nodes selected
694 * by this XPath expression
695 * @throws JaxenException if an XPath error occurs during expression evaluation
696 *
697 * @see #selectNodesForContext
698 */
699 protected Object selectSingleNodeForContext(Context context) throws JaxenException
700 {
701 List results = selectNodesForContext( context );
702
703 if ( results.isEmpty() )
704 {
705 return null;
706 }
707
708 return results.get( 0 );
709 }
710
711 }