dados de contexto produzir para primeira e última ocorrências de cada valor de um elemento
Pergunta
Dado o seguinte 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>
Eu gostaria de retornar algo como
2 - 1
2 - 3
4 - 1
4 - 3
Usando um nodeset eu tenho sido capaz de obter a última ocorrência via:
exsl:node-set($list)/container[not(val = following::val)]
mas eu não consigo descobrir como obter o primeiro.
Solução
Para obter a primeira e a última ocorrência (ordem do documento) em cada grupo "<val>
", você pode usar um <xsl:key>
assim:
<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: Um pouco de uma explicação já que este pode não ser óbvio direito longe:
- O
<xsl:key>
retorna todos os nós<container>
com um determinado<val>
. Você usar a funçãokey()
consultá-lo. - O
<xsl:variable>
é onde tudo acontece. Ele lê como:- para cada um dos nós
<container>
no documento ( "//container
") verificar ... - ... se ele tem o mesmo ID único (
generate-id()
) como o primeiro nó retornado porkey()
ou o último nó retornado porkey()
- onde
key('ContainerGroupByVal', val)
retorna o conjunto de nós<container>
combinando o<val>
atual - se os ids únicos corresponder, incluir o nó na seleção
- para cada um dos nós
- o
<xsl:for-each>
faz a saída. Ele poderia muito bem ser um<xsl:apply-templates>
.
EDIT # 2: Como Dimitre Novatchev aponta justamente nos comentários, você deve ser cuidadoso de usar o "//
" XPath taquigrafia. Se você pode evitá-lo, por todos os meios, fazê-lo - em parte porque potencialmente seleciona os nós que você não quer, e principalmente porque é mais lento do que uma expressão XPath mais específico. Por exemplo, se o documento se parece com:
<containers>
<container><!-- ... --></container>
<container><!-- ... --></container>
<container><!-- ... --></container>
</containers>
então você deve usar "/containers/container
" ou "/*/container
" em vez de "//container
".
EDIT # 3: Uma sintaxe alternativa do acima seria:
<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>
Explicação: A união XPath operador "|
" combina seus argumentos em um conjunto de nós. Por definição, um conjunto de nós não pode conter nós duplicados - por exemplo: ". | . | .
" irá criar um conjunto de nós contendo exatamente um nó (o nó atual)
Isto significa que se criar uma união nó-set do nó atual ( ""), o nó 'key(…)[1]
' eo nó 'key(…)[last()]
', é contagem de nós será 2 se (e somente se) a corrente nó é igual a um dos dois outros nós, em todos os outros casos, a contagem será 3.
Outras dicas
XPath :
//container[position() = 1] <- this is the first one
//container[position() = last()] <- this is the last one
Aqui está um conjunto de XPath funções em mais detalhes.
I. XSLT 1.0
Basicamente a mesma solução que o um por Tomalak, mas mais compreensível Também é completa, assim você só precisa copiar e colar o documento XML e a transformação e, em seguida, basta pressionar o botão "Transform" do seu 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>
quando esta transformação é aplicada sobre o documento XML originalmente fornecida-(editado para ser bem formadas):
<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>
o resultado queria é produzido :
2-1
2-3
4-1
4-3
Do nota ??strong>:
- Usamos o método Muenchian para agrupar para encontrar um elemento
container
para cada conjunto de tais elementos que têm o mesmo valor paraval
. - De todo nó-lista de elementos
container
com o mesmo valorval
, nós saída os dados necessários para o primeiro elementocontainer
no grupo e para o último elementocontainer
no grupo.
II. XSLT 2.0
Esta transformação :
<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>
quando aplicado sobre o mesmo documento XML como acima, prodices o resultado queria :
2-1
2-3
4-1
4-3
Do nota ??strong>:
-
O uso da instrução
<xsl:for-each-group>
XSLT. -
O uso da função
current-group()
.