Выбор (братьев и сестер) между двумя тегами с использованием XPath (в .NET) - PullRequest
3 голосов
/ 19 августа 2009

Я использую .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));                    
            }
        }
    }
}

Ответы [ 4 ]

1 голос
/ 20 августа 2009

Если в вашей ситуации у вас всегда есть ровно три «кусочка», разделенных br с, вы можете использовать этот XPath для получения среднего «кусочка»:

//node()[preceding::br and following::br]

, который использует оси preceding и following для возврата всех узлов между двумя br с, где угодно.

edit это мое тестовое приложение (прошу прощения за XmlDocument, я все еще работаю с .NET 2.0 ...)

using System;
using System.Xml;

namespace ConsoleApplication1
{
    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> <strong>multiple elements</strong><br />
  Component C
 </p>
</x>
            ");

            XmlNodeList nodes = doc.SelectNodes(
                "//node()[preceding::br and following::br]");

            Dump(nodes);

            Console.ReadLine();
        }

        private static void Dump(XmlNodeList nodes)
        {
            foreach (XmlNode node in nodes)
            {
                Console.WriteLine(string.Format("-->{0}<---", 
                                  node.OuterXml));                    
            }
        }
    }
}

И это вывод:

-->
      Component B <---
--><i>includes</i><---
-->includes<---
--><strong>multiple elements</strong><---
-->multiple elements<---

Как видите, вы получаете XmlNodeList со всеми вещами между br с.

Я думаю об этом так: этот XPath возвращает любой узел в любом месте, если для этого узла , предыдущая ось содержит br, и следующей оси содержит br.

0 голосов
/ 21 августа 2009

Это легко сделать с XPath 2.0 или с XPath 1.0 на XSLT .

С XPath 1.0, размещенным на .NET, это может быть достигнуто в несколько шагов:

  1. Сделать соответствующий узел "p" текущим узлом.

  2. Найти число всех <br /> дочерних элементов текущего узла "p":

    Количество (ш)

  3. , если N - количество, определенное на шаге 2. для $ k в 0 до N do:

    3.1 Найти все узлы, которым предшествует $ k <br /> элементов:

    node () [not (self :: br) и count (предшествующий :: br) = $ k]

    3.2 Для каждого такого найденного узла получить его строковое значение

    3.3 Объединить все строковые значения, полученные на шаге 3.2. Результатом этой конкатенации является весь текст, содержащийся в данном параграфе .

Примечание : Чтобы заменить то, что должно обозначать $k на шаге 3.1, необходимо динамически построить это выражение.

0 голосов
/ 19 августа 2009

Попробуйте использовать методы position () или, возможно, count (). Вот догадка , которая может помочь . Вы получите правильный синтаксис.

p/*[position() > position(/p/br[1]) and position() < position(/p/br[2])] 

РЕДАКТИРОВАТЬ: Пожалуйста, прочитайте комментарии, прежде чем голосовать или комментировать .

0 голосов
/ 19 августа 2009

Как насчет:

p/*[not(local-name()='br')]

И затем индексируйте это выражение для любого термина, который вы хотите

EDIT:

Для вашей проблемы с индексированием:

p/*[not(local-name()='br') and position() < x and position() > y]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...