Transforming XML without XSLT

Although XSLT is a powerful and flexible way to convert XML documents to alternate representations, it is often difficult and inconvenient to use in practice. The XSLT specification is large and complex, and platform support is inconsistent.

The TemplateEncoder class provided by the Kilo framework can be used to transform XML using a lightweight syntax similar to Mustache. Kilo’s ElementAdapter class provides access to the contents of an XML DOM Element via the Map interface. The contents of this map can be easily transformed to another representation via a template document.

For example, this tutorial from W3 Schools uses XML to model a simple breakfast menu:

<?xml version="1.0" encoding="UTF-8"?>

<breakfast_menu>
    <food>
        <name>Belgian Waffles</name>
        <price>$5.95</price>
        <description>Two of our famous Belgian Waffles with plenty of real maple syrup</description>
        <calories>650</calories>
    </food>

    <food>
        <name>Strawberry Belgian Waffles</name>
        <price>$7.95</price>
        <description>Light Belgian waffles covered with strawberries and whipped cream</description>
        <calories>900</calories>
    </food>

    <food>
        <name>Berry-Berry Belgian Waffles</name>
        <price>$8.95</price>
        <description>Light Belgian waffles covered with an assortment of fresh berries and whipped cream</description>
        <calories>900</calories>
    </food>

    <food>
        <name>French Toast</name>
        <price>$4.50</price>
        <description>Thick slices made from our homemade sourdough bread</description>
        <calories>600</calories>
    </food>

    <food>
        <name>Homestyle Breakfast</name>
        <price>$6.95</price>
        <description>Two eggs, bacon or sausage, toast, and our ever-popular hash browns</description>
        <calories>950</calories>
    </food>
</breakfast_menu>

The example uses the following XSLT template to transform the markup to HTML. Even in this fairly simple case, the verbosity of XSLT is obvious, and has a negative impact on readability:

<?xml version="1.0" encoding="UTF-8"?>
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
        <xsl:for-each select="breakfast_menu/food">
            <div style="background-color:teal;color:white;padding:4px">
                <span style="font-weight:bold"><xsl:value-of select="name"/> - </span>
                <xsl:value-of select="price"/>
            </div>
            <div style="margin-left:20px;margin-bottom:1em;font-size:10pt">
                <p>
                    <xsl:value-of select="description"/>
                    <span style="font-style:italic"> (<xsl:value-of select="calories"/> calories per serving)</span>
                </p>
            </div>
        </xsl:for-each>
    </body>
</html>

The javax.xml.transform.Transformer class can be used to apply the template to the XML document:

var source = new StreamSource(getClass().getResourceAsStream("breakfast-menu.xslt"));

var transformer = TransformerFactory.newInstance().newTransformer(source);

var xmlSource = new StreamSource(getClass().getResourceAsStream("breakfast-menu.xml"));

var homeDirectory = new File(System.getProperty("user.home"));
var outputFile = new File(homeDirectory, "breakfast-menu-1.html");

try (var outputStream = new FileOutputStream(outputFile)) {
    transformer.transform(xmlSource, new StreamResult(outputStream));
}

The result of the transformation is shown below:

XSLT Menu

The following is an example of how a similar template can be defined using Mustache-style syntax. It is much simpler and easier to read than the previous version, and can be edited using any HTML-aware text editor. The {{#food*}} markers refer to the list of <food> elements in the source document. The other markers refer to the various <food> sub-elements:

<html>
    <body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
        <!-- {{#food*}} -->
        <div style="background-color:teal;color:white;padding:4px">
            <span style="font-weight:bold">{{name}} - </span> {{price}}
        </div>
        <div style="margin-left:20px;margin-bottom:1em;font-size:10pt">
            <p>{{description}} <span style="font-style:italic"> ({{calories}} calories per serving)</span></p>
        </div>
        <!-- {{/food*}} -->
    </body>
</html>

This code applies the template to the XML document using TemplateEncoder:

var documentBuilderFactory = DocumentBuilderFactory.newInstance();

documentBuilderFactory.setExpandEntityReferences(false);
documentBuilderFactory.setIgnoringComments(true);

var documentBuilder = documentBuilderFactory.newDocumentBuilder();

Document document;
try (var inputStream = getClass().getResourceAsStream("breakfast-menu.xml")) {
    document = documentBuilder.parse(inputStream);
}

var templateEncoder = new TemplateEncoder(getClass().getResource("breakfast-menu.html"));

var homeDirectory = new File(System.getProperty("user.home"));
var outputFile = new File(homeDirectory, "breakfast-menu-2.html");

try (var outputStream = new FileOutputStream(outputFile)) {
    templateEncoder.write(new ElementAdapter(document.getDocumentElement()), outputStream);
}

The result is identical to the XSLT version:

Mustache Menu

Simplicity isn’t the only advantage of the Mustache-style approach. On average, the XSLT Transformer version takes about 125ms to run, compared to about 15ms for TemplateEncoder – about an 8x difference.

The complete source code for this example is available here. For more information, see the Kilo README.