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