001: package net.sf.saxon.xpath;
002:
003: import net.sf.saxon.Configuration;
004: import net.sf.saxon.expr.Atomizer;
005: import net.sf.saxon.expr.Expression;
006: import net.sf.saxon.expr.XPathContextMajor;
007: import net.sf.saxon.functions.NumberFn;
008: import net.sf.saxon.instruct.SlotManager;
009: import net.sf.saxon.om.*;
010: import net.sf.saxon.pattern.NodeTest;
011: import net.sf.saxon.sort.FixedSortKeyDefinition;
012: import net.sf.saxon.sort.SortedIterator;
013: import net.sf.saxon.trans.XPathException;
014: import net.sf.saxon.value.*;
015: import org.xml.sax.InputSource;
016:
017: import javax.xml.namespace.QName;
018: import javax.xml.transform.sax.SAXSource;
019: import javax.xml.xpath.XPathConstants;
020: import javax.xml.xpath.XPathExpression;
021: import javax.xml.xpath.XPathExpressionException;
022: import java.util.List;
023:
024: /**
025: * <p>The JAXP XPathExpression interface represents a compiled XPath expression that can be repeatedly
026: * evaluated. This class is Saxon's implementation of that interface.</p>
027: *
028: * <p>The class also includes some methods retained from Saxon's original XPath API. When these methods
029: * are used, the object contains the context node and other state, so it is not thread-safe.</p>
030: *
031: * @author Michael H. Kay
032: */
033:
034: public class XPathExpressionImpl implements XPathExpression {
035:
036: private Configuration config;
037: private Expression expression;
038: private NodeInfo contextNode;
039: private SlotManager stackFrameMap;
040: private XPathExpressionImpl sortKey = null;
041:
042: /**
043: * The constructor is protected, to ensure that instances can only be
044: * created using the createExpression() method of XPathEvaluator
045: */
046:
047: protected XPathExpressionImpl(Expression exp, Configuration config) {
048: expression = exp;
049: this .config = config;
050: }
051:
052: /**
053: * Define the number of slots needed for local variables within the expression.
054: * This method is for internal use only.
055: */
056:
057: protected void setStackFrameMap(SlotManager map) {
058: stackFrameMap = map;
059: }
060:
061: /**
062: * Define the sort order for the results of the expression. If this method is called, then
063: * the list returned by a subsequent call on the evaluate() method will first be sorted.
064: * @param sortKey an XPathExpression, which will be applied to each item in the sequence;
065: * the result of this expression determines the ordering of the list returned by the evaluate()
066: * method. The sortKey can be null, to clear a previous sort key.
067: */
068:
069: public void setSortKey(XPathExpressionImpl sortKey) {
070: this .sortKey = sortKey;
071: }
072:
073: /**
074: * Set the context node for evaluating the expression. If this method is not called,
075: * the context node will be the root of the document to which the prepared expression is
076: * bound.
077: */
078:
079: public void setContextNode(NodeInfo node) {
080: if (node == null) {
081: throw new NullPointerException(
082: "Context node cannot be null");
083: }
084: if (node.getNamePool() != config.getNamePool()) {
085: throw new IllegalArgumentException(
086: "Supplied node uses the wrong NamePool");
087: }
088: contextNode = node;
089: }
090:
091: /**
092: * Execute a prepared XPath expression, returning the results as a List. The context
093: * node must have been set previously using {@link #setContextNode(net.sf.saxon.om.NodeInfo)}.
094: * @return The results of the expression, as a List. The List represents the sequence
095: * of items returned by the expression. Each item in the list will either be an instance
096: * of net.sf.saxon.om.NodeInfo, representing a node, or a Java object representing an atomic value.
097: * For the types of Java object that may be returned, see {@link #evaluate(Object, javax.xml.namespace.QName)}
098: * with the second argument set to NODESET.
099: */
100:
101: public List evaluate() throws XPathException {
102: XPathContextMajor context = new XPathContextMajor(contextNode,
103: config);
104: context.openStackFrame(stackFrameMap);
105: SequenceIterator iter = expression.iterate(context);
106: SequenceExtent extent = new SequenceExtent(iter);
107: return (List) extent.convertToJava(Object.class, context);
108: }
109:
110: /**
111: * Execute a prepared XPath expression, returning the first item in the result.
112: * This is useful where it is known that the expression will only return
113: * a singleton value (for example, a single node, or a boolean). The context node
114: * must be set previously using {@link #setContextNode(net.sf.saxon.om.NodeInfo)}.
115: * @return The first item in the sequence returned by the expression. If the expression
116: * returns an empty sequence, this method returns null. Otherwise, it returns the first
117: * item in the result sequence, represented as a Java object using the same mapping as for
118: * the evaluate() method
119: */
120:
121: public Object evaluateSingle() throws XPathException {
122: XPathContextMajor context = new XPathContextMajor(contextNode,
123: config);
124: context.openStackFrame(stackFrameMap);
125: SequenceIterator iterator = expression.iterate(context);
126: Item item = iterator.next();
127: if (item == null) {
128: return null;
129: } else {
130: return Value.convert(item);
131: }
132: }
133:
134: /**
135: * Get a raw iterator over the results of the expression. This returns results without
136: * any conversion of the returned items to "native" Java classes. This method is intended
137: * for use by applications that need to process the results of the expression using
138: * internal Saxon interfaces.
139: */
140:
141: public SequenceIterator rawIterator() throws XPathException {
142: XPathContextMajor context = new XPathContextMajor(contextNode,
143: config);
144: context.openStackFrame(stackFrameMap);
145: SequenceIterator iterator = expression.iterate(context);
146: if (sortKey != null) {
147: Expression key = sortKey.expression;
148: if (key
149: .getItemType(config.getNamePool()
150: .getTypeHierarchy()) instanceof NodeTest) {
151: key = new Atomizer(key, config);
152: }
153:
154: FixedSortKeyDefinition[] sk = new FixedSortKeyDefinition[1];
155:
156: sk[0] = new FixedSortKeyDefinition();
157: sk[0].setSortKey(key);
158: sk[0].bindComparer(context);
159:
160: iterator = new SortedIterator(context, iterator, sk);
161: }
162: return iterator;
163: }
164:
165: /**
166: * JAXP 1.3 evaluate() method
167: * @param node The context node. This must use a representation of nodes that this implementation understands.
168: * This may be a Saxon NodeInfo, or a node in one of the external object models supported, for example
169: * DOM, JDOM, or XOM, provided the support module for that object model is loaded.
170: * @param qName Indicates the type of result required. This must be one of the constants defined in
171: * the JAXP {@link XPathConstants} class.
172: * Saxon will attempt to convert the actual result of the expression to the required type using the
173: * XPath 1.0 conversion rules.
174: * @return the result of the evaluation, as a Java object of the appropriate type. Saxon interprets the
175: * rules as follows:
176: * <table>
177: * <thead><tr><td>QName</td><td>Return Value</td></thead>
178: * <tbody>
179: * <tr><td>BOOLEAN</td>
180: * <td>The effective boolean value of the actual result,
181: * as a Java Boolean object</td></tr>
182: * <tr><td>STRING</td>
183: * <td>The result of applying the string() function to the actual result,
184: * as a Java String object</td></tr>
185: * <tr><td>NUMBER</td>
186: * <td>The result of applying the number() function to the actual result,
187: * as a Java Double object</td></tr>
188: * <tr><td>NODE</td>
189: * <td>A single node, in the native data model supplied as input. If the
190: * expression returns more than one node, the first is returned. If
191: * the expression returns an empty sequence, null is returned. If the
192: * expression returns an atomic value, or if the first item in the
193: * result sequence is an atomic value, an exception is thrown.</td></tr>
194: * <tr><td>NODESET</td>
195: * <td>This is interpreted as allowing any sequence, of nodes or atomic values.
196: * The result is returned as a Java List object, unless it is empty, in which
197: * case null is returned. The contents of the list may be node objects (in the
198: * native data model supplied as input, or Java objects representing the XPath
199: * atomic values in the actual result: String for an xs:string, Double for a xs:double,
200: * Long for an xs:integer, and so on. (For safety, cast the values to a type
201: * such as xs:string within the XPath expression). Note that the result is never
202: * returned as a DOM NodeList.</td></tr></table>
203: *
204: * @throws XPathExpressionException if evaluation of the expression fails or if the
205: * result cannot be converted to the requested type.
206: */
207: public Object evaluate(Object node, QName qName)
208: throws XPathExpressionException {
209: ExternalObjectModel model = null;
210: if (node instanceof NodeInfo) {
211: setContextNode((NodeInfo) node);
212: } else {
213: model = config.findExternalObjectModel(node);
214: if (model == null) {
215: throw new XPathExpressionException(
216: "Cannot locate an object model implementation for nodes of class "
217: + node.getClass().getName());
218: }
219: DocumentInfo doc = model.wrapDocument(node, "", config);
220: NodeInfo startNode = model.wrapNode(doc, node);
221: setContextNode(startNode);
222: }
223: XPathContextMajor context = new XPathContextMajor(contextNode,
224: config);
225: context.openStackFrame(stackFrameMap);
226: try {
227: if (qName.equals(XPathConstants.BOOLEAN)) {
228: return Boolean.valueOf(expression
229: .effectiveBooleanValue(context));
230: } else if (qName.equals(XPathConstants.STRING)) {
231: SequenceIterator iter = expression.iterate(context);
232:
233: Item first = iter.next();
234: if (first == null) {
235: return "";
236: }
237: return first.getStringValue();
238:
239: } else if (qName.equals(XPathConstants.NUMBER)) {
240: SequenceIterator iter = new Atomizer(expression, config)
241: .iterate(context);
242:
243: Item first = iter.next();
244: if (first == null) {
245: return new Double(Double.NaN);
246: }
247: if (first instanceof NumericValue) {
248: return new Double(((NumericValue) first)
249: .getDoubleValue());
250: } else {
251: DoubleValue v = NumberFn
252: .convert((AtomicValue) first);
253: return new Double(v.getDoubleValue());
254: }
255:
256: } else if (qName.equals(XPathConstants.NODE)) {
257: SequenceIterator iter = expression.iterate(context);
258: Item first = iter.next();
259: if (first instanceof VirtualNode) {
260: return ((VirtualNode) first).getUnderlyingNode();
261: }
262: if (first == null || first instanceof NodeInfo) {
263: return first;
264: }
265: throw new XPathExpressionException(
266: "Expression result is not a node");
267: } else if (qName.equals(XPathConstants.NODESET)) {
268: SequenceIterator iter = expression.iterate(context);
269: SequenceExtent extent = new SequenceExtent(iter);
270: if (model != null) {
271: Object result = model.convertToNodeList(extent);
272: if (result != null) {
273: return result;
274: }
275: }
276: return extent.convertToJava(List.class, context);
277: } else {
278: throw new IllegalArgumentException(
279: "Unknown type for expected result");
280: }
281: } catch (XPathException e) {
282: throw new XPathExpressionException(e);
283: }
284: }
285:
286: /**
287: * Evaluate the expression to return a string value
288: * @param node the initial context node. This must be an instance of NodeInfo
289: * @return the results of the expression, converted to a String
290: * @throws XPathExpressionException if evaluation fails
291: */
292:
293: public String evaluate(Object node) throws XPathExpressionException {
294: return (String) evaluate(node, XPathConstants.STRING);
295: }
296:
297: /**
298: * Evaluate the XPath expression against an input source to obtain a result of a specified type
299: * @param inputSource The input source document against which the expression is evaluated.
300: * (Note that there is no caching. This will be parsed, and the parsed result will be discarded.)
301: * @param qName The type required, identified by a constant in {@link XPathConstants}
302: * @return the result of the evaluation, as a Java object of the appropriate type:
303: * see {@link #evaluate(Object, javax.xml.namespace.QName)}
304: * @throws XPathExpressionException
305: */
306: public Object evaluate(InputSource inputSource, QName qName)
307: throws XPathExpressionException {
308: try {
309: NodeInfo doc = new XPathEvaluator()
310: .setSource(new SAXSource(inputSource));
311: return evaluate(doc, qName);
312: } catch (XPathException e) {
313: throw new XPathExpressionException(e);
314: }
315: }
316:
317: /**
318: * Evaluate the XPath expression against an input source to obtain a string result
319: * @param inputSource The input source document against which the expression is evaluated.
320: * (Note that there is no caching. This will be parsed, and the parsed result will be discarded.)
321: * @return the result of the evaluation, converted to a String
322: * @throws XPathExpressionException
323: */
324:
325: public String evaluate(InputSource inputSource)
326: throws XPathExpressionException {
327: try {
328: NodeInfo doc = new XPathEvaluator()
329: .setSource(new SAXSource(inputSource));
330: return (String) evaluate(doc, XPathConstants.STRING);
331: } catch (XPathException e) {
332: throw new XPathExpressionException(e);
333: }
334: }
335:
336: /**
337: * Low-level method to get the internal Saxon expression object. This exposes a wide range of
338: * internal methods that may be needed by specialized applications, and allows greater control
339: * over the dynamic context for evaluating the expression.
340: * @return the underlying Saxon expression object.
341: */
342:
343: public Expression getInternalExpression() {
344: return expression;
345: }
346:
347: }
348:
349: //
350: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
351: // you may not use this file except in compliance with the License. You may obtain a copy of the
352: // License at http://www.mozilla.org/MPL/
353: //
354: // Software distributed under the License is distributed on an "AS IS" basis,
355: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
356: // See the License for the specific language governing rights and limitations under the License.
357: //
358: // The Original Code is: all this file.
359:
360: // The Initial Developer of the Original Code is
361: // Michael H. Kay.
362: //
363: // Contributor(s):
364: //
|