私はC#アプリケーションとHtmlAgilityPackを使用して解析しようとしているかなり大きなXMLファイルを持っています。 XMLは次のようになります。
...
<tr>
<td><b>ABC-123</b></td>
<td>15</td>
<td>4</td>
</tr>
<tr>
<td>AB-4-320</td>
<td>11</td>
<td>2</td>
</tr>
<tr>
<td><b>ABC-123</b></td>
<td>15</td>
<td>4</td>
</tr>
<tr>
<td>AB-4-320</td>
<td>11</td>
<td>2</td>
</tr>
<tr>
<td>CONTROLLER1</td>
<td>4</td>
<td>3</td>
</tr>
<td>CONTROLLER2</td>
<td>4</td>
<td>3</td>
</tr>
...
基本的に、一連のテーブル行と列が繰り返されます。私は最初にコントローラを検索しています:
string xPath = @"//tr/td[starts-with(.,'CONTROLLER2')]";
HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes(xPath);
foreach (HtmlNode link in nodes) { ... }
正しいノードを返します。今度は、テキスト「ABC」で始まる最初の(最も近い)一致する<td>
ノードを後方に(上に)検索します。
string xPath = @link.XPath + @"/parent::tr/preceding-sibling::tr/td[starts-with(.,'ABC-')]";
これは、最も近いものだけでなく、すべての一致するノードを返します。このXPath文字列の最後に[1]を追加しようとすると、動作しないように見えました。このような軸関数で使用される述語を示す例は見つかりませんでした。あるいは、おそらく、私は間違っています。助言がありますか?
このXPathを使用することができます:
/parent::tr/preceding-sibling::tr[td[starts-with(.,'ABC-')]][1]/td[starts-with(.,'ABC-')]
それは、子<td>
が 'ABC-'で始まる最も近い先行する<tr>
を検索します。次に、特定の<td>
要素を取得します。
HtmlAgilityPackを使用するときは、少なくとも2つの方法があります。
foreach (HtmlNode link in nodes)
{
//approach 1 : notice dot(.) at the beginning of the XPath
string xPath1 =
@"./parent::tr/preceding-sibling::tr[td[starts-with(.,'ABC-')]][1]/td[starts-with(.,'ABC-')]";
var n1 = node.SelectSingleNode(xPath1);
Console.WriteLine(n1.InnerHtml);
//approach 2 : appending to XPath of current link
string xPath2 =
@"/parent::tr/preceding-sibling::tr[td[starts-with(.,'ABC-')]][1]/td[starts-with(.,'ABC-')]";
var n2 = node.SelectSingleNode(link.XPath + xPath2);
Console.WriteLine(n2.InnerHtml);
}
HAPの代わりにLINQ-to-XMLを使用できるのであれば、これはうまくいきます:
var node = xml.Root.Elements("tr")
.TakeWhile(tr => !tr.Elements("td")
.Any(td => td.Value.StartsWith("CONTROLLER2")))
.SelectMany(tr => tr.Elements("td"))
.Where(td => td.Value.StartsWith("ABC-"))
.Last();
私はこの結果を得た:
<td>
<b>ABC-123</b>
</td>
(私は最初のノードではなく、サンプル内の2番目に一致するノードをチェックしました)。