Question

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).

Was it helpful?

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.

OTHER TIPS

It works as you might expect if you either change the variable declaration to:

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

or declare the type of the variable using 'as' attribute:

<xsl:variable name="seq" as="item()*">
        <xsl:sequence select="*" />
</xsl:variable>

Not specifying any type information often leeds to surprising results in XSLT 2.0. If you are using Saxon, you can output how Saxon interprets the stylesheet using the explain extension attribute:

    <xsl:template match="sub" saxon:explain="yes" xmlns:saxon="http://saxon.sf.net/">
    <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>

As you can see, Saxon constructs a document-node out of the sequence:

Optimized expression tree for template at line 6 in :
                    let $seq[refCount=1] as document-node() :=
                      document-constructor
                        child::element()
                    return
                      message

You are selecting the sub nodes, and then counting each node - so it'll always be 1. You need to count the children, for example:

<xsl:value-of select="count($seq/*)" />

will give you the output you're expecting.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top