Как найти ближайшее совпадение от текущего контекстного узла

c# html-agility-pack xpath

Вопрос

У меня есть довольно большой 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) { ... }

Что возвращает правильный узел. Теперь я хочу искать назад (вверх) для первого (ближайшего) совпадающего узла <td> который начинается с текста «ABC»:

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-')]

Это приведет к поиску ближайшего предыдущего <tr> , у которого есть дочерний <td> начинающийся с 'ABC-'. Затем получим этот особый элемент <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>

(Который я проверил, был второй соответствующий узел в вашем примере, а не первый.)




Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему
Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему