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 }