XSLT: set name of transformed output file - java

I am using Apache Camel file component and xslt component. I have a route where i pickup a xml message, transform using xslt and drop to a different folder.
Apache camel DSL route:
<route id="normal-route">
<from uri="file:{{inputfilefolder}}?consumer.delay=5000" />
<to uri="xslt:stylesheets/simpletransform.xsl transformerFactoryClass=net.sf.saxon.TransformerFactoryImpl" />
<to uri="file:{{outputfilefolder}}" />
</route>
I am mentioning Apache camel also here , to check if there is a way to set the output file name using Camel. I think, even without Camel, there would be a mechanism with pure XSLT.
I need to rename the transformed output file. But always i am getting the same input filename with the transformed content, in the output folder.
eg: input file: books.xml
output file: books.xml [with the transformation applied]
What i am looking for is someotherfilename.xml as the output filename. The output data is correct.
I tried <xsl:result-document href="{title}.xml"> , but then the output xml is blank. Please help.
Input XML file:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book.child.1>
<title>Charithram</title>
<author>P Sudarsanan</author>
</book.child.1>
<book.child.2>
<title>Java Concurrency</title>
<author>Joshua Bloch</author>
</book.child.2>
</books>
XSLT:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="xml" version="1.0" encoding="UTF-8"
indent="yes" />
<xsl:variable name="filename" select="'newfilename'" />
<xsl:template match="/">
<xsl:result-document href="{$filename}.xml">
<traders>
<xsl:for-each select="books/*">
<trade>
<title>
<xsl:value-of select="title" />
</title>
</trade>
</xsl:for-each>
</traders>
</xsl:result-document>
</xsl:template>
</xsl:stylesheet>
Output XML when using <xsl:result-document href="" in XSLT
it is blank..
Output XML when not using <xsl:result-document href="" in XSLT
<?xml version="1.0" encoding="UTF-8"?>
<traders xmlns:xs="http://www.w3.org/2001/XMLSchema">
<trade>
<title>Charithram</title>
</trade>
<trade>
<title>Java Concurrency</title>
</trade>
</traders>
Edit: edited the XSLT as per MartinHonnen's comment

Looks like Camel's default is to use the same file name, but you can override it. As the docs mention you can specify the options of interest as follows:
file:directoryName[?options]
One such option is fileName:
Use Expression such as File Language to dynamically set the filename.
For consumers, it's used as a filename filter. For producers, it's
used to evaluate the filename to write.
In short, modify your route as follows:
<route id="normal-route">
<from uri="file:{{inputfilefolder}}?consumer.delay=5000" />
<to uri="xslt:stylesheets/simpletransform.xsl transformerFactoryClass=net.sf.saxon.TransformerFactoryImpl" />
<to uri="file:{{outputfilefolder}}?fileName=foo.xml" />
</route>
Where foo.xml will be the output file.
Update
You can use Simple or File language to set file names dynamically. There are a few examples in the links.

Related

If regEx is bad for matching XML, what is the correct way?

