/* GNU GENERAL LICENSE Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either verion 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General License for more details. You should have received a copy of the GNU General Public along with this program. If not, see <http://www.gnu.org/licenses/>. Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it */ /* * $Id: XPathResultImpl.java 1225426 2011-12-29 04:13:08Z mrglavas $ */ package org.lobobrowser.html.xpath; import javax.xml.transform.TransformerException; import org.apache.xpath.XPath; import org.apache.xpath.objects.XObject; import org.apache.xpath.res.XPATHErrorResources; import org.apache.xpath.res.XPATHMessages; import org.lobobrowser.w3c.xpath.XPathException; import org.lobobrowser.w3c.xpath.XPathResult; 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; /** * * The class provides an implementation XPathResult according to the DOM L3 * XPath Specification, Working Group Note 26 February 2004. * * <p> * See also the * <a href='http://www.w3.org/TR/2004/NOTE-DOM-Level-3-XPath-20040226'>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 * * @xsl.usage internal */ public class XPathResultImpl implements XPathResult, EventListener { /** The Constant ANY_TYPE. */ public static final short ANY_TYPE = 0; /** The Constant NUMBER_TYPE. */ public static final short NUMBER_TYPE = 1; /** The Constant STRING_TYPE. */ public static final short STRING_TYPE = 2; /** The Constant BOOLEAN_TYPE. */ public static final short BOOLEAN_TYPE = 3; /** The Constant UNORDERED_NODE_ITERATOR_TYPE. */ public static final short UNORDERED_NODE_ITERATOR_TYPE = 4; /** The Constant ORDERED_NODE_ITERATOR_TYPE. */ public static final short ORDERED_NODE_ITERATOR_TYPE = 5; /** The Constant UNORDERED_NODE_SNAPSHOT_TYPE. */ public static final short UNORDERED_NODE_SNAPSHOT_TYPE = 6; /** The Constant ORDERED_NODE_SNAPSHOT_TYPE. */ public static final short ORDERED_NODE_SNAPSHOT_TYPE = 7; /** The Constant ANY_UNORDERED_NODE_TYPE. */ public static final short ANY_UNORDERED_NODE_TYPE = 8; /** The Constant FIRST_ORDERED_NODE_TYPE. */ public static final short FIRST_ORDERED_NODE_TYPE = 9; /** The wrapped XObject. */ final private XObject m_resultObj; /** * The xpath object that wraps the expression used for this result. */ final private XPath m_xpath; /** * This the type specified by the user during construction. Typically the * constructor will be called by org.apache.xpath.XPath.evaluate(). */ final private short m_resultType; /** The m_is invalid iterator state. */ private boolean m_isInvalidIteratorState = false; /** * Only used to attach a mutation event handler when specified type is an * iterator type. */ final 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; /** * Instantiates a new x path result impl. */ public XPathResultImpl() { m_xpath = null; m_contextNode = null; m_resultType = 0; m_resultObj = null; } /** * Constructor for XPathResultImpl. * * For internal use only. * * @param type * the type * @param result * the result * @param contextNode * the context node * @param xpath * the xpath */ XPathResultImpl(short type, XObject result, Node contextNode, XPath xpath) { // Check that the type is valid if (!isValidType(type)) { String fmsg = XPATHMessages.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 = XPATHMessages.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; this.m_xpath = xpath; // 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 == ORDERED_NODE_ITERATOR_TYPE) || (m_resultType == UNORDERED_NODE_ITERATOR_TYPE))) { addEventListener(); } // 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 = XPATHMessages.createXPATHMessage(XPATHErrorResources.ER_INCOMPATIBLE_TYPES, new Object[] { m_xpath.getPatternString(), getTypeString(getTypeFromXObject(m_resultObj)), getTypeString(m_resultType) }); throw new XPathException(XPathException.TYPE_ERR, fmsg); // "The // XPathResult // of // XPath // expression // {0} // has // an // XPathResultType // of // {1} // which // cannot // be // coerced // into // the // specified // XPathResultType // of // {2}."}, } // 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 = XPATHMessages.createXPATHMessage(XPATHErrorResources.ER_INCOMPATIBLE_TYPES, new Object[] { m_xpath.getPatternString(), getTypeString(getTypeFromXObject(m_resultObj)), getTypeString(m_resultType) }); throw new XPathException(XPathException.TYPE_ERR, fmsg); } } } /** * Gets the result type. * * @return the result type * @see org.w3c.dom.xpath.XPathResult#getResultType() */ @Override public short getResultType() { return m_resultType; } /** * The value of this number result. * * @return the number value * @see org.w3c.dom.xpath.XPathResult#getNumberValue() * @exception XPathException * TYPE_ERR: raised if <code>resultType</code> is not * <code>NUMBER_TYPE</code>. */ @Override public double getNumberValue() throws XPathException { if (getResultType() != NUMBER_TYPE) { String fmsg = XPATHMessages.createXPATHMessage( XPATHErrorResources.ER_CANT_CONVERT_XPATHRESULTTYPE_TO_NUMBER, new Object[] { m_xpath.getPatternString(), getTypeString(m_resultType) }); throw new XPathException(XPathException.TYPE_ERR, fmsg); // "The XPathResult of XPath expression {0} has an XPathResultType // of {1} which cannot be converted 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. * * @return the string value * @see org.w3c.dom.xpath.XPathResult#getStringValue() * @exception XPathException * TYPE_ERR: raised if <code>resultType</code> is not * <code>STRING_TYPE</code>. */ @Override public String getStringValue() throws XPathException { if (getResultType() != STRING_TYPE) { String fmsg = XPATHMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_CONVERT_TO_STRING, new Object[] { m_xpath.getPatternString(), m_resultObj.getTypeString() }); throw new XPathException(XPathException.TYPE_ERR, fmsg); // "The XPathResult of XPath expression {0} has an XPathResultType // of {1} which cannot be converted 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()); } } } /** * Gets the boolean value. * * @return the boolean value * @throws XPathException * the x path exception * @see org.w3c.dom.xpath.XPathResult#getBooleanValue() */ @Override public boolean getBooleanValue() throws XPathException { if (getResultType() != BOOLEAN_TYPE) { String fmsg = XPATHMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_CONVERT_TO_BOOLEAN, new Object[] { m_xpath.getPatternString(), getTypeString(m_resultType) }); throw new XPathException(XPathException.TYPE_ERR, fmsg); // "The XPathResult of XPath expression {0} has an XPathResultType // of {1} which cannot be converted 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>. * * @return the single node value * @see org.w3c.dom.xpath.XPathResult#getSingleNodeValue() * @exception XPathException * TYPE_ERR: raised if <code>resultType</code> is not * <code>ANY_UNORDERED_NODE_TYPE</code> or * <code>FIRST_ORDERED_NODE_TYPE</code>. */ @Override public Node getSingleNodeValue() throws XPathException { if ((m_resultType != ANY_UNORDERED_NODE_TYPE) && (m_resultType != FIRST_ORDERED_NODE_TYPE)) { String fmsg = XPATHMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_CONVERT_TO_SINGLENODE, new Object[] { m_xpath.getPatternString(), getTypeString(m_resultType) }); throw new XPathException(XPathException.TYPE_ERR, fmsg); // "The XPathResult of XPath expression {0} has an XPathResultType // of {1} which cannot be converted to a single node. // This method applies only 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; } } /** * Gets the invalid iterator state. * * @return the invalid iterator state * @see org.w3c.dom.xpath.XPathResult#getInvalidIteratorState() */ @Override 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. * * @return the snapshot length * @see org.w3c.dom.xpath.XPathResult#getSnapshotLength() * @exception XPathException * TYPE_ERR: raised if <code>resultType</code> is not * <code>UNORDERED_NODE_SNAPSHOT_TYPE</code> or * <code>ORDERED_NODE_SNAPSHOT_TYPE</code>. */ @Override public int getSnapshotLength() throws XPathException { if ((m_resultType != UNORDERED_NODE_SNAPSHOT_TYPE) && (m_resultType != ORDERED_NODE_SNAPSHOT_TYPE)) { String fmsg = XPATHMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_GET_SNAPSHOT_LENGTH, new Object[] { m_xpath.getPatternString(), getTypeString(m_resultType) }); throw new XPathException(XPathException.TYPE_ERR, fmsg); // "The method getSnapshotLength cannot be called on the XPathResult // of XPath expression {0} because its XPathResultType is {1}. } 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. * @see org.w3c.dom.xpath.XPathResult#iterateNext() * @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. */ @Override public Node iterateNext() throws XPathException, DOMException { if ((m_resultType != UNORDERED_NODE_ITERATOR_TYPE) && (m_resultType != ORDERED_NODE_ITERATOR_TYPE)) { String fmsg = XPATHMessages.createXPATHMessage(XPATHErrorResources.ER_NON_ITERATOR_TYPE, new Object[] { m_xpath.getPatternString(), getTypeString(m_resultType) }); throw new XPathException(XPathException.TYPE_ERR, fmsg); // "The method iterateNext cannot be called on the XPathResult of // XPath expression {0} because its XPathResultType is {1}. // This method applies only to types UNORDERED_NODE_ITERATOR_TYPE // and ORDERED_NODE_ITERATOR_TYPE."}, } if (getInvalidIteratorState()) { String fmsg = XPATHMessages.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(); if (null == node) { removeEventListener(); // JIRA 1673 } // 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. * @see org.w3c.dom.xpath.XPathResult#snapshotItem(int) * @exception XPathException * TYPE_ERR: raised if <code>resultType</code> is not * <code>UNORDERED_NODE_SNAPSHOT_TYPE</code> or * <code>ORDERED_NODE_SNAPSHOT_TYPE</code>. */ @Override public Node snapshotItem(int index) throws XPathException { if ((m_resultType != UNORDERED_NODE_SNAPSHOT_TYPE) && (m_resultType != ORDERED_NODE_SNAPSHOT_TYPE)) { String fmsg = XPATHMessages.createXPATHMessage(XPATHErrorResources.ER_NON_SNAPSHOT_TYPE, new Object[] { m_xpath.getPatternString(), getTypeString(m_resultType) }); throw new XPathException(XPathException.TYPE_ERR, fmsg); // "The method snapshotItem cannot be called on the XPathResult of // XPath expression {0} because its XPathResultType is {1}. // This method applies only 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. */ 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; } } /** * Handle event. * * @param event * the event * @see org.w3c.dom.events.EventListener#handleEvent(Event) */ @Override public void handleEvent(Event event) { if (event.getType().equals("DOMSubtreeModified")) { // invalidate the iterator m_isInvalidIteratorState = true; // deregister as a listener to reduce computational load removeEventListener(); } } /** * Given a request type, return the equivalent string. For diagnostic * purposes. * * @param type * the type * @return type string */ private 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. * * @param object * the object * @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 * the 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; } } /** * Add m_contextNode to Event Listner to listen for Mutations Events. */ private void addEventListener() { if (m_contextNode instanceof EventTarget) { ((EventTarget) m_contextNode).addEventListener("DOMSubtreeModified", this, true); } } /** * Remove m_contextNode to Event Listner to listen for Mutations Events. */ private void removeEventListener() { if (m_contextNode instanceof EventTarget) { ((EventTarget) m_contextNode).removeEventListener("DOMSubtreeModified", this, true); } } /** * Gets the any type. * * @return the any type */ public static short getANY_TYPE() { return ANY_TYPE; } /** * Gets the number type. * * @return the number type */ public static short getNUMBER_TYPE() { return NUMBER_TYPE; } /** * Gets the string type. * * @return the string type */ public static short getSTRING_TYPE() { return STRING_TYPE; } /** * Gets the boolean type. * * @return the boolean type */ public static short getBOOLEAN_TYPE() { return BOOLEAN_TYPE; } /** * Gets the unordered node iterator type. * * @return the unordered node iterator type */ public static short getUNORDERED_NODE_ITERATOR_TYPE() { return UNORDERED_NODE_ITERATOR_TYPE; } /** * Gets the ordered node iterator type. * * @return the ordered node iterator type */ public static short getORDERED_NODE_ITERATOR_TYPE() { return ORDERED_NODE_ITERATOR_TYPE; } /** * Gets the unordered node snapshot type. * * @return the unordered node snapshot type */ public static short getUNORDERED_NODE_SNAPSHOT_TYPE() { return UNORDERED_NODE_SNAPSHOT_TYPE; } /** * Gets the ordered node snapshot type. * * @return the ordered node snapshot type */ public static short getORDERED_NODE_SNAPSHOT_TYPE() { return ORDERED_NODE_SNAPSHOT_TYPE; } /** * Gets the any unordered node type. * * @return the any unordered node type */ public static short getANY_UNORDERED_NODE_TYPE() { return ANY_UNORDERED_NODE_TYPE; } /** * Gets the first ordered node type. * * @return the first ordered node type */ public static short getFIRST_ORDERED_NODE_TYPE() { return FIRST_ORDERED_NODE_TYPE; } }