Html Agility Pack按类获取所有元素

c# html html-agility-pack

我正在攻击html敏捷包并且无法找到正确的方法来解决这个问题。

例如:

var findclasses = _doc.DocumentNode.Descendants("div").Where(d => d.Attributes.Contains("class"));

但是,显然你可以添加更多的类然后div,所以我尝试了这个..

var findclasses = _doc.DocumentNode.Descendants("div").Where(d => d.Attributes.Contains("class"));

但是这并没有处理你添加多个类的情况,而“float”只是其中之一。

var findclasses = _doc.DocumentNode.Descendants("div").Where(d => d.Attributes.Contains("class"));

有没有办法处理所有这些?我基本上想要选择所有具有class =且包含float的节点。

**答案已记录在我的博客上,并附有完整说明: Html Agility Pack按类获取所有元素

一般承认的答案

(2018-03-17更新)

问题:

正如您所发现的那样,问题是String.Contains不执行字边界检查,因此Contains("float")将为“foo float bar”(正确)和“unfloating”返回true (这是不正确的)。

解决方案是确保“浮动”(或任何您想要的类名称)出现在两端的字边界旁边 。字边界是字符串(或行)的开头(或结尾),空格,某些标点符号等。在大多数正则表达式中,这是\b 。所以你想要的正则表达式只是: \bfloat\b

使用Regex实例的一个缺点是,如果不使用.Compiled选项,它们可能运行缓慢 - 而且编译速度很慢。所以你应该缓存正则表达式实例。如果您要查找的类名在运行时更改,则会更加困难。

或者,您可以通过将正则表达式实现为C#字符串处理函数,而不使用正则表达式来按字边界搜索字符串,注意不要导致任何新字符串或其他对象分配(例如,不使用String.Split )。

方法1:使用正则表达式:

假设您只想查找具有单个设计时指定类名的元素:

class Program {

    private static readonly Regex _classNameRegex = new Regex( @"\bfloat\b", RegexOptions.Compiled );

    private static IEnumerable<HtmlNode> GetFloatElements(HtmlDocument doc) {
        return doc
            .Descendants()
            .Where( n => n.NodeType == NodeType.Element )
            .Where( e => e.Name == "div" && _classNameRegex.IsMatch( e.GetAttributeValue("class", "") ) );
    }
}

如果需要在运行时选择单个类名,那么可以构建一个正则表达式:

class Program {

    private static readonly Regex _classNameRegex = new Regex( @"\bfloat\b", RegexOptions.Compiled );

    private static IEnumerable<HtmlNode> GetFloatElements(HtmlDocument doc) {
        return doc
            .Descendants()
            .Where( n => n.NodeType == NodeType.Element )
            .Where( e => e.Name == "div" && _classNameRegex.IsMatch( e.GetAttributeValue("class", "") ) );
    }
}

如果您有多个类名并且想要匹配所有类名,则可以创建一个Regex对象数组并确保它们全部匹配,或者使用外观将它们组合成单个正则Regex ,但这会导致极其复杂的表达式 -所以使用Regex[]可能更好:

class Program {

    private static readonly Regex _classNameRegex = new Regex( @"\bfloat\b", RegexOptions.Compiled );

    private static IEnumerable<HtmlNode> GetFloatElements(HtmlDocument doc) {
        return doc
            .Descendants()
            .Where( n => n.NodeType == NodeType.Element )
            .Where( e => e.Name == "div" && _classNameRegex.IsMatch( e.GetAttributeValue("class", "") ) );
    }
}

方法2:使用非正则表达式字符串匹配:

使用自定义C#方法进行字符串匹配而不是正则表达式的优势在于假设性能更快,内存使用率更低(尽管在某些情况下正则Regex更快 - 总是先编写代码,孩子们!)

下面的方法: CheapClassListContains提供了一个快速的字边界检查字符串匹配函数,可以像regex.IsMatch一样使用:

class Program {

    private static readonly Regex _classNameRegex = new Regex( @"\bfloat\b", RegexOptions.Compiled );

    private static IEnumerable<HtmlNode> GetFloatElements(HtmlDocument doc) {
        return doc
            .Descendants()
            .Where( n => n.NodeType == NodeType.Element )
            .Where( e => e.Name == "div" && _classNameRegex.IsMatch( e.GetAttributeValue("class", "") ) );
    }
}

方法3:使用CSS Selector库:

HtmlAgilityPack有点停滞不支持.querySelector.querySelectorAll ,但是有第三方库用它来扩展HtmlAgilityPack:即FizzlerCssSelectors 。 Fizzler和CssSelectors都实现了QuerySelectorAll ,因此您可以像这样使用它:

class Program {

    private static readonly Regex _classNameRegex = new Regex( @"\bfloat\b", RegexOptions.Compiled );

    private static IEnumerable<HtmlNode> GetFloatElements(HtmlDocument doc) {
        return doc
            .Descendants()
            .Where( n => n.NodeType == NodeType.Element )
            .Where( e => e.Name == "div" && _classNameRegex.IsMatch( e.GetAttributeValue("class", "") ) );
    }
}

使用运行时定义的类:

class Program {

    private static readonly Regex _classNameRegex = new Regex( @"\bfloat\b", RegexOptions.Compiled );

    private static IEnumerable<HtmlNode> GetFloatElements(HtmlDocument doc) {
        return doc
            .Descendants()
            .Where( n => n.NodeType == NodeType.Element )
            .Where( e => e.Name == "div" && _classNameRegex.IsMatch( e.GetAttributeValue("class", "") ) );
    }
}

热门答案

您可以使用Xpath查询中的“包含”功能解决您的问题,如下所示:

var allElementsWithClassFloat = 
   _doc.DocumentNode.SelectNodes("//*[contains(@class,'float')]")

要在函数中重用它,请执行以下类似操作:

var allElementsWithClassFloat = 
   _doc.DocumentNode.SelectNodes("//*[contains(@class,'float')]")



许可下: CC-BY-SA with attribution
不隶属于 Stack Overflow
这个KB合法吗? 是的,了解原因
许可下: CC-BY-SA with attribution
不隶属于 Stack Overflow
这个KB合法吗? 是的,了解原因