Sunday 19 August 2012

How to use XInclude in XSLT stylesheets

XInclude is not enabled by default when using the built-in XSLT processor in Java 1.6.  The underlying Apache Xalan uses Apache Xerces which does support it, but this is not directly accessible through the standard API.

Here is an example of how we would normally use XSLT:

// During app startup
TransformerFactory transformerFactory =
                TransformerFactory.newInstance();
Templates stylesheet = transformerFactory.newTemplates(
                new StreamSource("stylesheet.xsl"));

// Transformation
Source source = new StreamSource(new File("input.xml"));
Result result = new StreamResult(new File("output.xml"));
stylesheet.newTransformer().transform(source, result);
To use XInclude within an XSLT stylesheet we need to read it using a DocumentBuilder which has been created a DocumentBuilderFactory.  This has the methods that allows us to enable XInclude.
// During app startup
DocumentBuilderFactory documentBuilderFactory =
                DocumentBuilderFactory.newInstance(); documentBuilderFactory.setXIncludeAware(true);
documentBuilderFactory.setNamespaceAware(true);
// We don't want xml:base and xml:lang attributes in the output.
documentBuilderFactory.setFeature(
                "http://apache.org/xml/features/xinclude/fixup-base-uris", false);
documentBuilderFactory.setFeature(
                "http://apache.org/xml/features/xinclude/fixup-language", false);

DocumentBuilder docBuilder =
                documentBuilderFactory.newDocumentBuilder();
Document stylesheetDoc = docBuilder.parse("stylesheet.xsl");
Source stylesheetSource = new DOMSource(stylesheetDoc);

TransformerFactory transformerFactory =
                TransformerFactory.newInstance();
Templates stylesheet =
                transformerFactory.newTemplates(stylesheetSource);

// Transformation
Source source = new StreamSource(new File("input.xml"));
Result result = new StreamResult(new File("output.xml"));
stylesheet.newTransformer().transform(source, result);
It is bit more verbose than the orginal, but this is the only way I have found to do this.
The stylesheet this uses might look like this:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:xi="http://www.w3.org/2001/XInclude"
      exclude-result-prefixes="xi xsl xml">
  <xsl:output method="html" />

  <xsl:template match="/myRootElement">
        <xi:include href="mail.html" parse="xml" />
  </xsl:template>

</xsl:stylesheet>
And the included sub-stylesheet that uses the data elements within the myRootElements might be:
  <html xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <body>
     <p><xsl:value-of select="name"/></p>
     <p><xsl:value-of select="email"/></p>
     <ul>
       <xsl:for-each select="list/a">
         <li><xsl:value-of select="."/></li>
       </xsl:for-each>
     </ul>
   </body>
  </html>
Of course you may wish to use XInclude within the input XML, in which case you need to build a DOMSource for the transformation in the same way.

No comments:

Post a Comment