Xpath для поиска потомков узла

У меня есть следующий метод С#, который выполняет некоторую операцию на всех узлах, доступных из refNode, через xpath

void foo(XmlNode refNode, string xpath)
{
    XmlNodeList list=refNode.SelectNodes(xpath);
    //perform operation on each element of the list
}

Один из входных xml, которые я получаю:

<A>
    <B>***
        <C>
                  <B>One</B>
            </C>
        <B>
                  <B>Two</B>
            </B>
    </B>
    <B>...</B>
    <B>...</B>
</A>

где мне нужно выбрать refNode <B> (помеченный ***) и передать его в foo() с помощью xpath, который выбирает все потомки <B> узлов refNode, но не вложен в какой-либо другой <B> узел

например, в данном вводе результат должен содержать:

1. <B>One</B>
2. <B><B>Two</B></B>

Я пробовал .//B, который дает мне 3 результата, и .//B[not(ancesotr::B)], который возвращает 0 результатов.

Какой Xpath следует использовать для получения желаемого результата?

Изменить

Я могу внести изменения в метод foo, но не в его сигнатуру. Этот метод является частью библиотеки и используется несколькими пользователями. Приведенный выше ввод является просто конкретным экземпляром, пользователь также может отправить узел A в качестве refnode и запросить все узлы C.

Edit2 Решение @Dimitre Novatchev работает для меня, если я могу получить xpath refnode внутри foo без изменения его подписи или если есть какой-то способ указать this node, то есть узел, на котором xpath находится применяемый.

.//B[not(ancesotr::B) or ancesotr::B[1]=**this**]

person Amit Khanna    schedule 29.12.2012    source источник


Ответы (4)


Используйте это чистое выражение XPath 1.0:

$vrefNode/descendant::B[count(ancestor::B) - count($vrefNode/ancestor::B) = 1]

где $vrefNode необходимо заменить (если вы не можете использовать ссылки на переменные) выражением XPath, которое выбирает "ссылочный узел".

Проверка на основе XSLT:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
  <xsl:copy-of select=
  "/*/B[1]/descendant::B[count(ancestor::B) - count(/*/B[1]/ancestor::B) = 1]"/>
 </xsl:template>
</xsl:stylesheet>

Когда это преобразование применяется к предоставленному XML-документу:

<A>
    <B>***
        <C>
            <B>One</B>
        </C>
        <B>
            <B>Two</B>
        </B>
    </B>
    <B>...</B>
    <B>...</B>
</A>

выражение XPath оценивается, и выбранные элементы этой оценки копируются в выходные данные:

<B>One</B>
<B>

   <B>Two</B>

</B>
person Dimitre Novatchev    schedule 29.12.2012
comment
спасибо за ваш ответ, невозможно получить xpath refnode при вызове функции. Есть ли способ получить его внутри foo() из refNode? - person Amit Khanna; 30.12.2012
comment
@AmitKhanna, определите foo() как: void foo(XmlNode refNode, string xpath, string refNodeXpath) и передайте в качестве последнего аргумента выражение XPath, которое выбирает refNode. Я скоро пойду спать - смогу ответить на будущие вопросы примерно через 8 часов. Доброй ночи. - person Dimitre Novatchev; 30.12.2012
comment
не могу этого сделать, так как это общедоступный метод, и другие уже используют его. не могу изменить свою подпись. - person Amit Khanna; 30.12.2012
comment
@AmitKhanna, затем добавьте в класс новый метод с новым аргументом - с этим проблем нет. - person Dimitre Novatchev; 30.12.2012
comment
это решит проблему, но я не думаю, что это будет хорошим решением с точки зрения дизайна. Я закончу тем, что у меня будет два метода, которые выполняют одну и ту же работу, запутывая пользователя, какой из них использовать. Также refnodexpath кажется мне избыточным, так как пользователь уже передает узел. Возможно, у пользователя тоже нет этого значения, он также может получить XmlNode в своем коде. Один из способов, о котором я подумал, - это построить refnodexpath внутри foo, пройдя от родителей refnode до корня и добавив имя каждого узла в пути. - person Amit Khanna; 01.01.2013
comment
@AmitKhanna, да, см. этот ответ: stackoverflow.com/questions/4746299/ для способа построения выражения XPath для узла - даже в XSLT. - person Dimitre Novatchev; 01.01.2013

Я не уверен, что чистый XPath может это сделать. XQuery обладает большей выразительной силой, например, вы можете легко использовать let для хранения элемента B, в поддереве которого вы ищете дальнейших B потомков в переменной, а затем вы можете убедиться, что это единственный предок B. Итак, вот пример XQuery:

let $inp := <A>
  <B>
    ***
    <C>
      <B>One</B>
    </C>
    <B>
      <B>Two</B>
    </B>
    <D>
      <E>
        <B>Three</B>
      </E>
    </D>
    <D>
      <B>Four<Foo>
        <B>Five</B>
      </Foo>
    </B>
    </D>
  </B>
  <B>...</B>
  <B>...</B>
</A>
let $b := $inp/B[1]
return $b/descendant::B[count(ancestor::B) = 1 and ancestor::B[1] is $b]

Это находит узлы

<B>One</B>
<B>
<B>Two</B>
</B>
<B>Three</B>
<B>Four<Foo>
<B>Five</B>
</Foo>
</B>

Существуют реализации XQuery для платформы .NET, такие как XmlPrime, версия Saxon 9 .NET, AltovaXML или http://qm.codeplex.com/.

person Martin Honnen    schedule 29.12.2012
comment
Мартин, Конечно, это можно выразить в чистом XPath 1.0 — см. мой ответ. - person Dimitre Novatchev; 29.12.2012

xpath, который выбирает все узлы-потомки <B> узла refNode, но не вложен в какой-либо другой узел <B>

В XSLT вы можете использовать

.//B[generate-id(ancestor::B[1]) = generate-id(current())]

При этом будут выбраны все те B потомки текущего узла контекста, ближайший охватывающий B предок которых является самим текущим узлом контекста. Но это зависит от generate-id() и current(), которые специфичны для XSLT и не являются частью обычного XPath.

Следующее было бы двухэтапной альтернативой:

foo(refNode, ".//B[count(ancestor::B) = " + refNode.SelectNodes("ancestor-or-self::B").Count + "]");

динамическое создание выражения XPath. Это найдет всех B потомков refNode, которые имеют то же количество B предков, что и refNode (плюс один, если refNode сам является элементом B), что приводит к исключению B потомков, которые находятся более чем в одном "слое B" в глубину. .

person Ian Roberts    schedule 29.12.2012
comment
Ян, generate-id() не является функцией XPath — она доступна только в начальном контексте оценки, созданном XSLT. - person Dimitre Novatchev; 29.12.2012

Обязательно ли использовать XPath? Если вы можете использовать Linq-to-XML,

XElement refNode = ...
XElement wantedBs = refNode.Descendants("B")
                           .Where(b => parent.Name.LocalName != "B");
person Chuck Savage    schedule 30.12.2012
comment
Спасибо за ваш ответ. отредактировал мой вопрос, пользователь может пометить A как узел ссылки и запросить все узлы C внутри него, так что это не сработает. - person Amit Khanna; 30.12.2012