Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find all immediately adjacent siblings with XPath

Tags:

xml

xpath

I want to find all the immediately adjacent siblings of a node using a single XPath expression, if at all possible. Given the input

<a id="1"/>
<start/>
<a id="2"/>
<a id="3"/>
<b/>
<a id="4"/>

and an XPath expression similar to //start/following-sibling::a, I want to select a[2], and a[3], but not a[4]. Also, if there are any intervening elements between start and a[2], nothing should be selected.

like image 996
svenax Avatar asked Oct 23 '25 05:10

svenax


2 Answers

The simplest one I can find was this:

//start/following-sibling::a intersect //start/following-sibling::*[name()!='a'][1]/preceding-sibling::a

What this does is:

  1. Take all the a siblings following start: //start/following-sibling::a. (Result: a2, a3, a4.) Set this to one side for now.
  2. Then take the first non-a sibling following start: //start/following-sibling::*[name()!='a'][1] (Result: b.)
  3. And find all the a nodes that precede it: /preceding-sibling::a. (Result: a1, a2, a3)
  4. Take the intersection of 1 and 3. (Result: a2, a3)

Update: Another way to phrase it is //start/following-sibling::*[name()!='a'][1]/preceding-sibling::a[preceding-sibling::start], this roughly translates to: take the first non-a sibling following start, count backwards but only choose elements that are still preceded by start.

Update 2: If you know that b will always be called b, you can of course replace the rather hard to read following-sibling::*[name()!='a'][1] part with following-sibling::b[1].

like image 175
biziclop Avatar answered Oct 25 '25 22:10

biziclop


Following a solution that works with XPath 1.0:

//start[following-sibling::*[1][name() = 'a']]/following-sibling::a[last() = count(preceding-sibling::*[name() != 'a'][1]/following-sibling::a)]
like image 28
drowa Avatar answered Oct 25 '25 21:10

drowa