Я использую .NET 3.5 (C #) и HTML Agility Pack , чтобы выполнить некоторые операции по просмотру веб-страниц. Некоторые поля, которые мне нужно извлечь, структурированы как абзацы, внутри которых компоненты разделены тегами разрыва строки. Я хотел бы иметь возможность выбирать отдельные компоненты между переносами строк. Каждый компонент может быть сформирован из нескольких элементов (то есть он не может быть просто одной строкой). Пример:
<h3>Section title</h3>
<p>
<b>Component A</b><br />
Component B <i>includes</i> <strong>multiple elements</strong><br />
Component C
</p>
Я бы хотел выбрать
<b>Component A</b>
Тогда:
Component B <i>includes</i> <strong>multiple elements</strong>
А потом:
Component C
Также может быть больше (<br />
разделенных) компонентов.
Я легко могу получить первый компонент с помощью:
p/br[1]/preceding-sibling::node()
Я также могу легко получить последний компонент с помощью:
p/br[2]/following-sibling::node()
Но я не смог разобраться, как извлечь набор узлов / между / двумя другими тегами (то есть, узлами, которые являются братьями и сестрами, но которые предшествуют узлу X и следуют за узлом Y).
Альтернатива - сканировать элементы вручную & ndash; если это самый простой способ сделать это, то именно это я и сделаю, но XPath до сих пор поражал меня своей краткостью, поэтому я надеюсь, что есть способ сделать это тоже.
Редактировать
Поскольку мне нужно справиться с ситуацией с более чем 3 компонентами, похоже, что для ответа потребуется как минимум несколько вызовов XPath, поэтому я продолжу решение, основанное на этом (это ответ, который я принял «). Ответ AakashM также помог мне понять мой XPath, поэтому я проголосовал за него.
Спасибо всем за помощь! Я надеюсь, что смогу вернуть одолжение однажды.
Редактировать 2
Новый ответ Дмитрия Новатчева с некоторыми изменениями действительно работает правильно.
Решение:
int i = 0;
do
{
yield return para.SelectNodes(String.Format(
"node()[not(self::br) and count(preceding-sibling::br) = {0}]", i));
++i;
} while (para.SelectSingleNode(String.Format("br[{0}]", i)) != null);
Я должен отметить, что этот цикл несколько неэффективен из-за повторяющихся запросов XPath, чтобы выяснить, есть ли еще br
теги. В моем случае неэффективность не является проблемой, но имейте в виду, если вы хотите использовать этот ответ в какой-то другой ситуации (опять же, если вы хотите сделать это в ситуации, чувствительной к производительности, вам, вероятно, следует в любом случае сканировать вручную, а не используя XPath).
И полный тестовый код (модифицированная версия тестового кода, включенная в AakashM):
using System;
using System.Collections.Generic;
using System.Xml;
namespace TestXPath
{
class Program
{
static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(@"
<x>
<h3>Section title</h3>
<p>
<b>Component A</b><br />
Component B <i>includes</i> multiple <strong>elements</strong><br />
Component C
</p>
</x>
");
foreach (var nodes in SplitOnLineBreak(doc.SelectSingleNode("x/p")))
{
Dump(nodes);
Console.WriteLine();
}
Console.ReadLine();
}
private static IEnumerable<XmlNodeList> SplitOnLineBreak(XmlNode para)
{
int i = 0;
do
{
yield return para.SelectNodes(String.Format(
"node()[not(self::br) and count(preceding-sibling::br) = {0}]", i));
++i;
} while (para.SelectSingleNode(String.Format("br[{0}]", i)) != null);
}
private static void Dump(XmlNodeList nodes)
{
foreach (XmlNode node in nodes)
{
Console.WriteLine(string.Format("-->{0}<---",
node.OuterXml));
}
}
}
}