HTML敏捷包空引用

14

我在使用HTML Agility Pack时遇到了一些问题。

当我在不包含特定节点的HTML上使用此方法时,会出现空引用异常。它起初是有效的,但后来停止工作了。这只是一个片段,还有大约另外10个foreach循环选择不同的节点。

我做错了什么?

public string Export(string html)
{
    var doc = new HtmlDocument();
    doc.LoadHtml(html);
    // exception gets thrown on below line
    foreach (var repeater in doc.DocumentNode.SelectNodes("//table[@class='mceRepeater']"))
    {
        if (repeater != null)
        {
            repeater.Name = "editor:repeater";
            repeater.Attributes.RemoveAll();
        }
    }

    var sw = new StringWriter();
    doc.Save(sw);
    sw.Flush();

    return sw.ToString();
}

异常在哪里抛出? - R. Martinho Fernandes
抱歉,我忘了提到。它是在这一行抛出的:"foreach (var repeater in doc.DocumentNode.SelectNodes("//table[@class='mceRepeater']"))"。 - tohereknowswhen
5个回答

31
据我所知,如果没有找到节点,DocumentNode.SelectNodes 可能会返回 null
这是默认行为,在 codeplex 上的讨论线程中可以看到: Why DocumentNode.SelectNodes returns null 因此,解决方法可能是重写 foreach 块:
var repeaters = doc.DocumentNode.SelectNodes("//table[@class='mceRepeater']");
if (repeaters != null)
{
    foreach (var repeater in repeaters)
    {
        if (repeater != null)
        {
            repeater.Name = "editor:repeater";
            repeater.Attributes.RemoveAll();
        }
    }
}

12

这已经得到更新,现在您可以通过设置doc.OptionEmptyCollection = true来防止SelectNodes返回null,具体请参见这个 Github 问题

如果没有与查询匹配的节点,这将使其返回一个空集合而不是null(虽然我不确定为什么这不是默认行为)。


对我没用: System.ArgumentOutOfRangeException HResult=0x80131502 Message=索引超出范围。必须是非负数且小于集合的大小。 - PastExpiry.com
这不像是这个函数会出现的错误类型。您确定是 doc.DocumentNode.SelectNodes 抛出的错误吗?您传递给 SelectNodes 的查询是什么? - Harry
@PastExpiry.com 或许你正在尝试类似于 doc.DocumentNode.SelectNodes(selector)[0] 的操作来获取列表中的第一个节点,但返回的列表是空的,因此该节点不存在? - Harry
是的... temp = doc.DocumentNode.SelectNodes("//*[@id='cr_cashflow']/div[2]/div[2]/table/thead/tr/th")[0].InnerText; - PastExpiry.com
所以不是 SelectNodes 引发了这个错误,而是 [0]SelectNodes 正确地返回了一个空列表,而不是 null。您正在尝试访问该空列表的第一个元素,但该元素不存在,因此会引发异常。 - Harry

3
根据Alex的回答,但我是这样解决的:
public static class HtmlAgilityPackExtensions
{
    public static HtmlAgilityPack.HtmlNodeCollection SafeSelectNodes(this HtmlAgilityPack.HtmlNode node, string selector)
    {
        return (node.SelectNodes(selector) ?? new HtmlAgilityPack.HtmlNodeCollection(node));
    }
}

2

在每个.前面添加简单的,以下是示例:

var titleTag = htdoc?.DocumentNode?.Descendants("title")?.FirstOrDefault()?.InnerText;

1
他为什么要这样做? - Uwe Keim

1
我已经创建了一个通用扩展,可以与任何 IEnumerable<T> 一起使用。
public static List<TSource> ToListOrEmpty<TSource>(this IEnumerable<TSource> source)
{
    return source == null ? new List<TSource>() : source.ToList();
}

使用方法如下:

var opnodes = bodyNode.Descendants("o:p").ToListOrEmpty();
opnodes.ForEach(x => x.Remove());

2
我喜欢这个解决方案背后的想法,但是使用Enumerable.Empty而不是ToList。这样你就不会迭代转换为List了。 - brianfeucht

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接