Re: [xsl] Variable scope issue.

Subject: Re: [xsl] Variable scope issue.
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Sat, 9 Feb 2002 13:24:09 +0000
Hi Nitin,

> The XML data is presorted.( wrt currency type and date )

This is a grouping problem that looks on the surface very similar to
the one that I just replied to about grouping airline elements, but
needs a more complex solution because of the requirement to provide
summary statistics about each group.

You want to group your reports elements by two things: Currency and
ChequeDate. You want to provide a total for all the reports with the
same Currency, and a subtotal for all the reports with the same date
within each Currency group.

The problem is, given that you're on a particular reports element, how
to get hold of all the reports in your list that have the same
Currency. You can do it by a simple XPath:

  ../reports[Currency = current()/Currency]

but this is fairly inefficient, especially because you'll be doing it
lots of times over the course of the transformation. It's better to
use a key to index all the reports elements by their Currency, as
follows:

<xsl:key name="reports-by-currency" match="reports"
         use="Currency" />

That enables you to collect together all the reports that have the
same Currency as the current reports element with:

  key('reports-by-currency', Currency)

Which is much more efficient. This enables you to create your total,
using the sum() function, with:

  Total: <xsl:value-of select="sum(key('reports-by-currency', Currency)
                                     /DepositAmt)" />

The Subtotals are a little more complicated, because you need to group
the reports elements by *both* Currency and ChequeDate. But you can
approach it in the same way - create a key that indexes the reports
elements by a combination of Currency and ChequeDate, as follows:

<xsl:key name="reports-by-currency-and-date" match="reports"
         use="concat(Currency, '+', ChequeDate)" />

And create the subtotal with:

  Subtotal: <xsl:value-of
              select="sum(key('reports-by-currency-and-date',
                              concat(Currency, '+', ChequeDate))
                            /DepositAmt)" />

To collect together the groups of reports themselves, I'd use the same
method as I recommended for grouping the airlines in the previous
mail. Apply templates to all the reports elements. Within the
template, check the immediately preceding sibling to see whether you
need to see whether you need to add a Date/Amount header and a
Currency header. Also check the immediately following sibling to see
whether you need to add a SubTotal footer and a Total footer, as
follows:

<xsl:template match="reports">
  <xsl:choose>
    <xsl:when test="preceding-sibling::reports[1]/Currency !=
                    Currency">
      Currency: <xsl:value-of select="Currency" />
      <xsl:text>&#xA;Date      Amount&#xA;</xsl:text>
    </xsl:when>
    <xsl:when test="preceding-sibling::reports[1]/ChequeDate !=
                    ChequeDate">
      <xsl:text>&#xA;Date      Amount&#xA;</xsl:text>
    </xsl:when>
  </xsl:choose>
  <xsl:value-of select="ChequeDate" />
  <xsl:text>     </xsl:text>
  <xsl:value-of select="DepositAmt" />
  <xsl:text>&#xA;</xsl:text>
  <xsl:choose>
    <xsl:when test="following-sibling::reports[1]/Currency !=
                    Currency">
      <xsl:text>Subtotal  </xsl:text>
      <xsl:value-of select="sum(key('reports-by-currency-and-date',
                                    concat(Currency, '+', ChequeDate))
                                  /DepositAmt)" />
      <xsl:text>&#xA;Total          </xsl:text>
      <xsl:value-of select="sum(key('reports-by-currency', Currency)
                                     /DepositAmt)" />
      <xsl:text>&#xA;&#xA;</xsl:text>
    </xsl:when>
    <xsl:when test="following-sibling::reports[1]/ChequeDate !=
                    ChequeDate">
      <xsl:text>Subtotal  </xsl:text>
      <xsl:value-of select="sum(key('reports-by-currency-and-date',
                                    concat(Currency, '+', ChequeDate))
                                  /DepositAmt)" />
      <xsl:text>&#xA;</xsl:text>
    </xsl:when>
  </xsl:choose>
</xsl:template>

It might be neater to split this up into separate templates, but
hopefully you get the idea.

---

Under the current XSLT 2.0 Working Draft, you could do this with:

  <xsl:for-each-group select="reports" group-by="Currency">
    <xsl:text>Currency: </xsl:text>
    <xsl:value-of select="Currency" />
    
    <xsl:for-each-group select="current-group()"
                        group-by="ChequeDate">
      <xsl:text>&#xA;Date      Amount&#xA;</xsl:text>

      <xsl:for-each select="current-group()">
        <xsl:value-of select="ChequeDate" />
        <xsl:text>     </xsl:text>
        <xsl:value-of select="DepositAmt" />
        <xsl:text>&#xA;</xsl:text>
      </xsl:for-each>

      <xsl:text>SubTotal  </xsl:text>
      <xsl:value-of select="sum(current-group()/DepositAmt)" />
      <xsl:text>&#xA;</xsl:text>
    </xsl:for-each-group>

    <xsl:text>Total          </xsl:text>
    <xsl:value-of select="sum(current-group()/DepositAmt)" />
    <xsl:text>&#xA;&#xA;</xsl:text>
  </xsl:for-each-group>

(Again, since the reports are in order, you could use group-adjacent
rather than group-by. I'm not certain whether there would be any
advantages in doing so.)
  
This is a nice illustration of the flexibility of the current-group()
function - using it to provide the items in the group both for
creating further subgroups or item details, and for providing summary
statistics for the group.

Cheers,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread