xsl transformar freeformater for ejemplos ejemplo xslt xpath xslt-1.0 xpath-1.0

xslt - transformar - xsl value of select



usando XPath para seleccionar elementos contiguos con un cierto valor de atributo (3)

Tengo XML así:

<span>1</span> <span class="x">2</span> <span class="x y">3</span> <span class="x">4</span> <span>5</span> <span class="x">6</span> <span>7</span> <span class="x">8</span>

Lo que quiero es usar una hoja de estilo XSLT para poner el contenido de todos los elementos cuyo atributo de class contiene x en un elemento <x> . Entonces la salida debería ser así:

1 <x>234</x> 5 <x>6</x> 7 <x>8</x>

(o, idealmente,

1 <x>2<y>3</y>4</x> 5 <x>6</x> 7 <x>8</x>

pero ese es un problema para abordar cuando he resuelto este.)

Este es el fragmento relevante de mi XSLT:

<xsl:template match="span[contains(@class,''x'') and preceding-sibling::span[1][not(contains(@class,''x''))]]"> <x><xsl:for-each select=". | following-sibling::span[contains(@class,''x'')]"> <xsl:value-of select="text()"/> </xsl:for-each></x> </xsl:template> <xsl:template match="span[contains(@class,''x'') and preceding-sibling::span[1][contains(@class,''x'')]]"> </xsl:template> <xsl:template match="span"> <xsl:value-of select="text()"/> </xsl:template>

Lo que esto produce es:

1 <x>23468</x> 5 <x>68</x> 7 <x>8</x>

Estoy bastante seguro de que tengo que usar un conteo en la expresión XPath para que no seleccione todos los siguientes elementos con clase x, solo los contiguos. ¿Pero cómo puedo contar los contiguos? ¿O estoy haciendo esto de la manera incorrecta?


Esto es complicado, pero factible (lea mucho más adelante, lo siento).

La clave de la "consecutividad" en términos de ejes XPath (que por definición no son consecutivos) es verificar si el nodo más cercano en la dirección opuesta que "cumple primero la condición" también es el que "inició" la serie en cuestión:

a b <- first node to fulfill the condition, starts series 1 b <- series 1 b <- series 1 a b <- first node to fulfill the condition, starts series 2 b <- series 2 b <- series 2 a

En su caso, una serie consta de nodos <span> que tienen la cadena x en su @class :

span[contains(concat('' '', @class, '' ''),'' x '')]

Tenga en cuenta que concavo espacios para evitar falsos positivos.

Un <span> que inicia una serie (es decir, uno que "cumple primero la condición") se puede definir como aquel que tiene una x en su clase y no está directamente precedido por otro <span> que también tenga una x :

not(preceding-sibling::span[1][contains(concat('' '', @class, '' ''),'' x '')])

Debemos verificar esta condición en un <xsl:if> para evitar que la plantilla genere resultados para los nodos que están en una serie (es decir, la plantilla hará el trabajo real solo para los "nodos iniciales").

Ahora a la parte difícil.

De cada uno de estos "nodos iniciales" debemos seleccionar todos los nodos following-sibling::span que tengan una x en su clase. También incluya el span actual para tener en cuenta series que solo tienen un elemento. De acuerdo, es bastante fácil:

. | following-sibling::span[contains(concat('' '', @class, '' ''),'' x '')]

Para cada uno de estos, ahora descubrimos si su "nodo inicial" más cercano es idéntico al que está trabajando la plantilla (es decir, que comenzó su serie). Esto significa:

  • deben ser parte de una serie (es decir, deben seguir un span con una x )

    preceding-sibling::span[1][contains(concat('' '', @class, '' ''),'' x '')]

  • ahora elimine cualquier span cuyo nodo de inicio no sea idéntico al inicio de la serie actual . Eso significa que verificamos cualquier span hermanos precedentes (que tiene una x ) que no está directamente precedido por un span con una x :

    preceding-sibling::span[contains(concat('' '', @class, '' ''),'' x '')][ not(preceding-sibling::span[1][contains(concat('' '', @class, '' ''),'' x '')]) ][1]

  • Luego usamos generate-id() para verificar la identidad del nodo. Si el nodo encontrado es idéntico a $starter , entonces el span actual es uno que pertenece a la serie consecutiva.

Poniendolo todo junto:

<xsl:template match="span[contains(concat('' '', @class, '' ''),'' x '')]"> <xsl:if test="not(preceding-sibling::span[1][contains(concat('' '', @class, '' ''),'' x '')])"> <xsl:variable name="starter" select="." /> <x> <xsl:for-each select=" . | following-sibling::span[contains(concat('' '', @class, '' ''),'' x '')][ preceding-sibling::span[1][contains(concat('' '', @class, '' ''),'' x '')] and generate-id($starter) = generate-id( preceding-sibling::span[contains(concat('' '', @class, '' ''),'' x '')][ not(preceding-sibling::span[1][contains(concat('' '', @class, '' ''),'' x '')]) ][1] ) ] "> <xsl:value-of select="text()" /> </xsl:for-each> </x> </xsl:if> </xsl:template>

