23

For the example HTML below, an XPath query that returns the siblings of the "a" elements with class='A' that have class='B' can be written as: //a[@class='A']/following-sibling::a[@class='B']. This query outputs 4 <a class="B"/> elements.

However, I would only like the <a class="B"/> elements that follow the current <a class="A"/> element, and no others that follow other <a class="B"/> elements/nodes. In other words, I only want the following <a class="B"/> sibling elements until the next <a class="B"/> element shows up.

Example HTML:

<a class='A' />
<a class='B' />
<a class='A' />
<a class='B' />
<a class='B' />
<a class='B' />

Any ideas on how to limit my current XPath query to just those siblings would be much appreciated :)

user3674248
  • 231
  • 1
  • 2
  • 3
  • Wouldn't `//a[@class='A']/following-sibling::a[@class='B'][1]` do it for you? – Jared Farrish May 25 '14 at 23:25
  • You need to apply the Kayessian method for that. See an example here: http://stackoverflow.com/a/10859928/3190413 – helderdarocha May 26 '14 at 03:37
  • possible duplicate of [XPath : select all following siblings until another sibling](http://stackoverflow.com/questions/2161766/xpath-select-all-following-siblings-until-another-sibling) – nwellnhof Sep 14 '15 at 00:10

3 Answers3

16

To select all a elements having a class attribute of B between some specific a with a class equal to A and the next such occurrence:

/*/a[@class='A'][$n]/following-sibling::a[
    @class='B' and count(preceding-sibling::a[@class='A'])=$n]

This selects everything between the nth a[@class='A'] and the next such element. For a specific example, consider the following input:

<r>
  <a class="A"/>  
  <a class="B"/>  
  <a class="A"/>  
  <a class="B"/>  
  <a class="B"/>  
  <a class="A"/>  
  <a class="B"/>  
  <a class="B"/>  
  <a class="B"/>
</r>

To get the two elements between the second <a class="A"/> and the third <a class="A"/>:

/*/a[@class='A'][2]/following-sibling::a[
    @class='B' and count(preceding-sibling::a[@class='A'])=2]

In English, this says:

Give me all of the a elements having a class attribute whose value is equal to B that come after the second a having a class attribute equal to A and that have only two preceding siblings having a class attribute equal to A

Similarly, and more generally, we can apply the Kayessian method for finding the intersection of two node sets. In the example given, we want the intersection of all the @class='B' elements in 1) the set of siblings after the second <a class="A"/> and 2) the set of siblings before the third <a class="A"/>. The intersection of these two sets is precisely the nodes that come between those two divider elements and can be expressed like this:

/*/a[@class='A'][2]/following-sibling::a[@class='B'][
    count(.|/*/a[@class='A'][3]/preceding-sibling::a[@class='B'])=
    count(/*/a[@class='A'][3]/preceding-sibling::a[@class='B'])]
Wayne
  • 59,728
  • 15
  • 131
  • 126
  • I completely follow the logic of your second xpath query except the /*/ part. With this syntax my identical situation query would not return any nodes, but if I changed the first characters to simple be two slashes // the query would return as expected. Any explanation on that part? – Daniel Oct 14 '15 at 07:50
  • 1
    Four years later I'm running into the same issue and your solution fits perfectly - cheers Wayne! – Sebastian May 16 '18 at 21:02
4

You can try this way :

//a[
    @class='B' 
        and 
    preceding-sibling::a[@class='A'] 
        and 
    following-sibling::a[@class='A']
   ]

Above XPath will select all <a class='B'> element(s) between the two <a class='A'> elements.

har07
  • 88,338
  • 12
  • 84
  • 137
-1

try it this way:

//a[@class='B'][preceding-sibling::*[1][name()='a'][@class='A']]
Joel M. Lamsen
  • 7,143
  • 1
  • 12
  • 14