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