Y sí, sé que no es bonito. Hay una solución basada en <xsl:key> que es más eficiente, la respuesta de Dimitre lo muestra.

Con su entrada de muestra, esta salida se genera:

1 <x>234</x> 5 <x>6</x> 7 <x>8</x>


Gracias por las soluciones. Mientras tanto, he logrado juntar algo usando una táctica completamente diferente. Estoy aprendiendo XSLT para este proyecto y lo más útil que he leído es que XSLT es como programación funcional. Así que escribí algo usando la recursión, después de haber sido apuntado en la dirección correcta por esto :

<xsl:template match="span[ contains(@class,''x'') and preceding-sibling::span[1][ not(contains(@class,''x'')) ] ]"> <x><xsl:value-of select="text()"/> <xsl:call-template name="continue"> <xsl:with-param name="next" select="following-sibling::span[1]"/> </xsl:call-template> </x> </xsl:template> <xsl:template name="continue"> <xsl:param name="next"/> <xsl:choose> <xsl:when test="$next[contains(@class,''x'')]"> <xsl:apply-templates mode="x" select="$next"/> <xsl:call-template name="continue"> <xsl:with-param name="next" select="$next/following-sibling::span[1]"/> </xsl:call-template> </xsl:when> <xsl:otherwise/><!-- Do nothing --> </xsl:choose> </xsl:template> <xsl:template match="span[ contains(@class,''x'') and preceding-sibling::span[1][ contains(@class,''x'') ] ]"/> <xsl:template match="span"> <xsl:value-of select="text()"/> </xsl:template> <xsl:template mode="x" match="span[contains(@class,''y'')]"> <y><xsl:value-of select="text()"/></y> </xsl:template> <xsl:template mode="x" match="span"> <xsl:value-of select="text()"/> </xsl:template>

No tengo idea de si esto es más o menos eficiente que hacerlo con generate-id() o claves, ¡pero ciertamente aprendí algo de tus soluciones!


I. Soluciones XSLT :

Lo que quiero es usar una hoja de estilo XSLT para poner el contenido de todos los elementos cuyo atributo de clase contiene x en un elemento <x> . Entonces la salida debería ser así:

1 <x>234</x> 5 <x>6</x> 7 <x>8</x>

Esta transformación :

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:key name="kFollowing" match= "span[contains(concat('' '', @class, '' ''), '' x '') ]" use="generate-id(preceding-sibling::span [not(contains(concat('' '', @class, '' ''), '' x '')) ][1] ) "/> <xsl:template match= "span[contains(concat('' '', @class, '' ''), '' x '') and not(contains(concat('' '', preceding-sibling::span[1]/@class, '' ''), '' x '' ) ) ]" > <x> <xsl:apply-templates mode="inGroup" select= "key(''kFollowing'', generate-id(preceding-sibling::span [not(contains(concat('' '', @class, '' ''), '' x '') ) ][1] ) ) "/> </x> </xsl:template> <xsl:template match= "span[contains(concat('' '', @class, '' ''), '' x '') and contains(concat('' '', preceding-sibling::span[1]/@class, '' ''), '' x '' ) ] "/> </xsl:stylesheet>

cuando se aplica en el documento XML provisto (envuelto en un solo elemento superior html para html bien):

<html> <span>1</span> <span class="x">2</span> <span class="x y">3</span> <span class="x">4</span> <span>5</span> <span class="x">6</span> <span>7</span> <span class="x">8</span> </html>

produce el resultado deseado y correcto :

1<x>234</x>5<x>6</x>7<x>8</x>

Entonces la adición "idealmente" :

o, idealmente,

1 <x>2<y>3</y>4</x> 5 <x>6</x> 7 <x>8</x>

pero ese es un problema para abordar cuando he resuelto este.)

Simplemente agregue a la solución anterior esta plantilla :

<xsl:template mode="inGroup" match= "span[contains(concat('' '', @class, '' ''), '' y '' ) ]"> <y><xsl:value-of select="."/></y> </xsl:template>

Al aplicar la solución así modificada al mismo documento XML, nuevamente se produce el resultado (nuevo) deseado :

1<x>2<y>3</y>4</x>5<x>6</x>7<x>8</x>

II. Solución XSLT 2.0 :

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:my="my:my" exclude-result-prefixes="my xs" > <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:template match="/*"> <xsl:for-each-group select="span" group-adjacent= "contains(concat('' '',@class,'' ''), '' x '')"> <xsl:sequence select= "if(current-grouping-key()) then my:formatGroup(current-group()) else data(current-group()) "/> </xsl:for-each-group> </xsl:template> <xsl:function name="my:formatGroup" as="node()*"> <xsl:param name="pGroup" as="node()*"/> <x> <xsl:apply-templates select="$pGroup"/> </x> </xsl:function> <xsl:template match= "span[contains(concat('' '',@class, '' ''), '' y '')]"> <y><xsl:apply-templates/></y> </xsl:template> </xsl:stylesheet>

Cuando esta transformación XSLT 2.0 se aplica en el mismo documento XML (arriba), se produce el resultado "ideal" deseado :

1<x>2<y>3</y>4</x>5<x>6</x>7<x>8</x>