1. /*
  2. * Copyright 1999-2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /*
  17. * $Id: ExsltDynamic.java,v 1.11 2004/02/11 17:56:36 minchau Exp $
  18. */
  19. package com.sun.org.apache.xalan.internal.lib;
  20. import javax.xml.parsers.DocumentBuilder;
  21. import javax.xml.parsers.DocumentBuilderFactory;
  22. import javax.xml.transform.TransformerException;
  23. import com.sun.org.apache.xalan.internal.extensions.ExpressionContext;
  24. import com.sun.org.apache.xalan.internal.res.XSLMessages;
  25. import com.sun.org.apache.xalan.internal.res.XSLTErrorResources;
  26. import com.sun.org.apache.xpath.internal.NodeSet;
  27. import com.sun.org.apache.xpath.internal.NodeSetDTM;
  28. import com.sun.org.apache.xpath.internal.XPath;
  29. import com.sun.org.apache.xpath.internal.XPathContext;
  30. import com.sun.org.apache.xpath.internal.objects.XBoolean;
  31. import com.sun.org.apache.xpath.internal.objects.XNodeSet;
  32. import com.sun.org.apache.xpath.internal.objects.XNumber;
  33. import com.sun.org.apache.xpath.internal.objects.XObject;
  34. import org.w3c.dom.Document;
  35. import org.w3c.dom.Element;
  36. import org.w3c.dom.Node;
  37. import org.w3c.dom.NodeList;
  38. import org.w3c.dom.Text;
  39. import org.xml.sax.SAXNotSupportedException;
  40. /**
  41. * This class contains EXSLT dynamic extension functions.
  42. *
  43. * It is accessed by specifying a namespace URI as follows:
  44. * <pre>
  45. * xmlns:dyn="http://exslt.org/dynamic"
  46. * </pre>
  47. * The documentation for each function has been copied from the relevant
  48. * EXSLT Implementer page.
  49. *
  50. * @see <a href="http://www.exslt.org/">EXSLT</a>
  51. * @xsl.usage general
  52. */
  53. public class ExsltDynamic extends ExsltBase
  54. {
  55. public static final String EXSL_URI = "http://exslt.org/common";
  56. /**
  57. * The dyn:max function calculates the maximum value for the nodes passed as
  58. * the first argument, where the value of each node is calculated dynamically
  59. * using an XPath expression passed as a string as the second argument.
  60. * <p>
  61. * The expressions are evaluated relative to the nodes passed as the first argument.
  62. * In other words, the value for each node is calculated by evaluating the XPath
  63. * expression with all context information being the same as that for the call to
  64. * the dyn:max function itself, except for the following:
  65. * <p>
  66. * <ul>
  67. * <li>the context node is the node whose value is being calculated.</li>
  68. * <li>the context position is the position of the node within the node set passed as
  69. * the first argument to the dyn:max function, arranged in document order.</li>
  70. * <li>the context size is the number of nodes passed as the first argument to the
  71. * dyn:max function.</li>
  72. * </ul>
  73. * <p>
  74. * The dyn:max function returns the maximum of these values, calculated in exactly
  75. * the same way as for math:max.
  76. * <p>
  77. * If the expression string passed as the second argument is an invalid XPath
  78. * expression (including an empty string), this function returns NaN.
  79. * <p>
  80. * This function must take a second argument. To calculate the maximum of a set of
  81. * nodes based on their string values, you should use the math:max function.
  82. *
  83. * @param myContext The ExpressionContext passed by the extension processor
  84. * @param nl The node set
  85. * @param expr The expression string
  86. *
  87. * @return The maximum evaluation value
  88. */
  89. public static double max(ExpressionContext myContext, NodeList nl, String expr)
  90. throws SAXNotSupportedException
  91. {
  92. XPathContext xctxt = null;
  93. if (myContext instanceof XPathContext.XPathExpressionContext)
  94. xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
  95. else
  96. throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
  97. if (expr == null || expr.length() == 0)
  98. return Double.NaN;
  99. NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt);
  100. xctxt.pushContextNodeList(contextNodes);
  101. double maxValue = - Double.MAX_VALUE;
  102. for (int i = 0; i < contextNodes.getLength(); i++)
  103. {
  104. int contextNode = contextNodes.item(i);
  105. xctxt.pushCurrentNode(contextNode);
  106. double result = 0;
  107. try
  108. {
  109. XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
  110. xctxt.getNamespaceContext(),
  111. XPath.SELECT);
  112. result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num();
  113. }
  114. catch (TransformerException e)
  115. {
  116. xctxt.popCurrentNode();
  117. xctxt.popContextNodeList();
  118. return Double.NaN;
  119. }
  120. xctxt.popCurrentNode();
  121. if (result > maxValue)
  122. maxValue = result;
  123. }
  124. xctxt.popContextNodeList();
  125. return maxValue;
  126. }
  127. /**
  128. * The dyn:min function calculates the minimum value for the nodes passed as the
  129. * first argument, where the value of each node is calculated dynamically using
  130. * an XPath expression passed as a string as the second argument.
  131. * <p>
  132. * The expressions are evaluated relative to the nodes passed as the first argument.
  133. * In other words, the value for each node is calculated by evaluating the XPath
  134. * expression with all context information being the same as that for the call to
  135. * the dyn:min function itself, except for the following:
  136. * <p>
  137. * <ul>
  138. * <li>the context node is the node whose value is being calculated.</li>
  139. * <li>the context position is the position of the node within the node set passed
  140. * as the first argument to the dyn:min function, arranged in document order.</li>
  141. * <li>the context size is the number of nodes passed as the first argument to the
  142. * dyn:min function.</li>
  143. * </ul>
  144. * <p>
  145. * The dyn:min function returns the minimum of these values, calculated in exactly
  146. * the same way as for math:min.
  147. * <p>
  148. * If the expression string passed as the second argument is an invalid XPath expression
  149. * (including an empty string), this function returns NaN.
  150. * <p>
  151. * This function must take a second argument. To calculate the minimum of a set of
  152. * nodes based on their string values, you should use the math:min function.
  153. *
  154. * @param myContext The ExpressionContext passed by the extension processor
  155. * @param nl The node set
  156. * @param expr The expression string
  157. *
  158. * @return The minimum evaluation value
  159. */
  160. public static double min(ExpressionContext myContext, NodeList nl, String expr)
  161. throws SAXNotSupportedException
  162. {
  163. XPathContext xctxt = null;
  164. if (myContext instanceof XPathContext.XPathExpressionContext)
  165. xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
  166. else
  167. throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
  168. if (expr == null || expr.length() == 0)
  169. return Double.NaN;
  170. NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt);
  171. xctxt.pushContextNodeList(contextNodes);
  172. double minValue = Double.MAX_VALUE;
  173. for (int i = 0; i < nl.getLength(); i++)
  174. {
  175. int contextNode = contextNodes.item(i);
  176. xctxt.pushCurrentNode(contextNode);
  177. double result = 0;
  178. try
  179. {
  180. XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
  181. xctxt.getNamespaceContext(),
  182. XPath.SELECT);
  183. result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num();
  184. }
  185. catch (TransformerException e)
  186. {
  187. xctxt.popCurrentNode();
  188. xctxt.popContextNodeList();
  189. return Double.NaN;
  190. }
  191. xctxt.popCurrentNode();
  192. if (result < minValue)
  193. minValue = result;
  194. }
  195. xctxt.popContextNodeList();
  196. return minValue;
  197. }
  198. /**
  199. * The dyn:sum function calculates the sum for the nodes passed as the first argument,
  200. * where the value of each node is calculated dynamically using an XPath expression
  201. * passed as a string as the second argument.
  202. * <p>
  203. * The expressions are evaluated relative to the nodes passed as the first argument.
  204. * In other words, the value for each node is calculated by evaluating the XPath
  205. * expression with all context information being the same as that for the call to
  206. * the dyn:sum function itself, except for the following:
  207. * <p>
  208. * <ul>
  209. * <li>the context node is the node whose value is being calculated.</li>
  210. * <li>the context position is the position of the node within the node set passed as
  211. * the first argument to the dyn:sum function, arranged in document order.</li>
  212. * <li>the context size is the number of nodes passed as the first argument to the
  213. * dyn:sum function.</li>
  214. * </ul>
  215. * <p>
  216. * The dyn:sum function returns the sumimum of these values, calculated in exactly
  217. * the same way as for sum.
  218. * <p>
  219. * If the expression string passed as the second argument is an invalid XPath
  220. * expression (including an empty string), this function returns NaN.
  221. * <p>
  222. * This function must take a second argument. To calculate the sumimum of a set of
  223. * nodes based on their string values, you should use the sum function.
  224. *
  225. * @param myContext The ExpressionContext passed by the extension processor
  226. * @param nl The node set
  227. * @param expr The expression string
  228. *
  229. * @return The sum of the evaluation value on each node
  230. */
  231. public static double sum(ExpressionContext myContext, NodeList nl, String expr)
  232. throws SAXNotSupportedException
  233. {
  234. XPathContext xctxt = null;
  235. if (myContext instanceof XPathContext.XPathExpressionContext)
  236. xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
  237. else
  238. throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
  239. if (expr == null || expr.length() == 0)
  240. return Double.NaN;
  241. NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt);
  242. xctxt.pushContextNodeList(contextNodes);
  243. double sum = 0;
  244. for (int i = 0; i < nl.getLength(); i++)
  245. {
  246. int contextNode = contextNodes.item(i);
  247. xctxt.pushCurrentNode(contextNode);
  248. double result = 0;
  249. try
  250. {
  251. XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
  252. xctxt.getNamespaceContext(),
  253. XPath.SELECT);
  254. result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num();
  255. }
  256. catch (TransformerException e)
  257. {
  258. xctxt.popCurrentNode();
  259. xctxt.popContextNodeList();
  260. return Double.NaN;
  261. }
  262. xctxt.popCurrentNode();
  263. sum = sum + result;
  264. }
  265. xctxt.popContextNodeList();
  266. return sum;
  267. }
  268. /**
  269. * The dyn:map function evaluates the expression passed as the second argument for
  270. * each of the nodes passed as the first argument, and returns a node set of those values.
  271. * <p>
  272. * The expressions are evaluated relative to the nodes passed as the first argument.
  273. * In other words, the value for each node is calculated by evaluating the XPath
  274. * expression with all context information being the same as that for the call to
  275. * the dyn:map function itself, except for the following:
  276. * <p>
  277. * <ul>
  278. * <li>The context node is the node whose value is being calculated.</li>
  279. * <li>the context position is the position of the node within the node set passed
  280. * as the first argument to the dyn:map function, arranged in document order.</li>
  281. * <li>the context size is the number of nodes passed as the first argument to the
  282. * dyn:map function.</li>
  283. * </ul>
  284. * <p>
  285. * If the expression string passed as the second argument is an invalid XPath
  286. * expression (including an empty string), this function returns an empty node set.
  287. * <p>
  288. * If the XPath expression evaluates as a node set, the dyn:map function returns
  289. * the union of the node sets returned by evaluating the expression for each of the
  290. * nodes in the first argument. Note that this may mean that the node set resulting
  291. * from the call to the dyn:map function contains a different number of nodes from
  292. * the number in the node set passed as the first argument to the function.
  293. * <p>
  294. * If the XPath expression evaluates as a number, the dyn:map function returns a
  295. * node set containing one exsl:number element (namespace http://exslt.org/common)
  296. * for each node in the node set passed as the first argument to the dyn:map function,
  297. * in document order. The string value of each exsl:number element is the same as
  298. * the result of converting the number resulting from evaluating the expression to
  299. * a string as with the number function, with the exception that Infinity results
  300. * in an exsl:number holding the highest number the implementation can store, and
  301. * -Infinity results in an exsl:number holding the lowest number the implementation
  302. * can store.
  303. * <p>
  304. * If the XPath expression evaluates as a boolean, the dyn:map function returns a
  305. * node set containing one exsl:boolean element (namespace http://exslt.org/common)
  306. * for each node in the node set passed as the first argument to the dyn:map function,
  307. * in document order. The string value of each exsl:boolean element is 'true' if the
  308. * expression evaluates as true for the node, and '' if the expression evaluates as
  309. * false.
  310. * <p>
  311. * Otherwise, the dyn:map function returns a node set containing one exsl:string
  312. * element (namespace http://exslt.org/common) for each node in the node set passed
  313. * as the first argument to the dyn:map function, in document order. The string
  314. * value of each exsl:string element is the same as the result of converting the
  315. * result of evaluating the expression for the relevant node to a string as with
  316. * the string function.
  317. *
  318. * @param myContext The ExpressionContext passed by the extension processor
  319. * @param nl The node set
  320. * @param expr The expression string
  321. *
  322. * @return The node set after evaluation
  323. */
  324. public static NodeList map(ExpressionContext myContext, NodeList nl, String expr)
  325. throws SAXNotSupportedException
  326. {
  327. XPathContext xctxt = null;
  328. Document lDoc = null;
  329. if (myContext instanceof XPathContext.XPathExpressionContext)
  330. xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
  331. else
  332. throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
  333. if (expr == null || expr.length() == 0)
  334. return new NodeSet();
  335. NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt);
  336. xctxt.pushContextNodeList(contextNodes);
  337. NodeSet resultSet = new NodeSet();
  338. resultSet.setShouldCacheNodes(true);
  339. for (int i = 0; i < nl.getLength(); i++)
  340. {
  341. int contextNode = contextNodes.item(i);
  342. xctxt.pushCurrentNode(contextNode);
  343. XObject object = null;
  344. try
  345. {
  346. XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
  347. xctxt.getNamespaceContext(),
  348. XPath.SELECT);
  349. object = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext());
  350. if (object instanceof XNodeSet)
  351. {
  352. NodeList nodelist = null;
  353. nodelist = ((XNodeSet)object).nodelist();
  354. for (int k = 0; k < nodelist.getLength(); k++)
  355. {
  356. Node n = nodelist.item(k);
  357. if (!resultSet.contains(n))
  358. resultSet.addNode(n);
  359. }
  360. }
  361. else
  362. {
  363. if (lDoc == null)
  364. {
  365. DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  366. dbf.setNamespaceAware(true);
  367. DocumentBuilder db = dbf.newDocumentBuilder();
  368. lDoc = db.newDocument();
  369. }
  370. Element element = null;
  371. if (object instanceof XNumber)
  372. element = lDoc.createElementNS(EXSL_URI, "exsl:number");
  373. else if (object instanceof XBoolean)
  374. element = lDoc.createElementNS(EXSL_URI, "exsl:boolean");
  375. else
  376. element = lDoc.createElementNS(EXSL_URI, "exsl:string");
  377. Text textNode = lDoc.createTextNode(object.str());
  378. element.appendChild(textNode);
  379. resultSet.addNode(element);
  380. }
  381. }
  382. catch (Exception e)
  383. {
  384. xctxt.popCurrentNode();
  385. xctxt.popContextNodeList();
  386. return new NodeSet();
  387. }
  388. xctxt.popCurrentNode();
  389. }
  390. xctxt.popContextNodeList();
  391. return resultSet;
  392. }
  393. /**
  394. * The dyn:evaluate function evaluates a string as an XPath expression and returns
  395. * the resulting value, which might be a boolean, number, string, node set, result
  396. * tree fragment or external object. The sole argument is the string to be evaluated.
  397. * <p>
  398. * If the expression string passed as the second argument is an invalid XPath
  399. * expression (including an empty string), this function returns an empty node set.
  400. * <p>
  401. * You should only use this function if the expression must be constructed dynamically,
  402. * otherwise it is much more efficient to use the expression literally.
  403. *
  404. * @param myContext The ExpressionContext passed by the extension processor
  405. * @param xpathExpr The XPath expression string
  406. *
  407. * @return The evaluation result
  408. */
  409. public static XObject evaluate(ExpressionContext myContext, String xpathExpr)
  410. throws SAXNotSupportedException
  411. {
  412. if (myContext instanceof XPathContext.XPathExpressionContext)
  413. {
  414. XPathContext xctxt = null;
  415. try
  416. {
  417. xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
  418. XPath dynamicXPath = new XPath(xpathExpr, xctxt.getSAXLocator(),
  419. xctxt.getNamespaceContext(),
  420. XPath.SELECT);
  421. return dynamicXPath.execute(xctxt, myContext.getContextNode(),
  422. xctxt.getNamespaceContext());
  423. }
  424. catch (TransformerException e)
  425. {
  426. return new XNodeSet(xctxt.getDTMManager());
  427. }
  428. }
  429. else
  430. throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); //"Invalid context passed to evaluate "
  431. }
  432. /**
  433. * The dyn:closure function creates a node set resulting from transitive closure of
  434. * evaluating the expression passed as the second argument on each of the nodes passed
  435. * as the first argument, then on the node set resulting from that and so on until no
  436. * more nodes are found. For example:
  437. * <pre>
  438. * dyn:closure(., '*')
  439. * </pre>
  440. * returns all the descendant elements of the node (its element children, their
  441. * children, their children's children and so on).
  442. * <p>
  443. * The expression is thus evaluated several times, each with a different node set
  444. * acting as the context of the expression. The first time the expression is
  445. * evaluated, the context node set is the first argument passed to the dyn:closure
  446. * function. In other words, the node set for each node is calculated by evaluating
  447. * the XPath expression with all context information being the same as that for
  448. * the call to the dyn:closure function itself, except for the following:
  449. * <p>
  450. * <ul>
  451. * <li>the context node is the node whose value is being calculated.</li>
  452. * <li>the context position is the position of the node within the node set passed
  453. * as the first argument to the dyn:closure function, arranged in document order.</li>
  454. * <li>the context size is the number of nodes passed as the first argument to the
  455. * dyn:closure function.</li>
  456. * <li>the current node is the node whose value is being calculated.</li>
  457. * </ul>
  458. * <p>
  459. * The result for a particular iteration is the union of the node sets resulting
  460. * from evaluting the expression for each of the nodes in the source node set for
  461. * that iteration. This result is then used as the source node set for the next
  462. * iteration, and so on. The result of the function as a whole is the union of
  463. * the node sets generated by each iteration.
  464. * <p>
  465. * If the expression string passed as the second argument is an invalid XPath
  466. * expression (including an empty string) or an expression that does not return a
  467. * node set, this function returns an empty node set.
  468. *
  469. * @param myContext The ExpressionContext passed by the extension processor
  470. * @param nl The node set
  471. * @param expr The expression string
  472. *
  473. * @return The node set after evaluation
  474. */
  475. public static NodeList closure(ExpressionContext myContext, NodeList nl, String expr)
  476. throws SAXNotSupportedException
  477. {
  478. XPathContext xctxt = null;
  479. if (myContext instanceof XPathContext.XPathExpressionContext)
  480. xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
  481. else
  482. throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
  483. if (expr == null || expr.length() == 0)
  484. return new NodeSet();
  485. NodeSet closureSet = new NodeSet();
  486. closureSet.setShouldCacheNodes(true);
  487. NodeList iterationList = nl;
  488. do
  489. {
  490. NodeSet iterationSet = new NodeSet();
  491. NodeSetDTM contextNodes = new NodeSetDTM(iterationList, xctxt);
  492. xctxt.pushContextNodeList(contextNodes);
  493. for (int i = 0; i < iterationList.getLength(); i++)
  494. {
  495. int contextNode = contextNodes.item(i);
  496. xctxt.pushCurrentNode(contextNode);
  497. XObject object = null;
  498. try
  499. {
  500. XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
  501. xctxt.getNamespaceContext(),
  502. XPath.SELECT);
  503. object = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext());
  504. if (object instanceof XNodeSet)
  505. {
  506. NodeList nodelist = null;
  507. nodelist = ((XNodeSet)object).nodelist();
  508. for (int k = 0; k < nodelist.getLength(); k++)
  509. {
  510. Node n = nodelist.item(k);
  511. if (!iterationSet.contains(n))
  512. iterationSet.addNode(n);
  513. }
  514. }
  515. else
  516. {
  517. xctxt.popCurrentNode();
  518. xctxt.popContextNodeList();
  519. return new NodeSet();
  520. }
  521. }
  522. catch (TransformerException e)
  523. {
  524. xctxt.popCurrentNode();
  525. xctxt.popContextNodeList();
  526. return new NodeSet();
  527. }
  528. xctxt.popCurrentNode();
  529. }
  530. xctxt.popContextNodeList();
  531. iterationList = iterationSet;
  532. for (int i = 0; i < iterationList.getLength(); i++)
  533. {
  534. Node n = iterationList.item(i);
  535. if (!closureSet.contains(n))
  536. closureSet.addNode(n);
  537. }
  538. } while(iterationList.getLength() > 0);
  539. return closureSet;
  540. }
  541. }