Search code examples
javaxmlxsltxpathxalan

How do I evaluate an XPath expression within a Xalan extension function implementation?


I am attempting to evaluate an XPath expression within context provided to an extension function by Xalan 2.7.1. For some reason this always fails with an internal exception in Xalan.

Note: you need xalan-j in classpath in order to run this.

package org.example;

import java.io.*;
import java.util.*;
import java.util.logging.*;
import javax.xml.namespace.NamespaceContext;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import javax.xml.xpath.*;
import org.w3c.dom.*;
import org.w3c.dom.traversal.NodeIterator;
import org.apache.xalan.extensions.ExpressionContext;

public class XalanExtension {

    public static final String NS = "org:example:foo";

    public static final String XSLT = "" +
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<xsl:stylesheet \n" +
"    xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n" +
"    xmlns:foo=\"org:example:foo\"\n" +
"    xmlns:fun=\"xalan://org.example.XalanExtension\"\n" +
"    version=\"1.0\"> \n" +
"    \n" +
"    <xsl:template match=\"*|@*\">\n" +
"        <xsl:apply-templates select=\"*|@*\"/>\n" +
"    </xsl:template>\n" +
"    \n" +
"    <xsl:template match=\"foo:test\">\n" +
"       <xsl:variable name=\"result\" select=\"fun:evaluate(.)\"/>\n" +
"        <xsl:message>\n" +
"            Test: <xsl:value-of select=\".\"/> Result: <xsl:value-of select=\"$result\"/>\n" +
"        </xsl:message>\n" +
"    </xsl:template>\n" +
"    \n" +
"</xsl:stylesheet>";

    public static final String XML = "" +
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
"<tests xmlns=\"org:example:foo\">\n" +
"   <a>1</a>\n" +
"   <b>2</b>\n" +
"   <c>3</c>\n" +
"   <test>../foo:a &gt; 5</test>\n" +
"</tests>";

    private TransformerFactory xalanTransFact;

    public XalanExtension() {
        xalanTransFact = new org.apache.xalan.processor.TransformerFactoryImpl();
    }

    public void transform() {
        try {
            StringWriter writer;

            writer = new StringWriter();

            System.out.println(org.apache.xalan.Version.getVersion());

            Transformer transformer = xalanTransFact.newTransformer(new StreamSource(new StringReader(XSLT)));
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.METHOD, "text");
            transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");

            transformer.transform(
                    new StreamSource(new StringReader(XML)),
                    new StreamResult(writer));

            System.out.println(writer.toString());
        } catch (TransformerConfigurationException ex) {
            Logger.getLogger(XalanExtension.class.getName()).log(Level.SEVERE, null, ex);
        } catch (TransformerException ex) {
            Logger.getLogger(XalanExtension.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static void main(String[] args) {
        XalanExtension instance = new XalanExtension();
        instance.transform();
    }

    public static final NodeList evaluate(ExpressionContext ctx, NodeIterator nodes) {
        // first node in document order
        Node node = nodes.nextNode();
        if (node != null && node.hasChildNodes()) {
            try {
                String xpathExpression = node.getFirstChild().getNodeValue();

                XPathFactory xpathFactory = XPathFactory.newInstance();
                XPath xpath = xpathFactory.newXPath();
                xpath.setNamespaceContext(new NamespaceContext() {
                    @Override
                    public String getNamespaceURI(String prefix) {
                        if ("foo".equals(prefix)) {
                            return NS;
                        }
                        return null;
                    }

                    @Override
                    public String getPrefix(String namespaceURI) {
                        if (NS.equals(namespaceURI)) {
                            return "foo";
                        }
                        return null;
                    }

                    @Override
                    public Iterator getPrefixes(String namespaceURI) {
                        return null;
                    }
                });

                return (NodeList) xpath.evaluate(xpathExpression, ctx.getContextNode(), XPathConstants.NODESET);
            } catch (XPathExpressionException ex) {
                Logger.getLogger(XalanExtension.class.getName()).log(Level.SEVERE, "xpath evaluation failed", ex);
            }
        }
        return null;
    }
}

The extension function is implemented in org.example.XalanExtension.evaluate(ExpressionContext, NodeIterator). It does get called by Xalan, however the XPath evaluation code I'm using is not working. The program simply finds a specific element with a well known value that is actually an XPath expression to be evaluated in the context where it appears.

I get the following output from the program:

Xalan Java 2.7.1
jan. 31, 2017 3:09:10 PM org.example.XalanExtension evaluate
SEVERE: xpath evaluation failed

javax.xml.transform.TransformerException: Unknown error in XPath.
    at org.apache.xpath.XPath.execute(XPath.java:365)
    at org.apache.xpath.XPath.execute(XPath.java:303)
    at org.apache.xpath.jaxp.XPathImpl.eval(XPathImpl.java:216)
    at org.apache.xpath.jaxp.XPathImpl.evaluate(XPathImpl.java:281)
    at org.example.XalanExtension.evaluate(XalanExtension.java:116)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.xalan.extensions.ExtensionHandlerJavaClass.callFunction(ExtensionHandlerJavaClass.java:367)
    at org.apache.xalan.extensions.ExtensionHandlerJavaClass.callFunction(ExtensionHandlerJavaClass.java:440)
    at org.apache.xalan.extensions.ExtensionsTable.extFunction(ExtensionsTable.java:222)
    at org.apache.xalan.transformer.TransformerImpl.extFunction(TransformerImpl.java:473)
    at org.apache.xpath.functions.FuncExtFunction.execute(FuncExtFunction.java:208)
    at org.apache.xpath.XPath.execute(XPath.java:337)
    at org.apache.xalan.templates.ElemVariable.getValue(ElemVariable.java:280)
    at org.apache.xalan.templates.ElemVariable.execute(ElemVariable.java:248)
    at org.apache.xalan.templates.ElemApplyTemplates.transformSelectedNodes(ElemApplyTemplates.java:395)
    at org.apache.xalan.templates.ElemApplyTemplates.execute(ElemApplyTemplates.java:178)
    at org.apache.xalan.templates.ElemApplyTemplates.transformSelectedNodes(ElemApplyTemplates.java:395)
    at org.apache.xalan.templates.ElemApplyTemplates.execute(ElemApplyTemplates.java:178)
    at org.apache.xalan.transformer.TransformerImpl.executeChildTemplates(TransformerImpl.java:2400)
    at org.apache.xalan.transformer.TransformerImpl.applyTemplateToNode(TransformerImpl.java:2270)
    at org.apache.xalan.transformer.TransformerImpl.transformNode(TransformerImpl.java:1356)
    at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:709)
    at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:1273)
    at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:1251)
    at org.example.XalanExtension.transform(XalanExtension.java:67)
    at org.example.XalanExtension.main(XalanExtension.java:81)
