Parsing html with the HTML Agility Pack and Linq

Timo Willemsen picture Timo Willemsen · Jan 6, 2011 · Viewed 21.6k times · Source

I have the following HTML

(..)
<tbody>
 <tr>
  <td class="name"> Test1 </td>
  <td class="data"> Data </td>
  <td class="data2"> Data 2 </td>
 </tr>
 <tr>
  <td class="name"> Test2 </td>
  <td class="data"> Data2 </td>
  <td class="data2"> Data 2 </td>
 </tr>
</tbody>
(..)

The information I have is the name => so "Test1" & "Test2". What I want to know is how can I get the data that's in "data" and "data2" based on the Name I have.

Currently I'm using:

var data =
    from
        tr in doc.DocumentNode.Descendants("tr")
    from   
        td in tr.ChildNodes.Where(x => x.Attributes["class"].Value == "name")
    where
        td.InnerText == "Test1"
    select tr;

But I get {"Object reference not set to an instance of an object."} when I try to look in data

Answer

Kobi picture Kobi · Jan 6, 2011

As for your attempt, you have two issues with your code:

  1. ChildNodes is weird - it also returns whitespace text nodes, which don't have a class attributes (can't have attributes, of course).
  2. As James Walford commented, the spaces around the text are significant, you probably want to trim them.

With these two corrections, the following works:

var data =
      from tr in doc.DocumentNode.Descendants("tr")
      from td in tr.Descendants("td").Where(x => x.Attributes["class"].Value == "name")
     where td.InnerText.Trim() == "Test1"
    select tr;