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);
}

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.

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>

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.

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

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 )


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']))

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;
                }
            }
        }
    }
}

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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.