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
265 * @see #stringValueOf
266 */
267 public String valueOf(Object node) throws JaxenException
268 {
269 return stringValueOf( node );
270 }
271
272 /*** Retrieves the string-value of the result of
273 * evaluating this XPath expression when evaluated
274 * against the specified context.
275 *
276 * <p>
277 * The string-value of the expression is determined per
278 * the <code>string(..)</code> core function defined
279 * in the XPath specification. This means that an expression
280 * that selects zero nodes will return the empty string,
281 * while an expression that selects one-or-more nodes will
282 * return the string-value of the first node.
283 * </p>
284 *
285 * @param node the node, node-set or Context object for evaluation. This value can be null.
286 *
287 * @return the string-value of the result of evaluating this expression with the specified context node
288 * @throws JaxenException if an XPath error occurs during expression evaluation
289 */
290 public String stringValueOf(Object node) throws JaxenException
291 {
292 Context context = getContext( node );
293
294 Object result = selectSingleNodeForContext( context );
295
296 if ( result == null )
297 {
298 return "";
299 }
300
301 return StringFunction.evaluate( result,
302 context.getNavigator() );
303 }
304
305 /*** Retrieve a boolean-value interpretation of this XPath
306 * expression when evaluated against a given context.
307 *
308 * <p>
309 * The boolean-value of the expression is determined per
310 * the <code>boolean(..)</code> function defined
311 * in the XPath specification. This means that an expression
312 * that selects zero nodes will return <code>false</code>,
313 * while an expression that selects one or more nodes will
314 * return <code>true</code>.
315 * </p>
316 *
317 * @param node the node, node-set or Context object for evaluation. This value can be null.
318 *
319 * @return the boolean-value of the result of evaluating this expression with the specified context node
320 * @throws JaxenException if an XPath error occurs during expression evaluation
321 */
322 public boolean booleanValueOf(Object node) throws JaxenException
323 {
324 Context context = getContext( node );
325 List result = selectNodesForContext( context );
326 if ( result == null ) return false;
327 return BooleanFunction.evaluate( result, context.getNavigator() ).booleanValue();
328 }
329
330 /*** Retrieve a number-value interpretation of this XPath
331 * expression when evaluated against a given context.
332 *
333 * <p>
334 * The number-value of the expression is determined per
335 * the <code>number(..)</code> core function as defined
336 * in the XPath specification. This means that if this
337 * expression selects multiple nodes, the number-value
338 * of the first node is returned.
339 * </p>
340 *
341 * @param node the node, node-set or Context object for evaluation. This value can be null.
342 *
343 * @return a <code>Double</code> indicating the numeric value of
344 * evaluating this expression against the specified context
345 * @throws JaxenException if an XPath error occurs during expression evaluation
346 */
347 public Number numberValueOf(Object node) throws JaxenException
348 {
349 Context context = getContext( node );
350 Object result = selectSingleNodeForContext( context );
351 return NumberFunction.evaluate( result,
352 context.getNavigator() );
353 }
354
355
356
357 /*** Add a namespace prefix-to-URI mapping for this XPath
358 * expression.
359 *
360 * <p>
361 * Namespace prefix-to-URI mappings in an XPath are independent
362 * of those used within any document. Only the mapping explicitly
363 * added to this XPath will be available for resolving the
364 * XPath expression.
365 * </p>
366 *
367 * <p>
368 * This is a convenience method for adding mappings to the
369 * default {@link NamespaceContext} in place for this XPath.
370 * If you have installed a specific custom <code>NamespaceContext</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 a <code>NamespaceContext</code>
378 * used by this XPath has been explicitly installed
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 custom 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 NamespaceContext answer = getContextSupport().getNamespaceContext();
480 if ( answer == null ) {
481 answer = createNamespaceContext();
482 getContextSupport().setNamespaceContext( answer );
483 }
484 return answer;
485 }
486
487 /*** Retrieve the <code>FunctionContext</code> used by this XPath
488 * expression.
489 *
490 * <p>
491 * A <code>FunctionContext</code> is responsible for resolving
492 * all function calls used within the expression.
493 * </p>
494 *
495 * <p>
496 * If this XPath expression has not previously had a <code>FunctionContext</code>
497 * installed, a new default <code>FunctionContext</code> will be created,
498 * installed and returned.
499 * </p>
500 *
501 * @return the <code>FunctionContext</code> used by this expression
502 *
503 * @see FunctionContext
504 */
505 public FunctionContext getFunctionContext()
506 {
507 FunctionContext answer = getContextSupport().getFunctionContext();
508 if ( answer == null ) {
509 answer = createFunctionContext();
510 getContextSupport().setFunctionContext( answer );
511 }
512 return answer;
513 }
514
515 /*** Retrieve the <code>VariableContext</code> used by this XPath
516 * expression.
517 *
518 * <p>
519 * A <code>VariableContext</code> is responsible for resolving
520 * all variables referenced within the expression.
521 * </p>
522 *
523 * <p>
524 * If this XPath expression has not previously had a <code>VariableContext</code>
525 * installed, a new default <code>VariableContext</code> will be created,
526 * installed and returned.
527 * </p>
528 *
529 * @return the <code>VariableContext</code> used by this expression
530 *
531 * @see VariableContext
532 */
533 public VariableContext getVariableContext()
534 {
535 VariableContext answer = getContextSupport().getVariableContext();
536 if ( answer == null ) {
537 answer = createVariableContext();
538 getContextSupport().setVariableContext( answer );
539 }
540 return answer;
541 }
542
543
544 /*** Retrieve the root expression of the internal
545 * compiled form of this XPath expression.
546 *
547 * <p>
548 * Internally, Jaxen maintains a form of Abstract Syntax
549 * Tree (AST) to represent the structure of the XPath expression.
550 * This is normally not required during normal consumer-grade
551 * usage of Jaxen. This method is provided for hard-core users
552 * who wish to manipulate or inspect a tree-based version of
553 * the expression.
554 * </p>
555 *
556 * @return the root of the AST of this expression
557 */
558 public Expr getRootExpr()
559 {
560 return xpath.getRootExpr();
561 }
562
563 /*** Return the original expression text.
564 *
565 * @return the normalized XPath expression string
566 */
567 public String toString()
568 {
569 return this.exprText;
570 }
571
572 /*** Returns the string version of this xpath.
573 *
574 * @return the normalized XPath expression string
575 *
576 * @see #toString
577 */
578 public String debug()
579 {
580 return this.xpath.toString();
581 }
582
583
584
585
586
587
588
589
590 /*** Create a {@link Context} wrapper for the provided
591 * implementation-specific object.
592 *
593 * @param node the implementation-specific object
594 * to be used as the context
595 *
596 * @return a <code>Context</code> wrapper around the object
597 */
598 protected Context getContext(Object node)
599 {
600 if ( node instanceof Context )
601 {
602 return (Context) node;
603 }
604
605 Context fullContext = new Context( getContextSupport() );
606
607 if ( node instanceof List )
608 {
609 fullContext.setNodeSet( (List) node );
610 }
611 else
612 {
613 List list = new SingletonList(node);
614 fullContext.setNodeSet( list );
615 }
616
617 return fullContext;
618 }
619
620 /*** Retrieve the {@link ContextSupport} aggregation of
621 * <code>NamespaceContext</code>, <code>FunctionContext</code>,
622 * <code>VariableContext</code>, and {@link Navigator}.
623 *
624 * @return aggregate <code>ContextSupport</code> for this
625 * XPath expression
626 */
627 protected ContextSupport getContextSupport()
628 {
629 if ( support == null )
630 {
631 support = new ContextSupport(
632 createNamespaceContext(),
633 createFunctionContext(),
634 createVariableContext(),
635 getNavigator()
636 );
637 }
638
639 return support;
640 }
641
642 /*** Retrieve the XML object-model-specific {@link Navigator}
643 * for us in evaluating this XPath expression.
644 *
645 * @return the implementation-specific <code>Navigator</code>
646 */
647 public Navigator getNavigator()
648 {
649 return navigator;
650 }
651
652
653
654
655
656
657
658
659
660 /*** Create a default <code>FunctionContext</code>.
661 *
662 * @return a default <code>FunctionContext</code>
663 */
664 protected FunctionContext createFunctionContext()
665 {
666 return XPathFunctionContext.getInstance();
667 }
668
669 /*** Create a default <code>NamespaceContext</code>.
670 *
671 * @return a default <code>NamespaceContext</code> instance
672 */
673 protected NamespaceContext createNamespaceContext()
674 {
675 return new SimpleNamespaceContext();
676 }
677
678 /*** Create a default <code>VariableContext</code>.
679 *
680 * @return a default <code>VariableContext</code> instance
681 */
682 protected VariableContext createVariableContext()
683 {
684 return new SimpleVariableContext();
685 }
686
687 /*** Select all nodes that match this XPath
688 * expression on the given Context object.
689 * If multiple nodes match, multiple nodes
690 * will be returned in document-order, as defined by the XPath
691 * specification. If the expression selects a non-node-set
692 * (i.e. a number, boolean, or string) then a List
693 * containing just that one object is returned.
694 * </p>
695 *
696 * @param context the Context which gets evaluated
697 *
698 * @return the node-set of all items selected
699 * by this XPath expression
700 * @throws JaxenException if an XPath error occurs during expression evaluation
701 *
702 */
703 protected List selectNodesForContext(Context context) throws JaxenException
704 {
705 List list = this.xpath.asList( context );
706 return list;
707
708 }
709
710
711 /*** Return only the first node that is selected by this XPath
712 * expression. If multiple nodes match, only one node will be
713 * returned. The selected node will be the first
714 * selected node in document-order, as defined by the XPath
715 * specification. If the XPath expression selects a double,
716 * String, or boolean, then that object is returned.
717 * </p>
718 *
719 * @param context the Context against which this expression is evaluated
720 *
721 * @return the first node in document order of all nodes selected
722 * by this XPath expression
723 * @throws JaxenException if an XPath error occurs during expression evaluation
724 *
725 * @see #selectNodesForContext
726 */
727 protected Object selectSingleNodeForContext(Context context) throws JaxenException
728 {
729 List results = selectNodesForContext( context );
730
731 if ( results.isEmpty() )
732 {
733 return null;
734 }
735
736 return results.get( 0 );
737 }
738
739 }