dados de contexto produzir para primeira e última ocorrências de cada valor de um elemento

StackOverflow https://stackoverflow.com/questions/807578

  •  03-07-2019
  •  | 
  •  

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.

Foi útil?

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="'&#10;'" /><!-- 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ção key() 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 por key() ou o último nó retornado por key()
    • 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
  • 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="'&#10;'" /><!-- 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,
              '&#xA;'
              )
      "/>
       <xsl:value-of select=
        "concat($vthisvalGroup[last()]/val,
              '-',
              $vthisvalGroup[last()]/id,
              '&#xA;'
              )
        "/>
      </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 :

  1. Usamos o método Muenchian para agrupar para encontrar um elemento container para cada conjunto de tais elementos que têm o mesmo valor para val.
  2. De todo nó-lista de elementos container com o mesmo valor val , nós saída os dados necessários para o primeiro elemento container no grupo e para o último elemento container 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, '&#xA;')"/>
        </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 :

  1. O uso da instrução <xsl:for-each-group> XSLT.

  2. O uso da função current-group().

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top