Efficient XSLT pipeline, with params, in Java

2020-04-07 03:12发布

问题:

The top answer to this question describes a technique to implement an efficient XSLT pipeline in Java:

Efficient XSLT pipeline in Java (or redirecting Results to Sources)

Unfortunately, while Transformer seems to expose an API for setting XSLT parameters, this does not seem to have any effect. For example, I have the following code:

Transformer.java

import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.Templates;
import javax.xml.transform.sax.TransformerHandler; 
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.Transformer;
import java.io.File;
public class MyTransformer {
    public static void main(String[] args) throws javax.xml.transform.TransformerConfigurationException, javax.xml.transform.TransformerException{
        SAXTransformerFactory stf = (SAXTransformerFactory)TransformerFactory.newInstance();

        // These templates objects could be reused and obtained from elsewhere.
        Templates templates1 = stf.newTemplates(new StreamSource( new File("MyStylesheet1.xslt")));
        Templates templates2 = stf.newTemplates(new StreamSource(new File("MyStylesheet2.xslt")));

        TransformerHandler th1 = stf.newTransformerHandler(templates1);
        TransformerHandler th2 = stf.newTransformerHandler(templates2);

        th1.setResult(new SAXResult(th2));
        th2.setResult(new StreamResult(System.out));

        Transformer t = stf.newTransformer();

            //SETTING PARAMETERS HERE
        t.setParameter("foo","this is from param 1");
        t.setParameter("bar","this is from param 2");

        t.transform(new StreamSource(new File("in.xml")), new SAXResult(th1));

        // th1 feeds th2, which in turn feeds System.out.
    }
}

MyStylesheet1.xslt

<?xml version="1.0"?>
<stylesheet xmlns="http://www.w3.org/1999/XSL/Transform"  xmlns:foo="urn:foo" version="1.0">
    <output method="xml"/>

    <param name="foo"/>

    <template match="@*|node()">
        <copy>
            <apply-templates select="@*|node()"/>
        </copy>
    </template>

    <template match="foo:my/foo:hello">
        <copy>
            <foo:world>
                foo is : <value-of select="$foo"/>
            </foo:world>
        </copy>

    </template>
</stylesheet>

MyStylesheet2.xslt

<?xml version="1.0"?>
<stylesheet xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:foo="urn:foo" version="1.0">
    <output method="xml"/>

    <param name="bar"/>

    <template match="@*|node()">
        <copy>
            <apply-templates select="@*|node()"/>
        </copy>
    </template>

    <template match="foo:my/foo:hello/foo:world">
        <copy>
            <apply-templates select="@*|node()"/>

            <attribute name="attr">
                <value-of select="$bar"/>
            </attribute>
        </copy>

    </template>
</stylesheet>

in.xml

<my xmlns="urn:foo">
    <hello/>
</my>

Which gives me the following output:

<?xml version="1.0" encoding="UTF-8"?><my xmlns="urn:foo">
        <hello><foo:world xmlns:foo="urn:foo">foo is : </foo:world></hello>
</my>

As you can see foo:world/@attr is empty, and the text contents of foo:world says "foo is:". The expected behaviour is that they should have been populated with the parameters passed into the setParameter method.

Is there a way to set XSL transformation parameters using this technique. If not, could anyone recommend an alternative technique for transforming stylesheets efficiently in Java, such that XSLT parameters may also be set?

回答1:

The problem is that each TransformerHandler has a separate Transformer associated with it. There's a problem with your 2nd template, but as this is an example I guess that doesn't matter. You want:

//SETTING PARAMETERS HERE
th1.getTransformer().setParameter("foo","this is from param 1");
th2.getTransformer().setParameter("bar","this is from param 2");

Note that you also don't need to create a 3rd transformer you can just start the transform chain on th1.getTransformer()



回答2:

related to the last note. invoking transform() on th1.getTransformer() with the result pointing again on th1 is not correct. It will be processed twice. using new Transformer() like shown in the initial post is the correct way.

t.transform(new StreamSource(new File("in.xml")), new SAXResult(th1));