如何从当前上下文节点查找最近的匹配项
本文关键字:最近 查找 节点 上下文 | 更新日期: 2023-09-27 17:55:25
我有一个相当大的XML文件,我试图使用C#应用程序和HtmlAgilityPack来解析它。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-')]";
这将返回所有匹配的节点,而不仅仅是最近的节点。当我尝试将 [1] 添加到此 XPath 字符串的末尾时,它似乎不起作用,并且我没有发现任何示例显示谓词与这样的轴函数一起使用。或者,更有可能的是,我做错了。有什么建议吗?
您可以使用此 XPath :
/parent::tr/preceding-sibling::tr[td[starts-with(.,'ABC-')]][1]/td[starts-with(.,'ABC-')]
这将搜索子<td>
以"ABC-"开头的最近的前<tr>
。然后获取该特定<td>
元素。
使用HtmlAgilityPack时,至少有两种方法可以选择:
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);
}
如果您能够使用 LINQ-to-XML 而不是 HAP,那么这就可以了:
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>
(我检查的是示例中的第二个匹配节点,而不是第一个。
您可以使用
//tr/td[starts-with(.,'CONTROLLER2')]/(parent::tr/preceding-sibling::tr/td[starts-with(normalize-space(.),'ABC-')])[1]
由于目标节点包含不需要的空间,因此必须使用 normalize-space
。
我认为像这样的 XPATH(来自当前 CONTROLLER2 节点)应该这样做:
string xPath = "../preceding-sibling::tr[starts-with(td , 'ABC-')][1]/td[starts-with(. , 'ABC-')]";
这意味着
- 祖先升级后返回(..)
- 从那里,选择具有以"ABC-"开头的TD元素的所有先前同级TR元素
- 获取这些 TR 的第一个(相反顺序)。
- 从此 TR 元素中获取以"ABC-"开头的 TD 元素