Znajdź pozycję węzła za pomocą xpath

86

Czy ktoś wie, jak uzyskać pozycję węzła za pomocą xpath?

Powiedzmy, że mam następujący plik XML:

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

Mogę użyć następującego zapytania xpath, aby wybrać trzeci <b> węzeł (<b> tsr </b>):

a/b[.='tsr']

Co jest w porządku, ale chcę zwrócić porządkową pozycję tego węzła, na przykład:

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

(ale trochę bardziej działa!)

Czy to w ogóle możliwe?

edycja : zapomniałem wspomnieć, że używam .net 2, więc jest to xpath 1.0!


Aktualizacja : skończyło się James Sulak „s doskonałą odpowiedź . Dla zainteresowanych moja implementacja w 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);
}
Wilfred Knievel
źródło
Staraj się nie publikować odpowiedzi w pytaniu -> lepiej byłoby zamieścić to jako odpowiedź, a następnie ewentualnie połączyć z nią w pytaniu.
Mayer

Odpowiedzi:

94

Próbować:

count(a/b[.='tsr']/preceding-sibling::*)+1.
James Sulak
źródło
1
'Bo używam .net i albo to, albo nie mogę obsłużyć mocy, z którą poszedłem: int position = doc.SelectNodes ("a / b [. =' Tsr '] / previous-Sibling :: b") .Count + 1; if (pozycja> 1 || doc.SelectSingleNode ("a / b [. = 'tsr']")! = null) // Sprawdź, czy węzeł faktycznie istnieje {// Zrób magię tutaj}
Wilfred Knievel
w językach indeksowanych przez zero nie potrzebujesz +1
JonnyRaa
9

Możesz to zrobić za pomocą XSLT, ale nie jestem pewien co do prostego 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>
Steven Huwig
źródło
9

Zdaję sobie sprawę, że post jest stary ... ale ...

zastąpienie gwiazdki nazwą węzła dałoby lepsze wyniki

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

zamiast

count(a/b[.='tsr']/preceding::*)+1.
user414661
źródło
4

Jeśli kiedykolwiek zaktualizujesz do XPath 2.0, pamiętaj, że zapewnia indeks funkcji , rozwiązuje problem w ten sposób:

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

Gdzie:

  • Pierwszy parametr to sekwencja wyszukiwania
  • 2. to, czego szukać
CroWell
źródło
Należy zauważyć, że będzie to działać tylko z XPath 2+. Wszystko poniżej, co będzie musiało skorzystać z funkcji liczenia „dziwnych”.
Dan Atkinson
1
@Dan, zauważono to w linku do oryginalnej dokumentacji, dodano wyraźne powiadomienie, dzięki!
CroWell
3

W przeciwieństwie do wcześniej wspomnianego „poprzedzającego rodzeństwa” jest tak naprawdę osią do użycia, a nie „poprzedzającym”, który robi coś zupełnie innego, zaznacza wszystko w dokumencie, co jest przed znacznikiem początkowym bieżącego węzła. (patrz http://www.w3schools.com/xpath/xpath_axes.asp )

Damien
źródło
4
Nie obejmuje węzłów przodków. Nie ufaj w3schools szczegółom! Ale zgadzam się ... chociaż poprzedzające :: działa w tym przypadku, ponieważ nie ma żadnych elementów przed odpowiednimi elementami b poza przodkiem, jest bardziej kruche niż poprzedzające rodzeństwo. OTOH, OP nie powiedział nam, w jakim kontekście chciał znać pozycję wewnątrz, więc potencjalnie poprzedzające: mogłoby mieć rację.
LarsH
2

Tylko notatka do odpowiedzi Jamesa Sulaka.

Jeśli chcesz wziąć pod uwagę, że węzeł może nie istnieć i chcesz, aby był on czysto XPATH, spróbuj wykonać następujące czynności, które zwrócą 0, jeśli węzeł nie istnieje.

count(a/b[.='tsr']/preceding-sibling::*)+number(boolean(a/b[.='tsr']))
Claus Jensen
źródło
0

Problem polega na tym, że pozycja węzła niewiele znaczy bez kontekstu.

Poniższy kod poda lokalizację węzła w jego nadrzędnych węzłach podrzędnych

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;
                }
            }
        }
    }
}
Andrew Cox
źródło
1
Więc nie jest to teraz wyszukiwarka XPath, ale wyszukiwarka C #.
jamesh
0

Zajmuję się wieloma rzeczami związanymi z Novell Identity Manager i XPATH w tym kontekście wygląda trochę inaczej.

Załóżmy, że szukana wartość znajduje się w zmiennej łańcuchowej o nazwie TARGET, a XPATH będzie wyglądać tak:

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

Dodatkowo zwrócono uwagę, że aby zaoszczędzić kilka znaków miejsca, sprawdzą się również:

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

Opublikowałem również ładniejszą wersję tego na stronie Novell's Cool Solutions: Using XPATH, aby uzyskać węzeł pozycji

geoffc
źródło