Is dyn:evaluate extension function known to have i

2019-02-28 09:50发布

问题:

I'm stumped as to why the following example program refuses to apply my stylesheet properly. It seems that dyn:evaluate in Xalan 2.7.1 is refusing to process certain XPath variables.

Running the following program with xalan-j in classpath yields the following results:

package com.testing2.xslt;

import java.io.*;
import java.util.logging.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

public class DynEvaluateTransform {

    private static final String XSLT = "" +
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n" +
"    xmlns:det=\"org:example:DynEvaluateTransform\"\n" +
"    xmlns:dyn=\"http://exslt.org/dynamic\"\n" +
"    extension-element-prefixes=\"dyn\"\n" +
"    version=\"1.0\">\n" +
"    \n" +
"    <xsl:variable name=\"input-doc\" select=\"document('input.xml', /)\" />\n" +
"    <xsl:variable name=\"selections\" select=\"$input-doc/det:selections\" />\n" +
"    \n" +
"    <xsl:template match=\"/\">\n" +
"        <xsl:choose>\n" +
"            <xsl:when test=\"function-available('dyn:evaluate')\">\n" +
"                <xsl:message>dyn:evaluate available</xsl:message>\n" +
"            </xsl:when>\n" +
"            <xsl:otherwise>\n" +
"                <xsl:message>dyn:evaluate not available</xsl:message>\n" +
"            </xsl:otherwise>\n" +
"        </xsl:choose>\n" +
"        <xsl:message>input.xml content:</xsl:message>\n" +
"        <xsl:for-each select=\"$selections/*\">\n" +
"            <xsl:message>{<xsl:value-of select=\"namespace-uri()\"/>}<xsl:value-of select=\"local-name()\"/></xsl:message>\n" +
"        </xsl:for-each>        \n" +
"        <xsl:for-each select=\"//@condition\">\n" +
"            <xsl:message><xsl:value-of select=\".\"/></xsl:message>\n" +
"            <xsl:message><xsl:value-of select=\"string(dyn:evaluate(string(.)))\"/></xsl:message>\n" +
"        </xsl:for-each>\n" +
"    </xsl:template>\n" +
"    \n" +
"</xsl:stylesheet>";

    private static final String EXAMPLE = "" +
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<foos xmlns=\"org:example:DynEvaluateTransform\">    \n" +
"    <foo condition=\"true()\" />    \n" +
"    <foo condition=\"false()\" />    \n" +
"    <foo condition=\"false() or true()\" />\n" +
"    <foo condition=\"$selections\" />\n" +
"    <foo condition=\"$selections/*\" />\n" +
"    <foo condition=\"$selections/*[local-name()='a']\" />\n" +
"    <foo condition=\"$selections/element::node()[local-name()='a']\" />\n" +
"    <foo condition=\"local-name($selections/*)\" />\n" +
"    <foo condition=\"not($selections/*[local-name()='a' and namespace-uri()='org:example:foo'])\" />\n" +
"    <foo condition=\"$selections/*[local-name()='b' and namespace-uri()='org:example:foo']\" />\n" +
"    <foo condition=\"$selections/*[local-name()='c' and namespace-uri()='org:example:foo']\" />\n" +
"    <foo condition=\"not($selections/*[local-name()='a' and namespace-uri()='org:example:foo']) or ($selections/*[local-name()='b' and namespace-uri()='org:example:foo'] and $selections/*[local-name()='c' and namespace-uri()='org:example:foo'])\" />\n" +
"</foos>";

    private static final String INPUT = "" +
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<selections xmlns=\"org:example:DynEvaluateTransform\">\n" +
"    <a xmlns=\"org:example:foo\"/>\n" +
"    <b xmlns=\"org:example:foo\"/>\n" +
"    <c xmlns=\"org:example:foo\"/>\n" +
"</selections>";

    private TransformerFactory xalanTransFact;    

    public DynEvaluateTransform() {
        xalanTransFact = new org.apache.xalan.processor.TransformerFactoryImpl();
        xalanTransFact.setURIResolver(new Resolver());
    }

