Produzca datos de contexto para la primera y última aparición de cada valor de un elemento.

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

  •  03-07-2019
  •  | 
  •  

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.

¿Fue útil?

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="'&#10;'" /><!-- 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 los key() 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
  • el key(…)[1] hace la salida. También podría ser un key(…)[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="'&#10;'" /><!-- 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,
              '&#xA;'
              )
      "/>
       <xsl:value-of select=
        "concat($vthisvalGroup[last()]/val,
              '-',
              $vthisvalGroup[last()]/id,
              '&#xA;'
              )
        "/>
      </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 :

  1. Utilizamos el método Muenchian para agrupar para encontrar un elemento container para cada conjunto de elementos que tengan el mismo valor para val.
  2. De la lista completa de nodos de <xsl:for-each-group> elementos con el mismo current-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, '&#xA;')"/>
        </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 :

  1. El uso de la instrucción <=> XSLT.

  2. El uso de la función <=>.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top