Re: [xsl] Conditional display of subtotal element

Subject: Re: [xsl] Conditional display of subtotal element
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Wed, 17 Apr 2002 10:12:23 +0100
Hi Steve,

> I've combined info from an archived thread to generate subtotals and
> totals (thanks, Jeni). My problem now is that there's a requirement
> for only the first date heading for a subtotal to be shown. The
> first report listing is what I'd like, the second is what I'm
> currently getting. Following the report examples are the xml file
> and the xsl. I'm looking for suggestions for how to show or not show
> the date based on if it's the first one for this grouping.

Hmm... the grouping method that you're using isn't particularly suited
to this kind of task. What you're doing is using a "flat" grouping
method, where you just process all the rows at the same time, and then
work out for each row whether it's at the beginning or end of a
particular group in order to work out what to display.

What I'd recommend is that you use a "hierarchical" grouping method,
where you process a representative of the group to get group-level
output, and then the instances of the group to get item-level output.

The grouping constructs in XSLT 2.0 make this approach easier to
understand, so I'll just show you that before showing you the XSLT 1.1
method. You want to group your rows by RESET_DATE:

  <xsl:for-each-group select="row" group-by="RESET_DATE">
    ...
  </xsl:for-each-group>

And for each of those groups, you need to show the items, grouped by
INDEX, followed by the total for that group (which I'll pretend you
can do through a separate template called 'total' rather than
repeating all the code):

  <xsl:for-each-group select="row" group-by="RESET_DATE">
    <!-- rows -->
    <xsl:for-each-group select="current-group()" group-by="INDEX">
      ...
    </xsl:for-each-group>
    <!-- total line -->
    <xsl:call-template name="total">
      <xsl:with-param name="value"
                      select="sum(current-group()/NTL)" />
    </xsl:call-template>
  </xsl:for-each-group>

For the groups of rows grouped by INDEX, you need to create a row that
contains the date (but only if they're the first of the groups). Again
I'll assume that you can do this through a named template rather than
repeating lots of code:

  <xsl:for-each-group select="row" group-by="RESET_DATE">
    <!-- rows -->
    <xsl:for-each-group select="current-group()" group-by="INDEX">
      <xsl:call-template name="row">
        <xsl:with-param name="date">
          <xsl:if test="position() = 1">
            <xsl:value-of select="RESET_DATE" />
          </xsl:if>
        </xsl:with-param>
        <xsl:with-param name="index" select="INDEX" />
        <xsl:with-param name="value"
                        select="sum(current-group()/NTL)" />
      </xsl:call-template>
    </xsl:for-each-group>
    <!-- total line -->
    <xsl:call-template name="total">
      <xsl:with-param name="value"
                      select="sum(current-group()/NTL)" />
    </xsl:call-template>
  </xsl:for-each-group>

That's the XSLT 2.0 way, now a XSLT 1.0 way. Usually I'd use the
Muenchian Method here (see http://www.jenitennison.com/xslt/grouping)
but your input is already sorted in order, which makes things slightly
easier.

The first task is to locate a representative row for each group of
rows, grouped by RESET_DATE. Picking the first of the rows in each
group is easy -- you just have to test whether the row has an
immediately preceding sibling whose RESET_DATE is the same as this one
-- if it hasn't, then you're on the first of the group:

  <xsl:for-each select="row[not(preceding-sibling::row[1]/RESET_DATE =
                                RESET_DATE)]">
    ...
  </xsl:for-each>

You can get the "current group" using the key that you've set up
already, 'rows-by-rdate':

  <xsl:for-each select="row[not(preceding-sibling::row[1]/RESET_DATE =
                                RESET_DATE)]">
    <xsl:variable name="current-date-group"
                  select="key('rows-by-rdate', RESET_DATE)" />
    ...
  </xsl:for-each>

and then use it to create the total row, and to group by INDEX -- when
grouping by INDEX you need to find those rows in the
$current-date-group that don't have a preceding sibling row whose
INDEX is the same as their own, and the rest of the template follows
similarly:

  <xsl:for-each select="row[not(preceding-sibling::row[1]/RESET_DATE =
                                RESET_DATE)]">
    <xsl:variable name="current-date-group"
                  select="key('rows-by-rdate', RESET_DATE)" />
    <!-- rows -->
    <xsl:for-each select="$current-date-group
                            [not(preceding-sibling::row[1]/INDEX =
                                 INDEX)]">
      <xsl:variable name="current-index-group"
                    select="key('rows-by-rdate-and-index',
                                concat(RESET_DATE, '+', INDEX))" />
      <xsl:call-template name="row">
        <xsl:with-param name="date">
          <xsl:if test="position() = 1">
            <xsl:value-of select="RESET_DATE" />
          </xsl:if>
        </xsl:with-param>
        <xsl:with-param name="index" select="INDEX" />
        <xsl:with-param name="value"
                        select="sum($current-index-group/NTL)" />
      </xsl:call-template>
    </xsl:for-each-group>
    <!-- total line -->
    <xsl:call-template name="total">
      <xsl:with-param name="value"
                      select="sum($current-date-group/NTL)" />
    </xsl:call-template>
  </xsl:for-each>

As you can see, if you use this method, you can test whether or not to
provide the RESET_DATE by whether the row is the first of the groups
grouped by INDEX, simply using position() = 1.
  
Cheers,

Jeni

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


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


Current Thread