1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. *
  5. * Copyright (c) 2002 The Apache Software Foundation. All rights
  6. * reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * 1. Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * 3. The end-user documentation included with the redistribution,
  21. * if any, must include the following acknowledgment:
  22. * "This product includes software developed by the
  23. * Apache Software Foundation (http://www.apache.org/)."
  24. * Alternately, this acknowledgment may appear in the software itself,
  25. * if and wherever such third-party acknowledgments normally appear.
  26. *
  27. * 4. The names "Xalan" and "Apache Software Foundation" must
  28. * not be used to endorse or promote products derived from this
  29. * software without prior written permission. For written
  30. * permission, please contact apache@apache.org.
  31. *
  32. * 5. Products derived from this software may not be called "Apache",
  33. * nor may "Apache" appear in their name, without prior written
  34. * permission of the Apache Software Foundation.
  35. *
  36. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  37. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  38. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  39. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  40. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  41. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  42. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  43. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  44. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  45. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  46. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  47. * SUCH DAMAGE.
  48. * ====================================================================
  49. *
  50. * This software consists of voluntary contributions made by many
  51. * individuals on behalf of the Apache Software Foundation and was
  52. * originally based on software copyright (c) 1999, Lotus
  53. * Development Corporation., http://www.lotus.com. For more
  54. * information on the Apache Software Foundation, please see
  55. * <http://www.apache.org/>.
  56. */
  57. package org.apache.xpath.domapi;
  58. import javax.xml.transform.TransformerException;
  59. import org.apache.xalan.res.XSLMessages;
  60. import org.apache.xml.dtm.DTM;
  61. import org.apache.xpath.objects.XObject;
  62. import org.apache.xpath.res.XPATHErrorResources;
  63. import org.w3c.dom.DOMException;
  64. import org.w3c.dom.Node;
  65. import org.w3c.dom.NodeList;
  66. import org.w3c.dom.events.Event;
  67. import org.w3c.dom.events.EventListener;
  68. import org.w3c.dom.events.EventTarget;
  69. import org.w3c.dom.traversal.NodeIterator;
  70. import org.w3c.dom.xpath.*;
  71. /**
  72. * <meta name="usage" content="experimental"/>
  73. *
  74. * The class provides an implementation XPathResult according
  75. * to the DOM L3 XPath Specification, Working Draft 28, March 2002.
  76. *
  77. * <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>
  78. *
  79. * <p>The <code>XPathResult</code> interface represents the result of the
  80. * evaluation of an XPath expression within the context of a particular
  81. * node. Since evaluation of an XPath expression can result in various
  82. * result types, this object makes it possible to discover and manipulate
  83. * the type and value of the result.</p>
  84. *
  85. * <p>This implementation wraps an <code>XObject</code>.
  86. *
  87. * @see org.apache.xpath.objects.XObject
  88. * @see org.w3c.dom.xpath.XPathResult
  89. *
  90. */
  91. public class XPathResultImpl implements XPathResult, EventListener {
  92. /**
  93. * The wrapped XObject
  94. */
  95. private XObject m_resultObj;
  96. /**
  97. * This the type specified by the user during construction. Typically
  98. * the constructor will be called by org.apache.xpath.XPath.evaluate().
  99. */
  100. private short m_resultType = ANY_TYPE;
  101. private boolean m_isInvalidIteratorState = false;
  102. /**
  103. * Only used to attach a mutation event handler when specified
  104. * type is an iterator type.
  105. */
  106. private Node m_contextNode;
  107. /**
  108. * The iterator, if this is an iterator type.
  109. */
  110. private NodeIterator m_iterator = null;
  111. /**
  112. * The list, if this is a snapshot type.
  113. */
  114. private NodeList m_list = null;
  115. /**
  116. * Constructor for XPathResultImpl.
  117. *
  118. * For internal use only.
  119. */
  120. XPathResultImpl(short type, XObject result, Node contextNode) {
  121. // Check that the type is valid
  122. if (!isValidType(type)) {
  123. String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_INVALID_XPATH_TYPE, new Object[] {new Integer(type)});
  124. throw new XPathException(XPathException.TYPE_ERR,fmsg); // Invalid XPath type argument: {0}
  125. }
  126. // Result object should never be null!
  127. if (null == result) {
  128. String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_EMPTY_XPATH_RESULT, null);
  129. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,fmsg); // Empty XPath result object
  130. }
  131. this.m_resultObj = result;
  132. this.m_contextNode = contextNode;
  133. // If specified result was ANY_TYPE, determine XObject type
  134. if (type == ANY_TYPE) {
  135. this.m_resultType = getTypeFromXObject(result);
  136. } else {
  137. this.m_resultType = type;
  138. }
  139. // If the context node supports DOM Events and the type is one of the iterator
  140. // types register this result as an event listener
  141. if (((m_resultType == XPathResult.ORDERED_NODE_ITERATOR_TYPE) ||
  142. (m_resultType == XPathResult.UNORDERED_NODE_ITERATOR_TYPE))&&
  143. (contextNode instanceof EventTarget)) {
  144. ((EventTarget)contextNode).addEventListener("MutationEvents",this,true);
  145. }// else can we handle iterator types if contextNode doesn't support EventTarget??
  146. // If this is an iterator type get the iterator
  147. if ((m_resultType == ORDERED_NODE_ITERATOR_TYPE) ||
  148. (m_resultType == UNORDERED_NODE_ITERATOR_TYPE) ||
  149. (m_resultType == ANY_UNORDERED_NODE_TYPE) ||
  150. (m_resultType == FIRST_ORDERED_NODE_TYPE)) {
  151. try {
  152. m_iterator = m_resultObj.nodeset();
  153. } catch (TransformerException te) {
  154. // probably not a node type
  155. String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_INCOMPATIBLE_TYPES, new Object[] {getTypeString(getTypeFromXObject(m_resultObj)),getTypeString(m_resultType)});
  156. throw new XPathException(XPathException.TYPE_ERR, fmsg); // The returned type: {0} can not be coerced into the specified type: {1}
  157. }
  158. // If user requested ordered nodeset and result is unordered
  159. // need to sort...TODO
  160. // if ((m_resultType == ORDERED_NODE_ITERATOR_TYPE) &&
  161. // (!(((DTMNodeIterator)m_iterator).getDTMIterator().isDocOrdered()))) {
  162. //
  163. // }
  164. // If it's a snapshot type, get the nodelist
  165. } else if ((m_resultType == UNORDERED_NODE_SNAPSHOT_TYPE) ||
  166. (m_resultType == ORDERED_NODE_SNAPSHOT_TYPE)) {
  167. try {
  168. m_list = m_resultObj.nodelist();
  169. } catch (TransformerException te) {
  170. // probably not a node type
  171. String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_INCOMPATIBLE_TYPES, new Object[] {getTypeString(getTypeFromXObject(m_resultObj)),getTypeString(m_resultType)});
  172. throw new XPathException(XPathException.TYPE_ERR, fmsg); // The returned type: {0} can not be coerced into the specified type: {1}
  173. }
  174. }
  175. }
  176. /**
  177. * @see org.w3c.dom.xpath.XPathResult#getResultType()
  178. */
  179. public short getResultType() {
  180. return m_resultType;
  181. }
  182. /**
  183. * The value of this number result.
  184. * @exception XPathException
  185. * TYPE_ERR: raised if <code>resultType</code> is not
  186. * <code>NUMBER_TYPE</code>.
  187. * @see org.w3c.dom.xpath.XPathResult#getNumberValue()
  188. */
  189. public double getNumberValue() throws XPathException {
  190. if (getResultType() != NUMBER_TYPE) {
  191. String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_CONVERT_TO_NUMBER, new Object[] {getTypeString(m_resultType)});
  192. throw new XPathException(XPathException.TYPE_ERR,fmsg); // Can not convert {0} to a number
  193. } else {
  194. try {
  195. return m_resultObj.num();
  196. } catch (Exception e) {
  197. // Type check above should prevent this exception from occurring.
  198. throw new XPathException(XPathException.TYPE_ERR,e.getMessage());
  199. }
  200. }
  201. }
  202. /**
  203. * The value of this string result.
  204. * @exception XPathException
  205. * TYPE_ERR: raised if <code>resultType</code> is not
  206. * <code>STRING_TYPE</code>.
  207. *
  208. * @see org.w3c.dom.xpath.XPathResult#getStringValue()
  209. */
  210. public String getStringValue() throws XPathException {
  211. if (getResultType() != STRING_TYPE) {
  212. String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_CONVERT_TO_STRING, new Object[] {m_resultObj.getTypeString()});
  213. throw new XPathException(XPathException.TYPE_ERR,fmsg); // Can not convert {0} to a string.
  214. } else {
  215. try {
  216. return m_resultObj.str();
  217. } catch (Exception e) {
  218. // Type check above should prevent this exception from occurring.
  219. throw new XPathException(XPathException.TYPE_ERR,e.getMessage());
  220. }
  221. }
  222. }
  223. /**
  224. * @see org.w3c.dom.xpath.XPathResult#getBooleanValue()
  225. */
  226. public boolean getBooleanValue() throws XPathException {
  227. if (getResultType() != BOOLEAN_TYPE) {
  228. String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_CONVERT_TO_BOOLEAN, new Object[] {getTypeString(m_resultType)});
  229. throw new XPathException(XPathException.TYPE_ERR,fmsg); // Can not convert {0} to a boolean
  230. } else {
  231. try {
  232. return m_resultObj.bool();
  233. } catch (TransformerException e) {
  234. // Type check above should prevent this exception from occurring.
  235. throw new XPathException(XPathException.TYPE_ERR,e.getMessage());
  236. }
  237. }
  238. }
  239. /**
  240. * The value of this single node result, which may be <code>null</code>.
  241. * @exception XPathException
  242. * TYPE_ERR: raised if <code>resultType</code> is not
  243. * <code>ANY_UNORDERED_NODE_TYPE</code> or
  244. * <code>FIRST_ORDERED_NODE_TYPE</code>.
  245. *
  246. * @see org.w3c.dom.xpath.XPathResult#getSingleNodeValue()
  247. */
  248. public Node getSingleNodeValue() throws XPathException {
  249. if ((m_resultType != ANY_UNORDERED_NODE_TYPE) &&
  250. (m_resultType != FIRST_ORDERED_NODE_TYPE)) {
  251. String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_CONVERT_TO_SINGLENODE, new Object[] {getTypeString(m_resultType)});
  252. throw new XPathException(XPathException.TYPE_ERR,fmsg); // Can not convert {0} to a single node. This getter applies to types
  253. // ANY_UNORDERED_NODE_TYPE and FIRST_ORDERED_NODE_TYPE.
  254. }
  255. NodeIterator result = null;
  256. try {
  257. result = m_resultObj.nodeset();
  258. } catch (TransformerException te) {
  259. throw new XPathException(XPathException.TYPE_ERR,te.getMessage());
  260. }
  261. if (null == result) return null;
  262. Node node = result.nextNode();
  263. // Wrap "namespace node" in an XPathNamespace
  264. if (isNamespaceNode(node)) {
  265. return new XPathNamespaceImpl(node);
  266. } else {
  267. return node;
  268. }
  269. }
  270. /**
  271. * @see org.w3c.dom.xpath.XPathResult#getInvalidIteratorState()
  272. */
  273. public boolean getInvalidIteratorState() {
  274. return m_isInvalidIteratorState;
  275. }
  276. /**
  277. * The number of nodes in the result snapshot. Valid values for
  278. * snapshotItem indices are <code>0</code> to
  279. * <code>snapshotLength-1</code> inclusive.
  280. * @exception XPathException
  281. * TYPE_ERR: raised if <code>resultType</code> is not
  282. * <code>UNORDERED_NODE_SNAPSHOT_TYPE</code> or
  283. * <code>ORDERED_NODE_SNAPSHOT_TYPE</code>.
  284. *
  285. * @see org.w3c.dom.xpath.XPathResult#getSnapshotLength()
  286. */
  287. public int getSnapshotLength() throws XPathException {
  288. if ((m_resultType != UNORDERED_NODE_SNAPSHOT_TYPE) &&
  289. (m_resultType != ORDERED_NODE_SNAPSHOT_TYPE)) {
  290. String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANT_GET_SNAPSHOT_LENGTH, new Object[] {getTypeString(m_resultType)});
  291. throw new XPathException(XPathException.TYPE_ERR,fmsg); // Can not get snapshot length on type: {0}. This getter applies to types
  292. //UNORDERED_NODE_SNAPSHOT_TYPE and ORDERED_NODE_SNAPSHOT_TYPE.
  293. }
  294. return m_list.getLength();
  295. }
  296. /**
  297. * Iterates and returns the next node from the node set or
  298. * <code>null</code>if there are no more nodes.
  299. * @return Returns the next node.
  300. * @exception XPathException
  301. * TYPE_ERR: raised if <code>resultType</code> is not
  302. * <code>UNORDERED_NODE_ITERATOR_TYPE</code> or
  303. * <code>ORDERED_NODE_ITERATOR_TYPE</code>.
  304. * @exception DOMException
  305. * INVALID_STATE_ERR: The document has been mutated since the result was
  306. * returned.
  307. * @see org.w3c.dom.xpath.XPathResult#iterateNext()
  308. */
  309. public Node iterateNext() throws XPathException, DOMException {
  310. if ((m_resultType != UNORDERED_NODE_ITERATOR_TYPE) &&
  311. (m_resultType != ORDERED_NODE_ITERATOR_TYPE)) {
  312. String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NON_ITERATOR_TYPE, new Object[] {getTypeString(m_resultType)});
  313. throw new XPathException(XPathException.TYPE_ERR, fmsg); // Can not iterate over non iterator type: {0}
  314. }
  315. if (getInvalidIteratorState()) {
  316. String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_DOC_MUTATED, null);
  317. throw new DOMException(DOMException.INVALID_STATE_ERR,fmsg); // Document mutated since result was returned. Iterator is invalid.
  318. }
  319. Node node = m_iterator.nextNode();
  320. // Wrap "namespace node" in an XPathNamespace
  321. if (isNamespaceNode(node)) {
  322. return new XPathNamespaceImpl(node);
  323. } else {
  324. return node;
  325. }
  326. }
  327. /**
  328. * Returns the <code>index</code>th item in the snapshot collection. If
  329. * <code>index</code> is greater than or equal to the number of nodes in
  330. * the list, this method returns <code>null</code>. Unlike the iterator
  331. * result, the snapshot does not become invalid, but may not correspond
  332. * to the current document if it is mutated.
  333. * @param index Index into the snapshot collection.
  334. * @return The node at the <code>index</code>th position in the
  335. * <code>NodeList</code>, or <code>null</code> if that is not a valid
  336. * index.
  337. * @exception XPathException
  338. * TYPE_ERR: raised if <code>resultType</code> is not
  339. * <code>UNORDERED_NODE_SNAPSHOT_TYPE</code> or
  340. * <code>ORDERED_NODE_SNAPSHOT_TYPE</code>.
  341. *
  342. * @see org.w3c.dom.xpath.XPathResult#snapshotItem(int)
  343. */
  344. public Node snapshotItem(int index) throws XPathException {
  345. if ((m_resultType != UNORDERED_NODE_SNAPSHOT_TYPE) &&
  346. (m_resultType != ORDERED_NODE_SNAPSHOT_TYPE)) {
  347. String fmsg = XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NON_SNAPSHOT_TYPE, new Object[] {getTypeString(m_resultType)});
  348. throw new XPathException(XPathException.TYPE_ERR, fmsg); // Can call snapshotItem on type: {0}. This method applies to types
  349. // UNORDERED_NODE_SNAPSHOT_TYPE and ORDERED_NODE_SNAPSHOT_TYPE.
  350. }
  351. Node node = m_list.item(index);
  352. // Wrap "namespace node" in an XPathNamespace
  353. if (isNamespaceNode(node)) {
  354. return new XPathNamespaceImpl(node);
  355. } else {
  356. return node;
  357. }
  358. }
  359. /**
  360. * Check if the specified type is one of the supported types.
  361. * @param type The specified type
  362. *
  363. * @return true If the specified type is supported; otherwise, returns false.
  364. */
  365. public static boolean isValidType( short type ) {
  366. switch (type) {
  367. case ANY_TYPE:
  368. case NUMBER_TYPE:
  369. case STRING_TYPE:
  370. case BOOLEAN_TYPE:
  371. case UNORDERED_NODE_ITERATOR_TYPE:
  372. case ORDERED_NODE_ITERATOR_TYPE:
  373. case UNORDERED_NODE_SNAPSHOT_TYPE:
  374. case ORDERED_NODE_SNAPSHOT_TYPE:
  375. case ANY_UNORDERED_NODE_TYPE:
  376. case FIRST_ORDERED_NODE_TYPE: return true;
  377. default: return false;
  378. }
  379. }
  380. /**
  381. * @see org.w3c.dom.events.EventListener#handleEvent(Event)
  382. */
  383. public void handleEvent(Event event) {
  384. if (event.getType().equals("MutationEvents")) {
  385. // invalidate the iterator
  386. m_isInvalidIteratorState = true;
  387. // deregister as a listener to reduce computational load
  388. ((EventTarget)m_contextNode).removeEventListener("MutationEvents",this,true);
  389. }
  390. }
  391. /**
  392. * Given a request type, return the equivalent string.
  393. * For diagnostic purposes.
  394. *
  395. * @return type string
  396. */
  397. public String getTypeString(int type)
  398. {
  399. switch (type) {
  400. case ANY_TYPE: return "ANY_TYPE";
  401. case ANY_UNORDERED_NODE_TYPE: return "ANY_UNORDERED_NODE_TYPE";
  402. case BOOLEAN_TYPE: return "BOOLEAN";
  403. case FIRST_ORDERED_NODE_TYPE: return "FIRST_ORDERED_NODE_TYPE";
  404. case NUMBER_TYPE: return "NUMBER_TYPE";
  405. case ORDERED_NODE_ITERATOR_TYPE: return "ORDERED_NODE_ITERATOR_TYPE";
  406. case ORDERED_NODE_SNAPSHOT_TYPE: return "ORDERED_NODE_SNAPSHOT_TYPE";
  407. case STRING_TYPE: return "STRING_TYPE";
  408. case UNORDERED_NODE_ITERATOR_TYPE: return "UNORDERED_NODE_ITERATOR_TYPE";
  409. case UNORDERED_NODE_SNAPSHOT_TYPE: return "UNORDERED_NODE_SNAPSHOT_TYPE";
  410. default: return "#UNKNOWN";
  411. }
  412. }
  413. /**
  414. * Given an XObject, determine the corresponding DOM XPath type
  415. *
  416. * @return type string
  417. */
  418. private short getTypeFromXObject(XObject object) {
  419. switch (object.getType()) {
  420. case XObject.CLASS_BOOLEAN: return BOOLEAN_TYPE;
  421. case XObject.CLASS_NODESET: return UNORDERED_NODE_ITERATOR_TYPE;
  422. case XObject.CLASS_NUMBER: return NUMBER_TYPE;
  423. case XObject.CLASS_STRING: return STRING_TYPE;
  424. // XPath 2.0 types
  425. // case XObject.CLASS_DATE:
  426. // case XObject.CLASS_DATETIME:
  427. // case XObject.CLASS_DTDURATION:
  428. // case XObject.CLASS_GDAY:
  429. // case XObject.CLASS_GMONTH:
  430. // case XObject.CLASS_GMONTHDAY:
  431. // case XObject.CLASS_GYEAR:
  432. // case XObject.CLASS_GYEARMONTH:
  433. // case XObject.CLASS_TIME:
  434. // case XObject.CLASS_YMDURATION: return STRING_TYPE; // treat all date types as strings?
  435. case XObject.CLASS_RTREEFRAG: return UNORDERED_NODE_ITERATOR_TYPE;
  436. case XObject.CLASS_NULL: return ANY_TYPE; // throw exception ?
  437. default: return ANY_TYPE; // throw exception ?
  438. }
  439. }
  440. /**
  441. * Given a node, determine if it is a namespace node.
  442. *
  443. * @param node
  444. *
  445. * @return boolean Returns true if this is a namespace node; otherwise, returns false.
  446. */
  447. private boolean isNamespaceNode(Node node) {
  448. if ((null != node) &&
  449. (node.getNodeType() == Node.ATTRIBUTE_NODE) &&
  450. (node.getNodeName().startsWith("xmlns:") || node.getNodeName().equals("xmlns"))) {
  451. return true;
  452. } else {
  453. return false;
  454. }
  455. }
  456. }