All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.xpath.XPathExpressionImpl Maven / Gradle / Ivy

There is a newer version: 10.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.xpath;

import net.sf.saxon.Configuration;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.instruct.Executable;
import net.sf.saxon.expr.instruct.SlotManager;
import net.sf.saxon.expr.sort.AtomicComparer;
import net.sf.saxon.expr.sort.SortKeyDefinition;
import net.sf.saxon.expr.sort.SortKeyEvaluator;
import net.sf.saxon.expr.sort.SortedIterator;
import net.sf.saxon.functions.NumberFn;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.NodeTest;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.wrapper.VirtualNode;
import net.sf.saxon.value.*;
import org.xml.sax.InputSource;

import javax.xml.namespace.QName;
import javax.xml.transform.sax.SAXSource;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import java.util.List;

/**
  * 

The JAXP XPathExpression interface represents a compiled XPath expression that can be repeatedly * evaluated. This class is Saxon's implementation of that interface.

* *

The class also includes some methods retained from Saxon's original XPath API. When these methods * are used, the object contains the context node and other state, so it is not thread-safe.

* * @author Michael H. Kay */ public class XPathExpressionImpl implements XPathExpression, SortKeyEvaluator { private Configuration config; private Executable executable; private Expression expression; private Expression atomizer; /*@Nullable*/ private NodeInfo contextNode; private SlotManager stackFrameMap; /*@Nullable*/ private XPathExpressionImpl sortKey = null; /** * The constructor is protected, to ensure that instances can only be * created using the createExpression() method of XPathEvaluator * @param exp the compiled expression * @param exec the executable */ protected XPathExpressionImpl(Expression exp, /*@NotNull*/ Executable exec) { expression = exp; executable = exec; config = exec.getConfiguration(); } /** * Define the number of slots needed for local variables within the expression. * This method is for internal use only. * @param map description of the stack frame */ protected void setStackFrameMap(SlotManager map) { stackFrameMap = map; } /** * Get the stack frame map. This holds information about the allocation of slots to variables. * This is needed by applications using low-level interfaces for evaluating the expression * @return a description of the stack frame */ public SlotManager getStackFrameMap() { return stackFrameMap; } /** * Get the Configuration under which this XPath expression was compiled * @return the Saxon configuration */ public Configuration getConfiguration() { return config; } /** * Define the sort order for the results of the expression. If this method is called, then * the list returned by a subsequent call on the evaluate() method will first be sorted. * @param sortKey an XPathExpression, which will be applied to each item in the sequence; * the result of this expression determines the ordering of the list returned by the evaluate() * method. The sortKey can be null, to clear a previous sort key. Note that the expression is * not automatically atomized; if it selects nodes, these should be explicitly converted to * atomic values by calling the string() or data() functions. * @deprecated since 9.0. This method is not present in the JAXP interface. The recommended * way to get a sorted result is to use XQuery instead of XPath. */ public void setSortKey(XPathExpressionImpl sortKey) { this.sortKey = sortKey; } /** * Set the context node for evaluating the expression. If this method is not called, * the context node will be the root of the document to which the prepared expression is * bound. * @param node the context node * @deprecated since 9.0. Using this method is not thread-safe. Use a method instead * such as {@link #evaluate(Object, QName)} that allows the context node to be specified * as a parameter to the call. */ public void setContextNode(/*@Nullable*/ NodeInfo node) { if (node==null) { throw new NullPointerException("Context node cannot be null"); } if (node.getConfiguration() != config) { throw new IllegalArgumentException("Supplied node uses the wrong Configuration"); } contextNode = node; } /** * Protected, undeprecated version of setContextNode() for use by deprecated paths within the package * (exists to avoid deprecation warnings when compiling Saxon) * @param node the context node */ protected void privatelySetContextNode(/*@Nullable*/ NodeInfo node) { if (node==null) { throw new NullPointerException("Context node cannot be null"); } if (node.getConfiguration() != config) { throw new IllegalArgumentException("Supplied node uses the wrong Configuration"); } contextNode = node; } /** * Execute a prepared XPath expression, returning the results as a List. The context * node must have been set previously using {@link #setContextNode(net.sf.saxon.om.NodeInfo)}. * @return The results of the expression, as a List. The List represents the sequence * of items returned by the expression. Each item in the list will either be an instance * of net.sf.saxon.om.NodeInfo, representing a node, or a Java object representing an atomic value. * For the types of Java object that may be returned, see {@link #evaluate(Object, javax.xml.namespace.QName)} * with the second argument set to NODESET. * @deprecated since 9.0. This method is not present in the JAXP interface. Either use * the JAXP methods such as {@link #evaluate(Object, QName)}, or use the Saxon XPath * API instead of JAXP. */ /*@NotNull*/ public List evaluate() throws XPathException { XPathContextMajor context = new XPathContextMajor(contextNode, executable); context.openStackFrame(stackFrameMap); SequenceIterator iter = expression.iterate(context); SequenceExtent extent = new SequenceExtent(iter); return (List)PJConverter.ToCollection.INSTANCE.convert(extent, List.class, context); } /** * Execute a prepared XPath expression, returning the first item in the result. * This is useful where it is known that the expression will only return * a singleton value (for example, a single node, or a boolean). The context node * must be set previously using {@link #setContextNode(net.sf.saxon.om.NodeInfo)}. * @return The first item in the sequence returned by the expression. If the expression * returns an empty sequence, this method returns null. Otherwise, it returns the first * item in the result sequence, represented as a Java object using the same mapping as for * the evaluate() method * @deprecated since 9.0. This method is not present in the JAXP interface. Either use * the JAXP methods such as {@link #evaluate(Object, QName)}, or use the Saxon XPath * API instead of JAXP. */ /*@Nullable*/ public Object evaluateSingle() throws XPathException { XPathContextMajor context = new XPathContextMajor(contextNode, executable); context.openStackFrame(stackFrameMap); SequenceIterator iterator = expression.iterate(context); Item item = iterator.next(); if (item == null) { return null; } else { return SequenceTool.convertToJava(item); } } /** * Get a raw iterator over the results of the expression. This returns results without * any conversion of the returned items to "native" Java classes. This method is intended * for use by applications that need to process the results of the expression using * internal Saxon interfaces. * @param contextItem the context item for evaluating the expression * @return an iterator over the results of the expression, with no conversion of returned items * @since 9.0 */ /*@Nullable*/ public SequenceIterator rawIterator(Item contextItem) throws XPathException { XPathContextMajor context = new XPathContextMajor(contextItem, executable); return rawIterator(context); } /*@Nullable*/ private SequenceIterator rawIterator(/*@NotNull*/ XPathContextMajor context) throws XPathException { context.openStackFrame(stackFrameMap); SequenceIterator iterator = expression.iterate(context); if (sortKey != null) { Expression key = sortKey.expression; if (key.getItemType(config.getTypeHierarchy()) instanceof NodeTest) { sortKey.expression = Atomizer.makeAtomizer(key); } SortKeyDefinition sk = new SortKeyDefinition(); sk.setSortKey(sortKey.expression, true); AtomicComparer comp = sk.makeComparator(context); AtomicComparer[] comps = {comp}; iterator = new SortedIterator(context, iterator, this, comps, true); ((SortedIterator)iterator).setHostLanguage(Configuration.XPATH); } return iterator; } /** * JAXP 1.3 evaluate() method * @param node The context node. This must use a representation of nodes that this implementation understands. * This may be a Saxon NodeInfo, or a node in one of the external object models supported, for example * DOM, DOM4J, JDOM, or XOM, provided the support module for that object model is loaded. * *

Contrary to the interface specification, Saxon does not supply an empty * document when the value is null. This is because Saxon supports multiple object models, * and it's unclear what kind of document node would be appropriate. Instead, Saxon uses * the node supplied to the {@link #setContextNode} method if available, and if none * is available, executes the XPath expression with the context item undefined.

*

Saxon does not allow a NodeList to be supplied for this parameter. It's not clear * what this would be intended to mean.

* @param qName Indicates the type of result required. This must be one of the constants defined in * the JAXP {@link XPathConstants} class. * Saxon will attempt to convert the actual result of the expression to the required type using the * XPath 1.0 conversion rules. * @return the result of the evaluation, as a Java object of the appropriate type. Saxon interprets the * rules as follows: * * * * * * * * * * * * *
QNameReturn Value
BOOLEANThe effective boolean value of the actual result, * as a Java Boolean object
STRINGThe result of applying the string() function to the actual result, * as a Java String object
NUMBERThe result of applying the number() function to the actual result, * as a Java Double object
NODEA single node, in the native data model supplied as input. If the * expression returns more than one node, the first is returned. If * the expression returns an empty sequence, null is returned. If the * expression returns an atomic value, or if the first item in the * result sequence is an atomic value, an exception is thrown.
NODESETThis is interpreted as allowing any sequence, of nodes or atomic values. * If the first argument is a wrapper around a DOM Node, then the result is * returned as a DOM NodeList, and an exception is then thrown if the result sequence * contains a value that is not a DOM Node. In all other cases * the result is returned as a Java List object, unless it is empty, in which * case null is returned. The contents of the list may be node objects (in the * native data model supplied as input), or Java objects representing the XPath * atomic values in the actual result: String for an xs:string, Double for a xs:double, * Long for an xs:integer, and so on. (For safety, cast the values to a type * such as xs:string within the XPath expression).
* * @throws XPathExpressionException if evaluation of the expression fails or if the * result cannot be converted to the requested type. */ /*@Nullable*/ public Object evaluate(/*@Nullable*/ Object node, /*@NotNull*/ QName qName) throws XPathExpressionException { NodeInfo contextNode = this.contextNode; if (node != null) { if (node instanceof SingletonItem) { node = ((SingletonItem)node).asItem(); } if (node instanceof NodeInfo) { if (!((NodeInfo)node).getConfiguration().isCompatible(config)) { throw new XPathExpressionException( "Supplied node must be built using the same or a compatible Configuration"); } if (node instanceof DocumentInfo && ((DocumentInfo)node).isTyped() && !executable.isSchemaAware()) { throw new XPathExpressionException( "The expression was compiled to handled untyped data, but the input is typed"); } contextNode = ((NodeInfo)node); } else { JPConverter converter = JPConverter.allocate(node.getClass(), config); Sequence val; try { val = converter.convert(node, new EarlyEvaluationContext(config, null)); } catch (XPathException e) { throw new XPathExpressionException( "Failure converting a node of class " + node.getClass().getName() + ": " + e.getMessage()); } if (val instanceof NodeInfo) { if (!((NodeInfo)val).getConfiguration().isCompatible(config)) { throw new XPathExpressionException( "Supplied node must be built using the same or a compatible Configuration"); } if (node instanceof DocumentInfo && ((DocumentInfo)node).isTyped() && !executable.isSchemaAware()) { throw new XPathExpressionException( "The expression was compiled to handled untyped data, but the input is typed"); } contextNode = (NodeInfo)val; } else { throw new XPathExpressionException( "Cannot locate an object model implementation for nodes of class " + node.getClass().getName()); } } } XPathContextMajor context = new XPathContextMajor(contextNode, executable); context.openStackFrame(stackFrameMap); try { if (qName.equals(XPathConstants.BOOLEAN)) { return Boolean.valueOf(expression.effectiveBooleanValue(context)); } else if (qName.equals(XPathConstants.STRING)) { SequenceIterator iter = expression.iterate(context); Item first = iter.next(); if (first == null) { return ""; } return first.getStringValue(); } else if (qName.equals(XPathConstants.NUMBER)) { if (atomizer == null) { atomizer = Atomizer.makeAtomizer(expression); } SequenceIterator iter = atomizer.iterate(context); Item first = iter.next(); if (first == null) { return new Double(Double.NaN); } if (first instanceof NumericValue) { return new Double(((NumericValue)first).getDoubleValue()); } else { DoubleValue v = NumberFn.convert((AtomicValue)first, getConfiguration()); return new Double(v.getDoubleValue()); } } else if (qName.equals(XPathConstants.NODE)) { SequenceIterator iter = expression.iterate(context); Item first = iter.next(); if (first instanceof VirtualNode) { return ((VirtualNode)first).getRealNode(); } if (first == null || first instanceof NodeInfo) { return first; } throw new XPathExpressionException("Expression result is not a node"); } else if (qName.equals(XPathConstants.NODESET)) { //SequenceIterator iter = expression.iterate(context); SequenceIterator iter = rawIterator(context); SequenceExtent extent = new SequenceExtent(iter); PJConverter converter = PJConverter.allocateNodeListCreator(config, node); return converter.convert(extent, Object.class, context); } else { throw new IllegalArgumentException("qName: Unknown type for expected result"); } } catch (XPathException e) { throw new XPathExpressionException(e); } } /** * Evaluate the expression to return a string value * @param node the initial context node. This must be either an instance of NodeInfo or a node * recognized by a known external object model. *

Contrary to the interface specification, Saxon does not supply an empty * document when the value is null. This is because Saxon supports multiple object models, * and it's unclear what kind of document node would be appropriate. Instead, Saxon uses * the node supplied to the {@link #setContextNode} method if available, and if none * is available, executes the XPath expression with the context item undefined.

* @return the results of the expression, converted to a String * @throws XPathExpressionException if evaluation fails */ /*@NotNull*/ public String evaluate(Object node) throws XPathExpressionException { return (String)evaluate(node, XPathConstants.STRING); } /** * Evaluate the XPath expression against an input source to obtain a result of a specified type * @param inputSource The input source document against which the expression is evaluated. * (Note that there is no caching. This will be parsed, and the parsed result will be discarded.) * If the supplied value is null then (contrary to the JAXP specifications), the XPath expression * is evaluated with the context item undefined. * @param qName The type required, identified by a constant in {@link XPathConstants} * @return the result of the evaluation, as a Java object of the appropriate type: * see {@link #evaluate(Object, javax.xml.namespace.QName)} * @throws XPathExpressionException */ /*@Nullable*/ public Object evaluate(/*@Nullable*/ InputSource inputSource, /*@Nullable*/ QName qName) throws XPathExpressionException { if (qName == null) { throw new NullPointerException("qName"); } try { NodeInfo doc = null; if (inputSource != null) { doc = config.buildDocument(new SAXSource(inputSource)); } return evaluate(doc, qName); } catch (XPathException e) { throw new XPathExpressionException(e); } } /** * Evaluate the XPath expression against an input source to obtain a string result * @param inputSource The input source document against which the expression is evaluated. * (Note that there is no caching. This will be parsed, and the parsed result will be discarded.) * @return the result of the evaluation, converted to a String * @throws XPathExpressionException in the event of an XPath dynamic error * @throws NullPointerException If inputSource is null. */ /*@NotNull*/ public String evaluate(/*@Nullable*/ InputSource inputSource) throws XPathExpressionException { if (inputSource == null) { throw new NullPointerException("inputSource"); } try { NodeInfo doc = config.buildDocument(new SAXSource(inputSource)); return (String)evaluate(doc, XPathConstants.STRING); } catch (XPathException e) { throw new XPathExpressionException(e); } } /** * Callback for evaluating the sort keys. For internal use only. */ /*@Nullable*/ public AtomicValue evaluateSortKey(int n, XPathContext c) throws XPathException { return (AtomicValue)sortKey.getInternalExpression().evaluateItem(c); } /** * Low-level method to get the internal Saxon expression object. This exposes a wide range of * internal methods that may be needed by specialized applications, and allows greater control * over the dynamic context for evaluating the expression. * @return the underlying Saxon expression object. */ public Expression getInternalExpression() { return expression; } }