How to pass a XML document to XSL file using Javax.xml.transformer API?
Asked Answered
T

5

7

I am using javax.xml.transform API to do XSL transformation . The API only allows one XML document as an input to apply transformation as below .

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    StringWriter stringWriter = new StringWriter();
    File xml = new File("C:\\abc");
    File xsl = new File("C:\\def.xsl");
    factory.setNamespaceAware(true);
    DocumentBuilder builder = factory.newDocumentBuilder();
    document = builder.parse(xml);
    TransformerFactory transformerFactory = 
    TransformerFactory.newInstance();
    StreamSource style = new StreamSource(xsl);
    Transformer transformer = transformerFactory.newTransformer(style);
    DOMSource source = new DOMSource(document);

Also , can pass simple String params as below , without any issue as below :

transformer.setParameter("mode", "CREATE");

But , i want to pass an XML Document as a parameter to the XSL file . I tried below code as suggested on one of SO pages , as below :

DocumentBuilder builder = factory.newDocumentBuilder();
 final Document documentFile = builder.parse(xml2);
 Map<String, Document> docs = new HashMap<String, Document>();
 docs.put("lookup", documentFile);
 transformer.setURIResolver(new DocumentURIResolver(docs));

And i set , the tag in XML to receive value as below :

<xsl:variable name="lookup" select="('documentFile')/>  . 

But its not working for me . Can anyone help me out with the correct pay to pass multiple XML documents to any XSL file via javax.xml.transform API ?

Update

Still stuck with the issue ,can any one let me how can i pass XML object into a XSLT 2.0 stylesheet as a param . I have tried different approaches but no luck still . I need to know a way out via JAVA xsl transform API .

Thermoplastic answered 23/1, 2019 at 21:1 Comment(14)
Which XSLT processor do you use if you ask about java.xml.tansform but tag the question as xslt-2.0? As you aware that XSLT since version 1.0 has the powerful document function that allows loading/acessing additional XML documents directly from within XSLT, even multiple in one step?Raceway
Hi Martin , i'm not using any processor specifically so i assume default XSLT processor is being used . I know XSLT has powerful document function , but i want to pass that document via transformer.setParameter() function , similar way i pass the string or integer params , so i need to know how we are doing that ?Thermoplastic
Any help would be appreciated !! :)Thermoplastic
Well, the default XSLT processor in the Oracle Java JRE is usually some internalized version of Xalan and Xalan is certainly no XSLT 2 processor so not sure why you tagged your question for that version. I would suppose that a single parameter can probably passed in as a W3C DOM Document or perhaps Node in general.Raceway
Thanks Martin !! May be i was unaware of this . But if i set it as a W3C DOM document , i am struggling what should be the correct expression on XSLT side to receive it .Thermoplastic
I would hope that is is as easy as doing transformer.setParameter("doc1", yourDocumentNode) on the Java side and then with XSLT the global parameter <xsl:param name="doc1"/> represents an XSLT/XPath document node e.g. you can use <xsl:copy-of select="$doc1"/> or path expressions on it like <xsl:copy-of select="$doc1/root/foo/bar"/>.Raceway
How is your question related to stackoverflow.com/questions/13381698 ?Krutz
i tried that , @Krutz , but it didn't worked for me . Is it working for you ?Thermoplastic
Did I understood right, that you need data from multiple XML sources to create your final transformation? Is this your use case?Krutz
Yes , you're 100% correct !! The link that you gave works for you ??? Becuase i'm not able to get it work , let me know if you tweaked something .Thermoplastic
Haven't tried it yet. Still trying to understand what you want to achieve. So it is basically something like discussed here: stackoverflow.com/questions/16323750 ?! You could try to construct your XSL on-the-fly using the mechanisms described. e.g. (1) read in a prepared basic XSL as XML-DOM (2) add new elements to source in your files. (2) Pass the modified XSL to your transformer.Krutz
Maybe simply change <xsl:variable name="lookup" select="('documentFile')/> to <xsl:variable name="lookup" select="document('lookup')/>? See my answer.Krutz
<xsl:variable name="lookup" select="('documentFile')/> is not a correct xml element, so maybe this is your main issue.Proparoxytone
@ChaturvediSaurabh consider to accept my answer to make this more visible to others.Krutz
K
3

I think your issue is in the XSLT. Change

<xsl:variable name="lookup" select="('documentFile')/>  . 

to

<xsl:variable name="lookup" select="document('lookup')/>

this will cause the transformer to make the DOM of your document accessible in the variable lookup. The key lookup comes from docs.put("lookup", documentFile);

Dynamically Pass Multiple XML Sources to XSL Transformation via URIResolver.

Full Working Example:

Be there three XML files: repo.xml, books.xml and articles.xml. The repo.xml contains status information about books and articles. The files articles.xml and books.xml contain title information about each item. The goal is to print status information of all books and articles together with the title information. The entries in all files are connected via id keys.

