org.jaxen.BaseXPath Maven / Gradle / Ivy
/*
* $Header$
* $Revision$
* $Date$
*
* ====================================================================
*
* Copyright 2000-2002 bob mcwhirter & James Strachan.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of the Jaxen Project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ====================================================================
* This software consists of voluntary contributions made by many
* individuals on behalf of the Jaxen Project and was originally
* created by bob mcwhirter and
* James Strachan . For more information on the
* Jaxen Project, please see .
*
* $Id$
*/
package org.jaxen;
import java.io.Serializable;
import java.util.List;
import org.jaxen.expr.Expr;
import org.jaxen.expr.XPathExpr;
import org.jaxen.function.BooleanFunction;
import org.jaxen.function.NumberFunction;
import org.jaxen.function.StringFunction;
import org.jaxen.saxpath.SAXPathException;
import org.jaxen.saxpath.XPathReader;
import org.jaxen.saxpath.helpers.XPathReaderFactory;
import org.jaxen.util.SingletonList;
/** Base functionality for all concrete, implementation-specific XPaths.
*
*
* This class provides generic functionality for further-defined
* implementation-specific XPaths.
*
*
*
* If you want to adapt the Jaxen engine to traverse your own
* object model, then this is a good base class to derive from.
* Typically you only really need to provide your own
* {@link org.jaxen.Navigator} implementation.
*
*
* @see org.jaxen.dom4j.Dom4jXPath XPath for dom4j
* @see org.jaxen.jdom.JDOMXPath XPath for JDOM
* @see org.jaxen.dom.DOMXPath XPath for W3C DOM
*
* @author bob mcwhirter
* @author James Strachan
*/
public class BaseXPath implements XPath, Serializable
{
private static final long serialVersionUID = -1993731281300293168L;
/** Original expression text. */
private final String exprText;
/** the parsed form of the XPath expression */
private final XPathExpr xpath;
/** the support information and function, namespace and variable contexts */
private ContextSupport support;
/** the implementation-specific Navigator for retrieving XML nodes **/
private Navigator navigator;
/** Construct given an XPath expression string.
*
* @param xpathExpr the XPath expression
*
* @throws JaxenException if there is a syntax error while
* parsing the expression
*/
protected BaseXPath(String xpathExpr) throws JaxenException
{
try
{
XPathReader reader = XPathReaderFactory.createReader();
JaxenHandler handler = new JaxenHandler();
reader.setXPathHandler( handler );
reader.parse( xpathExpr );
this.xpath = handler.getXPathExpr();
}
catch (org.jaxen.saxpath.XPathSyntaxException e)
{
throw new org.jaxen.XPathSyntaxException( e );
}
catch (SAXPathException e)
{
throw new JaxenException( e );
}
this.exprText = xpathExpr;
}
/** Construct given an XPath expression string.
*
* @param xpathExpr the XPath expression
*
* @param navigator the XML navigator to use
*
* @throws JaxenException if there is a syntax error while
* parsing the expression
*/
public BaseXPath(String xpathExpr, Navigator navigator) throws JaxenException
{
this( xpathExpr );
this.navigator = navigator;
}
/** Evaluate this XPath against a given context.
* The context of evaluation may be any object type
* the navigator recognizes as a node.
* The return value is either a String
,
* Double
, Boolean
, or List
* of nodes.
*
*
* When using this method, one must be careful to
* test the class of the returned object. If the returned
* object is a list, then the items in this
* list will be the actual Document
,
* Element
, Attribute
, etc. objects
* as defined by the concrete XML object-model implementation,
* directly from the context document. This method does
* not return copies of anything, but merely
* returns references to objects within the source document.
*
*
* @param context the node, node-set or Context object for evaluation.
* This value can be null.
*
* @return the result of evaluating the XPath expression
* against the supplied context
* @throws JaxenException if an XPath error occurs during expression evaluation
* @throws ClassCastException if the context is not a node
*/
public Object evaluate(Object context) throws JaxenException
{
List answer = selectNodes(context);
if ( answer != null
&&
answer.size() == 1 )
{
Object first = answer.get(0);
if ( first instanceof String
||
first instanceof Number
||
first instanceof Boolean )
{
return first;
}
}
return answer;
}
/**
* List all the nodes selected by this XPath
* expression. If multiple nodes match, multiple nodes
* are returned. Nodes are returned
* in document-order, as defined by the XPath
* specification. If the expression selects a non-node-set
* (i.e. a number, boolean, or string) then a List
* containing just that one object is returned.
*
* @param node the node, node-set or Context object for evaluation.
* This value can be null.
*
* @return the node-set of all items selected by this XPath expression
* @throws JaxenException if an XPath error occurs during expression evaluation
*
* @see #selectNodesForContext
*/
public List selectNodes(Object node) throws JaxenException
{
Context context = getContext( node );
return selectNodesForContext( context );
}
/**
* Return the first node selected by this XPath
* expression. If multiple nodes match, only one node is
* returned. The selected node will be the first
* selected node in document-order, as defined by the XPath
* specification.
*
* @param node the node, node-set or Context object for evaluation.
* This value can be null.
*
* @return the node-set of all items selected
* by this XPath expression
* @throws JaxenException if an XPath error occurs during expression evaluation
*
* @see #selectNodes
*/
public Object selectSingleNode(Object node) throws JaxenException
{
List results = selectNodes( node );
if ( results.isEmpty() )
{
return null;
}
return results.get( 0 );
}
/**
* Returns the XPath string-value of the argument node.
*
* @param node the node whose value to take
* @return the XPath string value of this node
* @throws JaxenException if an XPath error occurs during expression evaluation
* @deprecated replaced by {@link #stringValueOf}
*/
public String valueOf(Object node) throws JaxenException
{
return stringValueOf( node );
}
/** Retrieves the string-value of the result of
* evaluating this XPath expression when evaluated
* against the specified context.
*
*
* The string-value of the expression is determined per
* the string(..)
core function defined
* in the XPath specification. This means that an expression
* that selects zero nodes will return the empty string,
* while an expression that selects one-or-more nodes will
* return the string-value of the first node.
*
*
* @param node the node, node-set or Context object for evaluation. This value can be null.
*
* @return the string-value of the result of evaluating this expression with the specified context node
* @throws JaxenException if an XPath error occurs during expression evaluation
*/
public String stringValueOf(Object node) throws JaxenException
{
Context context = getContext( node );
Object result = selectSingleNodeForContext( context );
if ( result == null )
{
return "";
}
return StringFunction.evaluate( result,
context.getNavigator() );
}
/** Retrieve a boolean-value interpretation of this XPath
* expression when evaluated against a given context.
*
*
* The boolean-value of the expression is determined per
* the boolean(..)
function defined
* in the XPath specification. This means that an expression
* that selects zero nodes will return false
,
* while an expression that selects one or more nodes will
* return true
.
*
*
* @param node the node, node-set or Context object for evaluation. This value can be null.
*
* @return the boolean-value of the result of evaluating this expression with the specified context node
* @throws JaxenException if an XPath error occurs during expression evaluation
*/
public boolean booleanValueOf(Object node) throws JaxenException
{
Context context = getContext( node );
List result = selectNodesForContext( context );
if ( result == null ) return false;
return BooleanFunction.evaluate( result, context.getNavigator() ).booleanValue();
}
/** Retrieve a number-value interpretation of this XPath
* expression when evaluated against a given context.
*
*
* The number-value of the expression is determined per
* the number(..)
core function as defined
* in the XPath specification. This means that if this
* expression selects multiple nodes, the number-value
* of the first node is returned.
*
*
* @param node the node, node-set or Context object for evaluation. This value can be null.
*
* @return a Double
indicating the numeric value of
* evaluating this expression against the specified context
* @throws JaxenException if an XPath error occurs during expression evaluation
*/
public Number numberValueOf(Object node) throws JaxenException
{
Context context = getContext( node );
Object result = selectSingleNodeForContext( context );
return NumberFunction.evaluate( result,
context.getNavigator() );
}
// Helpers
/** Add a namespace prefix-to-URI mapping for this XPath
* expression.
*
*
* Namespace prefix-to-URI mappings in an XPath are independent
* of those used within any document. Only the mapping explicitly
* added to this XPath will be available for resolving the
* XPath expression.
*
*
*
* This is a convenience method for adding mappings to the
* default {@link NamespaceContext} in place for this XPath.
* If you have installed a custom NamespaceContext
* that is not a SimpleNamespaceContext
,
* then this method will throw a JaxenException
.
*
*
* @param prefix the namespace prefix
* @param uri the namespace URI
*
* @throws JaxenException if the NamespaceContext
* used by this XPath is not a SimpleNamespaceContext
*/
public void addNamespace(String prefix,
String uri) throws JaxenException
{
NamespaceContext nsContext = getNamespaceContext();
if ( nsContext instanceof SimpleNamespaceContext )
{
((SimpleNamespaceContext)nsContext).addNamespace( prefix,
uri );
return;
}
throw new JaxenException("Operation not permitted while using a non-simple namespace context.");
}
// ------------------------------------------------------------
// ------------------------------------------------------------
// Properties
// ------------------------------------------------------------
// ------------------------------------------------------------
/** Set a NamespaceContext
for use with this
* XPath expression.
*
*
* A NamespaceContext
is responsible for translating
* namespace prefixes within the expression into namespace URIs.
*
*
* @param namespaceContext the NamespaceContext
to
* install for this expression
*
* @see NamespaceContext
* @see NamespaceContext#translateNamespacePrefixToUri
*/
public void setNamespaceContext(NamespaceContext namespaceContext)
{
getContextSupport().setNamespaceContext(namespaceContext);
}
/** Set a FunctionContext
for use with this XPath
* expression.
*
*
* A FunctionContext
is responsible for resolving
* all function calls used within the expression.
*
*
* @param functionContext the FunctionContext
to
* install for this expression
*
* @see FunctionContext
* @see FunctionContext#getFunction
*/
public void setFunctionContext(FunctionContext functionContext)
{
getContextSupport().setFunctionContext(functionContext);
}
/** Set a VariableContext
for use with this XPath
* expression.
*
*
* A VariableContext
is responsible for resolving
* all variables referenced within the expression.
*
*
* @param variableContext The VariableContext
to
* install for this expression
*
* @see VariableContext
* @see VariableContext#getVariableValue
*/
public void setVariableContext(VariableContext variableContext)
{
getContextSupport().setVariableContext(variableContext);
}
/** Retrieve the NamespaceContext
used by this XPath
* expression.
*
*
* A NamespaceContext
is responsible for mapping
* prefixes used within the expression to namespace URIs.
*
*
*
* If this XPath expression has not previously had a NamespaceContext
* installed, a new default NamespaceContext
will be created,
* installed and returned.
*
*
* @return the NamespaceContext
used by this expression
*
* @see NamespaceContext
*/
public NamespaceContext getNamespaceContext()
{
return getContextSupport().getNamespaceContext();
}
/** Retrieve the FunctionContext
used by this XPath
* expression.
*
*
* A FunctionContext
is responsible for resolving
* all function calls used within the expression.
*
*
*
* If this XPath expression has not previously had a FunctionContext
* installed, a new default FunctionContext
will be created,
* installed and returned.
*
*
* @return the FunctionContext
used by this expression
*
* @see FunctionContext
*/
public FunctionContext getFunctionContext()
{
return getContextSupport().getFunctionContext();
}
/** Retrieve the VariableContext
used by this XPath
* expression.
*
*
* A VariableContext
is responsible for resolving
* all variables referenced within the expression.
*
*
*
* If this XPath expression has not previously had a VariableContext
* installed, a new default VariableContext
will be created,
* installed and returned.
*
*
* @return the VariableContext
used by this expression
*
* @see VariableContext
*/
public VariableContext getVariableContext()
{
return getContextSupport().getVariableContext();
}
/** Retrieve the root expression of the internal
* compiled form of this XPath expression.
*
*
* Internally, Jaxen maintains a form of Abstract Syntax
* Tree (AST) to represent the structure of the XPath expression.
* This is normally not required during normal consumer-grade
* usage of Jaxen. This method is provided for hard-core users
* who wish to manipulate or inspect a tree-based version of
* the expression.
*
*
* @return the root of the AST of this expression
*/
public Expr getRootExpr()
{
return xpath.getRootExpr();
}
/** Return the original expression text.
*
* @return the normalized XPath expression string
*/
public String toString()
{
return this.exprText;
}
/** Returns a string representation of the parse tree.
*
* @return a string representation of the parse tree.
*/
public String debug()
{
return this.xpath.toString();
}
// ------------------------------------------------------------
// ------------------------------------------------------------
// Implementation methods
// ------------------------------------------------------------
// ------------------------------------------------------------
/** Create a {@link Context} wrapper for the provided
* implementation-specific object.
*
* @param node the implementation-specific object
* to be used as the context
*
* @return a Context
wrapper around the object
*/
protected Context getContext(Object node)
{
if ( node instanceof Context )
{
return (Context) node;
}
Context fullContext = new Context( getContextSupport() );
if ( node instanceof List )
{
fullContext.setNodeSet( (List) node );
}
else
{
List list = new SingletonList(node);
fullContext.setNodeSet( list );
}
return fullContext;
}
/** Retrieve the {@link ContextSupport} aggregation of
* NamespaceContext
, FunctionContext
,
* VariableContext
, and {@link Navigator}.
*
* @return aggregate ContextSupport
for this
* XPath expression
*/
protected ContextSupport getContextSupport()
{
if ( support == null )
{
support = new ContextSupport(
createNamespaceContext(),
createFunctionContext(),
createVariableContext(),
getNavigator()
);
}
return support;
}
/** Retrieve the XML object-model-specific {@link Navigator}
* for us in evaluating this XPath expression.
*
* @return the implementation-specific Navigator
*/
public Navigator getNavigator()
{
return navigator;
}
// ------------------------------------------------------------
// ------------------------------------------------------------
// Factory methods for default contexts
// ------------------------------------------------------------
// ------------------------------------------------------------
/** Create a default FunctionContext
.
*
* @return a default FunctionContext
*/
protected FunctionContext createFunctionContext()
{
return XPathFunctionContext.getInstance();
}
/** Create a default NamespaceContext
.
*
* @return a default NamespaceContext
instance
*/
protected NamespaceContext createNamespaceContext()
{
return new SimpleNamespaceContext();
}
/** Create a default VariableContext
.
*
* @return a default VariableContext
instance
*/
protected VariableContext createVariableContext()
{
return new SimpleVariableContext();
}
/** Select all nodes that match this XPath
* expression on the given Context object.
* If multiple nodes match, multiple nodes
* will be returned in document-order, as defined by the XPath
* specification. If the expression selects a non-node-set
* (i.e. a number, boolean, or string) then a List
* containing just that one object is returned.
*
* @param context the Context which gets evaluated
*
* @return the node-set of all items selected
* by this XPath expression
* @throws JaxenException if an XPath error occurs during expression evaluation
*
*/
protected List selectNodesForContext(Context context) throws JaxenException
{
List list = this.xpath.asList( context );
return list;
}
/** Return only the first node that is selected by this XPath
* expression. If multiple nodes match, only one node will be
* returned. The selected node will be the first
* selected node in document-order, as defined by the XPath
* specification. If the XPath expression selects a double,
* String, or boolean, then that object is returned.
*
* @param context the Context against which this expression is evaluated
*
* @return the first node in document order of all nodes selected
* by this XPath expression
* @throws JaxenException if an XPath error occurs during expression evaluation
*
* @see #selectNodesForContext
*/
protected Object selectSingleNodeForContext(Context context) throws JaxenException
{
List results = selectNodesForContext(context);
if ( results.isEmpty() )
{
return null;
}
return results.get( 0 );
}
}