Caused by: java.lang.NullPointerException
    at org.apache.xpath.axes.AxesWalker.setRoot(AxesWalker.java:221)
    at org.apache.xpath.axes.ReverseAxesWalker.setRoot(ReverseAxesWalker.java:53)
    at org.apache.xpath.axes.WalkingIterator.setRoot(WalkingIterator.java:157)
    at org.apache.xpath.axes.NodeSequence.setRoot(NodeSequence.java:265)
    at org.apache.xpath.axes.LocPathIterator.execute(LocPathIterator.java:212)
    at org.apache.xpath.Expression.execute(Expression.java:155)
    at org.apache.xpath.operations.Operation.execute(Operation.java:109)
    at org.apache.xpath.XPath.execute(XPath.java:337)
    ... 28 more
---------
java.lang.NullPointerException
    at org.apache.xpath.axes.AxesWalker.setRoot(AxesWalker.java:221)
    at org.apache.xpath.axes.ReverseAxesWalker.setRoot(ReverseAxesWalker.java:53)
    at org.apache.xpath.axes.WalkingIterator.setRoot(WalkingIterator.java:157)
    at org.apache.xpath.axes.NodeSequence.setRoot(NodeSequence.java:265)
    at org.apache.xpath.axes.LocPathIterator.execute(LocPathIterator.java:212)
    at org.apache.xpath.Expression.execute(Expression.java:155)
    at org.apache.xpath.operations.Operation.execute(Operation.java:109)
    at org.apache.xpath.XPath.execute(XPath.java:337)
    at org.apache.xpath.XPath.execute(XPath.java:303)
    at org.apache.xpath.jaxp.XPathImpl.eval(XPathImpl.java:216)
    at org.apache.xpath.jaxp.XPathImpl.evaluate(XPathImpl.java:281)
    at org.example.XalanExtension.evaluate(XalanExtension.java:116)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.xalan.extensions.ExtensionHandlerJavaClass.callFunction(ExtensionHandlerJavaClass.java:367)
    at org.apache.xalan.extensions.ExtensionHandlerJavaClass.callFunction(ExtensionHandlerJavaClass.java:440)
    at org.apache.xalan.extensions.ExtensionsTable.extFunction(ExtensionsTable.java:222)
    at org.apache.xalan.transformer.TransformerImpl.extFunction(TransformerImpl.java:473)
    at org.apache.xpath.functions.FuncExtFunction.execute(FuncExtFunction.java:208)
    at org.apache.xpath.XPath.execute(XPath.java:337)
    at org.apache.xalan.templates.ElemVariable.getValue(ElemVariable.java:280)
    at org.apache.xalan.templates.ElemVariable.execute(ElemVariable.java:248)
    at org.apache.xalan.templates.ElemApplyTemplates.transformSelectedNodes(ElemApplyTemplates.java:395)
    at org.apache.xalan.templates.ElemApplyTemplates.execute(ElemApplyTemplates.java:178)
    at org.apache.xalan.templates.ElemApplyTemplates.transformSelectedNodes(ElemApplyTemplates.java:395)
    at org.apache.xalan.templates.ElemApplyTemplates.execute(ElemApplyTemplates.java:178)
    at org.apache.xalan.transformer.TransformerImpl.executeChildTemplates(TransformerImpl.java:2400)
    at org.apache.xalan.transformer.TransformerImpl.applyTemplateToNode(TransformerImpl.java:2270)
    at org.apache.xalan.transformer.TransformerImpl.transformNode(TransformerImpl.java:1356)
    at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:709)
    at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:1273)
    at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:1251)
    at org.example.XalanExtension.transform(XalanExtension.java:67)
    at org.example.XalanExtension.main(XalanExtension.java:81)
