Produce dati di contesto per le occorrenze prima e ultima di ogni valore di un elemento

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

  •  03-07-2019
  •  | 
  •  

Domanda

Dato il seguente 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>

Vorrei restituire qualcosa del genere

2 - 1
2 - 3
4 - 1
4 - 3

Utilizzando un set di nodi sono stato in grado di ottenere l'ultima occorrenza tramite:

exsl:node-set($list)/container[not(val = following::val)]

ma non riesco a capire come ottenere il primo.

È stato utile?

Soluzione

Per ottenere la prima e l'ultima occorrenza (ordine del documento) in ogni " <val> " gruppo, puoi utilizzare un <xsl:key> come questo:

<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: Un po 'di spiegazione poiché questo potrebbe non essere ovvio subito:

  • Il <container> restituisce tutti i key() nodi con un dato <xsl:variable>. Si utilizza la funzione //container per interrogarla.
  • Il generate-id() è dove succede tutto. Si legge come:
    • per ciascuno dei key('ContainerGroupByVal', val) nodi nel documento (" <xsl:for-each> ") seleziona & # 8230;
    • & # 8230; se ha lo stesso ID univoco (<xsl:apply-templates>) del primo nodo restituito da // o l'ultimo nodo restituito da /containers/container
    • dove /*/container restituisce l'insieme di | nodi corrispondenti all'attuale . | . | .
    • se gli ID univoci corrispondono, includi il nodo nella selezione
  • il key(…)[1] esegue l'output. Potrebbe anche essere un key(…)[last()].

EDIT # 2: Come Dimitre Novatchev sottolinea giustamente nei commenti, dovresti stare attento a usare " <=> " Stenografia XPath. Se puoi evitarlo, in ogni caso, fallo & # 8212; in parte perché potenzialmente seleziona nodi non desiderati e principalmente perché è più lento di un'espressione XPath più specifica. Ad esempio, se il tuo documento è simile a:

<containers>
  <container><!-- ... --></container>
  <container><!-- ... --></container>
  <container><!-- ... --></container>
</containers>

quindi dovresti usare " <=> " oppure " <=> " anziché " <=> " ;.


EDIT # 3: una sintassi alternativa di quanto sopra sarebbe:

<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>

Spiegazione: l'operatore unione XPath " <=> " combina i suoi argomenti in un set di nodi. Per definizione, un set di nodi non può contenere nodi duplicati & # 8212; ad esempio: " <=> " creerà un set di nodi contenente esattamente un nodo (il nodo corrente).

Ciò significa che se creiamo un set di nodi unione dal nodo corrente (". "), " <=> " nodo e il " <=> " nodo, il conteggio dei nodi sarà 2 se (e solo se) il nodo corrente è uguale a uno degli altri due nodi, in tutti gli altri casi il conteggio sarà 3.

Altri suggerimenti

Base XPath :

//container[position() = 1]  <- this is the first one
//container[position() = last()]  <- this is the last one

Ecco una serie di Funzioni XPath in modo più dettagliato.

I. XSLT 1.0

Fondamentalmente la stessa soluzione di quella di Tomalak, ma più comprensibile Inoltre è completa, quindi è sufficiente copiare e incollare il documento XML e la trasformazione e quindi premere semplicemente il " quot Transform &; pulsante del tuo IDE XSLT preferito :

<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 questa trasformazione viene applicata al documento XML fornito in origine (modificato per essere ben formato):

<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>

viene prodotto il risultato desiderato :

2-1
2-3
4-1
4-3

Nota :

  1. Utilizziamo il metodo Muenchian per raggruppare per trovare un container elemento per ogni set di tali elementi che hanno lo stesso valore per val.
  2. Dall'intero elenco di nodi di <xsl:for-each-group> elementi con lo stesso current-group() valore , abbiamo generato i dati richiesti per il primo <=> elemento nel gruppo e per l'ultimo <= > elemento nel gruppo.

II. XSLT 2.0

Questa trasformazione :

<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 applicato sullo stesso documento XML di cui sopra, produce il risultato desiderato :

<*>

Nota :

  1. L'uso dell'istruzione <=> XSLT.

  2. L'uso della funzione <=>.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top