Find complete example at github or copy/paste the listings below.

repo.xml

<repository>
    <book>
        <id>1</id>
        <status>available</status>
    </book>
    <book>
        <id>2</id>
        <status>lost</status>
    </book>
    <article>
        <id>1</id>
        <status>in transit</status>
    </article>
</repository>

books.xml

<books>
    <book id="1">
        <title>Book One</title>
    </book>
    <book id="2">
        <title>Book Two</title>
    </book>
    <book id="3">
        <title>Book Three</title>
    </book>
</books>

articles.xml

<articles>
    <article id="1">
         <title>Article One</title>
    </article>
    <article id="2">
        <title>Article Two</title>
    </article>
    <article id="3">
        <title>Article Three</title>
    </article>
</articles>

join.xsl

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


<xsl:output method="xml" encoding="UTF-8" indent="yes" />

<xsl:template match="/">
    <titleStatusJoin>
        <xsl:for-each select="//book">
            <xsl:variable name="myId" select="id" />
            <book>
                <status>
                    <xsl:value-of select="status" />
                </status>
                <title>
                    <xsl:for-each select="document('bookFile')//book">
                        <xsl:variable name="bookId" select="@id" />
                        <xsl:choose>
                            <xsl:when test="$myId = $bookId">
                                <xsl:value-of select="title" />
                            </xsl:when>
                        </xsl:choose>
                    </xsl:for-each>
                </title>
            </book>
        </xsl:for-each>
        <xsl:for-each select="//article">
            <xsl:variable name="myId" select="id" />
            <article>
                <status>
                    <xsl:value-of select="status" />
                </status>
                <title>
                    <xsl:for-each select="document('articleFile')//article">
                        <xsl:variable name="bookId" select="@id" />
                        <xsl:choose>
                            <xsl:when test="$myId = $bookId">
                                <xsl:value-of select="title" />
                            </xsl:when>
                        </xsl:choose>
                    </xsl:for-each>
                </title>
            </article>
        </xsl:for-each>

    </titleStatusJoin>
</xsl:template>
</xsl:stylesheet>

Use this Java code...

@Test
public void useMultipleXmlSourcesInOneXsl3() {
    InputStream xml = Thread.currentThread().getContextClassLoader().getResourceAsStream("stack54335576/repo.xml");
    InputStream xsl = Thread.currentThread().getContextClassLoader().getResourceAsStream("stack54335576/join3.xsl");
    InputStream booksXml = Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream("stack54335576/books.xml");
    InputStream articlesXml = Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream("stack54335576/articles.xml");
    Document booksDom = readXml(booksXml);
    Document articlesDom = readXml(articlesXml);
    Map<String, Document> parameters = new HashMap<>();
    parameters.put("bookFile", booksDom);
    parameters.put("articleFile", articlesDom);
    xslt(xml, xsl, parameters);
}

