- /*
- * The Apache Software License, Version 1.1
- *
- *
- * Copyright (c) 1999 The Apache Software Foundation. All rights
- * reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * 3. The end-user documentation included with the redistribution,
- * if any, must include the following acknowledgment:
- * "This product includes software developed by the
- * Apache Software Foundation (http://www.apache.org/)."
- * Alternately, this acknowledgment may appear in the software itself,
- * if and wherever such third-party acknowledgments normally appear.
- *
- * 4. The names "Xalan" and "Apache Software Foundation" must
- * not be used to endorse or promote products derived from this
- * software without prior written permission. For written
- * permission, please contact apache@apache.org.
- *
- * 5. Products derived from this software may not be called "Apache",
- * nor may "Apache" appear in their name, without prior written
- * permission of the Apache Software Foundation.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation and was
- * originally based on software copyright (c) 1999, Lotus
- * Development Corporation., http://www.lotus.com. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- */
- package org.apache.xalan.lib;
-
- import org.w3c.dom.*;
-
- import org.apache.xpath.objects.XObject;
- import org.apache.xpath.objects.XBoolean;
- import org.apache.xpath.objects.XNumber;
- import org.apache.xpath.objects.XNodeSet;
-
- import org.apache.xpath.XPath;
- import org.apache.xpath.XPathContext;
- import org.apache.xpath.NodeSet;
- import org.apache.xpath.NodeSetDTM;
-
- import org.xml.sax.SAXNotSupportedException;
-
- import org.apache.xalan.extensions.ExpressionContext;
- import org.apache.xalan.res.XSLMessages;
- import org.apache.xalan.res.XSLTErrorResources;
-
- import javax.xml.transform.*;
- import javax.xml.parsers.*;
-
- /**
- * <meta name="usage" content="general"/>
- * This class contains EXSLT dynamic extension functions.
- *
- * It is accessed by specifying a namespace URI as follows:
- * <pre>
- * xmlns:math="http://exslt.org/dynamic"
- * </pre>
- * The documentation for each function has been copied from the relevant
- * EXSLT Implementer page.
- *
- * @see <a href="http://www.exslt.org/">EXSLT</a>
-
- */
- public class ExsltDynamic extends ExsltBase
- {
-
- public static final String EXSL_URI = "http://exslt.org/common";
-
- /**
- * The dyn:max function calculates the maximum value for the nodes passed as
- * the first argument, where the value of each node is calculated dynamically
- * using an XPath expression passed as a string as the second argument.
- * <p>
- * The expressions are evaluated relative to the nodes passed as the first argument.
- * In other words, the value for each node is calculated by evaluating the XPath
- * expression with all context information being the same as that for the call to
- * the dyn:max function itself, except for the following:
- * <p>
- * <ul>
- * <li>the context node is the node whose value is being calculated.</li>
- * <li>the context position is the position of the node within the node set passed as
- * the first argument to the dyn:max function, arranged in document order.</li>
- * <li>the context size is the number of nodes passed as the first argument to the
- * dyn:max function.</li>
- * </ul>
- * <p>
- * The dyn:max function returns the maximum of these values, calculated in exactly
- * the same way as for math:max.
- * <p>
- * If the expression string passed as the second argument is an invalid XPath
- * expression (including an empty string), this function returns NaN.
- * <p>
- * This function must take a second argument. To calculate the maximum of a set of
- * nodes based on their string values, you should use the math:max function.
- *
- * @param myContext The ExpressionContext passed by the extension processor
- * @param nl The node set
- * @param expr The expression string
- *
- * @return The maximum evaluation value
- */
- public static double max(ExpressionContext myContext, NodeList nl, String expr)
- throws SAXNotSupportedException
- {
-
- XPathContext xctxt = null;
- if (myContext instanceof XPathContext.XPathExpressionContext)
- xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
- else
- throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
-
- if (expr == null || expr.length() == 0)
- return Double.NaN;
-
- NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt);
- xctxt.pushContextNodeList(contextNodes);
-
- double maxValue = Double.MIN_VALUE;
- for (int i = 0; i < contextNodes.getLength(); i++)
- {
- int contextNode = contextNodes.item(i);
- xctxt.pushCurrentNode(contextNode);
-
- double result = 0;
- try
- {
- XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
- xctxt.getNamespaceContext(),
- XPath.SELECT);
- result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num();
- }
- catch (TransformerException e)
- {
- xctxt.popCurrentNode();
- xctxt.popContextNodeList();
- return Double.NaN;
- }
-
- xctxt.popCurrentNode();
-
- if (result > maxValue)
- maxValue = result;
- }
-
- xctxt.popContextNodeList();
- return maxValue;
-
- }
-
- /**
- * The dyn:min function calculates the minimum value for the nodes passed as the
- * first argument, where the value of each node is calculated dynamically using
- * an XPath expression passed as a string as the second argument.
- * <p>
- * The expressions are evaluated relative to the nodes passed as the first argument.
- * In other words, the value for each node is calculated by evaluating the XPath
- * expression with all context information being the same as that for the call to
- * the dyn:min function itself, except for the following:
- * <p>
- * <ul>
- * <li>the context node is the node whose value is being calculated.</li>
- * <li>the context position is the position of the node within the node set passed
- * as the first argument to the dyn:min function, arranged in document order.</li>
- * <li>the context size is the number of nodes passed as the first argument to the
- * dyn:min function.</li>
- * </ul>
- * <p>
- * The dyn:min function returns the minimum of these values, calculated in exactly
- * the same way as for math:min.
- * <p>
- * If the expression string passed as the second argument is an invalid XPath expression
- * (including an empty string), this function returns NaN.
- * <p>
- * This function must take a second argument. To calculate the minimum of a set of
- * nodes based on their string values, you should use the math:min function.
- *
- * @param myContext The ExpressionContext passed by the extension processor
- * @param nl The node set
- * @param expr The expression string
- *
- * @return The minimum evaluation value
- */
- public static double min(ExpressionContext myContext, NodeList nl, String expr)
- throws SAXNotSupportedException
- {
-
- XPathContext xctxt = null;
- if (myContext instanceof XPathContext.XPathExpressionContext)
- xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
- else
- throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
-
- if (expr == null || expr.length() == 0)
- return Double.NaN;
-
- NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt);
- xctxt.pushContextNodeList(contextNodes);
-
- double minValue = Double.MAX_VALUE;
- for (int i = 0; i < nl.getLength(); i++)
- {
- int contextNode = contextNodes.item(i);
- xctxt.pushCurrentNode(contextNode);
-
- double result = 0;
- try
- {
- XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
- xctxt.getNamespaceContext(),
- XPath.SELECT);
- result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num();
- }
- catch (TransformerException e)
- {
- xctxt.popCurrentNode();
- xctxt.popContextNodeList();
- return Double.NaN;
- }
-
- xctxt.popCurrentNode();
-
- if (result < minValue)
- minValue = result;
- }
-
- xctxt.popContextNodeList();
- return minValue;
-
- }
-
- /**
- * The dyn:sum function calculates the sum for the nodes passed as the first argument,
- * where the value of each node is calculated dynamically using an XPath expression
- * passed as a string as the second argument.
- * <p>
- * The expressions are evaluated relative to the nodes passed as the first argument.
- * In other words, the value for each node is calculated by evaluating the XPath
- * expression with all context information being the same as that for the call to
- * the dyn:sum function itself, except for the following:
- * <p>
- * <ul>
- * <li>the context node is the node whose value is being calculated.</li>
- * <li>the context position is the position of the node within the node set passed as
- * the first argument to the dyn:sum function, arranged in document order.</li>
- * <li>the context size is the number of nodes passed as the first argument to the
- * dyn:sum function.</li>
- * </ul>
- * <p>
- * The dyn:sum function returns the sumimum of these values, calculated in exactly
- * the same way as for sum.
- * <p>
- * If the expression string passed as the second argument is an invalid XPath
- * expression (including an empty string), this function returns NaN.
- * <p>
- * This function must take a second argument. To calculate the sumimum of a set of
- * nodes based on their string values, you should use the sum function.
- *
- * @param myContext The ExpressionContext passed by the extension processor
- * @param nl The node set
- * @param expr The expression string
- *
- * @return The sum of the evaluation value on each node
- */
- public static double sum(ExpressionContext myContext, NodeList nl, String expr)
- throws SAXNotSupportedException
- {
- XPathContext xctxt = null;
- if (myContext instanceof XPathContext.XPathExpressionContext)
- xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
- else
- throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
-
- if (expr == null || expr.length() == 0)
- return Double.NaN;
-
- NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt);
- xctxt.pushContextNodeList(contextNodes);
-
- double sum = 0;
- for (int i = 0; i < nl.getLength(); i++)
- {
- int contextNode = contextNodes.item(i);
- xctxt.pushCurrentNode(contextNode);
-
- double result = 0;
- try
- {
- XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
- xctxt.getNamespaceContext(),
- XPath.SELECT);
- result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num();
- }
- catch (TransformerException e)
- {
- xctxt.popCurrentNode();
- xctxt.popContextNodeList();
- return Double.NaN;
- }
-
- xctxt.popCurrentNode();
-
- sum = sum + result;
-
- }
-
- xctxt.popContextNodeList();
- return sum;
- }
-
- /**
- * The dyn:map function evaluates the expression passed as the second argument for
- * each of the nodes passed as the first argument, and returns a node set of those values.
- * <p>
- * The expressions are evaluated relative to the nodes passed as the first argument.
- * In other words, the value for each node is calculated by evaluating the XPath
- * expression with all context information being the same as that for the call to
- * the dyn:map function itself, except for the following:
- * <p>
- * <ul>
- * <li>The context node is the node whose value is being calculated.</li>
- * <li>the context position is the position of the node within the node set passed
- * as the first argument to the dyn:map function, arranged in document order.</li>
- * <li>the context size is the number of nodes passed as the first argument to the
- * dyn:map function.</li>
- * </ul>
- * <p>
- * If the expression string passed as the second argument is an invalid XPath
- * expression (including an empty string), this function returns an empty node set.
- * <p>
- * If the XPath expression evaluates as a node set, the dyn:map function returns
- * the union of the node sets returned by evaluating the expression for each of the
- * nodes in the first argument. Note that this may mean that the node set resulting
- * from the call to the dyn:map function contains a different number of nodes from
- * the number in the node set passed as the first argument to the function.
- * <p>
- * If the XPath expression evaluates as a number, the dyn:map function returns a
- * node set containing one exsl:number element (namespace http://exslt.org/common)
- * for each node in the node set passed as the first argument to the dyn:map function,
- * in document order. The string value of each exsl:number element is the same as
- * the result of converting the number resulting from evaluating the expression to
- * a string as with the number function, with the exception that Infinity results
- * in an exsl:number holding the highest number the implementation can store, and
- * -Infinity results in an exsl:number holding the lowest number the implementation
- * can store.
- * <p>
- * If the XPath expression evaluates as a boolean, the dyn:map function returns a
- * node set containing one exsl:boolean element (namespace http://exslt.org/common)
- * for each node in the node set passed as the first argument to the dyn:map function,
- * in document order. The string value of each exsl:boolean element is 'true' if the
- * expression evaluates as true for the node, and '' if the expression evaluates as
- * false.
- * <p>
- * Otherwise, the dyn:map function returns a node set containing one exsl:string
- * element (namespace http://exslt.org/common) for each node in the node set passed
- * as the first argument to the dyn:map function, in document order. The string
- * value of each exsl:string element is the same as the result of converting the
- * result of evaluating the expression for the relevant node to a string as with
- * the string function.
- *
- * @param myContext The ExpressionContext passed by the extension processor
- * @param nl The node set
- * @param expr The expression string
- *
- * @return The node set after evaluation
- */
- public static NodeList map(ExpressionContext myContext, NodeList nl, String expr)
- throws SAXNotSupportedException
- {
- XPathContext xctxt = null;
- if (myContext instanceof XPathContext.XPathExpressionContext)
- xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
- else
- throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
-
- if (expr == null || expr.length() == 0)
- return new NodeSet();
-
- NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt);
- xctxt.pushContextNodeList(contextNodes);
-
- NodeSet resultSet = new NodeSet();
- resultSet.setShouldCacheNodes(true);
-
- for (int i = 0; i < nl.getLength(); i++)
- {
- int contextNode = contextNodes.item(i);
- xctxt.pushCurrentNode(contextNode);
-
- XObject object = null;
- try
- {
- XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
- xctxt.getNamespaceContext(),
- XPath.SELECT);
- object = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext());
-
- if (object instanceof XNodeSet)
- {
- NodeList nodelist = null;
- nodelist = ((XNodeSet)object).nodelist();
-
- for (int k = 0; k < nodelist.getLength(); k++)
- {
- Node n = nodelist.item(k);
- if (!resultSet.contains(n))
- resultSet.addNode(n);
- }
- }
- else
- {
- Document lDoc = null;
-
- DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
- dbf.setNamespaceAware(true);
- DocumentBuilder db = dbf.newDocumentBuilder();
- lDoc = db.newDocument();
-
- Element element = null;
- if (object instanceof XNumber)
- element = lDoc.createElementNS(EXSL_URI, "exsl:number");
- else if (object instanceof XBoolean)
- element = lDoc.createElementNS(EXSL_URI, "exsl:boolean");
- else
- element = lDoc.createElementNS(EXSL_URI, "exsl:string");
-
- Text textNode = lDoc.createTextNode(object.str());
- element.appendChild(textNode);
- resultSet.addNode(element);
- }
- }
- catch (Exception e)
- {
- xctxt.popCurrentNode();
- xctxt.popContextNodeList();
- return new NodeSet();
- }
-
- xctxt.popCurrentNode();
-
- }
-
- xctxt.popContextNodeList();
- return resultSet;
- }
-
- /**
- * The dyn:evaluate function evaluates a string as an XPath expression and returns
- * the resulting value, which might be a boolean, number, string, node set, result
- * tree fragment or external object. The sole argument is the string to be evaluated.
- * <p>
- * If the expression string passed as the second argument is an invalid XPath
- * expression (including an empty string), this function returns an empty node set.
- * <p>
- * You should only use this function if the expression must be constructed dynamically,
- * otherwise it is much more efficient to use the expression literally.
- *
- * @param myContext The ExpressionContext passed by the extension processor
- * @param xpathExpr The XPath expression string
- *
- * @return The evaluation result
- */
- public static XObject evaluate(ExpressionContext myContext, String xpathExpr)
- throws SAXNotSupportedException
- {
- if (myContext instanceof XPathContext.XPathExpressionContext)
- {
- XPathContext xctxt = null;
- try
- {
- xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
- XPath dynamicXPath = new XPath(xpathExpr, xctxt.getSAXLocator(),
- xctxt.getNamespaceContext(),
- XPath.SELECT);
-
- return dynamicXPath.execute(xctxt, myContext.getContextNode(),
- xctxt.getNamespaceContext());
- }
- catch (TransformerException e)
- {
- return new XNodeSet(xctxt.getDTMManager());
- }
- }
- else
- throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); //"Invalid context passed to evaluate "
- }
-
- /**
- * The dyn:closure function creates a node set resulting from transitive closure of
- * evaluating the expression passed as the second argument on each of the nodes passed
- * as the first argument, then on the node set resulting from that and so on until no
- * more nodes are found. For example:
- * <pre>
- * dyn:closure(., '*')
- * </pre>
- * returns all the descendant elements of the node (its element children, their
- * children, their children's children and so on).
- * <p>
- * The expression is thus evaluated several times, each with a different node set
- * acting as the context of the expression. The first time the expression is
- * evaluated, the context node set is the first argument passed to the dyn:closure
- * function. In other words, the node set for each node is calculated by evaluating
- * the XPath expression with all context information being the same as that for
- * the call to the dyn:closure function itself, except for the following:
- * <p>
- * <ul>
- * <li>the context node is the node whose value is being calculated.</li>
- * <li>the context position is the position of the node within the node set passed
- * as the first argument to the dyn:closure function, arranged in document order.</li>
- * <li>the context size is the number of nodes passed as the first argument to the
- * dyn:closure function.</li>
- * <li>the current node is the node whose value is being calculated.</li>
- * </ul>
- * <p>
- * The result for a particular iteration is the union of the node sets resulting
- * from evaluting the expression for each of the nodes in the source node set for
- * that iteration. This result is then used as the source node set for the next
- * iteration, and so on. The result of the function as a whole is the union of
- * the node sets generated by each iteration.
- * <p>
- * If the expression string passed as the second argument is an invalid XPath
- * expression (including an empty string) or an expression that does not return a
- * node set, this function returns an empty node set.
- *
- * @param myContext The ExpressionContext passed by the extension processor
- * @param nl The node set
- * @param expr The expression string
- *
- * @return The node set after evaluation
- */
- public static NodeList closure(ExpressionContext myContext, NodeList nl, String expr)
- throws SAXNotSupportedException
- {
- XPathContext xctxt = null;
- if (myContext instanceof XPathContext.XPathExpressionContext)
- xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
- else
- throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
-
- if (expr == null || expr.length() == 0)
- return new NodeSet();
-
- NodeSet closureSet = new NodeSet();
- closureSet.setShouldCacheNodes(true);
-
- NodeList iterationList = nl;
- do
- {
-
- NodeSet iterationSet = new NodeSet();
-
- NodeSetDTM contextNodes = new NodeSetDTM(iterationList, xctxt);
- xctxt.pushContextNodeList(contextNodes);
-
- for (int i = 0; i < iterationList.getLength(); i++)
- {
- int contextNode = contextNodes.item(i);
- xctxt.pushCurrentNode(contextNode);
-
- XObject object = null;
- try
- {
- XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
- xctxt.getNamespaceContext(),
- XPath.SELECT);
- object = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext());
-
- if (object instanceof XNodeSet)
- {
- NodeList nodelist = null;
- nodelist = ((XNodeSet)object).nodelist();
-
- for (int k = 0; k < nodelist.getLength(); k++)
- {
- Node n = nodelist.item(k);
- if (!iterationSet.contains(n))
- iterationSet.addNode(n);
- }
- }
- else
- {
- xctxt.popCurrentNode();
- xctxt.popContextNodeList();
- return new NodeSet();
- }
- }
- catch (TransformerException e)
- {
- xctxt.popCurrentNode();
- xctxt.popContextNodeList();
- return new NodeSet();
- }
-
- xctxt.popCurrentNode();
-
- }
-
- xctxt.popContextNodeList();
-
- iterationList = iterationSet;
-
- for (int i = 0; i < iterationList.getLength(); i++)
- {
- Node n = iterationList.item(i);
- if (!closureSet.contains(n))
- closureSet.addNode(n);
- }
-
- } while(iterationList.getLength() > 0);
-
- return closureSet;
-
- }
-
- }