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"はこのようなものの1つだけです。

class="className float anotherclassName"

このすべてを処理する方法はありますか?私は基本的にclass =を持ち、floatを含むすべてのノードを選択したいと考えています。

**答えは私のブログで完全な説明で文書化されています: Html Agility Packクラス別にすべての要素を取得

受け入れられた回答

(2018年3月17日更新)

問題:

問題は、 String.Containsは単語境界チェックを実行しないことです。そのため、 Contains("float")は "foo float bar"(正しい)と "unfloating"(正しい)の両方に対してtrueを返しtrue正しくありません。

解決策は、「float」(または希望するクラス名がなんであれ)が両端で単語境界に沿って表示されるようにすることです。単語境界は、文字列(または行)の開始(または終了)、空白、特定の句読点などです。ほとんどの正規表現では、これは\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セレクタライブラリを使う:

HtmlAgilityPackはやや停滞しており、 .querySelector.querySelectorAllサポートしていませんが、 .querySelectorAllを拡張したサードパーティ製のライブラリ、つまり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クエリ内の 'contains'関数を使用して問題を解決できます。

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は合法ですか? はい、理由を学ぶ