8

I tried to pass a w3c.dom.Document, Element and NodeList as parameters to a xslt transform.

I want to be able to process it within the xslt:

<xsl:param name="links" />
<xsl:template match="/">
    <record>
        <xsl:for-each select="$links/*">
            <test />
        </xsl:for-each>
    </record>
</xsl:template>

I pass the parameter as:

        Document params = createLinksParams(links);
        transformer.setParameter("links", params);

I get this exception:

'Invalid conversion from 'com.sun.org.apache.xerces.internal.dom.DocumentImpl' to 'node-set'.'

I tried also exslt:node-set(), xalan:nodeset() etc, but it doesn't work.

It seems that internally xalan excepts his own implementation of the Node.

How can I do something similar without incurring in this problem?

I cannot use document($param) because I construct the doc on the fly.

potame
  • 7,597
  • 4
  • 26
  • 33
mkm
  • 1,545
  • 1
  • 14
  • 21

5 Answers5

4

I found a solution (here: XSLT Processing with Java : passing xml content in parameter) which may work for your case as well:

String urls = "<urls><url id='google'>https://www.google.com</url>...";
trans.setParameter("lookupdoc", new StreamSource(new StringReader(urls)));

instead of creating a uriresolver from a string, just create a stream source from a string reader and pass it to the stylesheet.

After that, I was able to access the doc normally as XML:

<xsl:param name="lookupdoc"><urls/></xsl:param> 
... 
<xsl:variable name="googleurl" select="$lookupdoc/@id='google"/>

Did not test with xalan, but maybe the answer will help others who stumble onto this question :)

Community
  • 1
  • 1
sprockets
  • 981
  • 1
  • 6
  • 16
4

(Posting a new answer, as the previous one did not solve the issue and this new one is radically different from the previous)

Seems to be a known issue with XALAN compiling processor ( XALANJ-2057, How can I pass a node as parameter to translets for XSLTC Processor).

So, what are the alternatives?

  1. mess around with URIs as outlined in a response to How can I pass a node as parameter to translets for XSLTC Processor post
  2. Instead of XALAN compiling processor (XSLTC), use XALAN interpretive processor. Or any other XSLT processor that supports such behavior.
  3. Use DTMAxisIterator instead, also outlined in a response to How can I pass a node as parameter to translets for XSLTC Processor post - not sure if it will work, though.
  4. Create a new DOM tree, combining your "parameter" DOM and the original XSLT input document
Neeme Praks
  • 8,956
  • 5
  • 47
  • 47
1

Here's a working example with the URIResolver Gambit, #1 in the list of solutions:

import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.StringReader;
import java.io.StringWriter;

public class XSLTest {
    public static void main(String[] args) {

        class MyResolver implements URIResolver {
            String _xml;
            MyResolver(String xml) { _xml = xml; }
            @Override
            public Source resolve(String href, String base) throws TransformerException {
                return new StreamSource(new StringReader(_xml));
            }
        }

        String lookup =
            "<?xml version='1.0' encoding='utf-8'?>\n" +
            "<urls>\n" +
            "    <url id='google'>https://www.google.com</url>\n" +
            "    <url id='yahoo'>https://www.yahoo.com</url>\n" +
            "    <url id='apple'>https://www.apple.com</url>\n" +
            "</urls>";

        String main =
            "<?xml version='1.0' encoding='utf-8'?>" +
            "<list>"+
            "   <link ref='yahoo'>Yahoo</link>"+
            "   <link ref='google'>Google</link>"+
            "</list>";

        String xsl =
            "<?xml version='1.0' encoding='UTF-8'?>\n" +
            "<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>\n" +
            "    <xsl:param name='lookup-doc' />\n" +
            "    <xsl:variable name='lookup' select='document($lookup-doc)'/>\n" +
            "    <xsl:template match='/'>\n" +
            "        <xsl:for-each select='//link'>\n" +
            "            <xsl:variable name='ref' select='@ref'/>\n" +
            "            <xsl:element name='a'>\n" +
            "                <xsl:attribute name='href'>\n" +
            "                    <xsl:value-of select='$lookup//url[@id=$ref]'/>\n" +
            "                </xsl:attribute>\n" +
            "                <xsl:value-of select='text()'/>\n" +
            "            </xsl:element>\n" +
            "        </xsl:for-each>\n" +
            "    </xsl:template>\n" +
            "</xsl:stylesheet>";

        try {

            // xsl doc
            Source xsltSource = new StreamSource(new StringReader(xsl));

            TransformerFactory transFact = TransformerFactory.newInstance();
            Transformer trans = transFact.newTransformer(xsltSource);

            // main doc
            Source mainSource = new StreamSource(new StringReader(main));

            // lookup doc - stage it in the URI resolver
            trans.setURIResolver(new MyResolver(lookup));
            // dummy URL, you could use different values here to 
            // support multiple document parameters
            trans.setParameter("lookup-doc", "xml://lookup");

            StringWriter out = new StringWriter();
            trans.transform(mainSource, new StreamResult(out));

            System.out.println(out.toString());

        } catch (TransformerException e) {
            System.err.println("It's the wrong trousers Gromit, and they've gone wrong!");
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }

    }
}

