View Javadoc

    1   /*
    2    * $Header: /home/projects/jaxen/scm/jaxen/src/java/main/org/jaxen/BaseXPath.java,v 1.48 2005/09/24 23:05:15 elharo Exp $
    3    * $Revision: 1.48 $
    4    * $Date: 2005/09/24 23:05:15 $
    5    *
    6    * ====================================================================
    7    *
    8    * Copyright (C) 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
    13   * are met:
    14   *
    15   * 1. Redistributions of source code must retain the above copyright
    16   *    notice, this list of conditions, and the following disclaimer.
    17   *
    18   * 2. Redistributions in binary form must reproduce the above copyright
    19   *    notice, this list of conditions, and the disclaimer that follows
    20   *    these conditions in the documentation and/or other materials
    21   *    provided with the distribution.
    22   *
    23   * 3. The name "Jaxen" must not be used to endorse or promote products
    24   *    derived from this software without prior written permission.  For
    25   *    written permission, please contact license@jaxen.org.
    26   *
    27   * 4. Products derived from this software may not be called "Jaxen", nor
    28   *    may "Jaxen" appear in their name, without prior written permission
    29   *    from the Jaxen Project Management (pm@jaxen.org).
    30   *
    31   * In addition, we request (but do not require) that you include in the
    32   * end-user documentation provided with the redistribution and/or in the
    33   * software itself an acknowledgement equivalent to the following:
    34   *     "This product includes software developed by the
    35   *      Jaxen Project <http://www.jaxen.org/>."
    36   * Alternatively, the acknowledgment may be graphical using the logos
    37   * available at http://www.jaxen.org/
    38   *
    39   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
    40   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    41   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    42   * DISCLAIMED.  IN NO EVENT SHALL THE Jaxen AUTHORS OR THE PROJECT
    43   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    44   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    45   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
    46   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    47   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    48   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
    49   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    50   * SUCH DAMAGE.
    51   *
    52   * ====================================================================
    53   * This software consists of voluntary contributions made by many
    54   * individuals on behalf of the Jaxen Project and was originally
    55   * created by bob mcwhirter <bob@werken.com> and
    56   * James Strachan <jstrachan@apache.org>.  For more information on the
    57   * Jaxen Project, please see <http://www.jaxen.org/>.
    58   *
    59   * $Id: BaseXPath.java,v 1.48 2005/09/24 23:05:15 elharo Exp $
    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.SAXPathException;
    74  import org.jaxen.saxpath.XPathReader;
    75  import org.jaxen.saxpath.helpers.XPathReaderFactory;
    76  import org.jaxen.util.SingletonList;
    77
    78  /*** Base functionality for all concrete, implementation-specific XPaths.
    79   *
    80   *  <p>
    81   *  This class provides generic functionality for further-defined
    82   *  implementation-specific XPaths.
    83   *  </p>
    84   *
    85   *  <p>
    86   *  If you want to adapt the Jaxen engine so that it can traverse your own
    87   *  object model, then this is a good base class to derive from.
    88   *  Typically you only really need to provide your own
    89   *  {@link org.jaxen.Navigator} implementation.
    90   *  </p>
    91   *
    92   *  @see org.jaxen.dom4j.Dom4jXPath XPath for dom4j
    93   *  @see org.jaxen.jdom.JDOMXPath   XPath for JDOM
    94   *  @see org.jaxen.dom.DOMXPath     XPath for W3C DOM
    95   *
    96   *  @author <a href="mailto:bob@werken.com">bob mcwhirter</a>
    97   *  @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
    98   */
    99  public class BaseXPath implements XPath, Serializable
    100 {
    101     /*** Original expression text. */
    102     private String exprText;
    103
    104     /*** the parsed form of the XPath expression */
    105     private XPathExpr xpath;
    106
    107     /*** the support information and function, namespace and variable contexts */
    108     private ContextSupport support;
    109
    110     /*** the implementation-specific Navigator for retrieving XML nodes **/
    111     private Navigator navigator;
    112
    113     /*** Construct given an XPath expression string.
    114      *
    115      *  @param xpathExpr the XPath expression
    116      *
    117      *  @throws JaxenException if there is a syntax error while
    118      *          parsing the expression
    119      */
    120     protected BaseXPath(String xpathExpr) throws JaxenException
    121     {
    122         try
    123         {
    124             XPathReader reader = XPathReaderFactory.createReader();
    125             JaxenHandler handler = new JaxenHandler();
    126             reader.setXPathHandler( handler );
    127             reader.parse( xpathExpr );
    128             this.xpath = handler.getXPathExpr();
    129         }
    130         catch (org.jaxen.saxpath.XPathSyntaxException e)
    131         {
    132             throw new org.jaxen.XPathSyntaxException( e );
    133         }
    134         catch (SAXPathException e)
    135         {
    136             throw new JaxenException( e );
    137         }
    138
    139         this.exprText = xpathExpr;
    140     }
    141
    142     /*** Construct given an XPath expression string.
    143      *
    144      *  @param xpathExpr the XPath expression
    145      *
    146      *  @param navigator the XML navigator to use
    147      *
    148      *  @throws JaxenException if there is a syntax error while
    149      *          parsing the expression
    150      */
    151     public BaseXPath(String xpathExpr, Navigator navigator) throws JaxenException
    152     {
    153         this( xpathExpr );
    154         this.navigator = navigator;
    155     }
    156
    157     /*** Evaluate this XPath against a given context.
    158      *  The context of evaluation may be any object type
    159      *  the navigator recognizes as a node.
    160      *  The return value is either a <code>String</code>,
    161      *  <code>Double</code>, <code>Boolean</code>, or <code>List</code>
    162      *  of nodes.
    163      *
    164      *  <p>
    165      *  When using this method, one must be careful to
    166      *  test the class of the returned object.  If the returned
    167      *  object is a list, then the items in this
    168      *  list will be the actual <code>Document</code>,
    169      *  <code>Element</code>, <code>Attribute</code>, etc. objects
    170      *  as defined by the concrete XML object-model implementation,
    171      *  directly from the context document.  This method <strong>does
    172      *  not return <em>copies</em> of anything</strong>, but merely
    173      *  returns references to objects within the source document.
    174      *  </p>
    175      *
    176      * @param context the node, node-set or Context object for evaluation.
    177      *      This value can be null.
    178      *
    179      * @return the result of evaluating the XPath expression
    180      *          against the supplied context
    181      * @throws JaxenException if an XPath error occurs during expression evaluation
    182      * @throws ClassCastException if the context is not a node
    183      */
    184     public Object evaluate(Object context) throws JaxenException
    185     {
    186         List answer = selectNodes(context);
    187
    188         if ( answer != null
    189              &&
    190              answer.size() == 1 )
    191         {
    192             Object first = answer.get(0);
    193
    194             if ( first instanceof String
    195                  ||
    196                  first instanceof Number
    197                  ||
    198                  first instanceof Boolean )
    199             {
    200                 return first;
    201             }
    202         }
    203         return answer;
    204     }
    205
    206     /*** Select all nodes that are selected by this XPath
    207      *  expression. If multiple nodes match, multiple nodes
    208      *  will be returned. Nodes will be returned
    209      *  in document-order, as defined by the XPath
    210      *  specification. If the expression selects a non-node-set
    211      *  (i.e. a number, boolean, or string) then a List
    212      *  containing just that one object is returned.
    213      *  </p>
    214      *
    215      * @param node the node, node-set or Context object for evaluation.
    216      *     This value can be null.
    217      *
    218      * @return the node-set of all items selected
    219      *          by this XPath expression
    220      * @throws JaxenException if an XPath error occurs during expression evaluation
    221      *
    222      * @see #selectNodesForContext
    223      */
    224     public List selectNodes(Object node) throws JaxenException
    225     {
    226         Context context = getContext( node );
    227         return selectNodesForContext( context );
    228     }
    229
    230     /*** Select only the first node selected by this XPath
    231      *  expression.  If multiple nodes match, only one node will be
    232      *  returned. The selected node will be the first
    233      *  selected node in document-order, as defined by the XPath
    234      *  specification.
    235      *  </p>
    236      *
    237      * @param node the node, node-set or Context object for evaluation.
    238      *     This value can be null.
    239      *
    240      * @return the node-set of all items selected
    241      *          by this XPath expression
    242      * @throws JaxenException if an XPath error occurs during expression evaluation
    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      * Returns the XPath string-value of the argument node.
    260      *
    261      * @param node the node whose value to take
    262      * @return the XPath string value of this node
    263      * @throws JaxenException if an XPath error occurs during expression evaluation
    264      * @deprecated replaced by {@link #stringValueOf}
    265      */
    266     public String valueOf(Object node) throws JaxenException
    267     {
    268         return stringValueOf( node );
    269     }
    270
    271     /*** Retrieves the string-value of the result of
    272      *  evaluating this XPath expression when evaluated
    273      *  against the specified context.
    274      *
    275      *  <p>
    276      *  The string-value of the expression is determined per
    277      *  the <code>string(..)</code> core function defined
    278      *  in the XPath specification.  This means that an expression
    279      *  that selects zero nodes will return the empty string,
    280      *  while an expression that selects one-or-more nodes will
    281      *  return the string-value of the first node.
    282      *  </p>
    283      *
    284      * @param node the node, node-set or Context object for evaluation. This value can be null.
    285      *
    286      * @return the string-value of the result of evaluating this expression with the specified context node
    287      * @throws JaxenException if an XPath error occurs during expression evaluation
    288      */
    289     public String stringValueOf(Object node) throws JaxenException
    290     {
    291         Context context = getContext( node );
    292
    293         Object result = selectSingleNodeForContext( context );
    294
    295         if ( result == null )
    296         {
    297             return "";
    298         }
    299
    300         return StringFunction.evaluate( result,
    301                                         context.getNavigator() );
    302     }
    303
    304     /*** Retrieve a boolean-value interpretation of this XPath
    305      *  expression when evaluated against a given context.
    306      *
    307      *  <p>
    308      *  The boolean-value of the expression is determined per
    309      *  the <code>boolean(..)</code> function defined
    310      *  in the XPath specification.  This means that an expression
    311      *  that selects zero nodes will return <code>false</code>,
    312      *  while an expression that selects one or more nodes will
    313      *  return <code>true</code>.
    314      *  </p>
    315      *
    316      * @param node the node, node-set or Context object for evaluation. This value can be null.
    317      *
    318      * @return the boolean-value of the result of evaluating this expression with the specified context node
    319      * @throws JaxenException if an XPath error occurs during expression evaluation
    320      */
    321     public boolean booleanValueOf(Object node) throws JaxenException
    322     {
    323         Context context = getContext( node );
    324         List result = selectNodesForContext( context );
    325         if ( result == null ) return false;
    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> indicating the numeric value of
    343      *      evaluating this expression against the specified context
    344      * @throws JaxenException if an XPath error occurs during expression evaluation
    345      */
    346     public Number numberValueOf(Object node) throws JaxenException
    347     {
    348         Context context = getContext( node );
    349         Object result = selectSingleNodeForContext( context );
    350         return NumberFunction.evaluate( result,
    351                                         context.getNavigator() );
    352     }
    353
    354     // Helpers
    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 independent
    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 custom <code>NamespaceContext</code>
    370      *  that is not a <code>SimpleNamespaceContext</code>,
    371      *  then this method will throw a <code>JaxenException</code>.
    372      *  </p>
    373      *
    374      *  @param prefix the namespace prefix
    375      *  @param uri the namespace URI
    376      *
    377      *  @throws JaxenException if the <code>NamespaceContext</code>
    378      *          used by this XPath is not a <code>SimpleNamespaceContext</code>
    379      */
    380     public void addNamespace(String prefix,
    381                              String uri) throws JaxenException
    382     {
    383         NamespaceContext nsContext = getNamespaceContext();
    384         if ( nsContext instanceof SimpleNamespaceContext )
    385         {
    386             ((SimpleNamespaceContext)nsContext).addNamespace( prefix,
    387                                                               uri );
    388             return;
    389         }
    390
    391         throw new JaxenException("Operation not permitted while using a non-simple namespace context.");
    392     }
    393
    394
    395     // ------------------------------------------------------------
    396     // ------------------------------------------------------------
    397     //     Properties
    398     // ------------------------------------------------------------
    399     // ------------------------------------------------------------
    400
    401
    402     /*** Set a <code>NamespaceContext</code> for use with this
    403      *  XPath expression.
    404      *
    405      *  <p>
    406      *  A <code>NamespaceContext</code> is responsible for translating
    407      *  namespace prefixes within the expression into namespace URIs.
    408      *  </p>
    409      *
    410      *  @param namespaceContext the <code>NamespaceContext</code> to
    411      *         install for this expression
    412      *
    413      *  @see NamespaceContext
    414      *  @see NamespaceContext#translateNamespacePrefixToUri
    415      */
    416     public void setNamespaceContext(NamespaceContext namespaceContext)
    417     {
    418         getContextSupport().setNamespaceContext(namespaceContext);
    419     }
    420
    421     /*** Set a <code>FunctionContext</code> for use with this XPath
    422      *  expression.
    423      *
    424      *  <p>
    425      *  A <code>FunctionContext</code> is responsible for resolving
    426      *  all function calls used within the expression.
    427      *  </p>
    428      *
    429      *  @param functionContext the <code>FunctionContext</code> to
    430      *         install for this expression
    431      *
    432      *  @see FunctionContext
    433      *  @see FunctionContext#getFunction
    434      */
    435     public void setFunctionContext(FunctionContext functionContext)
    436     {
    437         getContextSupport().setFunctionContext(functionContext);
    438     }
    439
    440     /*** Set a <code>VariableContext</code> for use with this XPath
    441      *  expression.
    442      *
    443      *  <p>
    444      *  A <code>VariableContext</code> is responsible for resolving
    445      *  all variables referenced within the expression.
    446      *  </p>
    447      *
    448      *  @param variableContext The <code>VariableContext</code> to
    449      *         install for this expression
    450      *
    451      *  @see VariableContext
    452      *  @see VariableContext#getVariableValue
    453      */
    454     public void setVariableContext(VariableContext variableContext)
    455     {
    456         getContextSupport().setVariableContext(variableContext);
    457     }
    458
    459     /*** Retrieve the <code>NamespaceContext</code> used by this XPath
    460      *  expression.
    461      *
    462      *  <p>
    463      *  A <code>NamespaceContext</code> is responsible for mapping
    464      *  prefixes used within the expression to namespace URIs.
    465      *  </p>
    466      *
    467      *  <p>
    468      *  If this XPath expression has not previously had a <code>NamespaceContext</code>
    469      *  installed, a new default <code>NamespaceContext</code> will be created,
    470      *  installed and returned.
    471      *  </p>
    472      *
    473      *  @return the <code>NamespaceContext</code> used by this expression
    474      *
    475      *  @see NamespaceContext
    476      */
    477     public NamespaceContext getNamespaceContext()
    478     {
    479         return getContextSupport().getNamespaceContext();
    480     }
    481
    482     /*** Retrieve the <code>FunctionContext</code> used by this XPath
    483      *  expression.
    484      *
    485      *  <p>
    486      *  A <code>FunctionContext</code> is responsible for resolving
    487      *  all function calls used within the expression.
    488      *  </p>
    489      *
    490      *  <p>
    491      *  If this XPath expression has not previously had a <code>FunctionContext</code>
    492      *  installed, a new default <code>FunctionContext</code> will be created,
    493      *  installed and returned.
    494      *  </p>
    495      *
    496      *  @return the <code>FunctionContext</code> used by this expression
    497      *
    498      *  @see FunctionContext
    499      */
    500     public FunctionContext getFunctionContext()
    501     {
    502         return getContextSupport().getFunctionContext();
    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         return getContextSupport().getVariableContext();
    526     }
    527
    528
    529     /*** Retrieve the root expression of the internal
    530      *  compiled form of this XPath expression.
    531      *
    532      *  <p>
    533      *  Internally, Jaxen maintains a form of Abstract Syntax
    534      *  Tree (AST) to represent the structure of the XPath expression.
    535      *  This is normally not required during normal consumer-grade
    536      *  usage of Jaxen.  This method is provided for hard-core users
    537      *  who wish to manipulate or inspect a tree-based version of
    538      *  the expression.
    539      *  </p>
    540      *
    541      *  @return the root of the AST of this expression
    542      */
    543     public Expr getRootExpr()
    544     {
    545         return xpath.getRootExpr();
    546     }
    547
    548     /*** Return the original expression text.
    549      *
    550      *  @return the normalized XPath expression string
    551      */
    552     public String toString()
    553     {
    554         return this.exprText;
    555     }
    556
    557     /*** Returns a string representation of the parse tree.
    558      *
    559      *  @return a string representation of the parse tree.
    560      */
    561     public String debug()
    562     {
    563         return this.xpath.toString();
    564     }
    565
    566     // ------------------------------------------------------------
    567     // ------------------------------------------------------------
    568     //     Implementation methods
    569     // ------------------------------------------------------------
    570     // ------------------------------------------------------------
    571
    572
    573     /*** Create a {@link Context} wrapper for the provided
    574      *  implementation-specific object.
    575      *
    576      *  @param node the implementation-specific object
    577      *         to be used as the context
    578      *
    579      *  @return a <code>Context</code> wrapper around the object
    580      */
    581     protected Context getContext(Object node)
    582     {
    583         if ( node instanceof Context )
    584         {
    585             return (Context) node;
    586         }
    587
    588         Context fullContext = new Context( getContextSupport() );
    589
    590         if ( node instanceof List )
    591         {
    592             fullContext.setNodeSet( (List) node );
    593         }
    594         else
    595         {
    596             List list = new SingletonList(node);
    597             fullContext.setNodeSet( list );
    598         }
    599
    600         return fullContext;
    601     }
    602
    603     /*** Retrieve the {@link ContextSupport} aggregation of
    604      *  <code>NamespaceContext</code>, <code>FunctionContext</code>,
    605      *  <code>VariableContext</code>, and {@link Navigator}.
    606      *
    607      *  @return aggregate <code>ContextSupport</code> for this
    608      *          XPath expression
    609      */
    610     protected ContextSupport getContextSupport()
    611     {
    612         if ( support == null )
    613         {
    614             support = new ContextSupport(
    615                 createNamespaceContext(),
    616                 createFunctionContext(),
    617                 createVariableContext(),
    618                 getNavigator()
    619             );
    620         }
    621
    622         return support;
    623     }
    624
    625     /*** Retrieve the XML object-model-specific {@link Navigator}
    626      *  for us in evaluating this XPath expression.
    627      *
    628      *  @return the implementation-specific <code>Navigator</code>
    629      */
    630     public Navigator getNavigator()
    631     {
    632         return navigator;
    633     }
    634
    635
    636
    637     // ------------------------------------------------------------
    638     // ------------------------------------------------------------
    639     //     Factory methods for default contexts
    640     // ------------------------------------------------------------
    641     // ------------------------------------------------------------
    642
    643     /*** Create a default <code>FunctionContext</code>.
    644      *
    645      *  @return a default <code>FunctionContext</code>
    646      */
    647     protected FunctionContext createFunctionContext()
    648     {
    649         return XPathFunctionContext.getInstance();
    650     }
    651
    652     /*** Create a default <code>NamespaceContext</code>.
    653      *
    654      *  @return a default <code>NamespaceContext</code> instance
    655      */
    656     protected NamespaceContext createNamespaceContext()
    657     {
    658         return new SimpleNamespaceContext();
    659     }
    660
    661     /*** Create a default <code>VariableContext</code>.
    662      *
    663      *  @return a default <code>VariableContext</code> instance
    664      */
    665     protected VariableContext createVariableContext()
    666     {
    667         return new SimpleVariableContext();
    668     }
    669
    670     /*** Select all nodes that match this XPath
    671      *  expression on the given Context object.
    672      *  If multiple nodes match, multiple nodes
    673      *  will be returned in document-order, as defined by the XPath
    674      *  specification. If the expression selects a non-node-set
    675      *  (i.e. a number, boolean, or string) then a List
    676      *  containing just that one object is returned.
    677      *  </p>
    678      *
    679      * @param context the Context which gets evaluated
    680      *
    681      * @return the node-set of all items selected
    682      *          by this XPath expression
    683      * @throws JaxenException if an XPath error occurs during expression evaluation
    684      *
    685      */
    686     protected List selectNodesForContext(Context context) throws JaxenException
    687     {
    688         List list = this.xpath.asList( context );
    689         return list;
    690
    691     }
    692
    693
    694     /*** Return only the first node that is selected by this XPath
    695      *  expression.  If multiple nodes match, only one node will be
    696      *  returned. The selected node will be the first
    697      *  selected node in document-order, as defined by the XPath
    698      *  specification. If the XPath expression selects a double,
    699      *  String, or boolean, then that object is returned.
    700      *  </p>
    701      *
    702      * @param context the Context against which this expression is evaluated
    703      *
    704      * @return the first node in document order of all nodes selected
    705      *          by this XPath expression
    706      * @throws JaxenException if an XPath error occurs during expression evaluation
    707      *
    708      *  @see #selectNodesForContext
    709      */
    710     protected Object selectSingleNodeForContext(Context context) throws JaxenException
    711     {
    712         List results = selectNodesForContext( context );
    713
    714         if ( results.isEmpty() )
    715         {
    716             return null;
    717         }
    718
    719         return results.get( 0 );
    720     }
    721
    722 }