xsl tutorial transformar transformador test freeformater español ejemplos convertir xml xslt xpath

xml - tutorial - ¿Cómo se genera la ruta del elemento actual en XSLT?



xslt editor (5)

Desde XPath 3.0 como compatible con Saxon 9.8 (todas las ediciones) o Saxon 9.7 con version="3.0" en XSLT y XmlPrime 4 (usando --xt30 ) así como en las versiones de 2017 de Altova (usando version="3.0" stylesheets) allí es la función de path incorporada ( 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]

Ese resultado 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 (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.

En XSLT, ¿hay alguna forma de determinar dónde se encuentra en un documento XML al procesar un elemento?

Ejemplo: dado el siguiente fragmento de documento XML ...

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

En XSLT, si mi contexto es el Elemento "Ele111", ¿cómo puedo hacer que XSLT muestre 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.


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] . Debería ser /Doc[1]/Ele2[1] .

Aquí hay una plantilla XSLT 1.0 similar 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>

Aquí hay un ejemplo que agregará un atributo de path a todos los elementos.

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í hay otra versión que solo genera 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>

usando la entrada de arriba, esta salida 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


No creas que esto está integrado en XPath, probablemente necesites 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>

Hay algunas complicaciones; considera este árbol:

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

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


No estoy seguro de qué procesador XSLT está utilizando, pero si es Saxon, puede usar la función de extensión path() . Otros procesadores pueden tener la misma funcionalidad.


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

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