Найти позицию узла с помощью xpath

86

Кто-нибудь знает, как получить позицию узла с помощью xpath?

Скажем, у меня есть следующий xml:

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

Я могу использовать следующий запрос xpath для выбора третьего <b> узла (<b> tsr </b>):

a/b[.='tsr']

Это все хорошо, но я хочу вернуть порядковый номер этого узла, например:

a/b[.='tsr']/position()

(но немного больше работает!)

Это вообще возможно?

edit : Забыл упомянуть, что я использую .net 2, поэтому это xpath 1.0!


Обновление : Законченное использование Джеймс Сулак «s отличного ответа . Для тех, кому интересно, вот моя реализация на C #:

int position = doc.SelectNodes("a/b[.='tsr']/preceding-sibling::b").Count + 1;

// Check the node actually exists
if (position > 1 || doc.SelectSingleNode("a/b[.='tsr']") != null)
{
    Console.WriteLine("Found at position = {0}", position);
}
Уилфред Книвель
источник
Пожалуйста, постарайтесь не публиковать ответы в вопросе -> было бы лучше опубликовать это как ответ, а затем, возможно, связать его с вопросом.
theMayer

Ответы:

94

Пытаться:

count(a/b[.='tsr']/preceding-sibling::*)+1.
Джеймс Сулак
источник
1
'Потому что я использую .net и либо он, либо я не могу справиться с той мощностью, с которой я работал: int position = doc.SelectNodes ("a / b [. =' Tsr '] / previous-Sibling :: b") .Count + 1; if (position> 1 || doc.SelectSingleNode ("a / b [. = 'tsr']")! = null) // Проверяем, что узел действительно существует {// Делаем здесь магию}
Wilfred Knievel,
в языках с нулевым индексом вам не нужен +1
JonnyRaa
9

Вы можете сделать это с помощью XSLT, но я не уверен в прямом XPath.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" encoding="utf-8" indent="yes" 
              omit-xml-declaration="yes"/>
  <xsl:template match="a/*[text()='tsr']">
    <xsl:number value-of="position()"/>
  </xsl:template>
  <xsl:template match="text()"/>
</xsl:stylesheet>
Стивен Хьювиг
источник
9

Я понимаю, что пост древний .. но ..

замена звездочки на имя узла даст вам лучшие результаты

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

вместо того

count(a/b[.='tsr']/preceding::*)+1.
user414661
источник
4

Если вы когда-либо обновлялись до XPath 2.0, обратите внимание, что он предоставляет функцию index-of , это решает проблему следующим образом:

index-of(//b, //b[.='tsr'])

Куда:

  • 1-й параметр - последовательность для поиска
  • 2-й - это то, что искать
CroWell
источник
Следует отметить, что это будет работать только с XPath 2+. Все, что ниже, должно использовать «странную» функцию подсчета.
Дэн Аткинсон
1
@Dan, это было отмечено в ссылке на исходные документы, добавлено явное уведомление, спасибо!
CroWell
3

В отличие от заявленного ранее, «предыдущий брат» на самом деле является используемой осью, а не «предшествующий», который делает что-то совершенно другое, он выбирает все в документе, которое находится перед начальным тегом текущего узла. (см. http://www.w3schools.com/xpath/xpath_axes.asp )

Дэмиен
источник
4
Не включая узлы-предки. Не доверяйте w3schools в деталях! Но я согласен ... хотя предыдущий :: работает в этом случае, поскольку перед соответствующими элементами b нет других элементов, кроме предка, он более хрупкий, чем предыдущий брат. OTOH, OP не сказал нам, в каком контексте он хотел узнать позицию внутри, поэтому потенциально предшествующий :: мог быть правильным.
LarsH
2

Просто примечание к ответу Джеймса Сулака.

Если вы хотите принять во внимание, что узел может не существовать, и хотите сохранить его исключительно XPATH, попробуйте следующее, которое вернет 0, если узел не существует.

count(a/b[.='tsr']/preceding-sibling::*)+number(boolean(a/b[.='tsr']))
Клаус Дженсен
источник
0

Проблема в том, что положение узла не имеет большого значения без контекста.

Следующий код даст вам расположение узла в его родительских дочерних узлах.

using System;
using System.Xml;

public class XpathFinder
{
    public static void Main(string[] args)
    {
        XmlDocument xmldoc = new XmlDocument();
        xmldoc.Load(args[0]);
        foreach ( XmlNode xn in xmldoc.SelectNodes(args[1]) )
        {
            for (int i = 0; i < xn.ParentNode.ChildNodes.Count; i++)
            {
                if ( xn.ParentNode.ChildNodes[i].Equals( xn ) )
                {
                    Console.Out.WriteLine( i );
                    break;
                }
            }
        }
    }
}
Эндрю Кокс
источник
1
Так что теперь это не совсем средство поиска XPath, а средство поиска C #.
jamesh
0

Я много занимаюсь Novell Identity Manager, и XPATH в этом контексте выглядит немного иначе.

Предположим, что значение, которое вы ищете, находится в строковой переменной с именем TARGET, тогда XPATH будет:

count(attr/value[.='$TARGET']/preceding-sibling::*)+1

Кроме того, было указано, что для экономии нескольких символов места также будет работать следующее:

count(attr/value[.='$TARGET']/preceding::*) + 1

Я также разместил более красивую версию этого в Novell's Cool Solutions: Использование XPATH для получения узла позиции

Geoffc
источник