Produzca datos de contexto para la primera y última aparición de cada valor de un elemento.
Pregunta
Dado el siguiente xml:
<container>
<val>2</val>
<id>1</id>
</container>
<container>
<val>2</val>
<id>2</id>
</container>
<container>
<val>2</val>
<id>3</id>
</container>
<container>
<val>4</val>
<id>1</id>
</container>
<container>
<val>4</val>
<id>2</id>
</container>
<container>
<val>4</val>
<id>3</id>
</container>
Me gustaría devolver algo como
2 - 1
2 - 3
4 - 1
4 - 3
Utilizando un conjunto de nodos pude obtener la última aparición a través de:
exsl:node-set($list)/container[not(val = following::val)]
pero no puedo entender cómo obtener el primero.
Solución
Para obtener la primera y la última aparición (orden de documentos) en cada " <val>
" grupo, puede usar un <xsl:key>
como este:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" />
<xsl:key name="ContainerGroupByVal" match="container" use="val" />
<xsl:variable name="ContainerGroupFirstLast" select="//container[
generate-id() = generate-id(key('ContainerGroupByVal', val)[1])
or
generate-id() = generate-id(key('ContainerGroupByVal', val)[last()])
]" />
<xsl:template match="/">
<xsl:for-each select="$ContainerGroupFirstLast">
<xsl:value-of select="val" />
<xsl:text> - </xsl:text>
<xsl:value-of select="id" />
<xsl:value-of select="' '" /><!-- LF -->
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EDIT # 1: Una pequeña explicación ya que esto podría no ser obvio de inmediato:
-
<container>
devuelve todos loskey()
nodos que tienen un<xsl:variable>
dado. Utiliza la función//container
para consultarlo. - El
generate-id()
es donde sucede todo. Se lee como:- para cada uno de los
key('ContainerGroupByVal', val)
nodos en el documento ("<xsl:for-each>
") verifique & # 8230; - & # 8230; si tiene la misma identificación única (
<xsl:apply-templates>
) que el primer nodo devuelto por//
o el último nodo devuelto por/containers/container
- donde
/*/container
devuelve el conjunto de|
nodos que coinciden con el. | . | .
actual
- si los identificadores únicos coinciden, incluya el nodo en la selección
- para cada uno de los
- el
key(…)[1]
hace la salida. También podría ser unkey(…)[last()]
.
EDITAR # 2: Como Dimitre Novatchev señala correctamente en los comentarios, debe tener cuidado al usar " <=> " XPath taquigrafía. Si puede evitarlo, por supuesto, hágalo & # 8212; en parte porque potencialmente selecciona nodos que no desea, y principalmente porque es más lento que una expresión XPath más específica. Por ejemplo, si su documento se ve así:
<containers>
<container><!-- ... --></container>
<container><!-- ... --></container>
<container><!-- ... --></container>
</containers>
entonces deberías usar " <=> " o " <=> " en lugar de " <=> " ;.
EDIT # 3: Una sintaxis alternativa de lo anterior sería:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" />
<xsl:key name="ContainerGroupByVal" match="container" use="val" />
<xsl:variable name="ContainerGroupFirstLast" select="//container[
count(
.
| key('ContainerGroupByVal', val)[1]
| key('ContainerGroupByVal', val)[last()]
) = 2
]" />
<xsl:template match="/">
<xsl:for-each select="$ContainerGroupFirstLast">
<xsl:value-of select="val" />
<xsl:text> - </xsl:text>
<xsl:value-of select="id" />
<xsl:value-of select="' '" /><!-- LF -->
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Explicación: El operador de unión XPath " <=> " combina sus argumentos en un conjunto de nodos. Por definición, un conjunto de nodos no puede contener nodos duplicados & # 8212; por ejemplo: " <=> " creará un conjunto de nodos que contiene exactamente un nodo (el nodo actual).
Esto significa que si creamos un conjunto de nodos de unión desde el nodo actual (". "), el " <=> " nodo y el " <=> " nodo, su recuento de nodos será 2 si (y solo si) el nodo actual es igual a uno de los otros dos nodos, en todos los demás casos el recuento será 3.
Otros consejos
Básico XPath :
//container[position() = 1] <- this is the first one
//container[position() = last()] <- this is the last one
Aquí hay un conjunto de funciones XPath con más detalle.
I. XSLT 1.0
Básicamente la misma solución que la de Tomalak, pero más comprensible También está completa, por lo que solo necesita copiar y pegar el documento XML y la transformación y luego simplemente presione " Transformar " botón de su IDE XSLT favorito :
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kContByVal" match="container"
use="val"/>
<xsl:template match="/*">
<xsl:for-each select=
"container[generate-id()
=
generate-id(key('kContByVal',val)[1])
]
">
<xsl:variable name="vthisvalGroup"
select="key('kContByVal', val)"/>
<xsl:value-of select=
"concat($vthisvalGroup[1]/val,
'-',
$vthisvalGroup[1]/id,
'
'
)
"/>
<xsl:value-of select=
"concat($vthisvalGroup[last()]/val,
'-',
$vthisvalGroup[last()]/id,
'
'
)
"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
cuando esta transformación se aplica en el documento XML proporcionado originalmente (editado para que esté bien formado):
<t>
<container>
<val>2</val>
<id>1</id>
</container>
<container>
<val>2</val>
<id>2</id>
</container>
<container>
<val>2</val>
<id>3</id>
</container>
<container>
<val>4</val>
<id>1</id>
</container>
<container>
<val>4</val>
<id>2</id>
</container>
<container>
<val>4</val>
<id>3</id>
</container>
</t>
se produce el resultado deseado :
2-1
2-3
4-1
4-3
Nota :
- Utilizamos el método Muenchian para agrupar para encontrar un elemento
container
para cada conjunto de elementos que tengan el mismo valor paraval
. - De la lista completa de nodos de
<xsl:for-each-group>
elementos con el mismocurrent-group()
valor , mostramos los datos requeridos para el primer elemento <=> en el grupo y para el último <= > elemento en el grupo.
II. XSLT 2.0
Esta transformación :
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/*">
<xsl:for-each-group select="container"
group-by="val">
<xsl:for-each select="current-group()[1], current-group()[last()]">
<xsl:value-of select=
"concat(val, '-', id, '
')"/>
</xsl:for-each>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
cuando se aplica en el mismo documento XML que el anterior, indica el resultado deseado :
<*>Nota :
-
El uso de la instrucción <=> XSLT.
-
El uso de la función <=>.