    private void applyTransform() {
        // XSLT(EXAMPLE) --> output.xml
        //         ^
        //         |
        //       INPUT
        OutputStreamWriter writer = null;

        try {
            String outputFileName = DynEvaluateTransform.getLocalFileName("output.xml");

            File file = new File(outputFileName);
            writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");

            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, "xml");
            transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");

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

        } catch (UnsupportedEncodingException ex) {
            Logger.getLogger(DynEvaluateTransform.class.getName()).log(Level.SEVERE, null, ex);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(DynEvaluateTransform.class.getName()).log(Level.SEVERE, null, ex);
        } catch (TransformerConfigurationException ex) {
            Logger.getLogger(DynEvaluateTransform.class.getName()).log(Level.SEVERE, null, ex);
        } catch (TransformerException ex) {
            Logger.getLogger(DynEvaluateTransform.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException ex) {
                }
            }
        }
    }

    private void rebuildInput() {
        // ignore - this just writes the input.xml file, so we can later reference it
        StringReader strReader = null;
        OutputStreamWriter fileWriter = null;
        try {
            String fileName = getLocalFileName("input.xml");

            strReader = new StringReader(INPUT);
            File file = new File(fileName);
            fileWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");

            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");

            transformer.transform(
                    new StreamSource(strReader),
                    new StreamResult(fileWriter));

        } catch (UnsupportedEncodingException ex) {
            Logger.getLogger(DynEvaluateTransform.class.getName()).log(Level.SEVERE, null, ex);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(DynEvaluateTransform.class.getName()).log(Level.SEVERE, null, ex);
        } catch (TransformerConfigurationException ex) {
            Logger.getLogger(DynEvaluateTransform.class.getName()).log(Level.SEVERE, null, ex);
        } catch (TransformerException ex) {
            Logger.getLogger(DynEvaluateTransform.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(DynEvaluateTransform.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            if (strReader != null) {
                strReader.close();
            }
            if (fileWriter != null) {
                try {
                    fileWriter.close();
                } catch (IOException ex) {
                }
            }
        }
    }

    public static void main(String[] args) {
        DynEvaluateTransform det = new DynEvaluateTransform();
        det.rebuildInput();
        det.applyTransform();
    }   

    private static String getLocalFileName(String href) {
        String name = System.getProperty("user.dir");
        if (!name.endsWith(File.separator)) {
            name += File.separator;
        }
        name += href;
        return name;
    }

    private static class Resolver implements URIResolver {

        @Override
        public Source resolve(String href, String base) throws TransformerException {
            if ("input.xml".equals(href)) {                
                return new StreamSource(new File(DynEvaluateTransform.getLocalFileName(href)));
            } else {
                return null;
            }
        }
    }
}
Xalan Java 2.7.1
SystemId Unknown; Line #14; Column #30; dyn:evaluate available
SystemId Unknown; Line #20; Column #22; input.xml content:
SystemId Unknown; Line #22; Column #26; {org:example:foo}a
SystemId Unknown; Line #22; Column #26; {org:example:foo}b
SystemId Unknown; Line #22; Column #26; {org:example:foo}c
SystemId Unknown; Line #25; Column #26; true()
SystemId Unknown; Line #26; Column #26; true
SystemId Unknown; Line #25; Column #26; false()
SystemId Unknown; Line #26; Column #26; false
SystemId Unknown; Line #25; Column #26; false() or true()
SystemId Unknown; Line #26; Column #26; true
SystemId Unknown; Line #25; Column #26; $selections
SystemId Unknown; Line #26; Column #26; 
SystemId Unknown; Line #25; Column #26; $selections/*
SystemId Unknown; Line #26; Column #26; 
SystemId Unknown; Line #25; Column #26; $selections/*[local-name()='a']
SystemId Unknown; Line #26; Column #26; 
SystemId Unknown; Line #25; Column #26; $selections/element::node()[local-name()='a']
SystemId Unknown; Line #26; Column #26; 
SystemId Unknown; Line #25; Column #26; local-name($selections/*)
SystemId Unknown; Line #26; Column #26; 
SystemId Unknown; Line #25; Column #26; not($selections/*[local-name()='a' and namespace-uri()='org:example:foo'])
SystemId Unknown; Line #26; Column #26; true
SystemId Unknown; Line #25; Column #26; $selections/*[local-name()='b' and namespace-uri()='org:example:foo']
SystemId Unknown; Line #26; Column #26; 
SystemId Unknown; Line #25; Column #26; $selections/*[local-name()='c' and namespace-uri()='org:example:foo']
SystemId Unknown; Line #26; Column #26; 
SystemId Unknown; Line #25; Column #26; not($selections/*[local-name()='a' and namespace-uri()='org:example:foo']) or ($selections/*[local-name()='b' and namespace-uri()='org:example:foo'] and $selections/*[local-name()='c' and namespace-uri()='org:example:foo'])
SystemId Unknown; Line #26; Column #26; true

The example program transforms an example input (EXAMPLE) using the provided transformation (XSLT), which takes an input file (INPUT, input.xml), opened via document() function, as an "argument". This input file contains a set of elements that are tested with XPath expressions (located in EXAMPLE).

The output from the program indicates that the dyn:evaluate function is supported, that input.xml is properly read, that simple XPath expressions are evaluated properly, but as soon as an XPath variable is involved it breaks.

Readable versions of all documents involved below.

input.xml

<?xml version="1.0" encoding="UTF-8"?>
<selections xmlns="org:example:DynEvaluateTransform">
    <a xmlns="org:example:foo"/>
    <b xmlns="org:example:foo"/>
    <c xmlns="org:example:foo"/>
</selections>

EXAMPLE

<?xml version="1.0" encoding="UTF-8"?>
<foos xmlns="org:example:DynEvaluateTransform">    
    <foo condition="true()" />    
    <foo condition="false()" />    
    <foo condition="false() or true()" />
    <foo condition="$selections" />
    <foo condition="$selections/*" />
    <foo condition="$selections/*[local-name()='a']" />
    <foo condition="$selections/element::node()[local-name()='a']" />
    <foo condition="local-name($selections/*)" />
    <foo condition="not($selections/*[local-name()='a' and namespace-uri()='org:example:foo'])" />
    <foo condition="$selections/*[local-name()='b' and namespace-uri()='org:example:foo']" />
    <foo condition="$selections/*[local-name()='c' and namespace-uri()='org:example:foo']" />
    <foo condition="not($selections/*[local-name()='a' and namespace-uri()='org:example:foo']) or ($selections/*[local-name()='b' and namespace-uri()='org:example:foo'] and $selections/*[local-name()='c' and namespace-uri()='org:example:foo'])" />
</foos>

XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:det="org:example:DynEvaluateTransform"
    xmlns:dyn="http://exslt.org/dynamic"
    extension-element-prefixes="dyn"
    version="1.0">

    <xsl:variable name="input-doc" select="document('input.xml', /)" />
    <xsl:variable name="selections" select="$input-doc/det:selections" />

    <xsl:template match="/">
        <xsl:choose>
            <xsl:when test="function-available('dyn:evaluate')">
                <xsl:message>dyn:evaluate available</xsl:message>
            </xsl:when>
            <xsl:otherwise>
                <xsl:message>dyn:evaluate not available</xsl:message>
            </xsl:otherwise>
        </xsl:choose>
        <xsl:message>input.xml content:</xsl:message>
        <xsl:for-each select="$selections/*">
            <xsl:message>{<xsl:value-of select="namespace-uri()"/>}<xsl:value-of select="local-name()"/></xsl:message>
        </xsl:for-each>        
        <xsl:for-each select="//@condition">
            <xsl:message><xsl:value-of select="."/></xsl:message>
            <xsl:message><xsl:value-of select="string(dyn:evaluate(string(.)))"/></xsl:message>
        </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>

Does Xalan not support XPath variables in a dyn:evaluate argument? Am I defining the expressions properly? Trying to run this stylesheet in oXygen with Xalan selected, reports java.lang.RuntimeException: ElemTemplateElement error: Function not supported! when an XPath variable is first encountered.

Edit:

I have rewritten the question in order to clarify what my actual problem is.

回答1:

As feared, result tree fragments in XPath variables do not appear to be supported by dyn:evaluate in Xalan 2.7.1. Note that variables with non- result tree fragment values work.

I wrote my own extension function (dyn:evaluate equivalent) that evaluates XPath expressions but it also failed to process the result tree fragment in a variable (received this method is not yet supported exceptions). This may be a limitation of the XPath engine implementation provided by JAXP.

Perhaps it would work with a different XPath engine implementation.

I gave up on trying to make this work and will avoid filling result tree fragments into XPath variables when dyn:evaluate is in play. I will simply provide information needed to evaluate my conditions through an xsl:param string value instead of going the hard way of using an external XML file. An extension function to Xalan will check whether a selection is actually present or not.