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