Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XPath: Select following siblings until certain class

Tags:

xpath

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.

like image 819
Jonas Staudenmeir Avatar asked Sep 13 '15 21:09

Jonas Staudenmeir


1 Answers

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:

enter image description here

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.

like image 132
Abel Avatar answered Sep 30 '22 19:09

Abel