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: Extensions.java,v 1.29 2004/02/23 10:29:34 aruny Exp $
  18. */
  19. package com.sun.org.apache.xalan.internal.lib;
  20. import java.util.Hashtable;
  21. import java.util.StringTokenizer;
  22. import javax.xml.parsers.DocumentBuilder;
  23. import javax.xml.parsers.DocumentBuilderFactory;
  24. import javax.xml.parsers.ParserConfigurationException;
  25. import com.sun.org.apache.xalan.internal.extensions.ExpressionContext;
  26. import com.sun.org.apache.xalan.internal.xslt.EnvironmentCheck;
  27. import com.sun.org.apache.xpath.internal.NodeSet;
  28. import com.sun.org.apache.xpath.internal.objects.XBoolean;
  29. import com.sun.org.apache.xpath.internal.objects.XNumber;
  30. import com.sun.org.apache.xpath.internal.objects.XObject;
  31. import org.w3c.dom.Document;
  32. import org.w3c.dom.DocumentFragment;
  33. import org.w3c.dom.Node;
  34. import org.w3c.dom.NodeList;
  35. import org.w3c.dom.Text;
  36. import org.w3c.dom.traversal.NodeIterator;
  37. import org.xml.sax.SAXNotSupportedException;
  38. /**
  39. * This class contains many of the Xalan-supplied extensions.
  40. * It is accessed by specifying a namespace URI as follows:
  41. * <pre>
  42. * xmlns:xalan="http://xml.apache.org/xalan"
  43. * </pre>
  44. * @xsl.usage general
  45. */
  46. public class Extensions
  47. {
  48. /**
  49. * Constructor Extensions
  50. *
  51. */
  52. private Extensions(){} // Make sure class cannot be instantiated
  53. /**
  54. * This method is an extension that implements as a Xalan extension
  55. * the node-set function also found in xt and saxon.
  56. * If the argument is a Result Tree Fragment, then <code>nodeset</code>
  57. * returns a node-set consisting of a single root node as described in
  58. * section 11.1 of the XSLT 1.0 Recommendation. If the argument is a
  59. * node-set, <code>nodeset</code> returns a node-set. If the argument
  60. * is a string, number, or boolean, then <code>nodeset</code> returns
  61. * a node-set consisting of a single root node with a single text node
  62. * child that is the result of calling the XPath string() function on the
  63. * passed parameter. If the argument is anything else, then a node-set
  64. * is returned consisting of a single root node with a single text node
  65. * child that is the result of calling the java <code>toString()</code>
  66. * method on the passed argument.
  67. * Most of the
  68. * actual work here is done in <code>MethodResolver</code> and
  69. * <code>XRTreeFrag</code>.
  70. * @param myProcessor Context passed by the extension processor
  71. * @param rtf Argument in the stylesheet to the nodeset extension function
  72. *
  73. * NEEDSDOC ($objectName$) @return
  74. */
  75. public static NodeSet nodeset(ExpressionContext myProcessor, Object rtf)
  76. {
  77. String textNodeValue;
  78. if (rtf instanceof NodeIterator)
  79. {
  80. return new NodeSet((NodeIterator) rtf);
  81. }
  82. else
  83. {
  84. if (rtf instanceof String)
  85. {
  86. textNodeValue = (String) rtf;
  87. }
  88. else if (rtf instanceof Boolean)
  89. {
  90. textNodeValue = new XBoolean(((Boolean) rtf).booleanValue()).str();
  91. }
  92. else if (rtf instanceof Double)
  93. {
  94. textNodeValue = new XNumber(((Double) rtf).doubleValue()).str();
  95. }
  96. else
  97. {
  98. textNodeValue = rtf.toString();
  99. }
  100. // This no longer will work right since the DTM.
  101. // Document myDoc = myProcessor.getContextNode().getOwnerDocument();
  102. try
  103. {
  104. DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  105. DocumentBuilder db = dbf.newDocumentBuilder();
  106. Document myDoc = db.newDocument();
  107. Text textNode = myDoc.createTextNode(textNodeValue);
  108. DocumentFragment docFrag = myDoc.createDocumentFragment();
  109. docFrag.appendChild(textNode);
  110. return new NodeSet(docFrag);
  111. }
  112. catch(ParserConfigurationException pce)
  113. {
  114. throw new com.sun.org.apache.xml.internal.utils.WrappedRuntimeException(pce);
  115. }
  116. }
  117. }
  118. /**
  119. * Returns the intersection of two node-sets.
  120. *
  121. * @param nl1 NodeList for first node-set
  122. * @param nl2 NodeList for second node-set
  123. * @return a NodeList containing the nodes in nl1 that are also in nl2
  124. *
  125. * Note: The usage of this extension function in the xalan namespace
  126. * is deprecated. Please use the same function in the EXSLT sets extension
  127. * (http://exslt.org/sets).
  128. */
  129. public static NodeList intersection(NodeList nl1, NodeList nl2)
  130. {
  131. return ExsltSets.intersection(nl1, nl2);
  132. }
  133. /**
  134. * Returns the difference between two node-sets.
  135. *
  136. * @param nl1 NodeList for first node-set
  137. * @param nl2 NodeList for second node-set
  138. * @return a NodeList containing the nodes in nl1 that are not in nl2
  139. *
  140. * Note: The usage of this extension function in the xalan namespace
  141. * is deprecated. Please use the same function in the EXSLT sets extension
  142. * (http://exslt.org/sets).
  143. */
  144. public static NodeList difference(NodeList nl1, NodeList nl2)
  145. {
  146. return ExsltSets.difference(nl1, nl2);
  147. }
  148. /**
  149. * Returns node-set containing distinct string values.
  150. *
  151. * @param nl NodeList for node-set
  152. * @return a NodeList with nodes from nl containing distinct string values.
  153. * In other words, if more than one node in nl contains the same string value,
  154. * only include the first such node found.
  155. *
  156. * Note: The usage of this extension function in the xalan namespace
  157. * is deprecated. Please use the same function in the EXSLT sets extension
  158. * (http://exslt.org/sets).
  159. */
  160. public static NodeList distinct(NodeList nl)
  161. {
  162. return ExsltSets.distinct(nl);
  163. }
  164. /**
  165. * Returns true if both node-sets contain the same set of nodes.
  166. *
  167. * @param nl1 NodeList for first node-set
  168. * @param nl2 NodeList for second node-set
  169. * @return true if nl1 and nl2 contain exactly the same set of nodes.
  170. */
  171. public static boolean hasSameNodes(NodeList nl1, NodeList nl2)
  172. {
  173. NodeSet ns1 = new NodeSet(nl1);
  174. NodeSet ns2 = new NodeSet(nl2);
  175. if (ns1.getLength() != ns2.getLength())
  176. return false;
  177. for (int i = 0; i < ns1.getLength(); i++)
  178. {
  179. Node n = ns1.elementAt(i);
  180. if (!ns2.contains(n))
  181. return false;
  182. }
  183. return true;
  184. }
  185. /**
  186. * Returns the result of evaluating the argument as a string containing
  187. * an XPath expression. Used where the XPath expression is not known until
  188. * run-time. The expression is evaluated as if the run-time value of the
  189. * argument appeared in place of the evaluate function call at compile time.
  190. *
  191. * @param myContext an <code>ExpressionContext</code> passed in by the
  192. * extension mechanism. This must be an XPathContext.
  193. * @param xpathExtr The XPath expression to be evaluated.
  194. * @return the XObject resulting from evaluating the XPath
  195. *
  196. * @throws SAXNotSupportedException
  197. *
  198. * Note: The usage of this extension function in the xalan namespace
  199. * is deprecated. Please use the same function in the EXSLT dynamic extension
  200. * (http://exslt.org/dynamic).
  201. */
  202. public static XObject evaluate(ExpressionContext myContext, String xpathExpr)
  203. throws SAXNotSupportedException
  204. {
  205. return ExsltDynamic.evaluate(myContext, xpathExpr);
  206. }
  207. /**
  208. * Returns a NodeSet containing one text node for each token in the first argument.
  209. * Delimiters are specified in the second argument.
  210. * Tokens are determined by a call to <code>StringTokenizer</code>.
  211. * If the first argument is an empty string or contains only delimiters, the result
  212. * will be an empty NodeSet.
  213. *
  214. * Contributed to XalanJ1 by <a href="mailto:benoit.cerrina@writeme.com">Benoit Cerrina</a>.
  215. *
  216. * @param myContext an <code>ExpressionContext</code> passed in by the
  217. * extension mechanism. This must be an XPathContext.
  218. * @param toTokenize The string to be split into text tokens.
  219. * @param delims The delimiters to use.
  220. * @return a NodeSet as described above.
  221. */
  222. public static NodeList tokenize(String toTokenize, String delims)
  223. {
  224. Document doc = DocumentHolder.m_doc;
  225. StringTokenizer lTokenizer = new StringTokenizer(toTokenize, delims);
  226. NodeSet resultSet = new NodeSet();
  227. synchronized (doc)
  228. {
  229. while (lTokenizer.hasMoreTokens())
  230. {
  231. resultSet.addNode(doc.createTextNode(lTokenizer.nextToken()));
  232. }
  233. }
  234. return resultSet;
  235. }
  236. /**
  237. * Returns a NodeSet containing one text node for each token in the first argument.
  238. * Delimiters are whitespace. That is, the delimiters that are used are tab ( ),
  239. * linefeed ( ), return ( ), and space ( ).
  240. * Tokens are determined by a call to <code>StringTokenizer</code>.
  241. * If the first argument is an empty string or contains only delimiters, the result
  242. * will be an empty NodeSet.
  243. *
  244. * Contributed to XalanJ1 by <a href="mailto:benoit.cerrina@writeme.com">Benoit Cerrina</a>.
  245. *
  246. * @param myContext an <code>ExpressionContext</code> passed in by the
  247. * extension mechanism. This must be an XPathContext.
  248. * @param toTokenize The string to be split into text tokens.
  249. * @return a NodeSet as described above.
  250. */
  251. public static NodeList tokenize(String toTokenize)
  252. {
  253. return tokenize(toTokenize, " \t\n\r");
  254. }
  255. /**
  256. * Return a Node of basic debugging information from the
  257. * EnvironmentCheck utility about the Java environment.
  258. *
  259. * <p>Simply calls the {@link com.sun.org.apache.xalan.internal.xslt.EnvironmentCheck}
  260. * utility to grab info about the Java environment and CLASSPATH,
  261. * etc., and then returns the resulting Node. Stylesheets can
  262. * then maniuplate this data or simply xsl:copy-of the Node. Note
  263. * that we first attempt to load the more advanced
  264. * org.apache.env.Which utility by reflection; only if that fails
  265. * to we still use the internal version. Which is available from
  266. * <a href="http://xml.apache.org/commons/">http://xml.apache.org/commons/</a>.</p>
  267. *
  268. * <p>We throw a WrappedRuntimeException in the unlikely case
  269. * that reading information from the environment throws us an
  270. * exception. (Is this really the best thing to do?)</p>
  271. *
  272. * @param myContext an <code>ExpressionContext</code> passed in by the
  273. * extension mechanism. This must be an XPathContext.
  274. * @return a Node as described above.
  275. */
  276. public static Node checkEnvironment(ExpressionContext myContext)
  277. {
  278. Document factoryDocument;
  279. try
  280. {
  281. DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  282. DocumentBuilder db = dbf.newDocumentBuilder();
  283. factoryDocument = db.newDocument();
  284. }
  285. catch(ParserConfigurationException pce)
  286. {
  287. throw new com.sun.org.apache.xml.internal.utils.WrappedRuntimeException(pce);
  288. }
  289. Node resultNode = null;
  290. try
  291. {
  292. // First use reflection to try to load Which, which is a
  293. // better version of EnvironmentCheck
  294. resultNode = checkEnvironmentUsingWhich(myContext, factoryDocument);
  295. if (null != resultNode)
  296. return resultNode;
  297. // If reflection failed, fallback to our internal EnvironmentCheck
  298. EnvironmentCheck envChecker = new EnvironmentCheck();
  299. Hashtable h = envChecker.getEnvironmentHash();
  300. resultNode = factoryDocument.createElement("checkEnvironmentExtension");
  301. envChecker.appendEnvironmentReport(resultNode, factoryDocument, h);
  302. envChecker = null;
  303. }
  304. catch(Exception e)
  305. {
  306. throw new com.sun.org.apache.xml.internal.utils.WrappedRuntimeException(e);
  307. }
  308. return resultNode;
  309. }
  310. /**
  311. * Private worker method to attempt to use org.apache.env.Which.
  312. *
  313. * @param myContext an <code>ExpressionContext</code> passed in by the
  314. * extension mechanism. This must be an XPathContext.
  315. * @param factoryDocument providing createElement services, etc.
  316. * @return a Node with environment info; null if any error
  317. */
  318. private static Node checkEnvironmentUsingWhich(ExpressionContext myContext,
  319. Document factoryDocument)
  320. {
  321. final String WHICH_CLASSNAME = "org.apache.env.Which";
  322. final String WHICH_METHODNAME = "which";
  323. final Class WHICH_METHOD_ARGS[] = { java.util.Hashtable.class,
  324. java.lang.String.class,
  325. java.lang.String.class };
  326. try
  327. {
  328. // Use reflection to try to find xml-commons utility 'Which'
  329. Class clazz = ObjectFactory.findProviderClass(
  330. WHICH_CLASSNAME, ObjectFactory.findClassLoader(), true);
  331. if (null == clazz)
  332. return null;
  333. // Fully qualify names since this is the only method they're used in
  334. java.lang.reflect.Method method = clazz.getMethod(WHICH_METHODNAME, WHICH_METHOD_ARGS);
  335. Hashtable report = new Hashtable();
  336. // Call the method with our Hashtable, common options, and ignore return value
  337. Object[] methodArgs = { report, "XmlCommons;Xalan;Xerces;Crimson;Ant", "" };
  338. Object returnValue = method.invoke(null, methodArgs);
  339. // Create a parent to hold the report and append hash to it
  340. Node resultNode = factoryDocument.createElement("checkEnvironmentExtension");
  341. com.sun.org.apache.xml.internal.utils.Hashtree2Node.appendHashToNode(report, "whichReport",
  342. resultNode, factoryDocument);
  343. return resultNode;
  344. }
  345. catch (Throwable t)
  346. {
  347. // Simply return null; no need to report error
  348. return null;
  349. }
  350. }
  351. /**
  352. * This class is not loaded until first referenced (see Java Language
  353. * Specification by Gosling/Joy/Steele, section 12.4.1)
  354. *
  355. * The static members are created when this class is first referenced, as a
  356. * lazy initialization not needing checking against null or any
  357. * synchronization.
  358. *
  359. */
  360. private static class DocumentHolder
  361. {
  362. // Reuse the Document object to reduce memory usage.
  363. private static final Document m_doc;
  364. static
  365. {
  366. try
  367. {
  368. m_doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
  369. }
  370. catch(ParserConfigurationException pce)
  371. {
  372. throw new com.sun.org.apache.xml.internal.utils.WrappedRuntimeException(pce);
  373. }
  374. }
  375. }
  376. }