How to Implement XML Pipeline in Plain XSLT

Most often people implement XML Pipeline in regular programming languages, sequentially invoking XSLT transformations one-by-one, "manually" feeding resulting output from one transformation to another.

Bespoke slow performance, this results in lesser flexibility since people stop using XSLT's declarative power and revert back to imperative world, which is not acceptable for XML transformations.

In this article I will show the most efficient way of implementing XML Pipeline using native XSLT capabilities.

The code outlining the approach consists of the following files:

  • chain.xsl, the "magic", main file implementing the pipeline. [1]
    <?xml version="1.0" encoding="utf-8"?>
    
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common">
    
        <xsl:output method="xml" omit-xml-declaration="no" indent="no" encoding="UTF-8"/>
    
        <xsl:include href="link1.xsl"/>
        <xsl:include href="link2.xsl"/>
        <xsl:include href="link3.xsl"/>
    
        <xsl:template match="/">
            <xsl:variable name="link1">
                <xsl:apply-templates mode="link1" select="node()"/>
            </xsl:variable>
            <xsl:variable name="link2">
                <xsl:apply-templates mode="link2" select="exsl:node-set($link1)/node()"/>
            </xsl:variable>
            <xsl:variable name="link3">
                <xsl:apply-templates mode="link3" select="exsl:node-set($link2)/node()"/>
            </xsl:variable>
    
            <xsl:copy-of select="exsl:node-set($link3)/node()"/>
        </xsl:template>
    
    </xsl:stylesheet>
    
  • link1.xsl, linked transformation:
    <?xml version="1.0" encoding="UTF-8"?>
    
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
        <xsl:template priority="-9" mode="link1" match="@*|node()">
            <xsl:copy>
                <xsl:apply-templates mode="link1" select="@*|node()"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template mode="link1" match="a">
            <aa>
                <xsl:apply-templates mode="link1" select="@*|node()"/>
                <b/>
            </aa>
        </xsl:template>
    
    </xsl:stylesheet>
    
  • link2.xsl, linked transformation:
    <?xml version="1.0" encoding="UTF-8"?>
    
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
        <xsl:template priority="-9" mode="link2" match="@*|node()">
            <xsl:copy>
                <xsl:apply-templates mode="link2" select="@*|node()"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template mode="link2" match="b">
            <bb>
                <xsl:apply-templates mode="link2" select="@*|node()"/>
                <c/>
            </bb>
        </xsl:template>
    
    </xsl:stylesheet>
    
  • link3.xsl, linked transformation:
    <?xml version="1.0" encoding="UTF-8"?>
    
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
        <xsl:template priority="-9" mode="link3" match="@*|node()">
            <xsl:copy>
                <xsl:apply-templates mode="link3" select="@*|node()"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template mode="link3" match="c">
            <cc>
                <xsl:apply-templates mode="link3" select="@*|node()"/>
            </cc>
        </xsl:template>
    
    </xsl:stylesheet>
    

The key point of this approach is usage of EXSLT node-set function. This function converts result-nodes back into source-nodes. This way output of link1.xsl is fed as input to link2.xsl, and output of link2.xsl is fed as input to link3.xsl. [2]

This approach leverages full power of XSLT, it is fast and declarative. Transformations are applied without leaving XSLT processor, non-linear pipelines can be implemented easily:

        <xsl:variable name="link2">
            <!-- some logic whether or not launch link2.xsl -->
            <xsl:choose>
                <xsl:when test="not(.//d)">
                    <xsl:apply-templates mode="link2" select="exsl:node-set($link1)/node()"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="exsl:node-set($link1)/node()"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:variable>

I am using this technique for a number of years, and so far it works great.

[1] By default MSXML does not implement EXSLT, though it does have node-set function. It is in the xmlns:msxsl="urn:schemas-microsoft-com:xslt" namespace. There is chain_msxml.xsl — separate main file for MSXML that is using node-set function from msxsl namespace.
[2] xsl:copy-of in the last statement is included for debugging purposes. For example, if something goes wrong in link2.xsl, just change one symbol in xsl:copy-of to make it look like <xsl:copy-of select="exsl:node-set($link23)/node()"/> and pipeline will stop at 2nd transformation.
[3] exslt:node-set() is not required for XSLT 2.0 processor

Sample code is attached below.

AttachmentSize
xml-pipeline.zip4.63 KB

Comments

Imperative not acceptable ??

I suggest this comment is unfounded in fact "revert back to imperative world, which is not acceptable for XML transformations."

Exactly why is "imperative" "not acceptable for XML transformations" ?
By what criteria are you judging "acceptable" ?

If you really mean "bad performance" so consider some XML pipeline languages such as Xproc and xmlsh which perform extremely well in conjunction with XSLT.

Yes, it is a GoTo

Imperative XML transformations are not acceptable just like goto is not acceptable for structured languages, or cursors are not acceptable for SQL queries, or stateful is not acceptable for webservices.

About performance: XSLT is better on XML transformations than any other thing. Once XML pipeline is achieved in XSLT, it is better than xproc and xmlsh.

abc.exe or ASD macro :)

abc.exe or ASD macro :)

Post new comment

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