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 (&#x09),
269       * linefeed (&#x0A), return (&#x0D), and space (&#x20).
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    }