1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 */ 4 /* 5 * Licensed to the Apache Software Foundation (ASF) under one or more 6 * contributor license agreements. See the NOTICE file distributed with 7 * this work for additional information regarding copyright ownership. 8 * The ASF licenses this file to You under the Apache License, Version 2.0 9 * (the "License"); you may not use this file except in compliance with 10 * the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 package com.sun.org.apache.xalan.internal.lib; 22 23 import javax.xml.parsers.DocumentBuilder; 24 import javax.xml.parsers.DocumentBuilderFactory; 25 import javax.xml.transform.TransformerException; 26 27 import com.sun.org.apache.xalan.internal.extensions.ExpressionContext; 28 import com.sun.org.apache.xalan.internal.res.XSLMessages; 29 import com.sun.org.apache.xalan.internal.res.XSLTErrorResources; 30 import com.sun.org.apache.xpath.internal.NodeSet; 31 import com.sun.org.apache.xpath.internal.NodeSetDTM; 32 import com.sun.org.apache.xpath.internal.XPath; 33 import com.sun.org.apache.xpath.internal.XPathContext; 34 import com.sun.org.apache.xpath.internal.objects.XBoolean; 35 import com.sun.org.apache.xpath.internal.objects.XNodeSet; 36 import com.sun.org.apache.xpath.internal.objects.XNumber; 37 import com.sun.org.apache.xpath.internal.objects.XObject; 38 import jdk.xml.internal.JdkXmlUtils; 39 40 import org.w3c.dom.Document; 41 import org.w3c.dom.Element; 42 import org.w3c.dom.Node; 43 import org.w3c.dom.NodeList; 44 import org.w3c.dom.Text; 45 46 import org.xml.sax.SAXNotSupportedException; 47 48 /** 49 * This class contains EXSLT dynamic extension functions. 50 * 51 * It is accessed by specifying a namespace URI as follows: 52 * <pre> 53 * xmlns:dyn="http://exslt.org/dynamic" 54 * </pre> 55 * The documentation for each function has been copied from the relevant 56 * EXSLT Implementer page. 57 * 58 * @see <a href="http://www.exslt.org/">EXSLT</a> 59 60 * @xsl.usage general 61 */ 62 public class ExsltDynamic extends ExsltBase 63 { 64 65 public static final String EXSL_URI = "http://exslt.org/common"; 66 67 /** 68 * The dyn:max function calculates the maximum value for the nodes passed as 69 * the first argument, where the value of each node is calculated dynamically 70 * using an XPath expression passed as a string as the second argument. 71 * <p> 72 * The expressions are evaluated relative to the nodes passed as the first argument. 73 * In other words, the value for each node is calculated by evaluating the XPath 74 * expression with all context information being the same as that for the call to 75 * the dyn:max function itself, except for the following: 76 * <p> 77 * <ul> 78 * <li>the context node is the node whose value is being calculated.</li> 79 * <li>the context position is the position of the node within the node set passed as 80 * the first argument to the dyn:max function, arranged in document order.</li> 81 * <li>the context size is the number of nodes passed as the first argument to the 82 * dyn:max function.</li> 83 * </ul> 84 * <p> 85 * The dyn:max function returns the maximum of these values, calculated in exactly 86 * the same way as for math:max. 87 * <p> 88 * If the expression string passed as the second argument is an invalid XPath 89 * expression (including an empty string), this function returns NaN. 90 * <p> 91 * This function must take a second argument. To calculate the maximum of a set of 92 * nodes based on their string values, you should use the math:max function. 93 * 94 * @param myContext The ExpressionContext passed by the extension processor 95 * @param nl The node set 96 * @param expr The expression string 97 * 98 * @return The maximum evaluation value 99 */ 100 public static double max(ExpressionContext myContext, NodeList nl, String expr) 101 throws SAXNotSupportedException 102 { 103 104 XPathContext xctxt = null; 105 if (myContext instanceof XPathContext.XPathExpressionContext) 106 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext(); 107 else 108 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); 109 110 if (expr == null || expr.length() == 0) 111 return Double.NaN; 112 113 NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt); 114 xctxt.pushContextNodeList(contextNodes); 115 116 double maxValue = - Double.MAX_VALUE; 117 for (int i = 0; i < contextNodes.getLength(); i++) 118 { 119 int contextNode = contextNodes.item(i); 120 xctxt.pushCurrentNode(contextNode); 121 122 double result = 0; 123 try 124 { 125 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(), 126 xctxt.getNamespaceContext(), 127 XPath.SELECT); 128 result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num(); 129 } 130 catch (TransformerException e) 131 { 132 xctxt.popCurrentNode(); 133 xctxt.popContextNodeList(); 134 return Double.NaN; 135 } 136 137 xctxt.popCurrentNode(); 138 139 if (result > maxValue) 140 maxValue = result; 141 } 142 143 xctxt.popContextNodeList(); 144 return maxValue; 145 146 } 147 148 /** 149 * The dyn:min function calculates the minimum value for the nodes passed as the 150 * first argument, where the value of each node is calculated dynamically using 151 * an XPath expression passed as a string as the second argument. 152 * <p> 153 * The expressions are evaluated relative to the nodes passed as the first argument. 154 * In other words, the value for each node is calculated by evaluating the XPath 155 * expression with all context information being the same as that for the call to 156 * the dyn:min function itself, except for the following: 157 * <p> 158 * <ul> 159 * <li>the context node is the node whose value is being calculated.</li> 160 * <li>the context position is the position of the node within the node set passed 161 * as the first argument to the dyn:min function, arranged in document order.</li> 162 * <li>the context size is the number of nodes passed as the first argument to the 163 * dyn:min function.</li> 164 * </ul> 165 * <p> 166 * The dyn:min function returns the minimum of these values, calculated in exactly 167 * the same way as for math:min. 168 * <p> 169 * If the expression string passed as the second argument is an invalid XPath expression 170 * (including an empty string), this function returns NaN. 171 * <p> 172 * This function must take a second argument. To calculate the minimum of a set of 173 * nodes based on their string values, you should use the math:min function. 174 * 175 * @param myContext The ExpressionContext passed by the extension processor 176 * @param nl The node set 177 * @param expr The expression string 178 * 179 * @return The minimum evaluation value 180 */ 181 public static double min(ExpressionContext myContext, NodeList nl, String expr) 182 throws SAXNotSupportedException 183 { 184 185 XPathContext xctxt = null; 186 if (myContext instanceof XPathContext.XPathExpressionContext) 187 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext(); 188 else 189 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); 190 191 if (expr == null || expr.length() == 0) 192 return Double.NaN; 193 194 NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt); 195 xctxt.pushContextNodeList(contextNodes); 196 197 double minValue = Double.MAX_VALUE; 198 for (int i = 0; i < nl.getLength(); i++) 199 { 200 int contextNode = contextNodes.item(i); 201 xctxt.pushCurrentNode(contextNode); 202 203 double result = 0; 204 try 205 { 206 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(), 207 xctxt.getNamespaceContext(), 208 XPath.SELECT); 209 result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num(); 210 } 211 catch (TransformerException e) 212 { 213 xctxt.popCurrentNode(); 214 xctxt.popContextNodeList(); 215 return Double.NaN; 216 } 217 218 xctxt.popCurrentNode(); 219 220 if (result < minValue) 221 minValue = result; 222 } 223 224 xctxt.popContextNodeList(); 225 return minValue; 226 227 } 228 229 /** 230 * The dyn:sum function calculates the sum for the nodes passed as the first argument, 231 * where the value of each node is calculated dynamically using an XPath expression 232 * passed as a string as the second argument. 233 * <p> 234 * The expressions are evaluated relative to the nodes passed as the first argument. 235 * In other words, the value for each node is calculated by evaluating the XPath 236 * expression with all context information being the same as that for the call to 237 * the dyn:sum function itself, except for the following: 238 * <p> 239 * <ul> 240 * <li>the context node is the node whose value is being calculated.</li> 241 * <li>the context position is the position of the node within the node set passed as 242 * the first argument to the dyn:sum function, arranged in document order.</li> 243 * <li>the context size is the number of nodes passed as the first argument to the 244 * dyn:sum function.</li> 245 * </ul> 246 * <p> 247 * The dyn:sum function returns the sumimum of these values, calculated in exactly 248 * the same way as for sum. 249 * <p> 250 * If the expression string passed as the second argument is an invalid XPath 251 * expression (including an empty string), this function returns NaN. 252 * <p> 253 * This function must take a second argument. To calculate the sumimum of a set of 254 * nodes based on their string values, you should use the sum function. 255 * 256 * @param myContext The ExpressionContext passed by the extension processor 257 * @param nl The node set 258 * @param expr The expression string 259 * 260 * @return The sum of the evaluation value on each node 261 */ 262 public static double sum(ExpressionContext myContext, NodeList nl, String expr) 263 throws SAXNotSupportedException 264 { 265 XPathContext xctxt = null; 266 if (myContext instanceof XPathContext.XPathExpressionContext) 267 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext(); 268 else 269 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); 270 271 if (expr == null || expr.length() == 0) 272 return Double.NaN; 273 274 NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt); 275 xctxt.pushContextNodeList(contextNodes); 276 277 double sum = 0; 278 for (int i = 0; i < nl.getLength(); i++) 279 { 280 int contextNode = contextNodes.item(i); 281 xctxt.pushCurrentNode(contextNode); 282 283 double result = 0; 284 try 285 { 286 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(), 287 xctxt.getNamespaceContext(), 288 XPath.SELECT); 289 result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num(); 290 } 291 catch (TransformerException e) 292 { 293 xctxt.popCurrentNode(); 294 xctxt.popContextNodeList(); 295 return Double.NaN; 296 } 297 298 xctxt.popCurrentNode(); 299 300 sum = sum + result; 301 302 } 303 304 xctxt.popContextNodeList(); 305 return sum; 306 } 307 308 /** 309 * The dyn:map function evaluates the expression passed as the second argument for 310 * each of the nodes passed as the first argument, and returns a node set of those values. 311 * <p> 312 * The expressions are evaluated relative to the nodes passed as the first argument. 313 * In other words, the value for each node is calculated by evaluating the XPath 314 * expression with all context information being the same as that for the call to 315 * the dyn:map function itself, except for the following: 316 * <p> 317 * <ul> 318 * <li>The context node is the node whose value is being calculated.</li> 319 * <li>the context position is the position of the node within the node set passed 320 * as the first argument to the dyn:map function, arranged in document order.</li> 321 * <li>the context size is the number of nodes passed as the first argument to the 322 * dyn:map function.</li> 323 * </ul> 324 * <p> 325 * If the expression string passed as the second argument is an invalid XPath 326 * expression (including an empty string), this function returns an empty node set. 327 * <p> 328 * If the XPath expression evaluates as a node set, the dyn:map function returns 329 * the union of the node sets returned by evaluating the expression for each of the 330 * nodes in the first argument. Note that this may mean that the node set resulting 331 * from the call to the dyn:map function contains a different number of nodes from 332 * the number in the node set passed as the first argument to the function. 333 * <p> 334 * If the XPath expression evaluates as a number, the dyn:map function returns a 335 * node set containing one exsl:number element (namespace http://exslt.org/common) 336 * for each node in the node set passed as the first argument to the dyn:map function, 337 * in document order. The string value of each exsl:number element is the same as 338 * the result of converting the number resulting from evaluating the expression to 339 * a string as with the number function, with the exception that Infinity results 340 * in an exsl:number holding the highest number the implementation can store, and 341 * -Infinity results in an exsl:number holding the lowest number the implementation 342 * can store. 343 * <p> 344 * If the XPath expression evaluates as a boolean, the dyn:map function returns a 345 * node set containing one exsl:boolean element (namespace http://exslt.org/common) 346 * for each node in the node set passed as the first argument to the dyn:map function, 347 * in document order. The string value of each exsl:boolean element is 'true' if the 348 * expression evaluates as true for the node, and '' if the expression evaluates as 349 * false. 350 * <p> 351 * Otherwise, the dyn:map function returns a node set containing one exsl:string 352 * element (namespace http://exslt.org/common) for each node in the node set passed 353 * as the first argument to the dyn:map function, in document order. The string 354 * value of each exsl:string element is the same as the result of converting the 355 * result of evaluating the expression for the relevant node to a string as with 356 * the string function. 357 * 358 * @param myContext The ExpressionContext passed by the extension processor 359 * @param nl The node set 360 * @param expr The expression string 361 * 362 * @return The node set after evaluation 363 */ 364 public static NodeList map(ExpressionContext myContext, NodeList nl, String expr) 365 throws SAXNotSupportedException 366 { 367 XPathContext xctxt = null; 368 Document lDoc = null; 369 370 if (myContext instanceof XPathContext.XPathExpressionContext) 371 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext(); 372 else 373 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); 374 375 if (expr == null || expr.length() == 0) 376 return new NodeSet(); 377 378 NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt); 379 xctxt.pushContextNodeList(contextNodes); 380 381 NodeSet resultSet = new NodeSet(); 382 resultSet.setShouldCacheNodes(true); 383 384 for (int i = 0; i < nl.getLength(); i++) 385 { 386 int contextNode = contextNodes.item(i); 387 xctxt.pushCurrentNode(contextNode); 388 389 XObject object = null; 390 try 391 { 392 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(), 393 xctxt.getNamespaceContext(), 394 XPath.SELECT); 395 object = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()); 396 397 if (object instanceof XNodeSet) 398 { 399 NodeList nodelist = null; 400 nodelist = ((XNodeSet)object).nodelist(); 401 402 for (int k = 0; k < nodelist.getLength(); k++) 403 { 404 Node n = nodelist.item(k); 405 if (!resultSet.contains(n)) 406 resultSet.addNode(n); 407 } 408 } 409 else 410 { 411 if (lDoc == null) 412 { 413 lDoc = JdkXmlUtils.getDOMDocument(); 414 } 415 416 Element element = null; 417 if (object instanceof XNumber) 418 element = lDoc.createElementNS(EXSL_URI, "exsl:number"); 419 else if (object instanceof XBoolean) 420 element = lDoc.createElementNS(EXSL_URI, "exsl:boolean"); 421 else 422 element = lDoc.createElementNS(EXSL_URI, "exsl:string"); 423 424 Text textNode = lDoc.createTextNode(object.str()); 425 element.appendChild(textNode); 426 resultSet.addNode(element); 427 } 428 } 429 catch (Exception e) 430 { 431 xctxt.popCurrentNode(); 432 xctxt.popContextNodeList(); 433 return new NodeSet(); 434 } 435 436 xctxt.popCurrentNode(); 437 438 } 439 440 xctxt.popContextNodeList(); 441 return resultSet; 442 } 443 444 /** 445 * The dyn:evaluate function evaluates a string as an XPath expression and returns 446 * the resulting value, which might be a boolean, number, string, node set, result 447 * tree fragment or external object. The sole argument is the string to be evaluated. 448 * <p> 449 * If the expression string passed as the second argument is an invalid XPath 450 * expression (including an empty string), this function returns an empty node set. 451 * <p> 452 * You should only use this function if the expression must be constructed dynamically, 453 * otherwise it is much more efficient to use the expression literally. 454 * 455 * @param myContext The ExpressionContext passed by the extension processor 456 * @param xpathExpr The XPath expression string 457 * 458 * @return The evaluation result 459 */ 460 public static XObject evaluate(ExpressionContext myContext, String xpathExpr) 461 throws SAXNotSupportedException 462 { 463 if (myContext instanceof XPathContext.XPathExpressionContext) 464 { 465 XPathContext xctxt = null; 466 try 467 { 468 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext(); 469 XPath dynamicXPath = new XPath(xpathExpr, xctxt.getSAXLocator(), 470 xctxt.getNamespaceContext(), 471 XPath.SELECT); 472 473 return dynamicXPath.execute(xctxt, myContext.getContextNode(), 474 xctxt.getNamespaceContext()); 475 } 476 catch (TransformerException e) 477 { 478 return new XNodeSet(xctxt.getDTMManager()); 479 } 480 } 481 else 482 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); //"Invalid context passed to evaluate " 483 } 484 485 /** 486 * The dyn:closure function creates a node set resulting from transitive closure of 487 * evaluating the expression passed as the second argument on each of the nodes passed 488 * as the first argument, then on the node set resulting from that and so on until no 489 * more nodes are found. For example: 490 * <pre> 491 * dyn:closure(., '*') 492 * </pre> 493 * returns all the descendant elements of the node (its element children, their 494 * children, their children's children and so on). 495 * <p> 496 * The expression is thus evaluated several times, each with a different node set 497 * acting as the context of the expression. The first time the expression is 498 * evaluated, the context node set is the first argument passed to the dyn:closure 499 * function. In other words, the node set for each node is calculated by evaluating 500 * the XPath expression with all context information being the same as that for 501 * the call to the dyn:closure function itself, except for the following: 502 * <p> 503 * <ul> 504 * <li>the context node is the node whose value is being calculated.</li> 505 * <li>the context position is the position of the node within the node set passed 506 * as the first argument to the dyn:closure function, arranged in document order.</li> 507 * <li>the context size is the number of nodes passed as the first argument to the 508 * dyn:closure function.</li> 509 * <li>the current node is the node whose value is being calculated.</li> 510 * </ul> 511 * <p> 512 * The result for a particular iteration is the union of the node sets resulting 513 * from evaluting the expression for each of the nodes in the source node set for 514 * that iteration. This result is then used as the source node set for the next 515 * iteration, and so on. The result of the function as a whole is the union of 516 * the node sets generated by each iteration. 517 * <p> 518 * If the expression string passed as the second argument is an invalid XPath 519 * expression (including an empty string) or an expression that does not return a 520 * node set, this function returns an empty node set. 521 * 522 * @param myContext The ExpressionContext passed by the extension processor 523 * @param nl The node set 524 * @param expr The expression string 525 * 526 * @return The node set after evaluation 527 */ 528 public static NodeList closure(ExpressionContext myContext, NodeList nl, String expr) 529 throws SAXNotSupportedException 530 { 531 XPathContext xctxt = null; 532 if (myContext instanceof XPathContext.XPathExpressionContext) 533 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext(); 534 else 535 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); 536 537 if (expr == null || expr.length() == 0) 538 return new NodeSet(); 539 540 NodeSet closureSet = new NodeSet(); 541 closureSet.setShouldCacheNodes(true); 542 543 NodeList iterationList = nl; 544 do 545 { 546 547 NodeSet iterationSet = new NodeSet(); 548 549 NodeSetDTM contextNodes = new NodeSetDTM(iterationList, xctxt); 550 xctxt.pushContextNodeList(contextNodes); 551 552 for (int i = 0; i < iterationList.getLength(); i++) 553 { 554 int contextNode = contextNodes.item(i); 555 xctxt.pushCurrentNode(contextNode); 556 557 XObject object = null; 558 try 559 { 560 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(), 561 xctxt.getNamespaceContext(), 562 XPath.SELECT); 563 object = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()); 564 565 if (object instanceof XNodeSet) 566 { 567 NodeList nodelist = null; 568 nodelist = ((XNodeSet)object).nodelist(); 569 570 for (int k = 0; k < nodelist.getLength(); k++) 571 { 572 Node n = nodelist.item(k); 573 if (!iterationSet.contains(n)) 574 iterationSet.addNode(n); 575 } 576 } 577 else 578 { 579 xctxt.popCurrentNode(); 580 xctxt.popContextNodeList(); 581 return new NodeSet(); 582 } 583 } 584 catch (TransformerException e) 585 { 586 xctxt.popCurrentNode(); 587 xctxt.popContextNodeList(); 588 return new NodeSet(); 589 } 590 591 xctxt.popCurrentNode(); 592 593 } 594 595 xctxt.popContextNodeList(); 596 597 iterationList = iterationSet; 598 599 for (int i = 0; i < iterationList.getLength(); i++) 600 { 601 Node n = iterationList.item(i); 602 if (!closureSet.contains(n)) 603 closureSet.addNode(n); 604 } 605 606 } while(iterationList.getLength() > 0); 607 608 return closureSet; 609 610 } 611 612 }