Html Agility Pack按類獲取所有元素

c# html html-agility-pack

我正在攻擊html敏捷包並且無法找到正確的方法來解決這個問題。

例如:

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

但是,顯然你可以添加更多的類然後div,所以我嘗試了這個..

var allLinksWithDivAndClass = _doc.DocumentNode.SelectNodes("//*[@class=\"float\"]");

但是這並沒有處理你添加多個類的情況,而“float”只是其中之一。

class="className float anotherclassName"

有沒有辦法處理所有這些?我基本上想要選擇所有具有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", "") ) );
    }
}

如果需要在運行時選擇單個類名,那麼可以構建一個正則表達式:

private static IEnumerable<HtmlNode> GetElementsWithClass(HtmlDocument doc, String className) {

    Regex regex = new Regex( "\\b" + Regex.Escape( className ) + "\\b", RegexOptions.Compiled );

    return doc
        .Descendants()
        .Where( n => n.NodeType == NodeType.Element )
        .Where( e => e.Name == "div" && regex.IsMatch( e.GetAttributeValue("class", "") ) );
}

如果您有多個類名並且想要匹配所有類名,則可以創建一個Regex對像數組並確保它們全部匹配,或者使用外觀將它們組合成單個正則Regex ,但這會導致極其複雜的表達式 -所以使用Regex[]可能更好:

using System.Linq;

private static IEnumerable<HtmlNode> GetElementsWithClass(HtmlDocument doc, String[] classNames) {

    Regex[] exprs = new Regex[ classNames.Length ];
    for( Int32 i = 0; i < exprs.Length; i++ ) {
        exprs[i] = new Regex( "\\b" + Regex.Escape( classNames[i] ) + "\\b", RegexOptions.Compiled );
    }

    return doc
        .Descendants()
        .Where( n => n.NodeType == NodeType.Element )
        .Where( e =>
            e.Name == "div" &&
            exprs.All( r =>
                r.IsMatch( e.GetAttributeValue("class", "") )
            )
        );
}

方法2:使用非正則表達式字符串匹配:

使用自定義C#方法進行字符串匹配而不是正則表達式的優勢在於假設性能更快,內存使用率更低(儘管在某些情況下正則Regex更快 - 總是先編寫代碼,孩子們!)

下面的方法: CheapClassListContains提供了一個快速的字邊界檢查字符串匹配函數,可以像regex.IsMatch一樣使用:

private static IEnumerable<HtmlNode> GetElementsWithClass(HtmlDocument doc, String className) {

    return doc
        .Descendants()
        .Where( n => n.NodeType == NodeType.Element )
        .Where( e =>
            e.Name == "div" &&
            CheapClassListContains(
                e.GetAttributeValue("class", ""),
                className,
                StringComparison.Ordinal
            )
        );
}

/// <summary>Performs optionally-whitespace-padded string search without new string allocations.</summary>
/// <remarks>A regex might also work, but constructing a new regex every time this method is called would be expensive.</remarks>
private static Boolean CheapClassListContains(String haystack, String needle, StringComparison comparison)
{
    if( String.Equals( haystack, needle, comparison ) ) return true;
    Int32 idx = 0;
    while( idx + needle.Length <= haystack.Length )
    {
        idx = haystack.IndexOf( needle, idx, comparison );
        if( idx == -1 ) return false;

        Int32 end = idx + needle.Length;

        // Needle must be enclosed in whitespace or be at the start/end of string
        Boolean validStart = idx == 0               || Char.IsWhiteSpace( haystack[idx - 1] );
        Boolean validEnd   = end == haystack.Length || Char.IsWhiteSpace( haystack[end] );
        if( validStart && validEnd ) return true;

        idx++;
    }
    return false;
}

方法3:使用CSS Selector庫:

HtmlAgilityPack有點停滯不支持.querySelector.querySelectorAll ,但是有第三方庫用它來擴展HtmlAgilityPack:即FizzlerCssSelectors 。 Fizzler和CssSelectors都實現了QuerySelectorAll ,因此您可以像這樣使用它:

private static IEnumerable<HtmlNode> GetDivElementsWithFloatClass(HtmlDocument doc) {

    return doc.QuerySelectorAll( "div.float" );
}

使用運行時定義的類:

private static IEnumerable<HtmlNode> GetDivElementsWithClasses(HtmlDocument doc, IEnumerable<String> classNames) {

    String selector = "div." + String.Join( ".", classNames );

    return doc.QuerySelectorAll( selector  );
}

熱門答案

您可以使用Xpath查詢中的“包含”功能解決您的問題,如下所示:

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

要在函數中重用它,請執行以下類似操作:

string classToFind = "float";    
var allElementsWithClassFloat = 
   _doc.DocumentNode.SelectNodes(string.Format("//*[contains(@class,'{0}')]", classToFind));



許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因