Encontre a posição de um nó usando xpath

86

Alguém sabe como obter a posição de um nó usando o xpath?

Digamos que eu tenha o seguinte xml:

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

Posso usar a seguinte consulta xpath para selecionar o terceiro <b> nó (<b> tsr </b>):

a/b[.='tsr']

O que é muito bom, mas quero retornar a posição ordinal desse nó, algo como:

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

(mas um pouco mais de trabalho!)

É mesmo possível?

editar : Esqueci de mencionar que estou usando .net 2, então é xpath 1.0!


Atualização : Acabei usando a excelente resposta de James Sulak . Para aqueles que estão interessados, aqui está minha implementação em 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
fonte
Por favor, tente não postar respostas na pergunta -> seria melhor postar isso como uma resposta, então possivelmente um link para ela a partir da pergunta.
theMayer

Respostas:

94

Experimentar:

count(a/b[.='tsr']/preceding-sibling::*)+1.
James Sulak
fonte
1
'Porque estou usando .net e ele ou não consigo lidar com o poder que usei: int position = doc.SelectNodes ("a / b [. =' Tsr '] / previous-Sibling :: b") .Count + 1; if (position> 1 || doc.SelectSingleNode ("a / b [. = 'tsr']")! = null) // Verifique se o nó realmente existe {// Faça mágica aqui}
Wilfred Knievel
em nenhum idioma indexado, você não precisa do +1
JonnyRaa
9

Você pode fazer isso com XSLT, mas não tenho certeza sobre XPath direto.

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

Percebo que o posto é antigo .. mas ..

substituir o asterisco pelo nome do nó daria a você melhores resultados

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

ao invés de

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

Se você alguma vez atualizar para XPath 2.0, observe que ele fornece índice de função , ele resolve o problema desta forma:

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

Onde:

  • O primeiro parâmetro é a sequência de pesquisa
  • 2º é o que pesquisar
CroWell
fonte
Deve-se observar que isso só funcionará com XPath 2+. Qualquer coisa abaixo disso terá que usar a função de contagem 'estranha'.
Dan Atkinson
1
@Dan, foi anotado no link para os documentos originais, aviso explícito adicionado, obrigado!
CroWell
3

Ao contrário do que foi dito anteriormente, 'irmão-precedente' é realmente o eixo a ser usado, não 'precedente', que faz algo completamente diferente, ele seleciona tudo no documento que está antes da marca de início do nó atual. (consulte http://www.w3schools.com/xpath/xpath_axes.asp )

Damien
fonte
4
Não incluindo nós ancestrais. Não confie na w3schools nos detalhes! Mas eu concordo ... embora o precedente :: funcione neste caso, porque não há elementos antes dos elementos b relevantes além do ancestral a, é mais frágil do que o irmão anterior. OTOH, o OP não nos disse em qual contexto ele queria saber a posição interna, então, potencialmente, precedendo :: poderia estar certo.
LarsH de
2

Apenas um recado à resposta de James Sulak.

Se você deseja levar em consideração que o nó pode não existir e deseja mantê-lo puramente XPATH, tente o seguinte que retornará 0 se o nó não existir.

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

O problema é que a posição do nó não significa muito sem um contexto.

O código a seguir lhe dará a localização do nó em seus nós-filhos pais

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
fonte
1
Portanto, não é realmente um localizador XPath agora, mas um localizador C #.
jamesh
0

Eu faço muitas coisas do Novell Identity Manager e o XPATH, nesse contexto, parece um pouco diferente.

Suponha que o valor que você está procurando está em uma variável de string, chamada TARGET, então o XPATH seria:

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

Além disso, foi apontado que, para economizar alguns caracteres de espaço, o seguinte também funcionaria:

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

Eu também postei uma versão mais bonita disso em Cool Solutions da Novell: Usando XPATH para obter o nó de posição

geoffc
fonte