Найти положение узла в наборе узлов с помощью xpath - PullRequest
5 голосов
/ 09 апреля 2010

Поработав с position() напрасно, я искал решение и нашел этот старый вопрос о стеке , который почти описывает мою проблему.

Разница в том, что набор узлов, в котором я хочу разместить позицию, является динамическим, а не смежным разделом документа.

В качестве иллюстрации я изменю пример из связанного вопроса, чтобы он соответствовал моим требованиям. Обратите внимание, что каждый элемент <b> находится в отдельном элементе <a>. Это критический бит.

<root>
    <a>
        <b>zyx</b>
    </a>
    <a>
        <b>wvu</b>
    </a>
    <a>
        <b>tsr</b>
    </a>
    <a>
        <b>qpo</b>
    </a>
</root>

Теперь, если бы я запросил, используя XPath для a/b, я бы получил набор узлов из четырех <b> узлов. Затем я хочу найти позицию в этом наборе узлов узла, содержащего строку 'tsr'. Решение, описанное в другом посте, здесь разбивается: count(a/b[.='tsr']/preceding-sibling::*)+1 возвращает 1, потому что preceding-sibling перемещается по документу, а не по контекстному узлу.

Можно ли работать в рамках контекстного узла?

Ответы [ 6 ]

4 голосов
/ 25 августа 2010

Вот общее решение, которое работает на любом узле, принадлежащем к любому набору узлов в одном и том же документе :

Я использую XSLT для реализации решения, но в итоге получаю одно выражение XPath, которое можно использовать с любым другим языком хостинга.

Пусть $vNodeSet будет набором узлов, а $vNode будет узлом в этом наборе узлов, положение которого мы хотим найти.

Тогда пусть $vPrecNodes содержит все узлы в документе XML, предшествующие $vNode.

Тогда пусть $vAncNodes содержит все узлы в документе XML, которые являются предками $vNode.

Набор узлов в $vNodeSet, которые предшествуют $vNode в порядке документов, состоит из всех узлов в наборе узлов, которые также принадлежат $vPrecNodes, и всех узлов в наборе узлов, которые также принадлежат $vAncNodes.

Я буду использовать известную формулу Кайса для пересечения двух наборов узлов:

$ns1[count(.|$ns2) = count($ns2)]

содержит ровно узлы на пересечении $ns1 с $ns2.

На основании всего этого пусть $vPrecInNodeSet - это набор узлов в $vNodeSet, которые предшествуют $vNode в порядке документов. Следующее выражение XPath определяет $vPrecInNodeSet:

$vNodeSet
      [count(.|$vPrecNodes) = count($vPrecNodes)
      or
       count(.|$vAncNodes) = count($vAncNodes)
      ]

Наконец, требуемая позиция : count($vPrecInNodeSet) +1

Вот как все это работает вместе:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 <xsl:variable name="vNodeSet" select="/*/a/b"/>

 <xsl:variable name="vNode" select="$vNodeSet[. = 'tsr'][1]"/>

 <xsl:variable name="vPrecNodes" select="$vNode/preceding::node()"/>

 <xsl:variable name="vAncNodes" select="$vNode/ancestor::node()"/>

 <xsl:variable name="vPrecInNodeSet" select=
  "$vNodeSet
      [count(.|$vPrecNodes) = count($vPrecNodes)
      or
       count(.|$vAncNodes) = count($vAncNodes)
      ]
  "/>

 <xsl:template match="/">
   <xsl:value-of select="count($vPrecInNodeSet) +1"/>
 </xsl:template>
</xsl:stylesheet>

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

<root>
    <a>
        <b>zyx</b>
    </a>
    <a>
        <b>wvu</b>
    </a>
    <a>
        <b>tsr</b>
    </a>
    <a>
        <b>qpo</b>
    </a>
</root>

получен правильный результат :

3

Do note : Это решение не зависит от XSLT (используется только в иллюстративных целях). Вы можете собрать одно выражение XPath, подставляя переменные с их определением, пока не останется больше переменных для замены.

2 голосов
/ 18 августа 2010

Я думаю, что у меня есть рабочее решение

Идея состоит в том, чтобы подсчитать, сколько элементов предшествует нашему целевому элементу в документе, и подсчитать, сколько узлов в наборе узлов имеет меньше или равно много предыдущих элементов.,В XPath это:

