views:

230

answers:

3

I don't understand output from this stylesheet:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <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).

+1  A: 

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.

FinnNk
Thank you for answer. I am not sure I understand though... what exactly is stored in my `seq` variable then (e.g. when <sub id="two"> is being processed from samle input). I thought it is sequence of two `one` elements.
Peter Štibraný
+3  A: 

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.

Abel
Thank you very much! For the reference, here is link to relevant part of documentation (if I understand correctly): http://www.w3.org/TR/xslt20/#temporary-trees
Peter Štibraný
Yes, you do understand it correctly. That part of the spec shows that you implicitly have an `xsl:document` instruction. It also points at something else I didn't mention: you can use the `as` attribute if you need a specific output type. The sequence inside `xsl:variable` must be convertible to this type or an error occurs.
Abel
+1  A: 

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/"&gt;
    <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
xixxix
Thank you for `item()*` and `explain` hints (yes, I use saxon).
Peter Štibraný