Monthly Archive for December, 2005

Working with comma separated lists in XSL

Found this gem of knowledge for creating a comma separated list with XSLT without having to resort to the monstrous hacks I’ve used before.

My XML file is looking like this:

<message id="Login" initiator="client" system="lobby">
    <description>This messages is sent from the client when the client wants to login.</description>
    <request>
        <argument name="loginname" type="&string;" example="myUserName" maxsize="40"/>
        <argument name="password" type="&string;" example="myP4Ssw0rd!" maxsize="40">
            <description>Yes, this should be encrypted. Actual algorithm not yet
            decided.</description>
        </argument>
        <argument name="macaddress" type="&string;" example="09:00:07:A9:B2:EB">
            <description>We may wish to include even more information about the client PC at login,
                but for now see the CheckVersion message.</description>
        </argument>
    </request>
    <response> &frgUserId; <argument name="loginserverid" type="&integer;" example="12">
            <description>This is the id for the loginserver the client has been assigned
            to.</description>
        </argument>
        <argument name="token" type="&string;" example="33e24aeb44d25327" maxsize="2048"
            required="false">
            <description>This is a string for the unique token that identifies this particular
                client's login.</description>
        </argument>
        <argument name="errornumber" type="&integer;" example="0">
            <description>This should be >0 in case of an error. These error codes are
                described in another document.</description>
        </argument>
    </response>
</message>

Now, I want to generate source code from this using XSLT. In one instance I generate method signatures that need all request arguments as parameters for the method. I then have a snippet of XSL generate the code like this (simplified):

<xsl:for-each select="//message">
    <![CDATA[public void on]]><xsl:value-of select="@id"/>(<xsl:if test="request != ''">
        <xsl:for-each select="request/*">
            <xsl:value-of select="@type"/>
            <xsl:value-of select="concat(' ', @name)"/>
            <xsl:if test="position() != last()">, </xsl:if>
        </xsl:for-each>
    </xsl:if>);</xsl:for-each>

The beauty is of course in </xsl:if>);</xsl:for-each> which simply tests if this position is the same as the last position. Simple and elegant!

Now, if you are using XSLT 2.0 you could go one step further and skip the entire inner xsl:for-each and do it in one line. This solution is not quite as simple in this particular example considering that I need to concatenate several attributes for each item in the list, but it’s still doable.

<xsl:for-each select="//message">
    <![CDATA[public void on]]><xsl:value-of select="@id"/>(<xsl:if test="request != ''">
        <xsl:value-of select="for $s in (request/*) return (concat($s/attribute::type, ' ', $s/attribute::name))" separator=", "/>
    </xsl:if>);</xsl:for-each>

Lets say you wanted something simple, such as a list of all the message id’s defined in the XML file, this would be done like this in 1.0:

<xsl:for-each select="//message">
    <xsl:value-of select="@id"/>
    <xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>

And the 2.0 equivalent is:

<xsl:value-of select="//message/@id" separator=", "/>

In this example the 2.0 solution is much cleaner in my mind. But choose the one you like the most! :)