Home Books Software Projects Forums

XSLT by Example

Using flow control for conditional processing.

Table of Contents

How do you conditionally perform a transformation?

If you are not sure whether an input XML document will contain certain elements or not, you may perform an XSLT transformation conditionally. If the element exists, then an output will be produced in the result tree; if it doesn't exist, either another output will be produced or no output will be produced at all.

The following example is taken from the xsl:template named "supertypes" and will be used in several of the discussions to follow:

<!-- Generalizations identify supertypes -->
<xsl:variable name="generalizations" 
              select="Foundation.Core.GeneralizableElement.generalization/
                      Foundation.Core.Generalization"/>

<xsl:if test="count($generalizations) > 0">
    <tr>
        <td width="20%" class="info-title">Supertypes:</td>
        <td colspan="2" class="info">
        <xsl:for-each select="$generalizations">
        
            <!-- get the parent in the generalization -->
            <xsl:variable name="generalization"
                 select="key('generalization', ./@xmi.idref)" />
            <xsl:variable name="target"
                 select="$generalization/
                        Foundation.Core.Generalization.parent/
                        */@xmi.idref" /> 
            <xsl:call-template name="classify">
                <xsl:with-param name="target" select="$target"/>
            </xsl:call-template>
            
            <xsl:if test="position() != last()">
                <xsl:text>,  </xsl:text>
            </xsl:if>
        </xsl:for-each>
        </td>
    </tr>
</xsl:if>  

The goal we want to achieve is to generate a list of supertypes for a class definition. Each supertype identified will also serve as a hyperlink to take us to its definition if we wish to navigate. This production is conditionalized because we only want to create a row in our table for Supertypes if any exist. Moreover, if more than one exists, we would like to produce a comma-separated list. (Yes, some OO languages do have multiple inheritance!). For this we need control-flow.

We test to see if there is any work to do by creating a variable called 'generalizations' and assigning to it the list of generalization elements, if any exist. Our test is performed using the xsl:if instruction in conjunction with the count() function. The count() function is used to determine if there are any nodes in the list of generalization elements. If so, then the xsl:if conditional block is executed and a row (<tr>) is created for Supertypes.

Note that XSLT does not have an xsl:else instruction. If you need multiple conditional branches, then you will need the xsl:when instruction, described below.

References: [XSLT Programmer's Reference, xsl:if pp. 204-207, count() pp. 434-436]


How do you iterate over a set of nodes in a result set?

Since there may be more than one generalization relationship for a class (multiple inheritance or multiple subtypes) we need a way to iterate over a list. The xsl:for-each instruction provides this capability. An XPath expression must be present as the value of the 'select' attribute of an xsl:for-each instruction and is used to find the set of nodes over which the iteration will be applied.

In our case, we have employed an XSL variable called 'generalizations' to remember the XPath expression used for selection. Once we have our list of generalization elements, the body of the xsl:for-each instruction contains the rules to apply to each generalization element in the list. Since this example is creating a list of supertypes, we find the parent node and then output its name as a hyperlink.

References: [XSLT Programmer's Reference, xsl:for-each pp. 201-204]


How do you create a comma separated list?

A fairly useful task to perform in an XSLT stylesheet is the creation of a comma-separated list. We use commas to separate the names of both the Supertypes and Subtypes of a class. The trick to accomplishing this in XSLT is to know when you've reached the end of the list because you don't want a trailing comma at the end of a list.

XSLT provides a function called position() which allows you to test the current position in a list and a function called last() to determine when the last element of a list has been reached. When used together in an xsl:if instruction, a comparison of the results of these two functions can yield the decision to add a comma or not. From the example above, the way to read this conditional is: if the supertype we are adding to the list is not the last one in the list, then add a comma to separate if from the next one in the list.

References: [XSLT Programmer's Reference, position() pp. 500-504, last() pp. 478-483]


How do you select from amongst a list of possible results?

As we saw earlier, the xsl:if instruction is limited to a single test. Therefore, if you need a construct akin to a switch statement, wherein each case executes a different set of code, XSLT provides the analogous xsl:choose and xsl:when instructions.

The following example, taken from the xsl:template named "classify", illustrates the usage of these instructions. The purpose of the classify template is to determine if a classifier is either a Class, an Interface or a DataType and to style the name of the classifier accordingly.

<xsl:variable name="type" select="name($classifier)" />

<!-- Get the type of the classifier (class, interface, datatype) -->
<xsl:variable name="classifier_type">
<xsl:choose>
    <xsl:when test="$type='Foundation.Core.Class'">classifier</xsl:when>
    <xsl:when test="$type='Foundation.Core.Interface'">interface</xsl:when>
    <xsl:when test="$type='Foundation.Core.DataType'">datatype</xsl:when>
    <xsl:otherwise>classifier</xsl:otherwise>
</xsl:choose>
</xsl:variable>

<xsl:choose>
    <!-- Datatypes don't have hyperlinks -->
    <xsl:when test="$type='Foundation.Core.DataType'">
        <span class="datatype">
            <xsl:value-of select="$classifier_name"/>
        </span>
    </xsl:when>
    
    <!-- Classes and Interfaces have hyperlinks -->
    <!-- The classifier type is used to style appropriately -->
    <xsl:otherwise>
        <xsl:if test="string-length($classifier) > 0">
            <a class="{$classifier_type}" href="#{$classifier_name}">
            <xsl:value-of select="$classifier_name"/>
            </a>
        </xsl:if>
    </xsl:otherwise>
</xsl:choose>

The first step in the template is to find the type of the classifier. This is conveniently provided by the name() function, which returns the name of the current element in the document. Once we have the UML element name we can use it in an xsl:choose instruction to assign a type. The type is eventually used as the name of a CSS class for the purpose of styling.

Each case of an xsl:choose instruction is implemented using a test within an xsl:when instruction. If the test clause is evaluated and found to be true, then the production inside the xsl:when instruction is executed. In our case, the action performed in the first xsl:choose instruction is to assign the type (classifier, interface, or datatype) to a variable named "classifier_type". Note that if for some reason no xsl:when instuction is evaluated to be true, then an xsl:otherwise catches this case as a default.

In the second xsl:choose instruction there are just two cases. In the first case, if we find a DataType classifier, we don't create a hyperlink because there is nothing to which we want to link. In the second case, handled by default through the xsl:otherwise instruction, we use the "classifier_type" variable as the CSS style name and create a hyperlink using the "classifier_name" variable. The outcome is that an Interface is styled with italics while a Class is non-italicized (a UML style convention). You may look at the CSS style definitions for more detail.

References: [XSLT Programmer's Reference, xsl:choose pp. 176-178, xsl:when pp. 317-318]




Valid XHTML 1.0!