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"
このすべてを処理する方法はありますか? 私は基本的にクラス=を持ち、floatを含むすべてのノードを選択したいです。
**回答は、私のブログで完全な説明とともに文書化されています。 Html Agility Pack クラスによってすべての要素を取得する
どのように解決するのですか?
(2018-03-17更新)
問題の内容
問題は、お気づきのように
String.Contains
は単語境界のチェックを行わないので
Contains("float")
が返されます。
true
を返します。これは "foo float bar" (正しい) と "unfloating" (正しくない) の両方です。
解決策は、"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
というように、HtmlAgilityPack を拡張するサードパーティライブラリもあります。
Fizzler
と
CssSelectors
. 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 );
}
関連
-
[解決済み】コンパイルエラー「未割り当てのローカル変数を使用しています」が発生したのはなぜですか?
-
[解決済み】WebForms UnobtrusiveValidationModeは、jqueryのScriptResourceMappingを必要とする
-
[解決済み】パディングが無効で、削除できない?
-
[解決済み】プロジェクトビルド時のエラー。エディタでスクリプトにコンパイルエラーがあるため、Playerのビルドにエラーが発生する
-
[解決済み] DBNullから他の型にオブジェクトをキャストすることができない
-
[解決済み】WPFでXamlファイルにコメントを追加する方法は?
-
[解決済み】Visual studio 2019がデバッグ時にフリーズする件
-
[解決済み】Unityでゲームオブジェクトのすべての子をループスルーして破壊する方法?
-
[解決済み】「namespace」なのに「type」のように使われる。
-
[解決済み】名前 'ViewBag' が現在のコンテキストに存在しない - Visual Studio 2015
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】C#におけるtypedefの等価性
-
[解決済み] [Entity Framework 4.1でエンティティに関連オブジェクトを追加する際に、エンティティオブジェクトをIEntityChangeTracker.の複数のインスタンスから参照できない。
-
[解決済み】文字列が有効な DateTime " format dd/MM/yyyy " として認識されなかった。
-
[解決済み】ASP.NET Core Dependency Injectionのエラーです。アクティブ化しようとしているときに、タイプのサービスを解決できません。
-
[解決済み】Unity3DでOnTriggerEnterが動作しない件
-
[解決済み】非静的メソッドはターゲットを必要とする
-
[解決済み】Unityでゲームオブジェクトのすべての子をループスルーして破壊する方法?
-
[解決済み] 関数を終了するには?
-
[解決済み】WebResource.axdとは何ですか?
-
[解決済み】2つの名前を任意の順序で含む文字列をマッチさせる正規表現