| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * |
| * Copyright (c) 2002 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. 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. |
| * |
| * 3. The end-user documentation included with the redistribution, |
| * if any, must include the following acknowledgment: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, |
| * if and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "Xalan" and "Apache Software Foundation" must |
| * not be used to endorse or promote products derived from this |
| * software without prior written permission. For written |
| * permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache", |
| * nor may "Apache" appear in their name, without prior written |
| * permission of the Apache Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR |
| * ITS 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 Apache Software Foundation and was |
| * originally based on software copyright (c) 1999, Lotus |
| * Development Corporation., http://www.lotus.com. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| |
| |
| package org.apache.xpath.domapi; |
| |
| import javax.xml.transform.TransformerException; |
| |
| import org.apache.xalan.res.XSLMessages; |
| import org.apache.xml.dtm.DTM; |
| import org.apache.xpath.objects.XObject; |
| import org.apache.xpath.res.XPATHErrorResources; |
| import org.w3c.dom.DOMException; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.w3c.dom.events.Event; |
| import org.w3c.dom.events.EventListener; |
| import org.w3c.dom.events.EventTarget; |
| import org.w3c.dom.traversal.NodeIterator; |
| import org.w3c.dom.xpath.*; |
| |
| /** |
| * <meta name="usage" content="experimental"/> |
| * |
| * The class provides an implementation XPathResult according |
| * to the DOM L3 XPath Specification, Working Draft 28, March 2002. |
| * |
| * <p>See also the <a href='http://www.w3.org/TR/2002/WD-DOM-Level-3-XPath-20020328'>Document Object Model (DOM) Level 3 XPath Specification</a>.</p> |
| * |
| * <p>The <code>XPathResult</code> interface represents the result of the |
| * evaluation of an XPath expression within the context of a particular |
| * node. Since evaluation of an XPath expression can result in various |
| * result types, this object makes it possible to discover and manipulate |
| * the type and value of the result.</p> |
| * |
| * <p>This implementation wraps an <code>XObject</code>. |
| * |
| * @see org.apache.xpath.objects.XObject |
| * @see org.w3c.dom.xpath.XPathResult |
| * |
| */ |
| public class XPathResultImpl implements XPathResult, EventListener { |
| |
| /** |
| * The wrapped XObject |
| */ |
| private XObject m_resultObj; |
| |
| /** |
| * This the type specified by the user during construction. Typically |
| * the constructor will be called by org.apache.xpath.XPath.evaluate(). |
| */ |
| private short m_resultType = ANY_TYPE; |
| |
| private boolean m_isInvalidIteratorState = false; |
| |
| /** |
| * Only used to attach a mutation event handler when specified |
| * type is an iterator type. |
| */ |
| private Node m_contextNode; |
| |
| /** |
| * The iterator, if this is an iterator type. |
| */ |
| private NodeIterator m_iterator = null; |
| |
| /** |
| * The list, if this is a snapshot type. |
| */ |
| private NodeList m_list = null; |
| |
| |
| /** |
| * Constructor for XPathResultImpl. |
| * |
| * For internal use only. |
| */ |
| XPathResultImpl(short type, XObject result, Node contextNode) { |
| // Check that the type is valid |
| if (!isValidType(type)) { |
| String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_INVALID_XPATH_TYPE, new Object[] {new Integer(type)}); |
| throw new XPathException(XPathException.TYPE_ERR,fmsg); // Invalid XPath type argument: {0} |
| } |
| |
| // Result object should never be null! |
| if (null == result) { |
| String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_EMPTY_XPATH_RESULT, null); |
| throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,fmsg); // Empty XPath result object |
| } |
| |
| this.m_resultObj = result; |
| this.m_contextNode = contextNode; |
| |
| // If specified result was ANY_TYPE, determine XObject type |
| if (type == ANY_TYPE) { |
| this.m_resultType = getTypeFromXObject(result); |
| } else { |
| this.m_resultType = type; |
| } |
| |
| // If the context node supports DOM Events and the type is one of the iterator |
| // types register this result as an event listener |
| if (((m_resultType == XPathResult.ORDERED_NODE_ITERATOR_TYPE) || |
| (m_resultType == XPathResult.UNORDERED_NODE_ITERATOR_TYPE))&& |
| (contextNode instanceof EventTarget)) { |
| ((EventTarget)contextNode).addEventListener("MutationEvents",this,true); |
| }// else can we handle iterator types if contextNode doesn't support EventTarget?? |
| |
| // If this is an iterator type get the iterator |
| if ((m_resultType == ORDERED_NODE_ITERATOR_TYPE) || |
| (m_resultType == UNORDERED_NODE_ITERATOR_TYPE) || |
| (m_resultType == ANY_UNORDERED_NODE_TYPE) || |
| (m_resultType == FIRST_ORDERED_NODE_TYPE)) { |
| |
| try { |
| m_iterator = m_resultObj.nodeset(); |
| } catch (TransformerException te) { |
| // probably not a node type |
| String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_INCOMPATIBLE_TYPES, new Object[] {getTypeString(getTypeFromXObject(m_resultObj)),getTypeString(m_resultType)}); |
| throw new XPathException(XPathException.TYPE_ERR, fmsg); // The returned type: {0} can not be coerced into the specified type: {1} |
| } |
| |
| // If user requested ordered nodeset and result is unordered |
| // need to sort...TODO |
| // if ((m_resultType == ORDERED_NODE_ITERATOR_TYPE) && |
| // (!(((DTMNodeIterator)m_iterator).getDTMIterator().isDocOrdered()))) { |
| // |
| // } |
| |
| // If it's a snapshot type, get the nodelist |
| } else if ((m_resultType == UNORDERED_NODE_SNAPSHOT_TYPE) || |
| (m_resultType == ORDERED_NODE_SNAPSHOT_TYPE)) { |
| try { |
| m_list = m_resultObj.nodelist(); |
| } catch (TransformerException te) { |
| // probably not a node type |
| String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_INCOMPATIBLE_TYPES, new Object[] {getTypeString(getTypeFromXObject(m_resultObj)),getTypeString(m_resultType)}); |
| throw new XPathException(XPathException.TYPE_ERR, fmsg); // The returned type: {0} can not be coerced into the specified type: {1} |
| } |
| } |
| } |
| |
| /** |
| * @see org.w3c.dom.xpath.XPathResult#getResultType() |
| */ |
| public short getResultType() { |
| return m_resultType; |
| } |
| |
| /** |
| * The value of this number result. |
| * @exception XPathException |
| * TYPE_ERR: raised if <code>resultType</code> is not |
| * <code>NUMBER_TYPE</code>. |
| * @see org.w3c.dom.xpath.XPathResult#getNumberValue() |
| */ |
| public double getNumberValue() throws XPathException { |
| if (getResultType() != NUMBER_TYPE) { |
| String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_CONVERT_TO_NUMBER, new Object[] {getTypeString(m_resultType)}); |
| throw new XPathException(XPathException.TYPE_ERR,fmsg); // Can not convert {0} to a number |
| } else { |
| try { |
| return m_resultObj.num(); |
| } catch (Exception e) { |
| // Type check above should prevent this exception from occurring. |
| throw new XPathException(XPathException.TYPE_ERR,e.getMessage()); |
| } |
| } |
| } |
| |
| /** |
| * The value of this string result. |
| * @exception XPathException |
| * TYPE_ERR: raised if <code>resultType</code> is not |
| * <code>STRING_TYPE</code>. |
| * |
| * @see org.w3c.dom.xpath.XPathResult#getStringValue() |
| */ |
| public String getStringValue() throws XPathException { |
| if (getResultType() != STRING_TYPE) { |
| String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_CONVERT_TO_STRING, new Object[] {m_resultObj.getTypeString()}); |
| throw new XPathException(XPathException.TYPE_ERR,fmsg); // Can not convert {0} to a string. |
| } else { |
| try { |
| return m_resultObj.str(); |
| } catch (Exception e) { |
| // Type check above should prevent this exception from occurring. |
| throw new XPathException(XPathException.TYPE_ERR,e.getMessage()); |
| } |
| } |
| } |
| |
| /** |
| * @see org.w3c.dom.xpath.XPathResult#getBooleanValue() |
| */ |
| public boolean getBooleanValue() throws XPathException { |
| if (getResultType() != BOOLEAN_TYPE) { |
| String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_CONVERT_TO_BOOLEAN, new Object[] {getTypeString(m_resultType)}); |
| throw new XPathException(XPathException.TYPE_ERR,fmsg); // Can not convert {0} to a boolean |
| } else { |
| try { |
| return m_resultObj.bool(); |
| } catch (TransformerException e) { |
| // Type check above should prevent this exception from occurring. |
| throw new XPathException(XPathException.TYPE_ERR,e.getMessage()); |
| } |
| } |
| } |
| |
| /** |
| * The value of this single node result, which may be <code>null</code>. |
| * @exception XPathException |
| * TYPE_ERR: raised if <code>resultType</code> is not |
| * <code>ANY_UNORDERED_NODE_TYPE</code> or |
| * <code>FIRST_ORDERED_NODE_TYPE</code>. |
| * |
| * @see org.w3c.dom.xpath.XPathResult#getSingleNodeValue() |
| */ |
| public Node getSingleNodeValue() throws XPathException { |
| |
| if ((m_resultType != ANY_UNORDERED_NODE_TYPE) && |
| (m_resultType != FIRST_ORDERED_NODE_TYPE)) { |
| String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_CONVERT_TO_SINGLENODE, new Object[] {getTypeString(m_resultType)}); |
| throw new XPathException(XPathException.TYPE_ERR,fmsg); // Can not convert {0} to a single node. This getter applies to types |
| // ANY_UNORDERED_NODE_TYPE and FIRST_ORDERED_NODE_TYPE. |
| } |
| |
| NodeIterator result = null; |
| try { |
| result = m_resultObj.nodeset(); |
| } catch (TransformerException te) { |
| throw new XPathException(XPathException.TYPE_ERR,te.getMessage()); |
| } |
| |
| if (null == result) return null; |
| |
| Node node = result.nextNode(); |
| |
| // Wrap "namespace node" in an XPathNamespace |
| if (isNamespaceNode(node)) { |
| return new XPathNamespaceImpl(node); |
| } else { |
| return node; |
| } |
| } |
| |
| /** |
| * @see org.w3c.dom.xpath.XPathResult#getInvalidIteratorState() |
| */ |
| public boolean getInvalidIteratorState() { |
| return m_isInvalidIteratorState; |
| } |
| |
| /** |
| * The number of nodes in the result snapshot. Valid values for |
| * snapshotItem indices are <code>0</code> to |
| * <code>snapshotLength-1</code> inclusive. |
| * @exception XPathException |
| * TYPE_ERR: raised if <code>resultType</code> is not |
| * <code>UNORDERED_NODE_SNAPSHOT_TYPE</code> or |
| * <code>ORDERED_NODE_SNAPSHOT_TYPE</code>. |
| * |
| * @see org.w3c.dom.xpath.XPathResult#getSnapshotLength() |
| */ |
| public int getSnapshotLength() throws XPathException { |
| |
| if ((m_resultType != UNORDERED_NODE_SNAPSHOT_TYPE) && |
| (m_resultType != ORDERED_NODE_SNAPSHOT_TYPE)) { |
| String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_GET_SNAPSHOT_LENGTH, new Object[] {getTypeString(m_resultType)}); |
| throw new XPathException(XPathException.TYPE_ERR,fmsg); // Can not get snapshot length on type: {0}. This getter applies to types |
| //UNORDERED_NODE_SNAPSHOT_TYPE and ORDERED_NODE_SNAPSHOT_TYPE. |
| } |
| |
| return m_list.getLength(); |
| } |
| |
| /** |
| * Iterates and returns the next node from the node set or |
| * <code>null</code>if there are no more nodes. |
| * @return Returns the next node. |
| * @exception XPathException |
| * TYPE_ERR: raised if <code>resultType</code> is not |
| * <code>UNORDERED_NODE_ITERATOR_TYPE</code> or |
| * <code>ORDERED_NODE_ITERATOR_TYPE</code>. |
| * @exception DOMException |
| * INVALID_STATE_ERR: The document has been mutated since the result was |
| * returned. |
| * @see org.w3c.dom.xpath.XPathResult#iterateNext() |
| */ |
| public Node iterateNext() throws XPathException, DOMException { |
| if ((m_resultType != UNORDERED_NODE_ITERATOR_TYPE) && |
| (m_resultType != ORDERED_NODE_ITERATOR_TYPE)) { |
| String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NON_ITERATOR_TYPE, new Object[] {getTypeString(m_resultType)}); |
| throw new XPathException(XPathException.TYPE_ERR, fmsg); // Can not iterate over non iterator type: {0} |
| } |
| |
| if (getInvalidIteratorState()) { |
| String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_DOC_MUTATED, null); |
| throw new DOMException(DOMException.INVALID_STATE_ERR,fmsg); // Document mutated since result was returned. Iterator is invalid. |
| } |
| |
| Node node = m_iterator.nextNode(); |
| |
| // Wrap "namespace node" in an XPathNamespace |
| if (isNamespaceNode(node)) { |
| return new XPathNamespaceImpl(node); |
| } else { |
| return node; |
| } |
| } |
| |
| /** |
| * Returns the <code>index</code>th item in the snapshot collection. If |
| * <code>index</code> is greater than or equal to the number of nodes in |
| * the list, this method returns <code>null</code>. Unlike the iterator |
| * result, the snapshot does not become invalid, but may not correspond |
| * to the current document if it is mutated. |
| * @param index Index into the snapshot collection. |
| * @return The node at the <code>index</code>th position in the |
| * <code>NodeList</code>, or <code>null</code> if that is not a valid |
| * index. |
| * @exception XPathException |
| * TYPE_ERR: raised if <code>resultType</code> is not |
| * <code>UNORDERED_NODE_SNAPSHOT_TYPE</code> or |
| * <code>ORDERED_NODE_SNAPSHOT_TYPE</code>. |
| * |
| * @see org.w3c.dom.xpath.XPathResult#snapshotItem(int) |
| */ |
| public Node snapshotItem(int index) throws XPathException { |
| |
| if ((m_resultType != UNORDERED_NODE_SNAPSHOT_TYPE) && |
| (m_resultType != ORDERED_NODE_SNAPSHOT_TYPE)) { |
| String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NON_SNAPSHOT_TYPE, new Object[] {getTypeString(m_resultType)}); |
| throw new XPathException(XPathException.TYPE_ERR, fmsg); // Can call snapshotItem on type: {0}. This method applies to types |
| // UNORDERED_NODE_SNAPSHOT_TYPE and ORDERED_NODE_SNAPSHOT_TYPE. |
| } |
| |
| Node node = m_list.item(index); |
| |
| // Wrap "namespace node" in an XPathNamespace |
| if (isNamespaceNode(node)) { |
| return new XPathNamespaceImpl(node); |
| } else { |
| return node; |
| } |
| } |
| |
| |
| /** |
| * Check if the specified type is one of the supported types. |
| * @param type The specified type |
| * |
| * @return true If the specified type is supported; otherwise, returns false. |
| */ |
| public static boolean isValidType( short type ) { |
| switch (type) { |
| case ANY_TYPE: |
| case NUMBER_TYPE: |
| case STRING_TYPE: |
| case BOOLEAN_TYPE: |
| case UNORDERED_NODE_ITERATOR_TYPE: |
| case ORDERED_NODE_ITERATOR_TYPE: |
| case UNORDERED_NODE_SNAPSHOT_TYPE: |
| case ORDERED_NODE_SNAPSHOT_TYPE: |
| case ANY_UNORDERED_NODE_TYPE: |
| case FIRST_ORDERED_NODE_TYPE: return true; |
| default: return false; |
| } |
| } |
| |
| /** |
| * @see org.w3c.dom.events.EventListener#handleEvent(Event) |
| */ |
| public void handleEvent(Event event) { |
| |
| if (event.getType().equals("MutationEvents")) { |
| // invalidate the iterator |
| m_isInvalidIteratorState = true; |
| |
| // deregister as a listener to reduce computational load |
| ((EventTarget)m_contextNode).removeEventListener("MutationEvents",this,true); |
| |
| } |
| } |
| |
| /** |
| * Given a request type, return the equivalent string. |
| * For diagnostic purposes. |
| * |
| * @return type string |
| */ |
| public String getTypeString(int type) |
| { |
| switch (type) { |
| case ANY_TYPE: return "ANY_TYPE"; |
| case ANY_UNORDERED_NODE_TYPE: return "ANY_UNORDERED_NODE_TYPE"; |
| case BOOLEAN_TYPE: return "BOOLEAN"; |
| case FIRST_ORDERED_NODE_TYPE: return "FIRST_ORDERED_NODE_TYPE"; |
| case NUMBER_TYPE: return "NUMBER_TYPE"; |
| case ORDERED_NODE_ITERATOR_TYPE: return "ORDERED_NODE_ITERATOR_TYPE"; |
| case ORDERED_NODE_SNAPSHOT_TYPE: return "ORDERED_NODE_SNAPSHOT_TYPE"; |
| case STRING_TYPE: return "STRING_TYPE"; |
| case UNORDERED_NODE_ITERATOR_TYPE: return "UNORDERED_NODE_ITERATOR_TYPE"; |
| case UNORDERED_NODE_SNAPSHOT_TYPE: return "UNORDERED_NODE_SNAPSHOT_TYPE"; |
| default: return "#UNKNOWN"; |
| } |
| } |
| |
| /** |
| * Given an XObject, determine the corresponding DOM XPath type |
| * |
| * @return type string |
| */ |
| private short getTypeFromXObject(XObject object) { |
| switch (object.getType()) { |
| case XObject.CLASS_BOOLEAN: return BOOLEAN_TYPE; |
| case XObject.CLASS_NODESET: return UNORDERED_NODE_ITERATOR_TYPE; |
| case XObject.CLASS_NUMBER: return NUMBER_TYPE; |
| case XObject.CLASS_STRING: return STRING_TYPE; |
| // XPath 2.0 types |
| // case XObject.CLASS_DATE: |
| // case XObject.CLASS_DATETIME: |
| // case XObject.CLASS_DTDURATION: |
| // case XObject.CLASS_GDAY: |
| // case XObject.CLASS_GMONTH: |
| // case XObject.CLASS_GMONTHDAY: |
| // case XObject.CLASS_GYEAR: |
| // case XObject.CLASS_GYEARMONTH: |
| // case XObject.CLASS_TIME: |
| // case XObject.CLASS_YMDURATION: return STRING_TYPE; // treat all date types as strings? |
| |
| case XObject.CLASS_RTREEFRAG: return UNORDERED_NODE_ITERATOR_TYPE; |
| case XObject.CLASS_NULL: return ANY_TYPE; // throw exception ? |
| default: return ANY_TYPE; // throw exception ? |
| } |
| |
| } |
| |
| /** |
| * Given a node, determine if it is a namespace node. |
| * |
| * @param node |
| * |
| * @return boolean Returns true if this is a namespace node; otherwise, returns false. |
| */ |
| private boolean isNamespaceNode(Node node) { |
| |
| if ((null != node) && |
| (node.getNodeType() == Node.ATTRIBUTE_NODE) && |
| (node.getNodeName().startsWith("xmlns:") || node.getNodeName().equals("xmlns"))) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| } |