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    }