Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Html Agility Pack - Problem selecting subnode

I want to export my Asics running plan to iCal and since Asics do not offer this service, I decided to build a little scraper for my own personal use. What I want to do is to take all the scheduled runs from my plan and generate an iCal feed based on that. I am using C# and Html Agility Pack.

What I want to do is iterate through all my scheduled runs (they are div nodes). Then next I want to select a few different nodes with my run nodes. My code looks like this:

foreach (var run in doc.DocumentNode.SelectSingleNode("//div[@id='scheduleTable']").SelectNodes("//div[@class='pTdBox']"))
{
    number++;
    string date = run.SelectSingleNode("//div[@class='date']").InnerText;
    string type = run.SelectSingleNode("//span[@class='menu']").InnerHtml;
    string distance = run.SelectSingleNode("//span[@class='distance']").InnerHtml;
    string description = run.SelectSingleNode("//div[@class='description']").InnerHtml;
    ViewData["result"] += "Dato: " + date + "<br />";
    ViewData["result"] += "Tyep: " + type + "<br />";
    ViewData["result"] += "Distance: " + distance + "<br />";
    ViewData["result"] += "Description: " + description + "<br />";
    ViewData["result"] += run.InnerHtml.Replace("<", "&lt;").Replace(">", "&gt;") + "<br />" + "<br />" + "<br />";
}

My problem is that run.SelectSingleNode("//div[@class='date']").InnerText does not select the node with the given XPath within the given run node. It selects the first node that matches the XPath in the entire document.

How can I select the single node with the given XPath within the current node?

Thank you.

Update

I tried updating my XPath string to this:

string date = run.SelectSingleNode(".div[@class='date']").InnerText;

This should select the <div class="date"></div> element within the current node, right? Well, I tried this but got this error:

Expression must evaluate to a node-set. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Xml.XPath.XPathException: Expression must evaluate to a node-set.

Any suggestions?

like image 325
Sebastian Brandes Kraaijenzank Avatar asked May 30 '11 21:05

Sebastian Brandes Kraaijenzank


2 Answers

A few things that will help you when working with HtmlAgilityPack and XPath expressions.

If run is an HtmlNode, then:

  1. run.SelectNodes("//div[@class='date']")
    Will will behave exactly like doc.DocumentNode.SelectNodes("//div[@class='date']")

  2. run.SelectNodes("./div[@class='date']")
    Will give you all the <div> nodes that are children of run node. It won't search deeper, only at the very next depth level.

  3. run.SelectNodes(".//div[@class='date']")
    Will return all the <div> nodes with that class attribute, but not only next to the run node, but also will search in depth (every possible descendant of it)

You will have to choose between 2. or 3., depending on which one satisfy your needs :)

like image 152
Oscar Mederos Avatar answered Oct 18 '22 20:10

Oscar Mederos


In XPATH, // means all children and grand children below the current node. So you need to come up with a more restrictive XPATH expression. If you provide the real HTML, and what you're looking for exactly, we can help you dig further.

About the error you have:

.div[@class='date'] is invalid because . is sticked to div. You could use div[@class='date'], or ./div[@class='date'] which I believe are equivalent. This is because . is an XPATH axe, which is an alias for self and means "the current node".

like image 32
Simon Mourier Avatar answered Oct 18 '22 18:10

Simon Mourier