kb.erickguedes.com
XSLT: Transformação de Documentos

Grouping e Processamento Condicional

Aula 4 de 5

xsl:for-each-group (XSLT 2.0)

Agrupa elementos por um valor chave de forma nativa e eficiente.

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="/">
    <html>
    <body>
      <h1>Produtos por Categoria</h1>

      <xsl:for-each-group select="//produto" group-by="categoria">
        <h2>Categoria: <xsl:value-of select="current-grouping-key()"/></h2>
        <p>Total: <xsl:value-of select="count(current-group())"/> produtos</p>
        <ul>
          <xsl:for-each select="current-group()">
            <li><xsl:value-of select="concat(nome, ' - R$ ', preco)"/></li>
          </xsl:for-each>
        </ul>
      </xsl:for-each-group>
    </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

Muenchian Grouping (XSLT 1.0)

Técnica clássica para agrupar quando não há suporte nativo a grouping.

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <!-- Key para Muenchian Grouping -->
  <xsl:key name="produtos-por-categoria" match="produto" use="categoria"/>

  <xsl:template match="/">
    <html>
    <body>
      <h1>Produtos por Categoria (Muenchian)</h1>

      <!-- Seleciona cada categoria uma única vez -->
      <xsl:for-each select="//produto
        [generate-id() = generate-id(key('produtos-por-categoria', categoria)[1])]">

        <h2>Categoria: <xsl:value-of select="categoria"/></h2>
        <ul>
          <xsl:for-each select="key('produtos-por-categoria', categoria)">
            <li><xsl:value-of select="concat(nome, ' - R$ ', preco)"/></li>
          </xsl:for-each>
        </ul>
      </xsl:for-each>
    </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

Processamento Condicional

<xsl:template match="produto">
  <!-- xsl:choose / when / otherwise (switch-case) -->
  <xsl:choose>
    <xsl:when test="number(preco) > 1000">
      <tr class="caro">
        <td><xsl:value-of select="nome"/></td>
        <td style="color:red"><xsl:value-of select="preco"/></td>
      </tr>
    </xsl:when>
    <xsl:when test="number(preco) > 100">
      <tr class="medio">
        <td><xsl:value-of select="nome"/></td>
        <td><xsl:value-of select="preco"/></td>
      </tr>
    </xsl:when>
    <xsl:otherwise>
      <tr class="barato">
        <td><xsl:value-of select="nome"/></td>
        <td><xsl:value-of select="preco"/></td>
      </tr>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Sorting e Numbering

<xsl:template match="/">
  <!-- Sorting -->
  <xsl:for-each select="//produto">
    <xsl:sort select="preco" data-type="number" order="descending"/>
    <xsl:sort select="nome" data-type="text" order="ascending"/>
    <p><xsl:value-of select="concat(nome, ': R$ ', preco)"/></p>
  </xsl:for-each>

  <!-- Numbering -->
  <ol>
    <xsl:for-each select="//livro">
      <li value="{position()}">
        <xsl:value-of select="titulo"/>
      </li>
    </xsl:for-each>
  </ol>
</xsl:template>

Keys em XSLT

Keys criam índices para acesso rápido a nós.

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:key name="por-id" match="produto" use="@id"/>
  <xsl:key name="por-nome" match="produto" use="nome"/>

  <xsl:template match="/">
    <!-- Lookup por key -->
    <xsl:for-each select="key('por-id', 'P001')">
      <p><xsl:value-of select="nome"/></p>
    </xsl:for-each>

    <xsl:for-each select="key('por-nome', 'Notebook')">
      <p>ID: <xsl:value-of select="@id"/> - Preço: <xsl:value-of select="preco"/></p>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Lab: Relatório com Grouping e Condicionais

# 1. Documento XML
cat << 'EOF' > vendas.xml
<?xml version="1.0"?>
<vendas>
  <vendedor nome="Ana">
    <mes valor="10000" periodo="jan"/>
    <mes valor="15000" periodo="fev"/>
    <mes valor="8000" periodo="mar"/>
  </vendedor>
  <vendedor nome="Carlos">
    <mes valor="12000" periodo="jan"/>
    <mes valor="9000" periodo="fev"/>
    <mes valor="20000" periodo="mar"/>
  </vendedor>
  <vendedor nome="Beatriz">
    <mes valor="7000" periodo="jan"/>
    <mes valor="11000" periodo="fev"/>
    <mes valor="13000" periodo="mar"/>
  </vendedor>
</vendas>
EOF

# 2. Folha XSLT com grouping e condicionais
cat << 'EOF' > relatorio.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html" indent="yes"/>

  <xsl:key name="vendedor" match="vendedor" use="nome"/>

  <xsl:template match="/">
    <html>
    <head><title>Relatório de Vendas</title></head>
    <body>
      <h1>Relatório Trimestral</h1>

      <xsl:for-each select="//vendedor">
        <xsl:variable name="total"
          select="sum(mes/number(@valor))"/>

        <h2>Vendedor: <xsl:value-of select="@nome"/></h2>
        <table border="1">
          <tr><th>Período</th><th>Valor</th><th>Status</th></tr>
          <xsl:for-each select="mes">
            <xsl:sort select="@periodo"/>
            <tr>
              <td><xsl:value-of select="@periodo"/></td>
              <td><xsl:value-of select="format-number(@valor, '#.00')"/></td>
              <td>
                <xsl:choose>
                  <xsl:when test="number(@valor) > 15000">
                    <span style="color:green">Excelente</span>
                  </xsl:when>
                  <xsl:when test="number(@valor) > 10000">
                    <span style="color:blue">Bom</span>
                  </xsl:when>
                  <xsl:otherwise>
                    <span style="color:orange">Abaixo da meta</span>
                  </xsl:otherwise>
                </xsl:choose>
              </td>
            </tr>
          </xsl:for-each>
          <tr>
            <td><strong>Total</strong></td>
            <td colspan="2"><strong>
              <xsl:value-of select="format-number($total, '#.00')"/>
            </strong></td>
          </tr>
        </table>

        <xsl:if test="$total > 30000">
          <p style="color:green">★ Vendedor destaque do trimestre!</p>
        </xsl:if>
      </xsl:for-each>

      <hr/>
      <p><strong>Meta do trimestre: R$ 30.000 por vendedor</strong></p>
      <p>Vendedores acima da meta:
        <xsl:value-of select="count(//vendedor[sum(mes/number(@valor)) > 30000])"/>
      </p>
    </body>
    </html>
  </xsl:template>
</xsl:stylesheet>
EOF

# 3. Executar
xsltproc relatorio.xsl vendas.xml > relatorio.html
type relatorio.html

Muenchian grouping é elegante para XSLT 1.0; xsl:for-each-group simplifica drasticamente no XSLT 2.0.