12

I have the following example document:

<root>
  <p class="b">A</p>
  <p class="b">B</p>
  <p class="a">C</p>
  <p class="a">D</p>
  <p class="b">E</p>
  <x>
    <p class="b">F</p>
  </x>
</root>

I am looking for an xpath expression which selects all direct siblings of a given node with matching class attributes, not any sibling. In above example, the first two <p class="b"> A-B should be selected; likewise the two <p class="a"> C-D, likewise the fifth single <p class="b"> E as it has no direct siblings; likewise the single <p class="b"> F inside of <x>. Note that in this context B and C are not direct siblings because they have different class attribute valued!

What I have is this:

xml.xpath("//p") # This selects all six <p> elements.
xml.xpath("//p[@class='b']") # This selects all four <p class="b"> elements.
xml.xpath("//p/following-sibling::p[@class='b']") # This selects all <p class="b"> sibling elements, even though not direct siblings.

The last expression selects the fifth sibling as well, although there are non-matching siblings inbetween.

How do I select only direct siblings with the same class value?

Edit To clarify: note how the last two are individual selections, not siblings!

Edit I have saved an example here. The Xpath expression based on /root/p[1] is supposed to select A, B, C, D.

Jens
  • 8,423
  • 9
  • 58
  • 78

2 Answers2

20

To get the very next sibling, you can add the position - 1 meaning right beside.

following-sibling::*[1]

To ensure that the next sibling is of a specific node type, you can add the following filter, where p is the node type we want to match.

[self::p]

If you only want ones with the same attribute, you would also need to specify the attribute on the first p element.

So if you just want class b p elements that are immediately after a class b p element, you can do the following. This would just give you the second p element.

//p[@class='b']/following-sibling::*[1][@class='b'][self:p]

It sounds like you might actually want any class b element which is adjacent to another class b element. In that case, you can check the following and preceding sibling. The following would give you the first 2 p elements.

//p[@class='b'][following-sibling::*[1][@class='b'][self::p] 
                or preceding-sibling::*[1][@class='b'][self::p]]    
Justin Ko
  • 46,526
  • 5
  • 91
  • 101
  • Almost there, yes :) Now can I extend the immediate following/preceeding to *any* number, i.e. any directly immediate sibling of `

    ` ?

    – Jens Oct 18 '13 at 22:05
  • Can you give an example? Do you mean if there are three in a row then all three should get selected? This should already do that. – Justin Ko Oct 19 '13 at 02:14
  • Yes, if there were N `` elements in a row, I'd want to select all N of them. However, they all need to be `` nor just any of `class="b"`. I just added a link to an example where you can play around, see the 2nd edit of the question above. – Jens Oct 19 '13 at 05:52
  • I updated the answer to check that the adjacent sibling is of the specific type. For example `

    ` will not return anything (since the second class b is not of the same type). However, I am not sure why your new example would only return A/B/C/D. G/H/I and K/L are also groups of adjacent p elements with class b (just different groups). Are you also saying you only want the first group?
    – Justin Ko Oct 19 '13 at 11:48
  • Correct, that's what I mean with *direct* siblings, what you refer to as a group :-) D and G are not direct siblings because there are other siblings (the two `

    ` elements) separating the two, i.e. they are of different groups.

    – Jens Oct 19 '13 at 12:03
  • I agree that D and G are not direct siblings. But G and H are also direct siblings that match the criteria. I do not know off the top of my head how you would get the one group but not the other. I will have to think about it. – Justin Ko Oct 19 '13 at 12:15
  • Oh... oh I see what you're saying! The Xpath expression would select *all* groups into one selection? Didn't think of that. – Jens Oct 19 '13 at 16:28
1

How about something like this:

//p[@class='b']/following-sibling::p[following-sibling::p[@class='a'] and @class='b']

It returns all following siblings that are @class='b' and them self have following siblings with @class='a'. Though it would not work for last <p> as it does not have following siblings.

sinhayash
  • 2,693
  • 4
  • 19
  • 51
rokras
  • 11
  • 1