--------------- linked to ------------------
javax.xml.xpath.XPathExpressionException: javax.xml.transform.TransformerException: Unknown error in XPath.

    at org.apache.xpath.jaxp.XPathImpl.evaluate(XPathImpl.java:295)
    at org.example.XalanExtension.evaluate(XalanExtension.java:116)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.xalan.extensions.ExtensionHandlerJavaClass.callFunction(ExtensionHandlerJavaClass.java:367)
    at org.apache.xalan.extensions.ExtensionHandlerJavaClass.callFunction(ExtensionHandlerJavaClass.java:440)
    at org.apache.xalan.extensions.ExtensionsTable.extFunction(ExtensionsTable.java:222)
    at org.apache.xalan.transformer.TransformerImpl.extFunction(TransformerImpl.java:473)
    at org.apache.xpath.functions.FuncExtFunction.execute(FuncExtFunction.java:208)
    at org.apache.xpath.XPath.execute(XPath.java:337)
    at org.apache.xalan.templates.ElemVariable.getValue(ElemVariable.java:280)
    at org.apache.xalan.templates.ElemVariable.execute(ElemVariable.java:248)
    at org.apache.xalan.templates.ElemApplyTemplates.transformSelectedNodes(ElemApplyTemplates.java:395)
    at org.apache.xalan.templates.ElemApplyTemplates.execute(ElemApplyTemplates.java:178)
    at org.apache.xalan.templates.ElemApplyTemplates.transformSelectedNodes(ElemApplyTemplates.java:395)
    at org.apache.xalan.templates.ElemApplyTemplates.execute(ElemApplyTemplates.java:178)
    at org.apache.xalan.transformer.TransformerImpl.executeChildTemplates(TransformerImpl.java:2400)
    at org.apache.xalan.transformer.TransformerImpl.applyTemplateToNode(TransformerImpl.java:2270)
    at org.apache.xalan.transformer.TransformerImpl.transformNode(TransformerImpl.java:1356)
    at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:709)
    at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:1273)
    at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:1251)
    at org.example.XalanExtension.transform(XalanExtension.java:67)
    at org.example.XalanExtension.main(XalanExtension.java:81)
Caused by: javax.xml.transform.TransformerException: Unknown error in XPath.
    at org.apache.xpath.XPath.execute(XPath.java:365)
    at org.apache.xpath.XPath.execute(XPath.java:303)
    at org.apache.xpath.jaxp.XPathImpl.eval(XPathImpl.java:216)
    at org.apache.xpath.jaxp.XPathImpl.evaluate(XPathImpl.java:281)
    ... 25 more
Caused by: java.lang.NullPointerException
    at org.apache.xpath.axes.AxesWalker.setRoot(AxesWalker.java:221)
    at org.apache.xpath.axes.ReverseAxesWalker.setRoot(ReverseAxesWalker.java:53)
    at org.apache.xpath.axes.WalkingIterator.setRoot(WalkingIterator.java:157)
    at org.apache.xpath.axes.NodeSequence.setRoot(NodeSequence.java:265)
    at org.apache.xpath.axes.LocPathIterator.execute(LocPathIterator.java:212)
    at org.apache.xpath.Expression.execute(Expression.java:155)
    at org.apache.xpath.operations.Operation.execute(Operation.java:109)
    at org.apache.xpath.XPath.execute(XPath.java:337)
    ... 28 more

SystemId Unknown; Line #14; Column #22; 
            Test: ../foo:a > 5 Result: 

How do I evaluate an XPath expression within a Xalan extension function implmentation (in the context provided to it)? Did I not initialize the XPathFactory/XPath instance properly?

