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