How to Preprocess XSLT using STX

In my previous article I have shown how to preprocess XSLT using custom SAX ContentHandler that transforms XSLT source code on-the-fly.

The reason SAX transformation was chosen instead of simpler XSLT transformation is because SAX transformations preserve line and column information: if you will make an error in a preprocessed XSLT, XSLT processor will report exact line and column position of the error; if XSLT preprocessing was implemented in XSLT, line and column information would be distorted.

However it is very difficult to implement SAX transformations in plain Java. Luckily there is STX -- XSLT like programming language designed for writing SAX transformations. STX is very sweet programming language — it borrows to the max from XSLT, introducing new constructs only when absolutely required because of SAX specifics. This makes STX very viable option for writing SAX ContentHandler transformatins.

For example, compare previous article's ContentHandler implementation in Java

class XsltContentHandler extends DelegatingContentHandler {
	private String args;
	private String mode;

	XsltContentHandler(ContentHandler handler, String args) {
		...
	}

	public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
		if (uri.equals("http://www.w3.org/1999/XSL/Transform")) {
			if (localName.equals("template") && atts.getValue("", "match") != null || localName.equals("apply-templates")) {
				String mode = atts.getValue("", "mode");
				if (mode == null && this.mode != null) {
					AttributesImpl atts2 = new AttributesImpl(atts);
					atts2.addAttribute("", "mode", "mode", "CDATA", this.mode);
					atts = atts2;
				}
			} else if (localName.equals("include") || localName.equals("import")) {
				String href = atts.getValue("", "href");
				if (href != null && args != null) {
					if (href.contains("?")) {
						href = href + "&" + args;
					} else {
						href = href + "?" + args;
					}
					AttributesImpl atts2 = new AttributesImpl(atts);
					atts2.setAttribute(atts.getIndex("", "href"), "", "href", "href", "CDATA", href);
					atts = atts2;
				}
			}
		}
		super.startElement(uri, localName, qName, atts);
	}
}

and same code expressed in STX:

<stx:transform version="1.0" xmlns:stx="http://stx.sourceforge.net/2002/ns" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" pass-through="all">

	<stx:param name="args" />
	<stx:param name="mode" />

	<stx:template match="xsl:include[@href and $args and $args!=''] | xsl:import[@href and $args and $args!='']">
		<stx:copy attributes="@*[name()!='href']">
			<stx:choose>
				<stx:when test="contains(@href, '?')">
					<stx:attribute name="href" select="concat(@href, '&', $args)" />
				</stx:when>
				<stx:otherwise>
					<stx:attribute name="href" select="concat(@href, '?', $args)" />
				</stx:otherwise>
			</stx:choose>
			<stx:process-children />
		</stx:copy>
	</stx:template>

	<stx:template match="xsl:template[@match and not(@mode) and $mode and $mode!=''] | xsl:apply-templates[not(@mode) and $mode and $mode!='']">
		<stx:copy attributes="@*[name()!='@mode']">
			<stx:attribute name="mode" select="$mode" />
			<stx:process-children />
		</stx:copy>
	</stx:template>

</stx:transform>

IMHO STX is much easier to read.

Bespoke readability, STX can be easily extended. For example, if you have a lot of "rename element" templates in your XSLT code, like:

    <xsl:template match="oldElement">
        <newElement>
            <xsl:apply-templates select="@*|node()"/>
        </newElement>
    </xsl:template>

you can put the following in your STX preprocessing file:

	<stx:template match="xsl:template.rename-element/@new-name" />

	<stx:template match="xsl:template.rename-element">
		<stx:buffer name="result">
			<xsl:template>
				<stx:process-attributes />
				<stx:element name="{@new-name}">
					<xsl:apply-templates select="@*|node()" />
				</stx:element>
			</xsl:template>
		</stx:buffer>
		<stx:process-buffer name="result" />
	</stx:template>

and enjoy the following shortcut in the XSLT:

    <xsl:template.rename-element match="oldElement" new-name="newElement" />

You can easily introduce similar shortcuts like xsl:template.rename-attribute, xsl:template.wrap-element, xsl:template.wrap-children, etc. — the possibilities for extending XSLT via SAX preprocessing are virtually endless.

You can find ZIP w/ proof-of-concept source code attached below.

AttachmentSize
xml-pipeline4.zip9.78 KB

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.