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