Re: [xsl] MSXML ancestor-or-self

Subject: Re: [xsl] MSXML ancestor-or-self
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Wed, 7 Mar 2001 19:12:01 +0000
Hi Richard,

> Ok my example is a bit poor sorry for that but really my context is
> C with a file like...
> <A>
>         <P name="hello" value="blue"/>
>         <P name="aval" value="uppest"/>
>         <B>
>                 <P name="bval" value="upper"/>
>                 <P name="goodbye" value="red"/>
>                 <P name="hello" value="green"/>
>                 <C>
>                         <P name="goodbye" value="yellow"/>
>                 </C>
>                 <P name="goodbye" value="orange"/>
>         </B>
> </A>
> Expecting output like...
> ;goodbye=yellow;hello=green;bval=upper;hello=uppest

I think that you want (a) the children of the current node and (b) the
preceding-siblings of any ancestors of the current node (or the
current node itself).  So that gives you:

  P | ancestor-or-self::*/preceding-sibling::P

Now, from that set of nodes, you want the final P element with a
particular 'name' attribute. So you want to cycle over those P
elements (in reverse document order) and only output information for
those P elements for which there's no following P element with the
same name attribute that's also in the list:

   <xsl:variable name="Ps"
                 select="P | ancestor-or-self::*/preceding-sibling::P" />
   <xsl:for-each select="$Ps">
      <xsl:sort select="position()" data-type="number"
                order="descending" />
      <xsl:if test="not(following::P
                          [@name = current()/@name and
                           count(.|$Ps) = count($Ps)]">
         <xsl:text />;<xsl:value-of select="@name" />
         <xsl:text />=<xsl:value-of select="@value" />

You could also do it with a key, which may be a little more efficient
and would allow you to number the P elements if you wanted to.  Define
a key to easily access the P elements by name:

<xsl:key name="Ps" match="P" use="@name" />

Give a particular name, the key will produce all the P elements in the
document with that name.  To find the one that you want, you need to
filter it according to those P elements that are in your $Ps list:

  key('Ps', $name)[count(.|$Ps) = count($Ps)]

and then take the last of those in document order:

  key('Ps', $name)[count(.|$Ps) = count($Ps)][last()]

So to get the P elements that are the nearest, you need the hideous

  $Ps[count(.|key('Ps', @name)[count(.|$Ps) = count($Ps)][last()]) = 1]

You can use this expression as the path for the xsl:for-each:

         select="$Ps[count(.|key('Ps', @name)
                              [count(.|$Ps) = count($Ps)][last()])
                     = 1]">
      <xsl:sort select="position()" data-type="number"
                order="descending" />
      <xsl:text />;<xsl:value-of select="@name" />
      <xsl:text />=<xsl:value-of select="@value" />

I hope that helps,


Jeni Tennison

 XSL-List info and archive:

Current Thread