| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| /* |
| * $Id$ |
| */ |
| package org.apache.xalan.lib; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.transform.TransformerException; |
| |
| import org.apache.xalan.extensions.ExpressionContext; |
| import org.apache.xalan.res.XSLMessages; |
| import org.apache.xalan.res.XSLTErrorResources; |
| import org.apache.xpath.NodeSet; |
| import org.apache.xpath.NodeSetDTM; |
| import org.apache.xpath.XPath; |
| import org.apache.xpath.XPathContext; |
| import org.apache.xpath.objects.XBoolean; |
| import org.apache.xpath.objects.XNodeSet; |
| import org.apache.xpath.objects.XNumber; |
| import org.apache.xpath.objects.XObject; |
| |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.w3c.dom.Text; |
| |
| import org.xml.sax.SAXNotSupportedException; |
| |
| /** |
| * This class contains EXSLT dynamic extension functions. |
| * |
| * It is accessed by specifying a namespace URI as follows: |
| * <pre> |
| * xmlns:dyn="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> |
| |
| * @xsl.usage general |
| */ |
| 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.MAX_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; |
| Document lDoc = 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 |
| { |
| if (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; |
| |
| } |
| |
| } |