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 }