Знайдіть позицію вузла за допомогою 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()

(але трохи більше працює!)

Це взагалі можливо?

редагувати : забув згадати, що я використовую .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 '] / preceding-Sibling :: b") .Count + 1; if (position> 1 || doc.SelectSingleNode ("a / b [. = 'tsr']")! = null) // Перевірте, чи вузол насправді існує {// Робіть тут магію}
Вільфред Кнівель,

нульовими мовами, що індексуються, вам не потрібне +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.

4

Якщо ви коли-небудь переходили на XPath 2.0, зверніть увагу, що він надає індекс функції , це вирішує проблему таким чином:

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

Де:

  • 1-й параметр - це послідовність пошуку
  • 2-е - що шукати

Слід зазначити, що це буде працювати лише з XPath 2+. Все, що нижче, повинно використовувати функцію "дивна" кількість.
Ден Еткінсон,

1
@Dan, це було зазначено у посиланні на оригінальні документи, додано явне повідомлення, дякую!
CroWell

3

На відміну від зазначеного раніше, "preceding-sibling" насправді є віссю, яку слід використовувати, а не "preceding", яка робить щось зовсім інше, вона виділяє все в документі, що знаходиться перед тегом старту поточного вузла. (див. http://www.w3schools.com/xpath/xpath_axes.asp )


4
Не враховуючи вузли-предки. Не довіряйте w3schools у деталях! Але я погоджуюсь ... хоча preceding :: працює і в цьому випадку, оскільки до відповідних b-елементів немає елементів, крім предка, він більш крихкий, ніж попередній-брат. ОТО, ОП не сказав нам, в якому контексті він хотів би знати позицію всередині, тому потенційно preceding :: може бути правильним.
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 finder, а шукач C #.
jamesh

0

Я роблю багато матеріалів Novell Identity Manager, і XPATH у цьому контексті виглядає дещо інакше.

Припустимо, що значення, яке ви шукаєте, знаходиться у рядковій змінній, яка називається TARGET, тоді XPATH буде таким:

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

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

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

Я також опублікував кращу версію цього на Novell Cool Solutions: Використання XPATH для отримання позиційного вузла

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.