Robert Harvey
Robert Harvey

Reputation: 180808

How do I get my XPath to search only within each table?

I have a bit of HTML that looks like this:

<table class="resultsTable">
    <tbody>
        <tr class="even">
            <td width="35%"><strong>Name</strong></td>
            <td>ACME ANVILS, INC</td>
        </tr>
    </tbody>
</table>

and some C# code that looks like this:

var name = document.DocumentNode
                   .SelectSingleNode("//*[text()='Name']/following::td").InnerText

which happily returns

ACME ANVILS, INC.

However, there's a new wrinkle. The page in question now returns multiple results:

<table class="resultsTable">
    <tbody>
        <tr class="even">
            <td width="35%"><strong>Name</strong></td>
            <td>ACME ANVILS, INC.</td>
        </tr>
    </tbody>
</table>
<table class="resultsTable">
    <tbody>
        <tr class="even">
            <td width="35%"><strong>Name</strong></td>
            <td>ROAD RUNNER RACES, LLC</td>
        </tr>
    </tbody>
</table>

So now I'm working with

var tables = document.DocumentNode.SelectNodes("//table/tbody");
foreach (var table in tables)
{
    var name = table.SelectSingleNode("//*[text()='Name']/following::td").InnerText;
    ...
}

Which falls over, because SelectSingleNode returns null.

How do I get my XPath to actually return a result, searching only within the specific table I have selected?

Upvotes: 1

Views: 262

Answers (1)

kjhughes
kjhughes

Reputation: 111630

With the addition of a second table, two adjustments are required:

  1. Change your absolute XPath,

    //*[text()='Name']/following::td
    

    to one relative to the current table or tbody element:

    .//*[text()='Name']/following::td
    
  2. Account for there now being more than one td element on the following:: axis.

    Either just grab the first,

    (.//*[text()='Name']/following::td)[1]
    

    or, better, use the following-sibling:: axis instead in combination with a test on the string value of td rather than a test on a text node, which might be buried beneath intervening formatting elements:

     .//td[.='Name']/following-sibling::td
    

    See also Difference between Testing text() nodes vs string values in XPath.

Upvotes: 2

Related Questions