Re: [xsl] position() from the attribute axis for getting the position of the parent in respect to its siblings

Subject: Re: [xsl] position() from the attribute axis for getting the position of the parent in respect to its siblings
From: "Andrew Welch" <andrew.j.welch@xxxxxxxxx>
Date: Thu, 18 Jan 2007 11:40:37 +0000
On 1/18/07, Abel Braaksma <abel.online@xxxxxxxxx> wrote:
Dear XSLT'ers,

Somehow, I understood that position() retrieves the position of the
context node relative to its siblings, in most cases at least. However,
when at the attribute axis, this number is undefined, because the order
in which attributes are mapped to the internal tree representation of
the input is undefined.

But I am not after the position of the attribute, but I am after the
position of its parent. Below are two ways to do it, however without the
position() function. My question is: Is it not possible to use
position() to get the "position" of the parent node when I am at the
attribute axis? And if it is possible, how can I do it, what am I
overlooking?

The problem appeared while doing a post-processing after some
micro-pipelining. The solution provided suffices my needs, but I am
still wondering why I can't seem to get position() working for me....
Below are several approaches to the problem that I tried so far.

Base template, call it on itself. It changes the 'name' attribute and
replaces the text with the number, the number should equal the position
of the day-node. The template match is done on the attribute axis
(though in this simplified example, other approaches are possible too).

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
    version="2.0">

    <xsl:output indent="yes" />
    <xsl:variable name="week">
        <week>
            <day name="monday" />
            <day name="tuesday" />
            <day name="wednesday" />
        </week>
    </xsl:variable>

    <xsl:template match="/">
        <xsl:apply-templates select="$week/*" />
    </xsl:template>

    <!-- copy -->
    <xsl:template match="node() | @*">
        <xsl:copy>
            <xsl:apply-templates select="node() | @*" />
        </xsl:copy>
    </xsl:template>

    <!-- match attribute @name -->
    <xsl:template match="day/@name">
        <xsl:variable name="pos">
            <xsl:number select=".." />
        </xsl:variable>

        <!-- works -->
        <xsl:attribute name="daynr0" select="$pos" />

        <!-- works -->
        <xsl:attribute name="daynr1"
            select="count(../preceding-sibling::day | ..)" />

        <!-- position on attr axis -->
        <xsl:attribute name="daynr2" select="position()" />

        <!-- position of parent, always 1 -->
        <xsl:attribute name="daynr3" select="../position()" />

        <!-- position of parent opp siblings, always 1 -->
        <xsl:attribute name="daynr4" select="
                ../../day
                [generate-id(@name) = generate-id(current())]
                /position()" />

        <!-- position of parent opp siblings, always 1 (???) -->
        <xsl:attribute name="daynr5" select="
            max(
                for $elem in ../../day
                return
                    if(generate-id($elem/@name)
                       = generate-id(current()))
                    then position()
                    else 0)" />
    </xsl:template>
</xsl:stylesheet>

-------
Output:
-------

<week>
   <day daynr0="1" daynr1="1" daynr2="1"
        daynr3="1" daynr4="1" daynr5="1"/>
   <day daynr0="2" daynr1="2" daynr2="1"
        daynr3="1" daynr4="1" daynr5="1"/>
   <day daynr0="3" daynr1="3" daynr2="1"
        daynr3="1" daynr4="1" daynr5="1"/>
</week>


As you can see, the versions "daynr0" and "daynr1" are working correctly. "daynr2" cannot possibly return something useful. I hoped for "daynr3", but I understand now that the parent axis always is one node, so this will always be one.

Leaves out daynr4, but here the predicate always selects 1 node (namely
the current parent, again) and as such will always return "1" for the
position.

And daynr5, which I consider correct, but also returns "1" all the time.

Is it possible to use position() in a way to get the desired results
(i.e., same as daynr0 and daynr1)? Any thoughts are welcome, of course.

position() is always based on the nodes position within the currently selected node list, not its position within the XML document.

So to get the result you're after you need to build a node list and
then get the position of the node you're after:

<xsl:attribute name="daynr4">
 <xsl:variable name="genID" select="generate-id(parent::*)"/>
 <xsl:for-each select="../../day">
         ^^^^^^^^^^^^^^ This builds the node list
   <xsl:if test="generate-id() = $genID">
     <xsl:value-of select="position()"/>
   </xsl:if>
 </xsl:for-each>
</xsl:attribute>

The above is of course a waste of time, you should just use 1 +
count(parent::*/preceding-sibling::*))

cheers
andrew

Current Thread