使用Html Agility Pack以上下文敏感的方式解析节点

时间:2011-04-10 01:24:30

标签: html-agility-pack

<div class="mvb"><b>Date 1</b></div>
<div class="mxb"><b>Header 1</b></div>
<div>
   inner hmtl 1
</div>

<div class="mvb"><b>Date 2</b></div>
<div class="mxb"><b>Header 2</b></div>
<div>
inner html 2
</div>

我想以这样的方式解析标签之间的内部html

    *将内部html 1与标题1和日期1
相关联
    *将内部html 2与标题2和日期2
相关联

换句话说,在我解析内部html 1时,我想知道包含“Date 1”和“Header 1”的html节点已被解析(但是包含“Date 2”和“Header”的节点2“尚未解析”

如果我通过常规文本解析执行此操作,我会一次读取一行并记录最后一个“日期”和“标题”,而不是我解析过的。然后,当解析内部html 1时,我可以引用最后解析的“Date”和“Header”对象将它们关联在一起。

2 个答案:

答案 0 :(得分:1)

使用Html Agility Pack,你可以利用XPATH的力量 - 而忘记那个冗长的xlinq废话:-)。 XPATH position()函数是上下文敏感的。以下是示例代码:

    HtmlDocument doc = new HtmlDocument();
    doc.Load("your html file");

    // select all DIV without a CLASS attribute defined
    foreach (HtmlNode div in doc.DocumentNode.SelectNodes("//div[not(@class)]"))
    {
        Console.WriteLine("div=" + div.InnerText.Trim());
        Console.WriteLine("  header=" + div.SelectSingleNode("preceding-sibling::div[position()=1]/b").InnerText);
        Console.WriteLine("  date=" + div.SelectSingleNode("preceding-sibling::div[position()=2]/b").InnerText);
    }

这将用你的样本打印出来:

div=inner hmtl 1
  header=Header 1
  date=Date 1
div=inner html 2
  header=Header 2
  date=Date 2

答案 1 :(得分:0)

嗯,你可以用几种方式做到这一点......

例如,如果要解析的HTML是您在问题中编写的HTML,则可以采用以下方式:

  1. 将所有日期存储在HtmlNodeCollection
  2. 将所有标头存储在HtmlNodeCollection
  3. 将所有内部文本存储在另一个HtmlNodeCollection
  4. 如果一切正常并且HTML具有该布局,则3个集合中的元素数量将相同。

    然后你可以很容易地做到:

    for (int i = 0; i < innerTexts.Count; i++) {
        //Get Date, Headers and Inner Texts at position i
    }
    

    以下内容应该有效:

    var document = new HtmlWeb().Load("http://www.url.com"); //Or load it from a Stream, local file, etc.
    
    var dateNodes = document.DocumentNode.SelectNodes("//div[@class='mvb']/b");
    var headerNodes = document.DocumentNode.SelectNodes("//div[@class='mxb']/b");
    
    var innerTextNodes = (from node in document.DocumentNode.SelectNodes("//div")
                            let previous = node.PreviousSibling
                            where previous.Name == "div" && previous.GetAttributeValue("class", "") == "mxb"
                            select node).ToList();
    
    //Check here if the number of elements of the 3 collections are the same
    
    for (int i = 0; i < dateNodes.Count; i++) {
        var date = dateNodes[i].InnerText;
        var header = headerNodes[i].InnerText;
        var innerText = innerTextNodes[i].InnerText;
    
        //Now you have the set you want: You have the Date, Header and Inner Text
    }
    

    这是一种这样做的方式。 当然,您应该检查异常(.SelectNodes(..)方法没有返回null),在存储innerTextNodes时检查LINQ表达式中的错误,并重构for (...),也许进入一个接收HtmlNode的方法并返回它的InnerText属性。

    请记住,在您发布的HTML代码中,您可以知道包含内部文本的<div>标记的唯一方法是假设它是{{旁边的那个) 1}}包含标题的标记。这就是我使用LINQ表达式的原因。

    另一种了解它的方法可能是<div>具有某些特定属性(如<div>)或类似属性,或者其中是否包含一些标记而不仅仅是文本。解析HTML时没有魔力:)

    修改
    我还没有测试过这段代码。亲自测试,让我知道它是否有效。