001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: Extensions.java 468639 2006-10-28 06:52:33Z minchau $
020 */
021 package org.apache.xalan.lib;
022
023 import java.util.Hashtable;
024 import java.util.StringTokenizer;
025
026 import javax.xml.parsers.DocumentBuilder;
027 import javax.xml.parsers.DocumentBuilderFactory;
028 import javax.xml.parsers.ParserConfigurationException;
029
030 import org.apache.xalan.extensions.ExpressionContext;
031 import org.apache.xalan.xslt.EnvironmentCheck;
032 import org.apache.xpath.NodeSet;
033 import org.apache.xpath.objects.XBoolean;
034 import org.apache.xpath.objects.XNumber;
035 import org.apache.xpath.objects.XObject;
036
037 import org.w3c.dom.Document;
038 import org.w3c.dom.DocumentFragment;
039 import org.w3c.dom.Node;
040 import org.w3c.dom.NodeList;
041 import org.w3c.dom.Text;
042 import org.w3c.dom.traversal.NodeIterator;
043
044 import org.xml.sax.SAXNotSupportedException;
045
046 /**
047 * This class contains many of the Xalan-supplied extensions.
048 * It is accessed by specifying a namespace URI as follows:
049 * <pre>
050 * xmlns:xalan="http://xml.apache.org/xalan"
051 * </pre>
052 * @xsl.usage general
053 */
054 public class Extensions
055 {
056 /**
057 * Constructor Extensions
058 *
059 */
060 private Extensions(){} // Make sure class cannot be instantiated
061
062 /**
063 * This method is an extension that implements as a Xalan extension
064 * the node-set function also found in xt and saxon.
065 * If the argument is a Result Tree Fragment, then <code>nodeset</code>
066 * returns a node-set consisting of a single root node as described in
067 * section 11.1 of the XSLT 1.0 Recommendation. If the argument is a
068 * node-set, <code>nodeset</code> returns a node-set. If the argument
069 * is a string, number, or boolean, then <code>nodeset</code> returns
070 * a node-set consisting of a single root node with a single text node
071 * child that is the result of calling the XPath string() function on the
072 * passed parameter. If the argument is anything else, then a node-set
073 * is returned consisting of a single root node with a single text node
074 * child that is the result of calling the java <code>toString()</code>
075 * method on the passed argument.
076 * Most of the
077 * actual work here is done in <code>MethodResolver</code> and
078 * <code>XRTreeFrag</code>.
079 * @param myProcessor Context passed by the extension processor
080 * @param rtf Argument in the stylesheet to the nodeset extension function
081 *
082 * NEEDSDOC ($objectName$) @return
083 */
084 public static NodeSet nodeset(ExpressionContext myProcessor, Object rtf)
085 {
086
087 String textNodeValue;
088
089 if (rtf instanceof NodeIterator)
090 {
091 return new NodeSet((NodeIterator) rtf);
092 }
093 else
094 {
095 if (rtf instanceof String)
096 {
097 textNodeValue = (String) rtf;
098 }
099 else if (rtf instanceof Boolean)
100 {
101 textNodeValue = new XBoolean(((Boolean) rtf).booleanValue()).str();
102 }
103 else if (rtf instanceof Double)
104 {
105 textNodeValue = new XNumber(((Double) rtf).doubleValue()).str();
106 }
107 else
108 {
109 textNodeValue = rtf.toString();
110 }
111
112 // This no longer will work right since the DTM.
113 // Document myDoc = myProcessor.getContextNode().getOwnerDocument();
114 try
115 {
116 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
117 DocumentBuilder db = dbf.newDocumentBuilder();
118 Document myDoc = db.newDocument();
119
120 Text textNode = myDoc.createTextNode(textNodeValue);
121 DocumentFragment docFrag = myDoc.createDocumentFragment();
122
123 docFrag.appendChild(textNode);
124
125 return new NodeSet(docFrag);
126 }
127 catch(ParserConfigurationException pce)
128 {
129 throw new org.apache.xml.utils.WrappedRuntimeException(pce);
130 }
131 }
132 }
133
134 /**
135 * Returns the intersection of two node-sets.
136 *
137 * @param nl1 NodeList for first node-set
138 * @param nl2 NodeList for second node-set
139 * @return a NodeList containing the nodes in nl1 that are also in nl2
140 *
141 * Note: The usage of this extension function in the xalan namespace
142 * is deprecated. Please use the same function in the EXSLT sets extension
143 * (http://exslt.org/sets).
144 */
145 public static NodeList intersection(NodeList nl1, NodeList nl2)
146 {
147 return ExsltSets.intersection(nl1, nl2);
148 }
149
150 /**
151 * Returns the difference between two node-sets.
152 *
153 * @param nl1 NodeList for first node-set
154 * @param nl2 NodeList for second node-set
155 * @return a NodeList containing the nodes in nl1 that are not in nl2
156 *
157 * Note: The usage of this extension function in the xalan namespace
158 * is deprecated. Please use the same function in the EXSLT sets extension
159 * (http://exslt.org/sets).
160 */
161 public static NodeList difference(NodeList nl1, NodeList nl2)
162 {
163 return ExsltSets.difference(nl1, nl2);
164 }
165
166 /**
167 * Returns node-set containing distinct string values.
168 *
169 * @param nl NodeList for node-set
170 * @return a NodeList with nodes from nl containing distinct string values.
171 * In other words, if more than one node in nl contains the same string value,
172 * only include the first such node found.
173 *
174 * Note: The usage of this extension function in the xalan namespace
175 * is deprecated. Please use the same function in the EXSLT sets extension
176 * (http://exslt.org/sets).
177 */
178 public static NodeList distinct(NodeList nl)
179 {
180 return ExsltSets.distinct(nl);
181 }
182
183 /**
184 * Returns true if both node-sets contain the same set of nodes.
185 *
186 * @param nl1 NodeList for first node-set
187 * @param nl2 NodeList for second node-set
188 * @return true if nl1 and nl2 contain exactly the same set of nodes.
189 */
190 public static boolean hasSameNodes(NodeList nl1, NodeList nl2)
191 {
192
193 NodeSet ns1 = new NodeSet(nl1);
194 NodeSet ns2 = new NodeSet(nl2);
195
196 if (ns1.getLength() != ns2.getLength())
197 return false;
198
199 for (int i = 0; i < ns1.getLength(); i++)
200 {
201 Node n = ns1.elementAt(i);
202
203 if (!ns2.contains(n))
204 return false;
205 }
206
207 return true;
208 }
209
210 /**
211 * Returns the result of evaluating the argument as a string containing
212 * an XPath expression. Used where the XPath expression is not known until
213 * run-time. The expression is evaluated as if the run-time value of the
214 * argument appeared in place of the evaluate function call at compile time.
215 *
216 * @param myContext an <code>ExpressionContext</code> passed in by the
217 * extension mechanism. This must be an XPathContext.
218 * @param xpathExpr The XPath expression to be evaluated.
219 * @return the XObject resulting from evaluating the XPath
220 *
221 * @throws SAXNotSupportedException
222 *
223 * Note: The usage of this extension function in the xalan namespace
224 * is deprecated. Please use the same function in the EXSLT dynamic extension
225 * (http://exslt.org/dynamic).
226 */
227 public static XObject evaluate(ExpressionContext myContext, String xpathExpr)
228 throws SAXNotSupportedException
229 {
230 return ExsltDynamic.evaluate(myContext, xpathExpr);
231 }
232
233 /**
234 * Returns a NodeSet containing one text node for each token in the first argument.
235 * Delimiters are specified in the second argument.
236 * Tokens are determined by a call to <code>StringTokenizer</code>.
237 * If the first argument is an empty string or contains only delimiters, the result
238 * will be an empty NodeSet.
239 *
240 * Contributed to XalanJ1 by <a href="mailto:benoit.cerrina@writeme.com">Benoit Cerrina</a>.
241 *
242 * @param toTokenize The string to be split into text tokens.
243 * @param delims The delimiters to use.
244 * @return a NodeSet as described above.
245 */
246 public static NodeList tokenize(String toTokenize, String delims)
247 {
248
249 Document doc = DocumentHolder.m_doc;
250
251
252 StringTokenizer lTokenizer = new StringTokenizer(toTokenize, delims);
253 NodeSet resultSet = new NodeSet();
254
255 synchronized (doc)
256 {
257 while (lTokenizer.hasMoreTokens())
258 {
259 resultSet.addNode(doc.createTextNode(lTokenizer.nextToken()));
260 }
261 }
262
263 return resultSet;
264 }
265
266 /**
267 * Returns a NodeSet containing one text node for each token in the first argument.
268 * Delimiters are whitespace. That is, the delimiters that are used are tab (	),
269 * linefeed (
), return (
), and space ( ).
270 * Tokens are determined by a call to <code>StringTokenizer</code>.
271 * If the first argument is an empty string or contains only delimiters, the result
272 * will be an empty NodeSet.
273 *
274 * Contributed to XalanJ1 by <a href="mailto:benoit.cerrina@writeme.com">Benoit Cerrina</a>.
275 *
276 * @param toTokenize The string to be split into text tokens.
277 * @return a NodeSet as described above.
278 */
279 public static NodeList tokenize(String toTokenize)
280 {
281 return tokenize(toTokenize, " \t\n\r");
282 }
283
284 /**
285 * Return a Node of basic debugging information from the
286 * EnvironmentCheck utility about the Java environment.
287 *
288 * <p>Simply calls the {@link org.apache.xalan.xslt.EnvironmentCheck}
289 * utility to grab info about the Java environment and CLASSPATH,
290 * etc., and then returns the resulting Node. Stylesheets can
291 * then maniuplate this data or simply xsl:copy-of the Node. Note
292 * that we first attempt to load the more advanced
293 * org.apache.env.Which utility by reflection; only if that fails
294 * to we still use the internal version. Which is available from
295 * <a href="http://xml.apache.org/commons/">http://xml.apache.org/commons/</a>.</p>
296 *
297 * <p>We throw a WrappedRuntimeException in the unlikely case
298 * that reading information from the environment throws us an
299 * exception. (Is this really the best thing to do?)</p>
300 *
301 * @param myContext an <code>ExpressionContext</code> passed in by the
302 * extension mechanism. This must be an XPathContext.
303 * @return a Node as described above.
304 */
305 public static Node checkEnvironment(ExpressionContext myContext)
306 {
307
308 Document factoryDocument;
309 try
310 {
311 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
312 DocumentBuilder db = dbf.newDocumentBuilder();
313 factoryDocument = db.newDocument();
314 }
315 catch(ParserConfigurationException pce)
316 {
317 throw new org.apache.xml.utils.WrappedRuntimeException(pce);
318 }
319
320 Node resultNode = null;
321 try
322 {
323 // First use reflection to try to load Which, which is a
324 // better version of EnvironmentCheck
325 resultNode = checkEnvironmentUsingWhich(myContext, factoryDocument);
326
327 if (null != resultNode)
328 return resultNode;
329
330 // If reflection failed, fallback to our internal EnvironmentCheck
331 EnvironmentCheck envChecker = new EnvironmentCheck();
332 Hashtable h = envChecker.getEnvironmentHash();
333 resultNode = factoryDocument.createElement("checkEnvironmentExtension");
334 envChecker.appendEnvironmentReport(resultNode, factoryDocument, h);
335 envChecker = null;
336 }
337 catch(Exception e)
338 {
339 throw new org.apache.xml.utils.WrappedRuntimeException(e);
340 }
341
342 return resultNode;
343 }
344
345 /**
346 * Private worker method to attempt to use org.apache.env.Which.
347 *
348 * @param myContext an <code>ExpressionContext</code> passed in by the
349 * extension mechanism. This must be an XPathContext.
350 * @param factoryDocument providing createElement services, etc.
351 * @return a Node with environment info; null if any error
352 */
353 private static Node checkEnvironmentUsingWhich(ExpressionContext myContext,
354 Document factoryDocument)
355 {
356 final String WHICH_CLASSNAME = "org.apache.env.Which";
357 final String WHICH_METHODNAME = "which";
358 final Class WHICH_METHOD_ARGS[] = { java.util.Hashtable.class,
359 java.lang.String.class,
360 java.lang.String.class };
361 try
362 {
363 // Use reflection to try to find xml-commons utility 'Which'
364 Class clazz = ObjectFactory.findProviderClass(
365 WHICH_CLASSNAME, ObjectFactory.findClassLoader(), true);
366 if (null == clazz)
367 return null;
368
369 // Fully qualify names since this is the only method they're used in
370 java.lang.reflect.Method method = clazz.getMethod(WHICH_METHODNAME, WHICH_METHOD_ARGS);
371 Hashtable report = new Hashtable();
372
373 // Call the method with our Hashtable, common options, and ignore return value
374 Object[] methodArgs = { report, "XmlCommons;Xalan;Xerces;Crimson;Ant", "" };
375 Object returnValue = method.invoke(null, methodArgs);
376
377 // Create a parent to hold the report and append hash to it
378 Node resultNode = factoryDocument.createElement("checkEnvironmentExtension");
379 org.apache.xml.utils.Hashtree2Node.appendHashToNode(report, "whichReport",
380 resultNode, factoryDocument);
381
382 return resultNode;
383 }
384 catch (Throwable t)
385 {
386 // Simply return null; no need to report error
387 return null;
388 }
389 }
390
391 /**
392 * This class is not loaded until first referenced (see Java Language
393 * Specification by Gosling/Joy/Steele, section 12.4.1)
394 *
395 * The static members are created when this class is first referenced, as a
396 * lazy initialization not needing checking against null or any
397 * synchronization.
398 *
399 */
400 private static class DocumentHolder
401 {
402 // Reuse the Document object to reduce memory usage.
403 private static final Document m_doc;
404 static
405 {
406 try
407 {
408 m_doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
409 }
410
411 catch(ParserConfigurationException pce)
412 {
413 throw new org.apache.xml.utils.WrappedRuntimeException(pce);
414 }
415
416 }
417 }
418 }