count(//a/b[count(./preceding::node()) &lt;= count(//a/b[.='tsr']/preceding::node())])

Вы также можете использовать переменные в этом выражении для поиска разных наборов узлов или для сопоставления различного текстового содержимого.Важной частью здесь является то, что переменные имеют правильный тип.Ниже приведен пример XSLT и пример вывода с использованием примера документа вопроса в качестве входного файла

XSLT-документа

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output encoding="utf-8" method="text"/>

    <xsl:variable name="nodeset" select="//a/b"/>
    <xsl:variable name="path-string">//a/b</xsl:variable>
    <xsl:variable name="text">tsr</xsl:variable>

    <xsl:template match="/">
        <xsl:text>Find and print position of a node within a nodeset&#10;&#10;</xsl:text>

        <xsl:text>Position of "tsr" node in the nodeset = "</xsl:text>
        <xsl:value-of select="count(//a/b[count(./preceding::node()) &lt;= count(//a/b[.='tsr']/preceding::node()) ])"/>
        <xsl:text>"&#10;&#10;</xsl:text>

        <xsl:text>( Try the same using variables "$nodeset" and "$text" )&#10;</xsl:text>
        <xsl:text>Size of nodeset "$nodeset" = "</xsl:text>
        <xsl:value-of select="count($nodeset)"/>
        <xsl:text>"&#10;</xsl:text>
        <xsl:text>Variable "$text" = "</xsl:text>
        <xsl:value-of select="$text"/>
        <xsl:text>"&#10;</xsl:text>
        <xsl:text>Position of "</xsl:text>
        <xsl:value-of select="$text"/>
        <xsl:text>" node in the nodeset = "</xsl:text>
        <xsl:value-of select="count($nodeset[count(./preceding::node()) &lt;= count($nodeset[.=$text]/preceding::node()) ])"/>
        <xsl:text>"&#10;&#10;</xsl:text>

        <xsl:text>( Show that using a variable that has the path as a string does not work )&#10;</xsl:text>
        <xsl:text>Variable "$path-string" = "</xsl:text>
        <xsl:value-of select="$path-string"/>
        <xsl:text>"&#10;</xsl:text>
        <xsl:text>Result of "count($path-string)" = "</xsl:text>
        <xsl:value-of select="count($path-string)"/>
        <xsl:text>"&#10;&#10;</xsl:text>

        <xsl:text>End of tests&#10;</xsl:text>
    </xsl:template>

</xsl:stylesheet>

Вывод из примерадокумент

Find and print position of a node within a nodeset

Position of "tsr" node in the nodeset = "3"

( Try the same using variables "$nodeset" and "$text" )
Size of nodeset "$nodeset" = "4"
Variable "$text" = "tsr"
Position of "tsr" node in the nodeset = "3"

( Show that using a variable that has the path as a string does not work )
Variable "$path-string" = "//a/b"
Result of "count($path-string)" = "1"

End of tests

Я не тщательно протестировал мое решение, поэтому, если вы его используете, пожалуйста, оставьте отзыв.

1 голос
/ 19 августа 2010

Более ранние ответы «подсчет-предшествующий» (-sibling) в некоторых случаях хорошо работают; вы просто заново задаете набор узлов контекста с точки зрения выбранного элемента, а затем применяете к нему count(preceding:: ).

Но в других случаях подсчет предшествующего значения действительно трудно удержать в наборе узлов, с которым вы хотите работать, как вы намекали. Например. предположим, что ваш рабочий набор узлов был / html / body / div [3] // a (все якоря <a> в третьей <div> веб-страницы), и вы хотели найти положение a[@href="foo.html"] в этом наборе , Если бы вы попытались использовать count(preceding::a), вы бы случайно считали <a> якорей из других элементов div, то есть за пределами вашего рабочего набора узлов. И если вы попробуете count(preceding-sibling::a), вы не получите их все, потому что соответствующие элементы <a> могут быть на любом уровне.

Вы можете попытаться ограничить счет, используя preceding::a[ancestor::div[count(preceding-sibling::div) = 2]], но он становится очень неловким быстро и все же не будет возможным во всех случаях. Более того, вам придется переделать это выражение, если вы когда-нибудь обновите выражение XPath для своего рабочего набора, и сохранение их эквивалента будет нетривиальным.

Однако, если вы используете XSLT, следующее поможет избежать этих проблем. Если вы можете указать рабочий набор узлов, вы можете найти положение узла в нем, соответствующее предоставленным критериям. И вам не нужно указывать набор узлов дважды:

    <xsl:for-each select="/root/a/b">
        <xsl:if test=". = 'tsr'"><xsl:value-of select="position()"/></xsl:if>
    </xsl:for-each>

Это работает, потому что в for-each позиция контекста "идентифицирует позицию элемента контекста в обрабатываемой последовательности."

Если вы не работаете в XSLT, в какой среде вы находитесь? Вероятно, существует аналогичная конструкция для итерации по результату внешнего выражения XPath, и там вы можете поддерживать свой собственный счетчик (если нет доступной позиции контекста) и проверять каждый элемент на соответствие вашим внутренним критериям.

Причина, по которой попытка другого парня по старому вопросу , a/b[.='tsr']/position() не сработала, заключалась в том, что при каждом слэше новый стек помещается в стек, поэтому когда position () вызывается, позиция контекста всегда равна 1. (Кстати, этот синтаксис работает только в XPath 2.0.)

0 голосов
/ 28 июля 2010

Из (т.е. против) корня:

count(//a/b[.='tsr']/preceding::b)

Если вы сказали другой узел, например:

<c>
    <b>qqq</b>
</c>

и хотел игнорировать все элементы, не имеющие родителя "a", вы могли бы сделать что-то вроде

count(//a/b[.='tsr']/preceding::b[local-name(parent::node())='a'])

и т. Д.

0 голосов
/ 09 апреля 2010

Как насчет этого ..

count(a/b[.='tsr']/preceding-sibling::b) + count(a[b[.='tsr']]/preceding-sibling::a/b) + 1

Подсчитайте количество предыдущих элементов одного элемента b в текущем элементе a, а затем количество элементов b всех предыдущих элементов этого элемента a. Или что-то в этом роде.

0 голосов
/ 09 апреля 2010

Причина, по которой вы получаете 1, не имеет ничего общего с контекстом по сравнению с документом, а потому, что вы подсчитываете только b узлов в одном a узле (так что вы всегда получите счетчик 0, потому что никогда любые предшествующие узлы 'b'.

Скорее вам нужно найти количество предшествующих узлов 'a' перед 'b', которое содержит ваш 'a'.

Что-то вроде:

count(a[b[.='tsr']]/preceding-sibling::a)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...