public final void xslt(InputStream xml, InputStream xsl, Map<String, Document> parameters) {
    try {
        TransformerFactory factory = TransformerFactory.newInstance();
        Transformer transformer = factory.newTransformer(new StreamSource(xsl));
        transformer.setURIResolver((href, base) -> new DOMSource(parameters.get(href)));
        transformer.transform(new StreamSource(xml), new StreamResult(System.out));
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

private Document readXml(InputStream xmlin) {
    try {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder db = dbf.newDocumentBuilder();
        return db.parse(xmlin);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

...to produce this output

<?xml version="1.0" encoding="UTF-8"?>
<titleStatusJoin>
   <book>
      <status>available</status>
      <title>Book One</title>
   </book>
   <book>
     <status>lost</status>
     <title>Book Two</title>
   </book>
   <article>
     <status>in transit</status>
     <title>Article One</title>
   </article>
</titleStatusJoin>
Krutz answered 28/1, 2019 at 9:38 Comment(1)
Manipulating XSLT which is XML with namespaces with a default, not namespace-aware DocumentBuilder and DOM Level 1, not namespace aware createElement seems like asking for trouble. I would strongly suggest to make the DocumentBuilder namespace aware and use createElementNS with the XSLT namespace.Raceway
B
1

(Answer expanded to handle passing in a parsed W3C DOM document via a URIResolver)

This can be done in pure XSLT/XPath using the version of the Xalan XSLT processor that is supplied with the JRE.

As an example, say if the name of one of the input documents is passed in as a parameter to the Transformer:

    File parentDir = new File("c:\\dir");

    StringWriter stringWriter = new StringWriter();
    File xml = new File(parentDir, "input.xml");
    File xsl = new File(parentDir, "xslt-document-param.xslt");
    Source xsltSource = new StreamSource(xsl);
    Source xmlSource = new StreamSource(xml);

    TransformerFactory transformerFactory = TransformerFactory
            .newInstance();
    Transformer transformer = transformerFactory.newTransformer(xsltSource);
    transformer.setParameter("doc-name", "basic.xml");
    transformer.transform(xmlSource, new StreamResult(stringWriter));

    System.out.println(stringWriter);

This parameter can then be passed into the document() function in XPath as follows:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
 xmlns:xalan="http://xml.apache.org/xalan"
 version="1.0"
 exclude-result-prefixes="xalan">

 <xsl:output 
  method="xml"
  indent="yes" 
  xalan:indent-amount="2"/>

 <xsl:param name="doc-name"/>

 <xsl:template match="/">
  <xsl:variable name="doc-content" select="document($doc-name)"/>
  <parent>
   <xsl:for-each select="$doc-content/basic/*">
    <child>
     <xsl:value-of select="name(.)"/>
    </child>
   </xsl:for-each>
  </parent>
 </xsl:template>

</xsl:stylesheet>

This is able to read this basic.xml (from the parameter):

<basic>
 <one/>
 <two/>
 <three/>
</basic>

And transform it to:

<parent>
  <child>one</child>
  <child>two</child>
  <child>three</child>
</parent>

The parameter to the document() function is a URI. A relative path is resolved relative to the XSL file. Equally this could be a full URL, or resolved via a custom transformer.setURIResolver() as in the question.

(edit from here...)

To work with passing in a pre-parsed Document object to the XSLT, the URIResolver approach is able to handle passing this back to the document() function.

For example, with the lookup in the question:

    File lookupXml = new File(parentDir, "basic.xml");
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = factory.newDocumentBuilder();
    Document document = builder.parse(lookupXml);
    Map<String, Document> docs = new HashMap<>();
    docs.put("lookup", document);

    transformer.setURIResolver((href, base) -> new DOMSource(docs.get(href)));

This XSL is able to iterate through the same basic.xml as above...

 <xsl:template match="/">
  <xsl:variable name="doc-content" select="document('lookup')"/>
  <parent>
   <xsl:for-each select="$doc-content/basic/*">
    <child>
     <xsl:value-of select="name(.)"/>
    </child>
   </xsl:for-each>
  </parent>
 </xsl:template>

... and output the same result.

Belcher answered 26/1, 2019 at 9:18 Comment(0)
R
1

Did you already try this ?

org.w3c.dom.Document doc = ... // Your xml document
transformer.setParameter("demo", doc.getDocumentElement());
Rost answered 27/1, 2019 at 20:18 Comment(0)
S
0

Not sure about your issue , if you give usecase and ask how to solve will be more helpful insted of asking how to fix your code as we are not having end to end visiblity of your code and xml.

Following could be possible solution :

1) We can convert xml to string

try {
    StringReader _reader = new StringReader("<xml>vkhan</xml>");
    StringWriter _writer = new StringWriter();
    TransformerFactory tFactory = TransformerFactory.newInstance();
    Transformer transformer = tFactory.newTransformer(
            new javax.xml.transform.stream.StreamSource("styler.xsl"));//ur xsl

    transformer.transform(
            new javax.xml.transform.stream.StreamSource(_reader), 
            new javax.xml.transform.stream.StreamResult(_writer));

    String result = writer.toString();
} catch (Exception e) {
    e.printStackTrace();
}

2) Modify below code as per your requirments pass as List of object of call in for loop.

public class Data {

    public static final Document transformXmlDocument(Document sourceDocument, InputStream xsltFile) {
        DOMSource xmlSource = new DOMSource(sourceDocument);
        StreamSource xsltSource = new StreamSource(xsltFile);

        Document transformedData = null;

        try {        
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer(xsltSource);

            ByteArrayOutputStream output = new ByteArrayOutputStream();
            StreamResult result = new StreamResult(output);

            transformer.transform(xmlSource, result);

            DocumentBuilder resultBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            transformedData = resultBuilder.parse(
                new InputSource(
                    new StringReader(
                        new String(output.toByteArray())
                    )
                )
            );
        } catch (Exception e) {
            Log.e("XSLT Transformation", e.getMessage());
        }

        return transformedData;
    }
}
Syncopation answered 25/1, 2019 at 22:23 Comment(3)
If this answere not help you ,plz update your full java code and example xml and requirments .Syncopation
Hi vaquar , thanks for your reply . I'm not asking you to fix my code . I am already able to provide one XML as an input as you have shown above . I want to pass additional XML files to my XSL file to perform transformations . That is why i want to pass an XML file as an additional parameter , that is my use case to be precise . I tried different approches , but none of them working fine for me . Let me know if you can help , thanks !!Thermoplastic
plz update sample json in your question as need input json to solve problemSyncopation
J
0

Try to replace your <xsl:variable name="lookup" select="('documentFile')"/> instruction with <xsl:variable name="documentFile" select="document($lookup)"/> and pass your XML Document as a parameter with transformer.setParameter("lookup", "myfile.xml"); which means : load the document referenced by the lookup parameter into the documentFile variable.

See also Extract data from External XML file using XSL

Jorgan answered 26/1, 2019 at 13:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.