2009-06-04 21 views
24

En XSLT, ¿hay alguna manera de determinar dónde se encuentra en un documento XML al procesar un elemento?¿Cómo se genera la ruta del elemento actual en XSLT?

Ejemplo: Dado el siguiente código XML Doc Fragmento ...

<Doc> 
    <Ele1> 
    <Ele11> 
     <Ele111> 
     </Ele111> 
    </Ele11> 
    </Ele1> 
    <Ele2> 
    </Ele2> 
</Doc> 

En XSLT, si mi contexto es la "Ele111" Elemento, ¿cómo puedo obtener XSLT para dar salida a la ruta completa? Me gustaría que salga: "/ Doc/Ele1/Ele11/Ele111".

El contexto de esta pregunta: Tengo un documento muy grande y muy profundo que quiero recorrer de forma exhaustiva (genéricamente usando recursividad), y si encuentro un elemento con un atributo particular, quiero saber dónde lo encontré . Supongo que podría seguir mi camino actual mientras atravieso, pero creo que XSLT/XPath debería saberlo.

Respuesta

6

No crea que esto esté integrado en XPath, probablemente necesite una plantilla recursiva, como la de here, en la que he basado este ejemplo. Recorre cada elemento en un documento XML y muestra la ruta a ese elemento en un estilo similar al que ha descrito.

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
     xmlns:xs="http://www.w3.org/2001/XMLSchema" 
     exclude-result-prefixes="xs" 
     version="2.0"> 

    <xsl:template match="/"> 
     <paths> 
      <xsl:apply-templates/> 
     </paths> 
    </xsl:template> 

    <xsl:template match="//*"> 
     <path> 
     <xsl:for-each select="ancestor-or-self::*"> 
      <xsl:call-template name="print-step"/> 
     </xsl:for-each> 
     </path> 
     <xsl:apply-templates select="*"/> 
    </xsl:template> 

    <xsl:template name="print-step"> 
     <xsl:text>/</xsl:text> 
     <xsl:value-of select="name()"/> 
     <xsl:text>[</xsl:text> 
     <xsl:value-of select="1+count(preceding-sibling::*)"/> 
     <xsl:text>]</xsl:text> 
    </xsl:template> 

</xsl:stylesheet> 

Existen algunas complicaciones; considerar este árbol:

<root> 
    <child/> 
    <child/> 
</root> 

¿Cómo se puede diferenciar entre los dos nodos secundarios? Por lo tanto, necesita un índice en su secuencia de elementos, hijo 1 e hijo [2], por ejemplo.

3

Puede usar el antecesor XPath Axes para pasear a todos los padres y abuelos.

<xsl:for-each select="ancestor::*">... 
3

no estoy seguro de qué procesador XSLT que está usando, pero si es sajón, puede utilizar la función de extensión path(). Otros procesadores pueden tener la misma funcionalidad.

20

La respuesta aceptada actualmente devolverá rutas incorrectas. Por ejemplo, el elemento Ele2 en el XML de ejemplo OP devolvería la ruta /Doc[1]/Ele2[2]. Debe ser /Doc[1]/Ele2[1].

Aquí hay una plantilla similares XSLT 1.0 que devuelve las rutas correctas:

<xsl:template name="genPath"> 
    <xsl:param name="prevPath"/> 
    <xsl:variable name="currPath" select="concat('/',name(),'[', 
     count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/> 
    <xsl:for-each select="parent::*"> 
     <xsl:call-template name="genPath"> 
     <xsl:with-param name="prevPath" select="$currPath"/> 
     </xsl:call-template> 
    </xsl:for-each> 
    <xsl:if test="not(parent::*)"> 
     <xsl:value-of select="$currPath"/>  
    </xsl:if> 
    </xsl:template> 

He aquí un ejemplo que añadir un atributo path a todos los elementos.

de entrada XML

<Doc> 
    <Ele1> 
    <Ele11> 
     <Ele111> 
     <foo/> 
     <foo/> 
     <bar/> 
     <foo/> 
     <foo/> 
     <bar/> 
     <bar/> 
     </Ele111> 
    </Ele11> 
    </Ele1> 
    <Ele2/> 
</Doc> 

XSLT 1,0

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output indent="yes"/> 
    <xsl:strip-space elements="*"/> 

    <xsl:template match="text()|@*"> 
    <xsl:copy> 
     <xsl:apply-templates select="node()|@*"/> 
    </xsl:copy> 
    </xsl:template> 

    <xsl:template match="*"> 
    <xsl:copy> 
     <xsl:attribute name="path"> 
     <xsl:call-template name="genPath"/> 
     </xsl:attribute> 
     <xsl:apply-templates select="node()|@*"/> 
    </xsl:copy>  
    </xsl:template> 

    <xsl:template name="genPath"> 
    <xsl:param name="prevPath"/> 
    <xsl:variable name="currPath" select="concat('/',name(),'[', 
     count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/> 
    <xsl:for-each select="parent::*"> 
     <xsl:call-template name="genPath"> 
     <xsl:with-param name="prevPath" select="$currPath"/> 
     </xsl:call-template> 
    </xsl:for-each> 
    <xsl:if test="not(parent::*)"> 
     <xsl:value-of select="$currPath"/>  
    </xsl:if> 
    </xsl:template> 

