Good question!
The following expression will give you 1..2
, 3..5
or 6..7
, depending on input X + 1
, where X
is the set you want (2 gives 1-2, 3 gives 3-.5 etc). In the example, I select the third set, hence it has [4]
:
/table/tr[1]
/td[not(@class = 'foo')]
[
generate-id(../td[@class='foo'][4])
= generate-id(
preceding-sibling::td[@class='foo'][1]
/following-sibling::td[@class='foo'][1])
]
The beauty of this expression (imnsho) is that you can index by the given set (as opposed to index by relative position) and that is has only one place where you need to update the expression. If you want the sixth set, just type [7]
.
This expression works for any situation where you have siblings where you need the siblings between any two nodes of the same requirement (@class = 'foo'
). I'll update with an explanation.
Replace the [4]
in the expression with whatever set you need, plus 1. In oXygen, the above expression shows me the following selection:

Explanation
/table/tr[1]
Selects the first tr
.
/td[not(@class = 'foo')]
Selects any td
not foo
generate-id(../td[@class='foo'][4])
Gets the identity of the xth foo
, in this case, this selects empty, and returns empty. In all other cases, it will return the identity of the next foo
that we are interested in.
generate-id(
preceding-sibling::td[@class='foo'][1]
/following-sibling::td[@class='foo'][1])
Gets the identity of the first previous foo
(counting backward from any non-foo element) and from there, the first following foo
. In the case of node 7
, this returns the identity of nothingness, resulting in true
for our example case of [4]
. In the case of node 3
, this will result in c
, which is not equal to nothingness, resulting in false
.
If the example would have value [2]
, this last bit would return node b
for nodes 1
and 2
, which is equal to the identity of ../td[@class='foo'][2]
, returning true
. For nodes 4
and 7
etc, this will return false
.
Update, alternative #1
We can replace the generate-id
function with a count-preceding-sibling function. Since the count of the siblings before the two foo
nodes is different for each, this works as an alternative for generate-id
.
By now it starts to grow just as wieldy as GSerg's answer, though:
/table/tr[1]
/td[not(@class = 'foo')]
[
count(../td[@class='foo'][4]/preceding-sibling::*)
= count(
preceding-sibling::td[@class='foo'][1]
/following-sibling::td[@class='foo'][1]/preceding-sibling::*)
]
The same "indexing" method applies. Where I write [4]
above, replace it with the nth + 1 of the intersection position you are interested in.