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