</xsl:stylesheet> 

salida XML

<Doc path="/Doc[1]"> 
    <Ele1 path="/Doc[1]/Ele1[1]"> 
     <Ele11 path="/Doc[1]/Ele1[1]/Ele11[1]"> 
     <Ele111 path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]"> 
      <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[1]"/> 
      <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[2]"/> 
      <bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[1]"/> 
      <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[3]"/> 
      <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[4]"/> 
      <bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[2]"/> 
      <bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[3]"/> 
     </Ele111> 
     </Ele11> 
    </Ele1> 
    <Ele2 path="/Doc[1]/Ele2[1]"/> 
</Doc> 

Aquí está otra versión que solo emite el predicado posicional si es necesario. Este ejemplo también es diferente, ya que solo está generando la ruta en lugar de agregar un atributo.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output method="text"/> 
    <xsl:strip-space elements="*"/> 

    <xsl:template match="text()"/> 

    <xsl:template match="*"> 
     <xsl:for-each select="ancestor-or-self::*"> 
      <xsl:value-of select="concat('/',local-name())"/> 
      <!--Predicate is only output when needed.--> 
      <xsl:if test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]"> 
       <xsl:value-of select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"/> 
      </xsl:if> 
     </xsl:for-each> 
     <xsl:text>&#xA;</xsl:text> 
     <xsl:apply-templates select="node()"/> 
    </xsl:template> 

</xsl:stylesheet> 

utilizando la entrada anterior, esta salidas de hojas de estilo:

/Doc 
/Doc/Ele1 
/Doc/Ele1/Ele11 
/Doc/Ele1/Ele11/Ele111 
/Doc/Ele1/Ele11/Ele111/foo[1] 
/Doc/Ele1/Ele11/Ele111/foo[2] 
/Doc/Ele1/Ele11/Ele111/bar[1] 
/Doc/Ele1/Ele11/Ele111/foo[3] 
/Doc/Ele1/Ele11/Ele111/foo[4] 
/Doc/Ele1/Ele11/Ele111/bar[2] 
/Doc/Ele1/Ele11/Ele111/bar[3] 
/Doc/Ele2 
+0

+1 para expresiones XPATH. –

1

Desde XPath 3.0 como el apoyo de Saxon 9.8 (todas las ediciones) o Saxon 9,7 con version="3.0" en el XSLT y XmlPrime 4 (usando --xt30) así como 2017 comunicados de Altova (utilizando version="3.0" hojas de estilo) hay la incorporada en el path función (https://www.w3.org/TR/xpath-functions-30/#func-path, https://www.w3.org/TR/xpath-functions-31/#func-path), que para una entrada como

<?xml version="1.0" encoding="UTF-8"?> 
<Doc> 
    <Ele1> 
     <Ele11> 
      <Ele111> 
       <foo/> 
       <foo/> 
       <bar/> 
       <foo/> 
       <foo/> 
       <bar/> 
       <bar/> 
      </Ele111> 
     </Ele11> 
    </Ele1> 
    <Ele2/> 
</Doc> 

y una hoja de estilo como

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:math="http://www.w3.org/2005/xpath-functions/math" 
    exclude-result-prefixes="xs math" 
    version="3.0"> 

    <xsl:output method="text"/> 

    <xsl:template match="/"> 
     <xsl:value-of select="//*/path()" separator="&#10;"/> 
    </xsl:template> 

</xsl:stylesheet> 

da la salida

/Q{}Doc[1] 
/Q{}Doc[1]/Q{}Ele1[1] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[1] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[2] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[1] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[3] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[4] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[2] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[3] 
/Q{}Doc[1]/Q{}Ele2[1] 

que la producción no es tan compacto en caso de falta de espacios de nombres como la mayoría de los intentos hechos a mano, pero el formato tiene la ventaja (en al menos con soporte XPath 3.0 o 3.1) para permitir el uso de espacios de nombres y obtener un formato para la ruta devuelta que no requiere que el usuario de la expresión de ruta configure ningún enlace de espacio de nombres para evaluarlo.

+0

Agradable. Utilicé replace() para deshacerme de las cosas del espacio de nombres, lo cual era un desorden en mi contexto particular. Me alegro de que la información del espacio de nombres esté allí. – David

Cuestiones relacionadas