Showing posts with label XSLT. Show all posts
Showing posts with label XSLT. Show all posts

Saturday, October 07, 2017

Using XSLT 2.0 with Java and Saxon

In my previous post, I showed how you can split a string using the tokenize function in XSLT 2.0. In order to run an XSLT 2.0 stylesheet in Java, you need a transformer that supports XSLT 2.0. Unfortunately, the default JAXP transformer (com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl) does not. Instead, use Saxon, which supports both XSLT 2.0 and 3.0.

The class below shows how you can perform an XSL transformation in Java, using Saxon and the JAXP interface:

import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

public class XSLTransformer {

  private final Templates templates;

  public XSLTransformer(final String xslFileName) throws Exception {
    templates = new net.sf.saxon.BasicTransformerFactory().newTemplates(
        new StreamSource(XSLTransformer.class.getClassLoader()
                             .getResourceAsStream(xslFileName)));
  }

  public String transform(final String xml) throws Exception {
    final Transformer transformer = templates.newTransformer();
    final StringWriter writer = new StringWriter();
    transformer.transform(new StreamSource(new StringReader(xml)),
                          new StreamResult(writer));
    return writer.toString();
  }
}

An alternative to the JAXP interface is to use Saxon's own s9api interface, which is more robust:

import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.transform.stream.StreamSource;

import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XsltExecutable;
import net.sf.saxon.s9api.XsltTransformer;

public class SaxonTransformer {

  private final Processor processor;
  private final XsltExecutable xsltExec;

  public SaxonTransformer(final String xslFileName) throws SaxonApiException {
    processor = new Processor(false);
    xsltExec = processor.newXsltCompiler().compile(new StreamSource(
        XSLTransformer.class.getClassLoader().getResourceAsStream(xslFileName)));
  }

  public String transform(final String xml) throws Exception {
    final XsltTransformer transformer = xsltExec.load();
    transformer.setSource(new StreamSource(new StringReader(xml)));
    final StringWriter writer = new StringWriter();
    transformer.setDestination(processor.newSerializer(writer));
    transformer.transform();
    return writer.toString();
  }
}

Splitting a string using XSLT 2.0

The tokenize function available in XSL Transformations (XSLT) Version 2.0 allows you to split a string on any separator that matches a given regular expression.

The example below shows how you can split a comma-delimited string:

Input XML:

<data>
  <stringToSplit>foo,bar,baz</stringToSplit>
</data>

XSL 2.0 Stylesheet:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="data">
    <items>
      <xsl:variable name="stringToSplit" select="stringToSplit" />
      <xsl:for-each select="tokenize($stringToSplit, ',')">
        <item>
          <xsl:value-of select="." />
        </item>
      </xsl:for-each>
    </items>
  </xsl:template>
</xsl:stylesheet>

Output XML:

<?xml version="1.0" encoding="UTF-8"?>
<items>
  <item>foo</item>
  <item>bar</item>
  <item>baz</item>
</items>

Saturday, July 19, 2014

Converting XML to CSV using XSLT 1.0

This post shows you how can convert a simple XML file to CSV using XSLT.

Consider the following sample XML:

<library>
  <book>
    <author>Dan Simmons</author>
    <title>Hyperion</title>
    <publishDate>1989</publishDate>
  </book>
  <book>
    <author>Douglas Adams</author>
    <title>The Hitchhiker's Guide to the Galaxy</title>
    <publishDate>1979</publishDate>
  </book>
</library>

This is the desired CSV output:

author,title,publishDate
Dan Simmons,Hyperion,1989
Douglas Adams,The Hitchhiker's Guide to the Galaxy,1979

The following XSL Style Sheet (compatible with XSLT 1.0) can be used to transform the XML into CSV. It is quite generic and can easily be configured to handle different xml elements by changing the list of fields defined ar the beginning.

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

  <xsl:variable name="delimiter" select="','" />

  <!-- define an array containing the fields we are interested in -->
  <xsl:variable name="fieldArray">
    <field>author</field>
    <field>title</field>
    <field>publishDate</field>
  </xsl:variable>
  <xsl:param name="fields" select="document('')/*/xsl:variable[@name='fieldArray']/*" />

  <xsl:template match="/">

    <!-- output the header row -->
    <xsl:for-each select="$fields">
      <xsl:if test="position() != 1">
        <xsl:value-of select="$delimiter"/>
      </xsl:if>
      <xsl:value-of select="." />
    </xsl:for-each>

    <!-- output newline -->
    <xsl:text>&#xa;</xsl:text>

    <xsl:apply-templates select="library/book"/>
  </xsl:template>

  <xsl:template match="book">
    <xsl:variable name="currNode" select="." />

    <!-- output the data row -->
    <!-- loop over the field names and find the value of each one in the xml -->
    <xsl:for-each select="$fields">
      <xsl:if test="position() != 1">
        <xsl:value-of select="$delimiter"/>
      </xsl:if>
      <xsl:value-of select="$currNode/*[name() = current()]" />
    </xsl:for-each>

    <!-- output newline -->
    <xsl:text>&#xa;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

Let's try it out:

$ xsltproc xml2csv.xsl books.xml
author,title,publishDate
Dan Simmons,Hyperion,1989
Douglas Adams,The Hitchhiker's Guide to the Galaxy,1979