Reputation: 2530
Let's say I'm querying an xhtml document, and I want to query all of the siblings following a table with id='target'
. Also, I neither want the first <table>
sibling nor the first <ol>
sibling of this particular element. Here's the best I could come up with:
//table[@id='target']/following-sibling::*[not(self::table[1]) and not(self::ol[1])]
However, this isn't returning any results when it should. Obviously I'm not understanding some of the syntax for this (I couldn't find a good source of information). I would certainly appreciate it if someone more experienced with XPath syntax could give me a hand. Also, for purely academic purposes, I'd be curious what the above is actually doing.
UPDATE:
See LarsH's answer for the explanation of why my XPath wasn't working, and see Dimitre's answer for the accepted solution.
Upvotes: 10
Views: 19659
Reputation: 27996
Answering the second question first: what the above is doing is selecting all following siblings that are neither table
nor ol
elements.
Here's why: self::table[1]
selects the context node's self (iff it passes the table
element name test) and filters to select only the first node along the self:: axis. There is at most one node on the self:: axis passing the element name test, so the [1]
is redundant. self::table[1]
selects the context node whenever it is a table element, regardless of its position among its siblings. So not(self::table[1])
returns false whenever the context node is a table element, regardless of its position among siblings.
Similarly for self::ol[1]
.
How to do what you're trying to do:
@John Kugelman's answer is almost correct, but misses the fact that we must ignore sibling elements before and including table[@id='target']
. I don't think it's possible to do correctly in pure XPath 1.0. Do you have the possibility to use XPath 2.0? If you're working in a browser, the answer is generally no.
Some workarounds would be:
//table[@id='target']
as a nodeset, return it to the host environment (i.e. outside of XPath, e.g. in JavaScript), loop through that nodeset; inside the loop: select following-sibling::*
via XPath, iterate through that outside of XPath, and test each result (outside of XPath) to see if it is the first table or ol.//table[@id='target']
as a nodeset, return it to the host environment (i.e. outside of XPath, e.g. in JavaScript), loop through that nodeset; inside the loop: select generate-id(following-sibling::table[1])
and generate-id(following-sibling::ol[1])
via XPath, receive those values into JS variables t1id and o1id, and construct a string for the XPath expression using the form 'following-sibling::*[generate-id() != ' + t1id + ' and generate-id() != ' + o1id + ']'
. Select that string in XPath and you have your answer! :-pUpdate: A solution is possible in XSLT 1.0 - see @Dimitre's.
Upvotes: 2
Reputation: 243459
Use:
/table[@id='target']/following-sibling::*[not(self::table) and not(self::ol)]
|
/table[@id='target']/following-sibling::table[position() > 1]
|
/table[@id='target']/following-sibling::ol[position() > 1]
This selects all the following siblings of the table that are not table
and are not ol
and all the following table
siblings with position 2 or greater and all the following ol
siblings with position 2 or greater.
Which is exactly what you want: all following siblings with the exception of the first table
following sibling and the first ol
following siblings.
This is pure XPath 1.0 and not using any XSLT functions.
Upvotes: 15
Reputation: 361547
There's only going to be one node when you use the self::
axis, so I believe self::*[1]
will always be true. Every node is going to be the first (and only) node on its own self::
axis. This means your bracketed expression is equivalent to [not(self::table) and not(self::ol)]
, meaning all the tables and lists get filtered out.
I don't have a test environment set up, but off the top of my head this might do better:
/table[@id='target']/following-sibling::*
[not(self::table and not(preceding-sibling::table)) and
not(self::ol and not(preceding-sibling::ol))]
It'll need some tweaking, but the idea is to filter out table
s that do not have preceding-sibling table
s, and ol
s that do not have preceding-sibling ol
s.
Upvotes: 1