Encuentra la posición de un nodo usando xpath

86

¿Alguien sabe cómo obtener la posición de un nodo usando xpath?

Digamos que tengo el siguiente xml:

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

Puedo usar la siguiente consulta xpath para seleccionar el tercer <b> nodo (<b> tsr </b>):

a/b[.='tsr']

Lo cual está muy bien, pero quiero devolver la posición ordinal de ese nodo, algo como:

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

(¡pero un poco más de trabajo!)

¿Es siquiera posible?

editar : ¡Olvidé mencionar que estoy usando .net 2 así que es xpath 1.0!


Actualización : Terminé usando la excelente respuesta de James Sulak . Para aquellos que estén interesados, aquí está mi implementación en 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
fuente
Por favor, intente no publicar respuestas en la pregunta -> sería mejor haber publicado esto como una respuesta, luego posiblemente vinculado a ella desde la pregunta.
theMayer

Respuestas:

94

Tratar:

count(a/b[.='tsr']/preceding-sibling::*)+1.
James Sulak
fuente
1
'Porque estoy usando .net y, o no puedo manejar el poder con el que fui: int position = doc.SelectNodes ("a / b [. =' Tsr '] / previous-Sibling :: b") .Contar + 1; if (position> 1 || doc.SelectSingleNode ("a / b [. = 'tsr']")! = null) // Comprueba que el nodo realmente existe {// Haz magia aquí}
Wilfred Knievel
en cero idiomas indexados, no necesita el +1
JonnyRaa
9

Puede hacer esto con XSLT pero no estoy seguro acerca de XPath directo.

<?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
fuente
9

Me doy cuenta de que la publicación es antigua .. pero ..

reemplazar el asterisco con el nombre de nodo le daría mejores resultados

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

en vez de

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

Si alguna vez actualiza a XPath 2.0, tenga en cuenta que proporciona índice de función , resuelve el problema de esta manera:

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

Dónde:

  • El primer parámetro es la secuencia de búsqueda
  • 2do es lo que buscar
CroWell
fuente
Cabe señalar que esto solo funcionará con XPath 2+. Cualquier cosa a continuación tendrá que usar la función de conteo 'extraño'.
Dan Atkinson
1
@ Dan, se señaló en el enlace a los documentos originales, se agregó un aviso explícito, ¡gracias!
CroWell
3

A diferencia de lo que se indicó anteriormente, 'hermano anterior' es realmente el eje a usar, no 'precedente' que hace algo completamente diferente, selecciona todo en el documento que está antes de la etiqueta de inicio del nodo actual. (consulte http://www.w3schools.com/xpath/xpath_axes.asp )

Damien
fuente
4
Sin incluir los nodos ancestros. ¡No confíe en los detalles de w3schools! Pero estoy de acuerdo ... aunque precedente :: funciona en este caso, debido a que no hay elementos antes de los elementos b relevantes aparte del antepasado a, es más frágil que el hermano anterior. OTOH, el OP no nos dijo en qué contexto quería conocer la posición dentro, por lo que potencialmente anterior :: podría ser correcto.
LarsH
2

Solo una nota a la respuesta hecha por James Sulak.

Si desea tener en cuenta que es posible que el nodo no exista y desea mantenerlo puramente XPATH, intente lo siguiente que devolverá 0 si el nodo no existe.

count(a/b[.='tsr']/preceding-sibling::*)+number(boolean(a/b[.='tsr']))
Claus Jensen
fuente
0

El problema es que la posición del nodo no significa mucho sin un contexto.

El siguiente código le dará la ubicación del nodo en sus nodos secundarios principales

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
fuente
1
Así que ahora no es realmente un buscador de XPath, sino un buscador de C #.
jamesh
0

Hago muchas cosas de Novell Identity Manager, y XPATH en ese contexto se ve un poco diferente.

Suponga que el valor que está buscando está en una variable de cadena, llamada TARGET, entonces el XPATH sería:

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

Además, se señaló que para ahorrar algunos caracteres de espacio, lo siguiente también funcionaría:

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

También publiqué una versión más bonita de esto en Novell's Cool Solutions: Using XPATH to get the position node

geoffc
fuente