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 public String stringValueOf(Object node) throws JaxenException
271 {
272 Context context = getContext( node );
273
274 Object result = selectSingleNodeForContext( context );
275
276 if ( result == null )
277 {
278 return "";
279 }
280
281 return StringFunction.evaluate( result,
282 context.getNavigator() );
283 }
284
285 /*** Retrieve a boolean-value interpretation of this XPath
286 * expression when evaluated against a given context.
287 *
288 * <p>
289 * The boolean-value of the expression is determined per
290 * the <code>boolean(..)</code> core function as defined
291 * in the XPath specification. This means that an expression
292 * that selects zero nodes will return <code>false</code>,
293 * while an expression that selects one-or-more nodes will
294 * return <code>true</code>.
295 * </p>
296 *
297 * @param node the node, node-set or Context object for evaluation. This value can be null.
298 *
299 * @return the boolean-value interpretation of this expression
300 */
301 public boolean booleanValueOf(Object node) throws JaxenException
302 {
303 Context context = getContext( node );
304
305 List result = selectNodesForContext( context );
306
307 if ( result == null ) return false;
308
309 return BooleanFunction.evaluate( result, context.getNavigator() ).booleanValue();
310 }
311
312 /*** Retrieve a number-value interpretation of this XPath
313 * expression when evaluated against a given context.
314 *
315 * <p>
316 * The number-value of the expression is determined per
317 * the <code>number(..)</code> core function as defined
318 * in the XPath specification. This means that if this
319 * expression selects multiple nodes, the number-value
320 * of the first node is returned.
321 * </p>
322 *
323 * @param node the node, node-set or Context object for evaluation. This value can be null.
324 *
325 * @return a <code>Double</code> interpretation of this expression
326 */
327 public Number numberValueOf(Object node) throws JaxenException
328 {
329 Context context = getContext( node );
330
331 Object result = selectSingleNodeForContext( context );
332
333 return NumberFunction.evaluate( result,
334 context.getNavigator() );
335 }
336
337
338
339 /*** Add a namespace prefix-to-URI mapping for this XPath
340 * expression.
341 *
342 * <p>
343 * Namespace prefix-to-URI mappings in an XPath are independant
344 * of those used within any document. Only the mapping explicitly
345 * added to this XPath will be available for resolving the
346 * XPath expression.
347 * </p>
348 *
349 * <p>
350 * This is a convenience method for adding mappings to the
351 * default {@link NamespaceContext} in place for this XPath.
352 * If you have installed a specific custom <code>NamespaceContext</code>,
353 * then this method will throw a <code>JaxenException</code>.
354 * </p>
355 *
356 * @param prefix the namespace prefix
357 * @param uri The namespace URI.
358 *
359 * @throws JaxenException if a <code>NamespaceContext</code>
360 * used by this XPath has been explicitly installed
361 */
362 public void addNamespace(String prefix,
363 String uri) throws JaxenException
364 {
365 NamespaceContext nsContext = getNamespaceContext();
366
367 if ( nsContext instanceof SimpleNamespaceContext )
368 {
369 ((SimpleNamespaceContext)nsContext).addNamespace( prefix,
370 uri );
371
372 return;
373 }
374
375 throw new JaxenException("Operation not permitted while using a custom namespace context.");
376 }
377
378
379
380
381
382
383
384
385
386 /*** Set a <code>NamespaceContext</code> for use with this
387 * XPath expression.
388 *
389 * <p>
390 * A <code>NamespaceContext</code> is responsible for translating
391 * namespace prefixes within the expression into namespace URIs.
392 * </p>
393 *
394 * @param namespaceContext the <code>NamespaceContext</code> to
395 * install for this expression
396 *
397 * @see NamespaceContext
398 * @see NamespaceContext#translateNamespacePrefixToUri
399 */
400 public void setNamespaceContext(NamespaceContext namespaceContext)
401 {
402 getContextSupport().setNamespaceContext(namespaceContext);
403 }
404
405 /*** Set a <code>FunctionContext</code> for use with this XPath
406 * expression.
407 *
408 * <p>
409 * A <code>FunctionContext</code> is responsible for resolving
410 * all function calls used within the expression.
411 * </p>
412 *
413 * @param functionContext the <code>FunctionContext</code> to
414 * install for this expression
415 *
416 * @see FunctionContext
417 * @see FunctionContext#getFunction
418 */
419 public void setFunctionContext(FunctionContext functionContext)
420 {
421 getContextSupport().setFunctionContext(functionContext);
422 }
423
424 /*** Set a <code>VariableContext</code> for use with this XPath
425 * expression.
426 *
427 * <p>
428 * A <code>VariableContext</code> is responsible for resolving
429 * all variables referenced within the expression.
430 * </p>
431 *
432 * @param variableContext The <code>VariableContext</code> to
433 * install for this expression
434 *
435 * @see VariableContext
436 * @see VariableContext#getVariableValue
437 */
438 public void setVariableContext(VariableContext variableContext)
439 {
440 getContextSupport().setVariableContext(variableContext);
441 }
442
443 /*** Retrieve the <code>NamespaceContext</code> used by this XPath
444 * expression.
445 *
446 * <p>
447 * A <code>FunctionContext</code> is responsible for resolving
448 * all function calls used within the expression.
449 * </p>
450 *
451 * <p>
452 * If this XPath expression has not previously had a <code>NamespaceContext</code>
453 * installed, a new default <code>NamespaceContext</code> will be created,
454 * installed and returned.
455 * </p>
456 *
457 * @return the <code>NamespaceContext</code> used by this expression
458 *
459 * @see NamespaceContext
460 */
461 public NamespaceContext getNamespaceContext()
462 {
463 NamespaceContext answer = getContextSupport().getNamespaceContext();
464 if ( answer == null ) {
465 answer = createNamespaceContext();
466 getContextSupport().setNamespaceContext( answer );
467 }
468 return answer;
469 }
470
471 /*** Retrieve the <code>FunctionContext</code> used by this XPath
472 * expression.
473 *
474 * <p>
475 * A <code>FunctionContext</code> is responsible for resolving
476 * all function calls used within the expression.
477 * </p>
478 *
479 * <p>
480 * If this XPath expression has not previously had a <code>FunctionContext</code>
481 * installed, a new default <code>FunctionContext</code> will be created,
482 * installed and returned.
483 * </p>
484 *
485 * @return the <code>FunctionContext</code> used by this expression
486 *
487 * @see FunctionContext
488 */
489 public FunctionContext getFunctionContext()
490 {
491 FunctionContext answer = getContextSupport().getFunctionContext();
492 if ( answer == null ) {
493 answer = createFunctionContext();
494 getContextSupport().setFunctionContext( answer );
495 }
496 return answer;
497 }
498
499 /*** Retrieve the <code>VariableContext</code> used by this XPath
500 * expression.
501 *
502 * <p>
503 * A <code>VariableContext</code> is responsible for resolving
504 * all variables referenced within the expression.
505 * </p>
506 *
507 * <p>
508 * If this XPath expression has not previously had a <code>VariableContext</code>
509 * installed, a new default <code>VariableContext</code> will be created,
510 * installed and returned.
511 * </p>
512 *
513 * @return the <code>VariableContext</code> used by this expression
514 *
515 * @see VariableContext
516 */
517 public VariableContext getVariableContext()
518 {
519 VariableContext answer = getContextSupport().getVariableContext();
520 if ( answer == null ) {
521 answer = createVariableContext();
522 getContextSupport().setVariableContext( answer );
523 }
524 return answer;
525 }
526
527
528 /*** Retrieve the root expression of the internal
529 * compiled form of this XPath expression.
530 *
531 * <p>
532 * Internally, Jaxen maintains a form of Abstract Syntax
533 * Tree (AST) to represent the structure of the XPath expression.
534 * This is normally not required during normal consumer-grade
535 * usage of Jaxen. This method is provided for hard-core users
536 * who wish to manipulate or inspect a tree-based version of
537 * the expression.
538 * </p>
539 *
540 * @return the root of the AST of this expression
541 */
542 public Expr getRootExpr()
543 {
544 return xpath.getRootExpr();
545 }
546
547 /*** Return the original expression text.
548 *
549 * @return the normalized XPath expression string
550 */
551 public String toString()
552 {
553 return this.exprText;
554 }
555
556 /*** Returns the string version of this xpath.
557 *
558 * @return the normalized XPath expression string
559 *
560 * @see #toString
561 */
562 public String debug()
563 {
564 return this.xpath.toString();
565 }
566
567
568
569
570
571
572
573
574 /*** Create a {@link Context} wrapper for the provided
575 * implementation-specific object.
576 *
577 * @param node the implementation-specific object
578 * to be used as the context
579 *
580 * @return a <code>Context</code> wrapper around the object
581 */
582 protected Context getContext(Object node)
583 {
584 if ( node instanceof Context )
585 {
586 return (Context) node;
587 }
588
589 Context fullContext = new Context( getContextSupport() );
590
591 if ( node instanceof List )
592 {
593 fullContext.setNodeSet( (List) node );
594 }
595 else
596 {
597 List list = new SingletonList(node);
598
599 fullContext.setNodeSet( list );
600 }
601
602 return fullContext;
603 }
604
605 /*** Retrieve the {@link ContextSupport} aggregation of
606 * <code>NamespaceContext</code>, <code>FunctionContext</code>,
607 * <code>VariableContext</code>, and {@link Navigator}.
608 *
609 * @return aggregate <code>ContextSupport</code> for this
610 * XPath expression
611 */
612 protected ContextSupport getContextSupport()
613 {
614 if ( support == null )
615 {
616 support = new ContextSupport(
617 createNamespaceContext(),
618 createFunctionContext(),
619 createVariableContext(),
620 getNavigator()
621 );
622 }
623
624 return support;
625 }
626
627 /*** Retrieve the XML object-model-specific {@link Navigator}
628 * for us in evaluating this XPath expression.
629 *
630 * @return the implementation-specific <code>Navigator</code>
631 */
632 public Navigator getNavigator()
633 {
634 return navigator;
635 }
636
637
638
639
640
641
642
643
644
645 /*** Create a default <code>FunctionContext</code>.
646 *
647 * @return a default <code>FunctionContext</code>
648 */
649 protected FunctionContext createFunctionContext()
650 {
651 return XPathFunctionContext.getInstance();
652 }
653
654 /*** Create a default <code>NamespaceContext</code>.
655 *
656 * @return a default <code>NamespaceContext</code> instance
657 */
658 protected NamespaceContext createNamespaceContext()
659 {
660 return new SimpleNamespaceContext();
661 }
662
663 /*** Create a default <code>VariableContext</code>.
664 *
665 * @return a default <code>VariableContext</code> instance
666 */
667 protected VariableContext createVariableContext()
668 {
669 return new SimpleVariableContext();
670 }
671
672 /*** Select all nodes that match this XPath
673 * expression on the given Context object.
674 * If multiple nodes match, multiple nodes
675 * will be returned in document-order, as defined by the XPath
676 * specification.
677 * </p>
678 *
679 * @param context is the Context which gets evaluated
680 *
681 * @return the node-set of all items selected
682 * by this XPath expression
683 *
684 */
685 protected List selectNodesForContext(Context context) throws JaxenException
686 {
687 List list = this.xpath.asList( context );
688 return list;
689
690 }
691
692
693 /*** Return only the first node that is selected by this XPath
694 * expression. If multiple nodes match, only one node will be
695 * returned. The selected node will be the first
696 * selected node in document-order, as defined by the XPath
697 * specification.
698 * </p>
699 *
700 * @param context is the Context which gets evaluated
701 *
702 * @return the node-set of all items selected
703 * by this XPath expression
704 *
705 * @see #selectNodesForContext
706 */
707 protected Object selectSingleNodeForContext(Context context) throws JaxenException
708 {
709 List results = selectNodesForContext( context );
710
711 if ( results.isEmpty() )
712 {
713 return null;
714 }
715
716 return results.get( 0 );
717 }
718
719 }