I was trying to do a simple string delete in XML.
I want to delete something like the following.
<A>
<B>Test Name</B>
</A>
Has to work with all possible XML, though.
<Test><A><B>Test Name</B></A></Test>
<Test ><A ><B >Test Name</B ></A ></Test >
<Test>
<A>
<B>Test Name</B>
</A>
</Test>
etc, etc.
The regularEX I got so far, is simply:
<A>\s*(\r\n|\r|\n)*\s*<B>Test Name<\/B>\s*(\r\n|\r|\n)*\s*<\/A>
Everyone always says regEx is bad for match XML, which it clearly is. So what should I use instead.
GC_
The best approach for this case would be using XSLT. And even with XSLT-1.0 this is simple (You can use the Java XSLT-processor, linux'es xsltproc or any other XSLT processor; every XSLT processor supports at least XSLT-1.0):
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<!-- identity template - matches everything except the things matched by other templates -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- Removes the elements you do not want -->
<xsl:template match="A[B[normalize-space(.)='Test Name']]" />
</xsl:stylesheet>
The output of your sample case (with a hypothetical root element) would be
<Test/>
<Test/>
<Test/>
Trying to use RegEx would be error-prone and no good-practice at all.
Why would you make it complicated if it could be so easy?

Dynamic XML response based on the XML input

We are building a web service which takes an XML input and provides and XML response based on the input. The input XML contains the XML structure with empty value, the empty values will be replaced by the actual value based on the data in the database. The user can reduce the number of nodes requested in the input XML, if they do not want all the details.
Sample Input XML
<?xml version="1.0" encoding="UTF-8"?>
<XML>
<RequestHeader>
<id>123</id>
</RequestHeader>
<RequestedElement>
<ABC>
<DEF>
<GHI />
<JKL>
<MNO />
</JKL>
</DEF>
</ABC>
</RequestedElement>
</XML>
Sample Response
<XML>
<ABC>
<DEF>
<GHI>Value1</GHI>
<JKL>
<MNO>Value2</MNO>
</JKL>
</DEF>
</ABC>
</XML>
The way I have implemented now, is to having a mapping of the XML nodes to the table names and column names, and then use reflection to retrieve the data from the database and generate the XML. However using reflection is slowing down the whole process.
The other option which I can think of is to get rid of reflection and to create the XML with all the nodes, and then use XSLT to generate the final XML with only the requested nodes. Is this possible to do so with XSLT?
Is there any better option to do the same which can increase the performance and get the desired results?
We are building a web service which takes an XML input and provides
and XML response based on the input. The input XML contains the XML
structure with empty value, the empty values will be replaced by the
actual value based on the data in the database. The user can reduce
the number of nodes requested in the input XML, if they do not want
all the details
One way to do this is to invoke the chosen XSLT processor (how to do this differs from one XSLT processor to another -- read the documentation) passing to it global transformation parameters, whose names are the names of the elements that should receive the corresponding values.
In the transformation below the values of the parameters are hardcoded, but in the real implementation these must be provided by the invoker -- so in the XSLT code they will look like this:
<xsl:param name="GHI"/>
<xsl:param name="MNO"/>
Here is the complete transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="GHI">Value1</xsl:param>
<xsl:param name="MNO">Value2</xsl:param>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="RequestHeader"/>
<xsl:template match="*[not(node()) and name() = document('')/*/xsl:param/#name]">
<xsl:copy>
<xsl:value-of select="document('')/*/xsl:param[#name = name(current())]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<XML>
<RequestHeader>
<id>123</id>
</RequestHeader>
<RequestedElement>
<ABC>
<DEF>
<GHI />
<JKL>
<MNO />
</JKL>
</DEF>
</ABC>
</RequestedElement>
</XML>
the wanted result is produced:
<XML>
<RequestedElement>
<ABC>
<DEF>
<GHI>Value1</GHI>
<JKL>
<MNO>Value2</MNO>
</JKL>
</DEF>
</ABC>
</RequestedElement>
</XML>

convert xml file into csv format sing camel?

i want to create .xml file into csv file using camel. here is my code
CamelContext context = new DefaultCamelContext();
from("file://Input?fileName=test.xml").marshal().csv().to("file://test?fileName=test.csv");
context.start();
But its't creating any file in desired folder "test".
Please spend just a bit more time on the Camel docs, and try out the examples, and read the FAQ. And the introduction articles and whatnot.
The code above isn't even valid, as you would need to put it inside a RouteBuilder.
Also when you start CamelContext, read the javadoc of the start method. And read this FAQ
http://camel.apache.org/running-camel-standalone-and-have-it-keep-running.html
Also Camel offers a tracer so you can see the message flow as the messages are being processed. The tracer will be default log this at INFO level to the logger.
http://camel.apache.org/tracer
Here is a sample using camel
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="file:src/xmldata?noop=true"/>
<to uri="xslt:file:src/main/fruits.xslt"/>
<to uri="file://TESTOUT?fileName=output.csv"/>
</route>
</camelContext>
sample xml file in src/xmldata folder
<AllFruits xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- All fruits below. -->
<Fruit>
<FruitId>Bannana</FruitId>
<FruitColor>Yellow</FruitColor>
<FruitShape>Moon</FruitShape>
<Customer>
<Name>Joe</Name>
<NumberEaten>5</NumberEaten>
<Weight>2.6</Weight>
</Customer>
<Customer>
<Name>Mark</Name>
<NumberEaten>8</NumberEaten>
<Weight>5.0</Weight>
</Customer>
</Fruit>
</AllFruits>
src/main/fruits.xslt
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text" encoding="ISO-8859-1" />
<xsl:variable name="newline" select="'
'"/>
<xsl:template match="Fruit">
<xsl:for-each select="Customer">
<xsl:value-of select="preceding-sibling::FruitId" />
<xsl:text>,</xsl:text>
<xsl:value-of select="NumberEaten" />
<xsl:text>,</xsl:text>
<xsl:value-of select="Weight" />
<xsl:value-of select="$newline" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Using XSLT to output multiple files

I'm trying to get an example that I found for using XSLT 2.0 to output multiple files working.
Using Saxon B 9.7.0.1 with Java 1.6, I get this error:
C:\Documents and Settings\Administrator\Desktop\saxon>java -jar saxon9.jar -s:input.xml -xsl:transform.xml
Error on line 15 of transform.xml:
java.net.URISyntaxException: Illegal character in path at index 20: file:///C:/Documents
and Settings/Administrator/Desktop/saxon/output1/test1.html
at xsl:for-each (file:/C:/Documents%20and%20Settings/Administrator/Desktop/saxon/transform.xml#10)
processing /tests/testrun[1]
Transformation failed: Run-time errors were reported
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<tests>
<testrun run="test1">
<test name="foo" pass="true" />
<test name="bar" pass="true" />
<test name="baz" pass="true" />
</testrun>
<testrun run="test2">
<test name="foo" pass="true" />
<test name="bar" pass="false" />
<test name="baz" pass="false" />
</testrun>
<testrun run="test3">
<test name="foo" pass="false" />
<test name="bar" pass="true" />
<test name="baz" pass="false" />
</testrun>
</tests>
transform.xml
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<xsl:output method="html" indent="yes" name="html"/>
<xsl:template match="/">
<xsl:for-each select="//testrun">
<xsl:variable name="filename"
select="concat('output1/',#run,'.html')" />
<xsl:value-of select="$filename" /> <!-- Creating -->
<xsl:result-document href="{$filename}" format="html">
<html><body>
<xsl:value-of select="#run"/>
</body></html>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Character 20 in your URI is the first space in "Documents and Settings". As a quick fix, try moving the files to a path without spaces. (Say, "C:\test" or some such.) I suspect the long-term fix is to change your XSLT to encode spaces to %20 before feeding $filename to xsl:result-document, but I'm afraid my XSLT-2.0-fu isn't strong enough to tell you how.
Edit: I haven't tested this, as I don't have an XSLT 2.0 processor handy, but after glancing at the docs, it looks like you want the encode-for-uri function. Something like the following may work for you:
<xsl:result-document href="{fn:encode-for-uri($filename)}" format="html">
I had the same issue with saxon -o: outputfile replacing the spaces with %20..
found out the issue is saxon and java versions.
Linux JAVA 1.7.0_45 : Saxon creates %20
Unix JAVA 1.5.0_61 : SAXON creates %20
Unix JAVA 1.4.2_22 : SAXON Does Not creates %20 directory

Dynamic xml filtering and transform (in Java)

I have an XML file that looks like
<?xml version='1.0' encoding='UTF-8'?>
<root>
<node name="foo1" value="bar1" />
<node name="foo2" value="bar2" />
</root>
I have a method
String processBar(String bar)
and I want to end up with
<?xml version='1.0' encoding='UTF-8'?>
<root>
<node name="foo1" value="processBar("bar1")" />
<node name="foo2" value="processBar("bar2")" />
</root>
Is there an easy way to do this? Preferably in Java. Note that the file is too large to safely load completely into memory. The data in the XML roughly arbitrary and processBar may be complex, so I don't want to use regular expressions.
Assuming you mean replacing the attribute values with the result of calling processBar on said attribute values...
Use the JDK's XSLT API to run the following:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:java="http://xml.apache.org/xalan/java"
extension-element-prefixes="java">
<xsl:template match="/root/node/#value">
<xsl:attribute name="value">
<xsl:value-of select="java:com.example.yourclass.processBar(string(.))"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This uses the Xalan-Java extensions and assumes a static method. You can get an instance of an object and store it in an xsl:variable, like this:
<xsl:variable name="frobber" select="java:com.example.Frobber.new()"/>
<xsl:value-of select="java:processBar($frobber, string(.))"/>
Or somesuch.
This only works with Xalan, but since that's the XSLT processor distributed with the JDK, I doubt it will be onerous to use Xalan.
you can either parse the whole thing in a java xml parser OR just get the file content into a string and then do a regexp replace on it (using i.e. http://java.sun.com/j2se/1.5.0/docs/api/java/lang/String.html#replaceAll%28java.lang.String,%20java.lang.String%29)

Categories

Resources