1

I have the following HTML:

<table>
  <tbody>
    <tr class="Menu indent0 picker-parent open"></tr>
    <tr class="divider MenuGroup"></tr>
    <tr class="MenuGroup closed indent1 picker-parent"></tr>
    <tr class="MenuGroup indent1 picker-parent open"></tr>
    <tr class="divider MenuGroup"></tr>
    ---><tr class="MenuItem indent2 picker-parent"></tr>
    ---><tr class="MenuItem indent2 picker-parent"></tr>
    ---><tr class="MenuItem closed indent2 picker-parent"></tr>
    ---><tr class="MenuItem closed indent2 picker-parent"></tr>
    <tr class="divider MenuGroup"></tr>
  </tbody>
</table>

I need to select all tr tags with the class 'MenuItem' between the 2nd and third tr tags with the class 'divider'.

I understand generally how to select tags by class with the following generic syntax from this question:

//*[contains(concat(" ", normalize-space(@class), " "), " foo ")]

But I'm unsure how to combine it with the syntax provided in this answer.

Ex.: select all '''p''' tags that have only one preceding sibling '''divider''' tag)

/*/p[count(preceding-sibling::divider)=1]

My attempt so far is invalid:

//*/tbody/tr[count(preceding-sibling::[contains(concat(" ", normalize-space(@class), " "), " divider")])=2]

  • Confused with this statement- "I need to select all tr tags with the class 'MenuItem' between the 2nd and third tr tags with the class 'divider'." There are no tr tag with the class MenuItem between the 2nd and third tr tags with the class 'divider'. Did you miss something? By the way to check what is missing and wrong in your XPath, you can use SelectotsHub (a browser plugin) https://chrome.google.com/webstore/detail/selectorshub/ndgimibanhlabgdgjcpbbndiehljcpfh/ – SelectorsHub ChroPath Creator Nov 21 '20 at 18:55
  • You need to tell us which version of XPath. This kind of thing is much easier with XPath 2.0+ – Michael Kay Nov 21 '20 at 19:02
  • @SelectorsHubChroPathCreator do the tr tags I highlighted with arrows not fit that exact description? – devintraining Nov 21 '20 at 19:04
  • @MichaelKay I am variably using either an XPATH extension on Chrome or the selenium package for python 3.9. How do I know which version of XPATH each is utilizing? – devintraining Nov 21 '20 at 19:05
  • If your product doesn't tell you which version of XPath, then it's probably XPath 1.0, because that used to be the only version there was, and they would shout about it if they supported anything more recent. – Michael Kay Nov 21 '20 at 19:28

3 Answers3

1

Let's start with XPath 3.1

I need to select all tr tags with the class 'MenuItem' between the 2nd and third tr tags with the class 'divider'.

That's

/table/tbody/ (
   let $dividers := tr[contains-token(@class, 'divider')] 
   return tr[contains-token(@class, 'MenuItem'][. > > $dividers[2] 
             and . < < $dividers[3]])

(I've had to put a space into the operators < < and > > because they get turned into chevrons if I type them without the space).

In XPath 2.0 it's a bit trickier because (a) you don't have the contains-token() function, and (b) there's no "let". To cope with the lack of contains-token you have to use the horrible contains(concat(" ", normalize-space(@class), " "), " divider") as you've done in your example. Coping with the lack of let is more difficult, but you do have for, so you can do:

/table/tbody/ (
   for $start := tr[contains-token(@class, 'divider')][2],
       $end := tr[contains-token(@class, 'divider')][3]
   return tr[contains-token(@class, 'MenuItem'][. > > $start 
             and . < < $end])

(but expanding contains-token as described above)

Now what about XPath 1.0. This gets tricky. Basically you have to find the nodes that are following siblings of the start node, provided they are also preceding siblings of the end node. Conceptually that's ($start/following-sibling::tr[...]) intersect $end/preceding-sibling[...] but there's no intersect operator in 1.0 so you have to use the equivalence that A intersect B can be written A[count(.|B)=count(B)]. I'll leave you to work out the gory detail (or upgrade to a later XPath version...)

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
0

Try:

//table//tr[@class="divider MenuGroup"]//following-sibling::tr[contains(@class,"MenuItem")]
Jack Fleeting
  • 24,385
  • 6
  • 23
  • 45
  • MIght work in this particular case but it doesn't match the stated requirement. – Michael Kay Nov 21 '20 at 19:03
  • This is the solution that ended up working for me. Thanks a lot. – devintraining Nov 22 '20 at 20:26
  • What if I had a few more tr's of class MenuItem after the 2nd divider but I still only wanted to select those first 4 between the two dividers? I've done a lot of experimenting but haven't figured out how to cut off the selection. – devintraining Nov 22 '20 at 23:34
  • @devintraining It would depend on the actual structure of the html. You should probably post it as a separate question. – Jack Fleeting Nov 22 '20 at 23:44
0

Please try this-

//tr[@class='MenuGroup']//following-sibling::tr[@class="divider MenuGroup"]//following-sibling::tr[contains(@class,"MenuItem")]
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343