I have the following html snippet:
<table>
<tr>
<td class="foo">a</td>
<td class="bar">1</td>
<td class="bar">2</td>
<td class="foo">b</td>
<td class="bar">3</td>
<td class="bar">4</td>
<td class="bar">5</td>
<td class="foo">c</td>
<td class="bar">6</td>
<td class="bar">7</td>
</tr>
</table>
I'm looking for a XPath 1.0 expression that starts at a .foo
element and selects all following .bar
elements before the next .foo
element.
For example: I start at a
and want to select only 1
and 2
.
Or I start at b
and want to select 3
, 4
and 5
.
Background: I have to find an XPath expression for this method (using Java and Selenium):
public List<WebElement> bar(WebElement foo) {
return foo.findElements(By.xpath("./following-sibling::td[@class='bar']..."));
}
Is there a way to solve the problem?
The expression should work for all .foo
elements without using any external variables.
Thanks for your help!
Update: There is apparently no solution for these special circumstances. But if you have fewer limitations, the provided expressions work perfectly.
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:
/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
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With