I also had a working version where I put the xml source in the URI like

xml://<urls><url><url id='google'>https://www.google.com</url>...

but I figured that might run into length limits somewhere.

Mike
  • 2,429
  • 1
  • 27
  • 30
0

If you look at the Document JavaDoc, you can see that it extends Node interface, but not NodeList. Not sure if it will work, but you could try to pass in params.getChildNodes() instead of params.

Neeme Praks
  • 8,956
  • 5
  • 47
  • 47
  • Maybe it is related to this bug? http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5059947 – Neeme Praks Sep 24 '10 at 17:48
  • Anyway, I would suggest to take the stacktrace of that error and track down in Xalan source code the specific circumstance when this exception is thrown. – Neeme Praks Sep 24 '10 at 17:50
  • Yes, before posting here I checked the sources: – mkm Sep 24 '10 at 19:02
  • java.lang.RuntimeException: Invalid conversion from 'com.sun.org.apache.xerces.internal.dom.DocumentImpl' to 'node-set'. at com.sun.org.apache.xalan.internal.xsltc.runtime.BasisLibrary.runTimeError(BasisLibrary.java:1523) at com.sun.org.apache.xalan.internal.xsltc.runtime.BasisLibrary.runTimeError(BasisLibrary.java:1531) at com.sun.org.apache.xalan.internal.xsltc.runtime.BasisLibrary.referenceToNodeSet(BasisLibrary.java:999) – mkm Sep 24 '10 at 19:02
  • method "referenceToNodeSet" accepts only "com.sun.org.apache.xalan.internal.xsltc.runtime.Node" instances, which are wrappers to dom nodes. There is a helper used to create those wrappers, in BasisLibrary, which requires a "Translet" instance as a mandatory parameter. The translet instance has to be taken from a protected member of the TransformerImpl instance. Unfortunately the jar is sealed and I get a access exception trying to access that protected getter. – mkm Sep 24 '10 at 19:05
  • I'm also interested in alternative approaches to my problem. For now I will quickly hack my solution by wrapping the input document together with my parameter class "...". But I don't really like this kind of hacks. Other ideas would be to create a custom url protocol and use 'select="document('mydummyprotocol:somekey')' and hook 'somekey' to my generated xml. Also this sounds too hackish. – mkm Sep 24 '10 at 19:09
  • Seems to be a known issue with XALAN compiling processor ( https://issues.apache.org/jira/browse/XALANJ-2057, http://old.nabble.com/How-can-I-pass-a-node-as-parameter-to-translets-for-XSLTC-Processor-td8649149.html). – Neeme Praks Sep 25 '10 at 10:38
0

It's an annoyance, one way or the other. In the end, I've always found it easiest and most compatible with other XSLT processors to serialize the XML fragment to a temp file, pass that file's URI to XSLT as a string parameter, and from there load the URI via the XPath document() function.

ujay68
  • 390
  • 4
  • 11