3

I am new to XML, XSLT and javax.xml

Currently my objective is to merge two XML files using XSLT Version 1.0 and everything works fine.

But I feel that there is a limitation in my code and I would like to get rid of it, if possible.

These are my resources: 'file1.xml' 'file2.xml' 'merge.xslt'

This is my merge method:

public ByteArrayOutputStream merge(final InputStream file1) {
    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    final TransformerFactory tFactory = TransformerFactory.newInstance();
    Transformer transformer;
    try {
        transformer = tFactory.newTransformer(new StreamSource("merge.xslt"));
        transformer.transform(new StreamSource(file1), new StreamResult(outputStream));
    } catch (final TransformerConfigurationException e) {
        LOG.warn("Problem occurred transforming files configuration issue", e);
    } catch (final TransformerException e) {
        LOG.warn("Problem occurred transforming files", e);
    }
    return outputStream;
}

This is how I am passing file2.xml inside the XSLT

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:param name="lookup" select="document('/file2.xml')"/>

<xsl:template match="/">
  Do the processing how I want
</xsl:template>
</xsl:stylesheet>

What I want to achieve is that, I would like to modify my merge method to pass file1.xml and file2.xml.

public ByteArrayOutputStream merge(final InputStream file1,final InputStream file2)

And I want to somehow pass this InputStream file2 to the XSLT, so that the limitation of reading the file from file system is eliminated.

Can someone guide me if this is possible and how to achieve it, I would really appreciate all the help.

Thank you.

I tried a small example, referred here XSLT Processing with Java : passing xml content in parameter But it didn't work for me.

final InputStream file1 = new FileInputStream("file1.xml");
    final InputStream file2 = new FileInputStream("file2.xml");
    final TransformerFactory tFactory = TransformerFactory.newInstance();
    Transformer transformer;
    transformer = tFactory.newTransformer(new StreamSource("merge.xslt"));
    transformer.setParameter("lookup", new StreamSource(file2));
    transformer.transform(new StreamSource(file1), new StreamResult(new FileOutputStream("test.xml")));

And updated the XSLT as follows:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:param name="lookup"/>

<xsl:template match="/">
  Do the processing how I want
</xsl:template>
</xsl:stylesheet>

Error that I am getting is as follows:

ERROR:  'Invalid conversion from 'javax.xml.transform.stream.StreamSource' to 'node-set'.'
Exception in thread "main" javax.xml.transform.TransformerException: java.lang.RuntimeException: Invalid conversion from 'javax.xml.transform.stream.StreamSource' to 'node-set'.
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:755)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:359)

Also Using :

<xsl:param name="lookup"/>

Will I get access to file2.xml inside the XSLT.

Community
  • 1
  • 1
Aniks
  • 1,011
  • 3
  • 17
  • 29
  • Using XSLT, you can only have one input document other than documents that you read with document(). You could pass the file names to the document command. Or, as an alternative, you could parse the documents with a DOM parser and import each document into your master output document using the importNode function. – terrywb Oct 01 '14 at 21:30
  • @terrywb I am new to all this so I am actually confused and not understanding what you are trying to guide. I Actually want to avoid using Document to read file2.xml inside the xslt. Want I want to do is that pass that file2.xml as a parameter to the XSLT in my java code and read it to compare my processing logic to merge. My implementation currently works. But I want to avoid reading from a filesystem and keep that file in some InputStream. And I don't know how to pass this in java and read this the info inside XSLT? – Aniks Oct 01 '14 at 21:37
  • I do not believe that it is possible to do what you want in XSLT without using the document() function. See my solution below for merging the input documents together using code. – terrywb Oct 01 '14 at 22:17
  • 1
    What you probably need is to define a Java `UriResolver`. When defined it will serve as a trigger called whenever the system has to resolve a `document()` reference. At that point you could simply pass whatever input stream you have your included document in. – Marcus Rickert Oct 02 '14 at 04:28
  • @MarcusRickert Can you show me an example code on what you are guiding, I actually don't understand in detail where you are trying to guide me, Thank you. – Aniks Oct 02 '14 at 06:00
  • You have to look at http://docs.oracle.com/javase/7/docs/api/javax/xml/transform/Source.html and http://docs.oracle.com/javase/7/docs/api/javax/xml/transform/Source.html. As far as I can analyze this (working from my mobile phone) the `StreamInput` classes implement the `Source` interface. So if you have your XML document to be included opened in a stream you just pass this stream class instance back in the `UriResolver` which you have registered with your XSLT processing class. – Marcus Rickert Oct 02 '14 at 06:08

2 Answers2

3

After doing a lot of research and reading different posts and blogs, I was finally able to resolve my issue.

I referred the questions asked here and got the idea for doing this.

Pass document as parameter to XSL Translation in Java

The other solutions suggested in this thread didn't workout for me.

Here is what I did,

Used a URIResolver instead of parameter.

public class DocumentURIResolver implements URIResolver {

final Map<String, Document> _documents;

public DocumentURIResolver(final Map<String, Document> documents) {
    _documents = documents;
}

public Source resolve(final String href, final String base) {
    final Document doc = _documents.get(href);
    return (doc != null) ? new DOMSource(doc) : null;
    }
}

This is how I modified my method:

public ByteArrayOutputStream merge(final InputStream file1,final InputStream file2) {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer;
try {
    transformer = tFactory.newTransformer(new StreamSource("merge.xslt"));
    final DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
    final Document documentFile = db.parse(file2);
    Map<String, Document> docs = new HashMap<String, Document>();
    docs.put("lookup", documentFile);
    transformer.setURIResolver(new DocURIResolver(docs));
    transformer.transform(new StreamSource(file1), new StreamResult(outputStream));
} catch (final TransformerConfigurationException e) {
    LOG.warn("Problem occurred transforming files configuration issue", e);
} catch (final TransformerException e) {
    LOG.warn("Problem occurred transforming files", e);
}
return outputStream;
}

And this is how I refereed it in my XSLT:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

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

<xsl:template match="/">
  Do the processing how you want to
</xsl:template>
</xsl:stylesheet>
Community
  • 1
  • 1
Aniks
  • 1,011
  • 3
  • 17
  • 29
0

Since you do not want to use the document() function in your XSLT, you could merge your input files using DOM functions in Java.

DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document merge = db.newDocument();
Element root = merge.createElement("root");
merge.appendChild(root);
Document d1 = db.parse(new File("file1.xml"));
Document d2 = db.parse(new File("file2.xml"));
root.appendChild(merge.importNode(d1.getDocumentElement(), true));
root.appendChild(merge.importNode(d2.getDocumentElement(), true));

The merged document could then be passed to XSLT if needed.

terrywb
  • 3,740
  • 3
  • 25
  • 50
  • Well I get what you are trying to guide me. But I don't want to merge the two documents and filter them using XSLT. I also edited the question, as I was trying a test the functionality looking for the solutions online. I think it is possible, but I don't know how to do it. The error I am getting I have added it to the question. – Aniks Oct 01 '14 at 22:29
  • Do you have experience with this? If you see my edit to the question maybe that will help. I have also linked the other question. – Aniks Oct 01 '14 at 23:15
  • @terrywd I have posted the solution, I was able to resolve it. – Aniks Oct 03 '14 at 03:20