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: ExsltDynamic.java 468639 2006-10-28 06:52:33Z minchau $ 020 */ 021 package org.apache.xalan.lib; 022 023 import javax.xml.parsers.DocumentBuilder; 024 import javax.xml.parsers.DocumentBuilderFactory; 025 import javax.xml.transform.TransformerException; 026 027 import org.apache.xalan.extensions.ExpressionContext; 028 import org.apache.xalan.res.XSLMessages; 029 import org.apache.xalan.res.XSLTErrorResources; 030 import org.apache.xpath.NodeSet; 031 import org.apache.xpath.NodeSetDTM; 032 import org.apache.xpath.XPath; 033 import org.apache.xpath.XPathContext; 034 import org.apache.xpath.objects.XBoolean; 035 import org.apache.xpath.objects.XNodeSet; 036 import org.apache.xpath.objects.XNumber; 037 import org.apache.xpath.objects.XObject; 038 039 import org.w3c.dom.Document; 040 import org.w3c.dom.Element; 041 import org.w3c.dom.Node; 042 import org.w3c.dom.NodeList; 043 import org.w3c.dom.Text; 044 045 import org.xml.sax.SAXNotSupportedException; 046 047 /** 048 * This class contains EXSLT dynamic extension functions. 049 * 050 * It is accessed by specifying a namespace URI as follows: 051 * <pre> 052 * xmlns:dyn="http://exslt.org/dynamic" 053 * </pre> 054 * The documentation for each function has been copied from the relevant 055 * EXSLT Implementer page. 056 * 057 * @see <a href="http://www.exslt.org/">EXSLT</a> 058 059 * @xsl.usage general 060 */ 061 public class ExsltDynamic extends ExsltBase 062 { 063 064 public static final String EXSL_URI = "http://exslt.org/common"; 065 066 /** 067 * The dyn:max function calculates the maximum value for the nodes passed as 068 * the first argument, where the value of each node is calculated dynamically 069 * using an XPath expression passed as a string as the second argument. 070 * <p> 071 * The expressions are evaluated relative to the nodes passed as the first argument. 072 * In other words, the value for each node is calculated by evaluating the XPath 073 * expression with all context information being the same as that for the call to 074 * the dyn:max function itself, except for the following: 075 * <p> 076 * <ul> 077 * <li>the context node is the node whose value is being calculated.</li> 078 * <li>the context position is the position of the node within the node set passed as 079 * the first argument to the dyn:max function, arranged in document order.</li> 080 * <li>the context size is the number of nodes passed as the first argument to the 081 * dyn:max function.</li> 082 * </ul> 083 * <p> 084 * The dyn:max function returns the maximum of these values, calculated in exactly 085 * the same way as for math:max. 086 * <p> 087 * If the expression string passed as the second argument is an invalid XPath 088 * expression (including an empty string), this function returns NaN. 089 * <p> 090 * This function must take a second argument. To calculate the maximum of a set of 091 * nodes based on their string values, you should use the math:max function. 092 * 093 * @param myContext The ExpressionContext passed by the extension processor 094 * @param nl The node set 095 * @param expr The expression string 096 * 097 * @return The maximum evaluation value 098 */ 099 public static double max(ExpressionContext myContext, NodeList nl, String expr) 100 throws SAXNotSupportedException 101 { 102 103 XPathContext xctxt = null; 104 if (myContext instanceof XPathContext.XPathExpressionContext) 105 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext(); 106 else 107 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); 108 109 if (expr == null || expr.length() == 0) 110 return Double.NaN; 111 112 NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt); 113 xctxt.pushContextNodeList(contextNodes); 114 115 double maxValue = - Double.MAX_VALUE; 116 for (int i = 0; i < contextNodes.getLength(); i++) 117 { 118 int contextNode = contextNodes.item(i); 119 xctxt.pushCurrentNode(contextNode); 120 121 double result = 0; 122 try 123 { 124 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(), 125 xctxt.getNamespaceContext(), 126 XPath.SELECT); 127 result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num(); 128 } 129 catch (TransformerException e) 130 { 131 xctxt.popCurrentNode(); 132 xctxt.popContextNodeList(); 133 return Double.NaN; 134 } 135 136 xctxt.popCurrentNode(); 137 138 if (result > maxValue) 139 maxValue = result; 140 } 141 142 xctxt.popContextNodeList(); 143 return maxValue; 144 145 } 146 147 /** 148 * The dyn:min function calculates the minimum value for the nodes passed as the 149 * first argument, where the value of each node is calculated dynamically using 150 * an XPath expression passed as a string as the second argument. 151 * <p> 152 * The expressions are evaluated relative to the nodes passed as the first argument. 153 * In other words, the value for each node is calculated by evaluating the XPath 154 * expression with all context information being the same as that for the call to 155 * the dyn:min function itself, except for the following: 156 * <p> 157 * <ul> 158 * <li>the context node is the node whose value is being calculated.</li> 159 * <li>the context position is the position of the node within the node set passed 160 * as the first argument to the dyn:min function, arranged in document order.</li> 161 * <li>the context size is the number of nodes passed as the first argument to the 162 * dyn:min function.</li> 163 * </ul> 164 * <p> 165 * The dyn:min function returns the minimum of these values, calculated in exactly 166 * the same way as for math:min. 167 * <p> 168 * If the expression string passed as the second argument is an invalid XPath expression 169 * (including an empty string), this function returns NaN. 170 * <p> 171 * This function must take a second argument. To calculate the minimum of a set of 172 * nodes based on their string values, you should use the math:min function. 173 * 174 * @param myContext The ExpressionContext passed by the extension processor 175 * @param nl The node set 176 * @param expr The expression string 177 * 178 * @return The minimum evaluation value 179 */ 180 public static double min(ExpressionContext myContext, NodeList nl, String expr) 181 throws SAXNotSupportedException 182 { 183 184 XPathContext xctxt = null; 185 if (myContext instanceof XPathContext.XPathExpressionContext) 186 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext(); 187 else 188 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); 189 190 if (expr == null || expr.length() == 0) 191 return Double.NaN; 192 193 NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt); 194 xctxt.pushContextNodeList(contextNodes); 195 196 double minValue = Double.MAX_VALUE; 197 for (int i = 0; i < nl.getLength(); i++) 198 { 199 int contextNode = contextNodes.item(i); 200 xctxt.pushCurrentNode(contextNode); 201 202 double result = 0; 203 try 204 { 205 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(), 206 xctxt.getNamespaceContext(), 207 XPath.SELECT); 208 result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num(); 209 } 210 catch (TransformerException e) 211 { 212 xctxt.popCurrentNode(); 213 xctxt.popContextNodeList(); 214 return Double.NaN; 215 } 216 217 xctxt.popCurrentNode(); 218 219 if (result < minValue) 220 minValue = result; 221 } 222 223 xctxt.popContextNodeList(); 224 return minValue; 225 226 } 227 228 /** 229 * The dyn:sum function calculates the sum for the nodes passed as the first argument, 230 * where the value of each node is calculated dynamically using an XPath expression 231 * passed as a string as the second argument. 232 * <p> 233 * The expressions are evaluated relative to the nodes passed as the first argument. 234 * In other words, the value for each node is calculated by evaluating the XPath 235 * expression with all context information being the same as that for the call to 236 * the dyn:sum function itself, except for the following: 237 * <p> 238 * <ul> 239 * <li>the context node is the node whose value is being calculated.</li> 240 * <li>the context position is the position of the node within the node set passed as 241 * the first argument to the dyn:sum function, arranged in document order.</li> 242 * <li>the context size is the number of nodes passed as the first argument to the 243 * dyn:sum function.</li> 244 * </ul> 245 * <p> 246 * The dyn:sum function returns the sumimum of these values, calculated in exactly 247 * the same way as for sum. 248 * <p> 249 * If the expression string passed as the second argument is an invalid XPath 250 * expression (including an empty string), this function returns NaN. 251 * <p> 252 * This function must take a second argument. To calculate the sumimum of a set of 253 * nodes based on their string values, you should use the sum function. 254 * 255 * @param myContext The ExpressionContext passed by the extension processor 256 * @param nl The node set 257 * @param expr The expression string 258 * 259 * @return The sum of the evaluation value on each node 260 */ 261 public static double sum(ExpressionContext myContext, NodeList nl, String expr) 262 throws SAXNotSupportedException 263 { 264 XPathContext xctxt = null; 265 if (myContext instanceof XPathContext.XPathExpressionContext) 266 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext(); 267 else 268 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); 269 270 if (expr == null || expr.length() == 0) 271 return Double.NaN; 272 273 NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt); 274 xctxt.pushContextNodeList(contextNodes); 275 276 double sum = 0; 277 for (int i = 0; i < nl.getLength(); i++) 278 { 279 int contextNode = contextNodes.item(i); 280 xctxt.pushCurrentNode(contextNode); 281 282 double result = 0; 283 try 284 { 285 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(), 286 xctxt.getNamespaceContext(), 287 XPath.SELECT); 288 result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num(); 289 } 290 catch (TransformerException e) 291 { 292 xctxt.popCurrentNode(); 293 xctxt.popContextNodeList(); 294 return Double.NaN; 295 } 296 297 xctxt.popCurrentNode(); 298 299 sum = sum + result; 300 301 } 302 303 xctxt.popContextNodeList(); 304 return sum; 305 } 306 307 /** 308 * The dyn:map function evaluates the expression passed as the second argument for 309 * each of the nodes passed as the first argument, and returns a node set of those values. 310 * <p> 311 * The expressions are evaluated relative to the nodes passed as the first argument. 312 * In other words, the value for each node is calculated by evaluating the XPath 313 * expression with all context information being the same as that for the call to 314 * the dyn:map function itself, except for the following: 315 * <p> 316 * <ul> 317 * <li>The context node is the node whose value is being calculated.</li> 318 * <li>the context position is the position of the node within the node set passed 319 * as the first argument to the dyn:map function, arranged in document order.</li> 320 * <li>the context size is the number of nodes passed as the first argument to the 321 * dyn:map function.</li> 322 * </ul> 323 * <p> 324 * If the expression string passed as the second argument is an invalid XPath 325 * expression (including an empty string), this function returns an empty node set. 326 * <p> 327 * If the XPath expression evaluates as a node set, the dyn:map function returns 328 * the union of the node sets returned by evaluating the expression for each of the 329 * nodes in the first argument. Note that this may mean that the node set resulting 330 * from the call to the dyn:map function contains a different number of nodes from 331 * the number in the node set passed as the first argument to the function. 332 * <p> 333 * If the XPath expression evaluates as a number, the dyn:map function returns a 334 * node set containing one exsl:number element (namespace http://exslt.org/common) 335 * for each node in the node set passed as the first argument to the dyn:map function, 336 * in document order. The string value of each exsl:number element is the same as 337 * the result of converting the number resulting from evaluating the expression to 338 * a string as with the number function, with the exception that Infinity results 339 * in an exsl:number holding the highest number the implementation can store, and 340 * -Infinity results in an exsl:number holding the lowest number the implementation 341 * can store. 342 * <p> 343 * If the XPath expression evaluates as a boolean, the dyn:map function returns a 344 * node set containing one exsl:boolean element (namespace http://exslt.org/common) 345 * for each node in the node set passed as the first argument to the dyn:map function, 346 * in document order. The string value of each exsl:boolean element is 'true' if the 347 * expression evaluates as true for the node, and '' if the expression evaluates as 348 * false. 349 * <p> 350 * Otherwise, the dyn:map function returns a node set containing one exsl:string 351 * element (namespace http://exslt.org/common) for each node in the node set passed 352 * as the first argument to the dyn:map function, in document order. The string 353 * value of each exsl:string element is the same as the result of converting the 354 * result of evaluating the expression for the relevant node to a string as with 355 * the string function. 356 * 357 * @param myContext The ExpressionContext passed by the extension processor 358 * @param nl The node set 359 * @param expr The expression string 360 * 361 * @return The node set after evaluation 362 */ 363 public static NodeList map(ExpressionContext myContext, NodeList nl, String expr) 364 throws SAXNotSupportedException 365 { 366 XPathContext xctxt = null; 367 Document lDoc = null; 368 369 if (myContext instanceof XPathContext.XPathExpressionContext) 370 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext(); 371 else 372 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); 373 374 if (expr == null || expr.length() == 0) 375 return new NodeSet(); 376 377 NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt); 378 xctxt.pushContextNodeList(contextNodes); 379 380 NodeSet resultSet = new NodeSet(); 381 resultSet.setShouldCacheNodes(true); 382 383 for (int i = 0; i < nl.getLength(); i++) 384 { 385 int contextNode = contextNodes.item(i); 386 xctxt.pushCurrentNode(contextNode); 387 388 XObject object = null; 389 try 390 { 391 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(), 392 xctxt.getNamespaceContext(), 393 XPath.SELECT); 394 object = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()); 395 396 if (object instanceof XNodeSet) 397 { 398 NodeList nodelist = null; 399 nodelist = ((XNodeSet)object).nodelist(); 400 401 for (int k = 0; k < nodelist.getLength(); k++) 402 { 403 Node n = nodelist.item(k); 404 if (!resultSet.contains(n)) 405 resultSet.addNode(n); 406 } 407 } 408 else 409 { 410 if (lDoc == null) 411 { 412 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 413 dbf.setNamespaceAware(true); 414 DocumentBuilder db = dbf.newDocumentBuilder(); 415 lDoc = db.newDocument(); 416 } 417 418 Element element = null; 419 if (object instanceof XNumber) 420 element = lDoc.createElementNS(EXSL_URI, "exsl:number"); 421 else if (object instanceof XBoolean) 422 element = lDoc.createElementNS(EXSL_URI, "exsl:boolean"); 423 else 424 element = lDoc.createElementNS(EXSL_URI, "exsl:string"); 425 426 Text textNode = lDoc.createTextNode(object.str()); 427 element.appendChild(textNode); 428 resultSet.addNode(element); 429 } 430 } 431 catch (Exception e) 432 { 433 xctxt.popCurrentNode(); 434 xctxt.popContextNodeList(); 435 return new NodeSet(); 436 } 437 438 xctxt.popCurrentNode(); 439 440 } 441 442 xctxt.popContextNodeList(); 443 return resultSet; 444 } 445 446 /** 447 * The dyn:evaluate function evaluates a string as an XPath expression and returns 448 * the resulting value, which might be a boolean, number, string, node set, result 449 * tree fragment or external object. The sole argument is the string to be evaluated. 450 * <p> 451 * If the expression string passed as the second argument is an invalid XPath 452 * expression (including an empty string), this function returns an empty node set. 453 * <p> 454 * You should only use this function if the expression must be constructed dynamically, 455 * otherwise it is much more efficient to use the expression literally. 456 * 457 * @param myContext The ExpressionContext passed by the extension processor 458 * @param xpathExpr The XPath expression string 459 * 460 * @return The evaluation result 461 */ 462 public static XObject evaluate(ExpressionContext myContext, String xpathExpr) 463 throws SAXNotSupportedException 464 { 465 if (myContext instanceof XPathContext.XPathExpressionContext) 466 { 467 XPathContext xctxt = null; 468 try 469 { 470 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext(); 471 XPath dynamicXPath = new XPath(xpathExpr, xctxt.getSAXLocator(), 472 xctxt.getNamespaceContext(), 473 XPath.SELECT); 474 475 return dynamicXPath.execute(xctxt, myContext.getContextNode(), 476 xctxt.getNamespaceContext()); 477 } 478 catch (TransformerException e) 479 { 480 return new XNodeSet(xctxt.getDTMManager()); 481 } 482 } 483 else 484 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); //"Invalid context passed to evaluate " 485 } 486 487 /** 488 * The dyn:closure function creates a node set resulting from transitive closure of 489 * evaluating the expression passed as the second argument on each of the nodes passed 490 * as the first argument, then on the node set resulting from that and so on until no 491 * more nodes are found. For example: 492 * <pre> 493 * dyn:closure(., '*') 494 * </pre> 495 * returns all the descendant elements of the node (its element children, their 496 * children, their children's children and so on). 497 * <p> 498 * The expression is thus evaluated several times, each with a different node set 499 * acting as the context of the expression. The first time the expression is 500 * evaluated, the context node set is the first argument passed to the dyn:closure 501 * function. In other words, the node set for each node is calculated by evaluating 502 * the XPath expression with all context information being the same as that for 503 * the call to the dyn:closure function itself, except for the following: 504 * <p> 505 * <ul> 506 * <li>the context node is the node whose value is being calculated.</li> 507 * <li>the context position is the position of the node within the node set passed 508 * as the first argument to the dyn:closure function, arranged in document order.</li> 509 * <li>the context size is the number of nodes passed as the first argument to the 510 * dyn:closure function.</li> 511 * <li>the current node is the node whose value is being calculated.</li> 512 * </ul> 513 * <p> 514 * The result for a particular iteration is the union of the node sets resulting 515 * from evaluting the expression for each of the nodes in the source node set for 516 * that iteration. This result is then used as the source node set for the next 517 * iteration, and so on. The result of the function as a whole is the union of 518 * the node sets generated by each iteration. 519 * <p> 520 * If the expression string passed as the second argument is an invalid XPath 521 * expression (including an empty string) or an expression that does not return a 522 * node set, this function returns an empty node set. 523 * 524 * @param myContext The ExpressionContext passed by the extension processor 525 * @param nl The node set 526 * @param expr The expression string 527 * 528 * @return The node set after evaluation 529 */ 530 public static NodeList closure(ExpressionContext myContext, NodeList nl, String expr) 531 throws SAXNotSupportedException 532 { 533 XPathContext xctxt = null; 534 if (myContext instanceof XPathContext.XPathExpressionContext) 535 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext(); 536 else 537 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); 538 539 if (expr == null || expr.length() == 0) 540 return new NodeSet(); 541 542 NodeSet closureSet = new NodeSet(); 543 closureSet.setShouldCacheNodes(true); 544 545 NodeList iterationList = nl; 546 do 547 { 548 549 NodeSet iterationSet = new NodeSet(); 550 551 NodeSetDTM contextNodes = new NodeSetDTM(iterationList, xctxt); 552 xctxt.pushContextNodeList(contextNodes); 553 554 for (int i = 0; i < iterationList.getLength(); i++) 555 { 556 int contextNode = contextNodes.item(i); 557 xctxt.pushCurrentNode(contextNode); 558 559 XObject object = null; 560 try 561 { 562 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(), 563 xctxt.getNamespaceContext(), 564 XPath.SELECT); 565 object = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()); 566 567 if (object instanceof XNodeSet) 568 { 569 NodeList nodelist = null; 570 nodelist = ((XNodeSet)object).nodelist(); 571 572 for (int k = 0; k < nodelist.getLength(); k++) 573 { 574 Node n = nodelist.item(k); 575 if (!iterationSet.contains(n)) 576 iterationSet.addNode(n); 577 } 578 } 579 else 580 { 581 xctxt.popCurrentNode(); 582 xctxt.popContextNodeList(); 583 return new NodeSet(); 584 } 585 } 586 catch (TransformerException e) 587 { 588 xctxt.popCurrentNode(); 589 xctxt.popContextNodeList(); 590 return new NodeSet(); 591 } 592 593 xctxt.popCurrentNode(); 594 595 } 596 597 xctxt.popContextNodeList(); 598 599 iterationList = iterationSet; 600 601 for (int i = 0; i < iterationList.getLength(); i++) 602 { 603 Node n = iterationList.item(i); 604 if (!closureSet.contains(n)) 605 closureSet.addNode(n); 606 } 607 608 } while(iterationList.getLength() > 0); 609 610 return closureSet; 611 612 } 613 614 }