XSL Transformation With Xalan - The Anatomy Of A Transformation (
Page 4 of 6 )
As always with Java, the first step involves pulling all the required classes
into the application.
// import required classes
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import
java.io.*;
In case you're wondering, first come the classes for JAXP, followed by the classes
for exception handling and file I/O. Now, I bet you're wondering, what's JAXP?
According to the Unofficial JAXP FAQ, available at
http://xml.apache.org/~edwingo/jaxp-faq.html, the Java API for XML Processing (JAXP) "enables applications to parse and transform
XML documents using an API that is independent of a particular XML processor implementation".
Or, to put it very simply, JAXP provides an abstraction layer, or standard API,
that allows for code reuse across different XSLT processors. You might be wondering
how JAXP, as an abstraction layer, knows which class to use during the transformation
process. This information is available via the javax.xml.transform.TransformerFactory
property. In case this didn't make any sense to you, don't worry about it; if
it did, and you want to know more, take a look at
http://xml.apache.org/xalan-j/apidocs/javax/xml/transform/TransformerFactory.html Next, I've instantiated some variables to hold the names of the various files
I'll be using in the application.
// store the names of the files
public static String xmlFile, xslFile,
resultFile = "";
And now for the constructor:
// constructor
public addressBookConverter(String xmlFile, String xslFile,
String
resultFile) {
try {
// create an instance of the TransformerFactory
// this allows the developer to use an API that is independent
of
// a particular XML processor implementation.
TransformerFactory
tFactory = TransformerFactory.newInstance();
// create a transformer
which takes the name of the stylesheet
// as an input parameter
Transformer transformer = tFactory.newTransformer(new
StreamSource(xslFile));
// transform the given XML file
transformer.transform(new
StreamSource(xmlFile), new
StreamResult(resultFile));
System.out.println("Done!");
} catch (TransformerException e) {
System.err.println("The
following error occured: " + e);
}
}
The first step is to create an instance of the TransformerFactory class. This
can be used to create a Transformer object, which reads the XSLT stylesheet and
converts the templates within it into a Templates object. This Templates object
is a dynamic representation of the instructions present in the XSLT file - you
won't see any reference to it in the code above, because it all happens under
the hood, but trust me, it exists.
Once the stylesheet is processed, a new Transformer object is generated. This
Transformer object does the hard work of applying the templates within the Templates
object to the XML data to produce a new result tree, via its transform() method.
The result tree is stored in the specified output file.
Finally, the main() method sets the ball in motion:
// everything starts here
public static void main (String[] args) {
if(args.length != 3) {
System.err.println("Please specify three
parameters:n1.
The name and path to the XML file.n2. The name and path to the
XSL
file.n3. The name of the output file.");
return;
}
// assign the parameters passed as input parameters to
// the variables defined above
xmlFile = args[0];
xslFile = args[1];
resultFile = args[2];
addressBookConverter myFirstExample = new
addressBookConverter
(xmlFile, xslFile, resultFile);
}
This method first checks to see if the correct number of arguments was passed.
If so, it invokes the constructor to create an instance of the addressBookConverter
class; if not, it displays an appropriate error message.{mospagebreak title=Six
Degrees Of Separation} Let's move on to something a little more complicated. Let's
suppose that I wanted to convert my address book from XML into a delimiter-separated
ASCII file format, for easy import into another application. With XSLT and Xalan,
the process is a snap.
Here's the XML data:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl"
href="/home/me/xsl/xml2csv.xsl"?>
<addressbook>
<item>
<name>Bugs
Bunny</name>
<address>The Rabbit Hole, The Field behind Your House</address>
<email>bugs@bunnyplanet.com</email>
</item>
<item>
<name>Batman</name>
<address>The Batcave, Gotham City</address>
<tel>123 7654</tel>
<url>http://www.belfry.net/</url>
<email>bruce@gotham-millionaires.com</email>
</item>
</addressbook>
Now, in order to convert this XML document into a delimiter-separated file, I
need an XSLT stylesheet like this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<!-
Set the "|" symbol as the default delimiter à
<xsl:param name="delimiter"
select="normalize-space('|')"/>
<xsl:template match="/addressbook">
<xsl:for-each select="item">
<xsl:value-of select="normalize-space(name)"/>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="normalize-space(address)"/>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="normalize-space(tel)"/>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="normalize-space(email)"/>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="normalize-space(url)"/>
<!- hexadecimal value for the new-line character à
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
And here's the Java code to tie it all together:
// imported java classes
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import
java.io.*;
public class xml2csv {
// store the names of the files
public
static String xmlFile, xslFile, resultFile,delimiterValue =
"";
// parameters
for the getAssociatedStylesheet() method of the
TransformerFactory class
//
Set them to null as they are not essential here
String media = null , title
= null, charset = null;
public xml2csv(String xmlFile, String resultFile,
String
delimiterValue) {
try {
// create an instance of the TransformerFactory
class
TransformerFactory tFactory = TransformerFactory.newInstance();
// get the name of the stylesheet that has been defined in
the
XML
file.
// create a Source object for use by the upcoming
newTransformer()
method
Source stylesheet = tFactory.getAssociatedStylesheet (new
StreamSource(xmlFile),media,
title, charset);
// create a transformer which takes the name
of the stylesheet
// as an input parameter
Transformer transformer
= tFactory.newTransformer(stylesheet);
// set a delimiter
via
the setParameter() method of the
transformer
transformer.setParameter("delimiter",delimiterValue);
// perform the transformation
transformer.transform(new
StreamSource(xmlFile), new
StreamResult(resultFile));
System.out.println("Done!");
} catch (TransformerException e) {
System.err.println("The
following error occured: " + e);
}
}
// everything starts
here
public static void main (String[] args) {
if(args.length != 3)
{
System.err.println("Please specify three parameters:n1. The
name
and
path to the XML file.n2. The name of the output file.n3. The
delimiter to
be used.");
return;
}
// set some variables
xmlFile
= args[0];
resultFile = args[1];
delimiterValue = args[2];
xml2csv
mySecondExample = new xml2csv(xmlFile, resultFile,
delimiterValue);
}
}
Most of the code is identical to the previous example. There are a couple of
important differences, though. First, the parameters passed to the constructor
are different in this case.
public xml2csv(String xmlFile, String resultFile, String
delimiterValue) {
// snip
}
Over here, I'm passing three parameters to the constructor: the name of the XML
file, the name of the output file, and the delimiter to be used between the various
elements of a record. What about the XSLT file, you ask? Well, that's sourced
automatically from the XML file via the getAssociatedStylesheet() method of the
TransformerFactory class.
// create an instance of the TransformerFactory class
TransformerFactory
tFactory = TransformerFactory.newInstance();
// get the name
of the stylesheet that has been defined in the
XML file.
// create a
Source object for use by the upcoming
newTransformer() method
Source
stylesheet = tFactory.getAssociatedStylesheet (new
StreamSource(xmlFile),media,
title, charset); [/code]
A new Source object is created to represent this stylesheet; this Source
object
is ultimately passed to the Transformer class.
// create a transformer which takes the name of the stylesheet
// as an input parameter
Transformer transformer = tFactory.newTransformer(stylesheet);
If you take a close look at the XSLT file above, you'll see that I've
defined
an XSLT parameter named "delimiter". This parameter is
essentially a variable
which can be accessed by XSLT, and a value can be
assigned to it via the setParameter()
method of the Transformer class.
// set a delimiter via the setParameter() method of the
transformer
transformer.setParameter("delimiter",delimiterValue);
In this case, the "delimiter" parameter is set to whatever delimiter was
specified
by the user.
Finally, the actual transformation is performed, the output stored in
the desired
output file, and a result code generated.
[code]
//
perform the transformation
transformer.transform(new StreamSource(xmlFile),
new
StreamResult(resultFile));
System.out.println("Done!");
Here's what it looks like, assuming you use a pipe (|) as delimiter (I haven't
used a comma simply because some of the entries already contain commas):
Bugs Bunny|The Rabbit Hole, The Field behind Your
House||bugs@bunnyplanet.com|
Batman|The
Batcave, Gotham City|123
7654|bruce@gotham-millionaires.com|http://www.belfry.net/
Isn't it simple when you know how?