xsltxpathxslt-2.0xsl-variable

Is xsl:sequence always non-empty?


I don't understand output from this stylesheet:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <xsl:apply-templates select="root/sub"/>
    </xsl:template>

    <xsl:template match="sub">
        <xsl:variable name="seq">
            <xsl:sequence select="*" />
        </xsl:variable>

        <xsl:message>
            <xsl:value-of select="@id" />
            <xsl:text>: </xsl:text>
            <xsl:value-of select="count($seq)" />
        </xsl:message>
    </xsl:template>
</xsl:stylesheet>

when applied to following XML:

<root>
    <sub id="empty" />
    <sub id="one"><one/></sub>
    <sub id="two"><one/><one/></sub>
    <sub id="three"><one/><one/><one/></sub>
</root>

Output, written by xsl:message element, is:

empty: 1
one: 1
two: 1
three: 1

I expected this one instead:

empty: 0
one: 1
two: 2
three: 3

Why does count($seq) always return 1 in this case? How would you change variable definition, so that I can later test it for emptiness? (Simple <xsl:variable name='seq' select='*' /> would return expected answer, but is not an option ... I want to change between variable in this template, and test it for emptiness later).


Solution

  • Let me try to answer the "why" part of your question.

    If you write the following statement:

    <xsl:variable name="x" select="*" />
    

    the variable $x contains the sequence of the child nodes of the current node. There's no implicit parent node in $x, because you use select. Now consider the following:

    <xsl:variable name="x">
        <content />
        <content />
    </xsl:variable>
    

    where variable $x contains a sequence of one node: the parent node of content. Here, count($x) will always give you 1. To get the amount of content elements, you need to count the children of the $x implicit root node: count($x/content).

    As a rule of thumb, you can remember that: if xsl:variable is itself an empty element with a select attribute, the result set will be assigned to the variable. If you use xsl:variable without the select attribute, you always create an implicit parent with the variable, and the contents of the variable are the children underneath it.

    The same applies for your example with xsl:sequence as a child to xsl:variable. By having a non-empty xsl:variable you create an implicit parent for the sequence, which is why you always get 1 if you count the variable itself.

    If you need both: a bundle of statements inside xsl:variable, but the behavior of the select attribute, you can workaround this by using the following:

    <xsl:variable name="x" select="$y/*" />
    
    <xsl:variable name="y">
        <xsl:sequence select="foo" />
    </xsl:variable>
    

    which will now yield the expected amount for count($x), but whether or not that's really beneficial or makes your code clearer is arguable.