2

Imagine I am given a table like this:

<table>
  <tr><td>A</td></tr>
  <tr><td><a href="#">B</a></td></tr>
  <tr><td><a href="#">C</a></td></tr>
  <tr><td>D</td></tr>
  <tr><td><a href="#">E</a></td></tr>
  <tr><td>B</td></tr>
</table>

I'd like to construct a CSS selector (preferred) or an XPath (accepted) that picks out the n'th row that contains an a anchor, such that:

selector(1) => <a href="#">B</a>
selector(2) => <a href="#">C</a>
selector(3) => <a href="#">E</a>

CSS selectors

At this point, I'm pretty sure that CSS won't do the job, but

'table tr:nth-child(' + n + ')'

will pick out the n'th row, but that selects rows whether or not they have an a anchor. Similarly,

'table tr:nth-child(' + n + ') a'

will pick out rows with an a anchor, but only if n is 2, 3 or 5.

XPath

With XPath, this matches all the tr that have an a

`//table//tr//a/ancestor::tr`

but I can't figure out how to select the n'th match. In particular,

`//table//tr//a/ancestor::tr[position() = 2]`

doesn't appear to select anything.

fearless_fool
  • 29,889
  • 20
  • 114
  • 193

3 Answers3

2

You can't do this with a CSS selector1 for a number of reasons:

Your XPath is incorrect because a/ancestor::tr[position() = 2] returns the second tr ancestor of the a element. That is, the [position() = 2] predicate is connected to the ancestor:: axis. This XPath would match the middle-level tr in the following HTML:

<table>
  <tr><td><table>
    <tr><td><table>
      <tr><td><a href="#"></a>
    </table>
  </table>
</table>

In your HTML, each a element has only one tr ancestor, so this will not select anything.

The XPath you should use is:

(//table//tr[descendant::a])[2]

This matches the second tr element that contains an a descendant.


1 In Selectors 4, a potential solution would be table tr:nth-match(2 of :has(a)).

Community
  • 1
  • 1
BoltClock
  • 630,065
  • 150
  • 1,295
  • 1,284
  • Bingo. Good explanation as well. Strictly speaking I suppose the real answer is `(//table//tr[descendant::a])[2]//a` to pick out the `a` elements. – fearless_fool May 27 '15 at 20:22
1

If I understand you correctly, you can find the nth td which has an <a href like so (you want C to be the 2nd match?):

(/table//tr/td[a[@href]])[2]

If you can't guarantee a td element, you can wild card the path and elements:

(/table//tr//*[a[@href]])[2]
StuartLC
  • 96,413
  • 17
  • 181
  • 256
  • Ah! This works as well. I didn't previously know that you could use parentheses in XPath for -- essentially -- operator precedence. This approach feels more straightforward than using descendant:: – fearless_fool May 27 '15 at 20:30
  • I guess one thing to point out is that [// is short for /descendant-or-self::node()](http://www.w3.org/TR/xpath/). :) – StuartLC May 27 '15 at 20:38
0

Answers from @BoltClock and @StuartLC both work. But now that I know parentheses in XPath can control operator precedence, a more straightforward solution seems to be:

(//table//tr//a)[2]

Am I missing something?

fearless_fool
  • 29,889
  • 20
  • 114
  • 193