Here are the two documents from code above:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:foo="org:example:foo"
    xmlns:fun="xalan://org.example.XalanExtension"
    version="1.0"> 

    <xsl:template match="*|@*">
        <xsl:apply-templates select="*|@*"/>
    </xsl:template>

    <xsl:template match="foo:test">
       <xsl:variable name="result" select="fun:evaluate(.)"/>
        <xsl:message>
            Test: <xsl:value-of select="."/> Result: <xsl:value-of select="$result"/>
        </xsl:message>
    </xsl:template>

</xsl:stylesheet>
<?xml version="1.0" encoding="utf-8"?>
<tests xmlns="org:example:foo">
    <a>1</a>
    <b>2</b>
    <c>3</c>
    <test>../foo:a &gt; 5</test>
</tests>

Note: I'm well aware of dyn:evaluate function. This question has nothing to do with it.


Solution

  • I was too quick asking the question. After taking a look at how some Xalan extension functions are implemented, I found the Xalan specific code that implements XPath evaluation (yes, for dyn:evaluate).

    package org.example;
    
    import java.io.*;
    import java.util.logging.*;
    import javax.xml.transform.*;
    import javax.xml.transform.stream.*;
    import org.w3c.dom.*;
    import org.w3c.dom.traversal.NodeIterator;
    import org.apache.xalan.extensions.ExpressionContext;
    import org.apache.xpath.XPathContext;
    import org.apache.xpath.objects.XNodeSet;
    
    public class XalanExtension {
    
        public static final String NS = "org:example:foo";
    
        public static final String XSLT = "" +
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
    "<xsl:stylesheet \n" +
    "    xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n" +
    "    xmlns:foo=\"org:example:foo\"\n" +
    "    xmlns:fun=\"xalan://org.example.XalanExtension\"\n" +
    "    version=\"1.0\"> \n" +
    "    \n" +
    "    <xsl:template match=\"*|@*\">\n" +
    "        <xsl:apply-templates select=\"*|@*\"/>\n" +
    "    </xsl:template>\n" +
    "    \n" +
    "    <xsl:template match=\"foo:test\">\n" +
    "       <xsl:variable name=\"result\" select=\"fun:evaluate(.)\"/>\n" +
    "        <xsl:message>\n" +
    "            Test: <xsl:value-of select=\".\"/> Result: <xsl:value-of select=\"$result\"/>\n" +
    "        </xsl:message>\n" +
    "    </xsl:template>\n" +
    "    \n" +
    "</xsl:stylesheet>";
    
        public static final String XML = "" +
    "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
    "<tests xmlns=\"org:example:foo\">\n" +
    "   <a>1</a>\n" +
    "   <b>2</b>\n" +
    "   <c>3</c>\n" +
    "   <test>../foo:a &gt; 5</test>\n" +
    "</tests>";
    
        private TransformerFactory xalanTransFact;
    
        public XalanExtension() {
            xalanTransFact = new org.apache.xalan.processor.TransformerFactoryImpl();
        }
    
        public void transform() {
            try {
                StringWriter writer;
    
                writer = new StringWriter();
    
                System.out.println(org.apache.xalan.Version.getVersion());
    
                Transformer transformer = xalanTransFact.newTransformer(new StreamSource(new StringReader(XSLT)));
                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                transformer.setOutputProperty(OutputKeys.METHOD, "text");
                transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
    
                transformer.transform(
                        new StreamSource(new StringReader(XML)),
                        new StreamResult(writer));
    
                System.out.println(writer.toString());
            } catch (TransformerConfigurationException ex) {
                Logger.getLogger(XalanExtension.class.getName()).log(Level.SEVERE, null, ex);
            } catch (TransformerException ex) {
                Logger.getLogger(XalanExtension.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    
        public static void main(String[] args) {
            XalanExtension instance = new XalanExtension();
            instance.transform();
        }
    
        public static final Object evaluate(ExpressionContext myContext, NodeIterator nodes) {
            if (myContext instanceof XPathContext.XPathExpressionContext) {
                XPathContext xctxt = null;
                Node node = nodes.nextNode();
                if (node != null && node.hasChildNodes()) {
                    String xpathExpr = node.getFirstChild().getNodeValue();
                    try {
                        xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
                        org.apache.xpath.XPath dynamicXPath = new org.apache.xpath.XPath(xpathExpr, xctxt.getSAXLocator(),
                                xctxt.getNamespaceContext(),
                                org.apache.xpath.XPath.SELECT);
    
                        return dynamicXPath.execute(xctxt, myContext.getContextNode(), xctxt.getNamespaceContext());
                    } catch (TransformerException e) {
                        return new XNodeSet(xctxt.getDTMManager());
                    }
                }
            }
            return null;
        }
    }
    

    The code relies on several classes from org.apache.xalan and org.apache.xpath packages. You probably cannot achieve the same by relying an a standard JAXP solution, like my original attempt.