tags:

views:

543

answers:

3

I have a document, something like this:

<root>
   <A node="1"/>
   <B node="2"/>
   <A node="3"/>
   <A node="4"/>
   <B node="5"/>
   <B node="6"/>
   <A node="7"/>
   <A node="8"/>
   <B node="9"/>
</root>

Using xpath, How can I select all B elements that consecutively follow a given A element?

It's something like following-silbing::B, except I want them to be only the immediately following elements.

If I am on A (node==1), then I want to select node 2. If I am on A (node==3), then I want to select nothing. If I am on A (node==4), then I want to select 5 and 6.

Can I do this in xpath? EDIT: It is within an XSL stylesheet select statement.


EDIT2: I don't want to use the node attribute on the various elements as a unique identifier. I included the node attribute only for purposes of illustrating my point. In the actual XML doc, I don't have an attribute that I use as a unique identifer. The xpath "following-sibling::UL[preceding-sibling::LI[1]/@node = current()/@node]" keys on the node attribute, and that's not what I want.

+3  A: 

Short answer (assuming current() is ok, since this is tagged xslt):

following-sibling::B[preceding-sibling::A[1]/@node = current()/@node]

Example stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output method="xml"/>
    <xsl:template match="/">
        <xsl:apply-templates select="/root/A"/>
    </xsl:template>

    <xsl:template match="A">
        <div>A: <xsl:value-of select="@node"/></div>
        <xsl:apply-templates select="following-sibling::B[preceding-sibling::A[1]/@node = current()/@node]"/>
    </xsl:template>

    <xsl:template match="B">
        <div>B: <xsl:value-of select="@node"/></div>
    </xsl:template>
</xsl:stylesheet>

Good luck!

Chris Nielsen
Oh, thanks, this is perfect!
Cheeso
A: 

A solution might be to first gather up all the following nodes using following-sibling::*, grab the first of these and require it to be a 'B' node.

following-sibling::*[position()=1][name()='B']
Søren Løvborg
Oh, this is a good idea. This I can understand. Thank you.
Cheeso
+2  A: 

While @Chris Nielsen's answer is the right approach, it leaves an uncertainty in cases where the compared attribute is not unique. The more correct way of solving this is:

following-sibling::B[
  generate-id(preceding-sibling::A[1]) = generate-id(current())
]

This makes sure that the preceding-sibling::A is identical to the current A, instead of just comparing some attribute values. Unless you have attributes that are guaranteed to be unique, this is the only safe way.

Tomalak
+1; I was going to say following-sibling::B[count(preceding-sibling::A[1] | current()) = 1], but your way seems more understandable.
Chris Nielsen
IMHO, the `count(...)` method to determine node identity is semantically inferior to the `generate-id()` method, but occasionally I use it as well. Depends a bit on the context, but in general I prefer `generate-id()` for being more explicit.
Tomalak