xsl-list
[Top] [All Lists]

RE: Re: [xsl] XSLT 2.0 multi-level grouping challenge/problem

2007-05-24 13:39:03
Thank you for your interest and courtesy.
--
Charles Knell
cknell(_at_)onebox(_dot_)com - email



-----Original Message-----
From:     Abel Braaksma <abel(_dot_)online(_at_)xs4all(_dot_)nl>
Sent:     Thu, 24 May 2007 22:31:14 +0200
To:       xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com
Subject:  Re: [xsl] XSLT 2.0 multi-level grouping challenge/problem

Hi Charles,

I see that you already received some answers, but this afternoon I was trying two different approaches and, even though you have resolved your issue by now, I thought I'd share my solutions with you anyway ;)

Method 1: a variant on tree traversal and grouping, pretty easy to follow, uses next-match, perform-sort and for-each-group

Method 2: an exercise in being concise, (almost) all in one XPath. But it does not add to readability and is basically the same as Andrew's solution

Both methods output exactly the same content. I did the exercise because I actually hoped that the XPath solution could become more clear than the XSLT solution, but by looking at it now, it reminds me of my imperative programming times ;)


Cheers,
-- Abel Braaksma


<!-- METHOD 1 -->

   <xsl:template match="/">
       <xsl:variable name="donors">
           <xsl:perform-sort select="donors/donor" >
               <xsl:sort select="state" />
               <xsl:sort select="city" />
               <xsl:sort select="organ" />
           </xsl:perform-sort>
       </xsl:variable>
<xsl:for-each-group select="$donors/*" group-adjacent="concat(city, organ)">
           <xsl:apply-templates select="current-group()[1]" />
           <tr>
               <td /><td />
               <td><xsl:value-of select="organ" /></td>
               <td><xsl:value-of select="count(current-group())" /></td>
           </tr>
       </xsl:for-each-group>
   </xsl:template>

<xsl:template match="donor[(preceding-sibling::donor[1]/state, '')[1] != state]" priority="1">
       <tr>
           <td><xsl:value-of select="state" /></td>
           <td /><td /><td />
       </tr>
       <xsl:next-match />
   </xsl:template>

<xsl:template match="donor[(preceding-sibling::donor[1]/city, '')[1] != city]">
       <tr>
           <td />
           <td><xsl:value-of select="city" /></td>
           <td /><td />
       </tr>
   </xsl:template>

   <xsl:template match="donor" />





<!-- METHOD 2: terseness -->

   <xsl:template match="/" name="main">
       <xsl:variable name="donors" select="donors/donor" />
       <xsl:copy-of select="
           for $state in distinct-values($donors/state)
           return (f:makerow($state),
for $city in distinct-values($donors[state = $state]/city) return (f:makerow(('', $city)), for $organ in distinct-values($donors[state = $state and city = $city]/organ) return f:makerow(('', '', $organ, count($donors[state = $state and city = $city and organ=$organ])))))" />

   </xsl:template>

   <xsl:function name="f:makerow">
       <xsl:param name="values" as="item()*" />
       <tr>
           <xsl:for-each select="($values, '', '', '')[position() le 4]">
               <td><xsl:sequence select="." /></td>
           </xsl:for-each>
       </tr>
   </xsl:function>




cknell(_at_)onebox(_dot_)com wrote:
Consider this multi-level grouping problem for XSLT 2.0.

I think I should be able to do this with for-each-group, but I am so far 
frustrated. I usually find the most baroque way of accomplishing a programming 
task when I first start, only later learning how to pare it down.

I am told that Blaise Pascal once began a letter to a friend saying, "Please excuse 
this long letter. I didn't have time to write a short one."

The input document is a list of organ donors.
The donor element lists the city and state of the donor and the organ to be 
donated.

The output should be a table of four columns.

The table should have a new row when the value of the <state/> element changes 
with the value of the state appearing in column one.

The table should have a new row when the value of the <city/> element changes 
with the value of the city appearing in column two.

The table should have a new row when the value of the <organ/> element changes, 
listing the organ in column three and the count of all organs of that type in the 
current city and the current state in column four.

The rows of the table should be sorted by state, city, and organ.

I've been puzzling over this for several hours. I think the correct approach is 
to create an in-memory XML document containing one element for each state, a 
second in-memory XML document containing an element for each city and state 
combination, and a third in-memory document for each city, state, and organ 
combination. I would then process the successive rows in the states table, 
using the values as a key into the cities table, using the values as a key into 
the organs table, ... well, I kind of lose track here.

Surely one of you has done something like this. Would you be so kind as to 
explain how it's done?

Thank you.

<states>
  <state></state>
<states>

<cities>
  <city></city>
  <state></state>
</cities>

<organs>
  <organ></organ>
  <city></city>
  <state></state>
</organs>

<?xml version="1.0" encoding="windows-1252"?>
<donors>
  <donor>
    <city>Pittsburgh</city>
    <state>Pennsylvania</state>
    <organ>liver</organ>
  <donor>
  <donor>
    <city>Pittsburgh</city>
    <state>Pennsylvania</state>
    <organ>liver</organ>
  <donor>
  <donor>
    <city>Pittsburgh</city>
    <state>Pennsylvania</state>
    <organ>lungs</organ>
  <donor>
  <donor>
    <city>Pittsburgh</city>
    <state>Pennsylvania</state>
    <organ>lungs</organ>
  <donor>
  <donor>
    <city>Pittsburgh</city>
    <state>Pennsylvania</state>
    <organ>kidney</organ>
  <donor>
  <donor>
    <city>Akron</city>
    <state>Ohio</state>
    <organ>kidney</organ>
  <donor>
  <donor>
    <city>Akron</city>
    <state>Ohio</state>
    <organ>kidney</organ>
  <donor>
  <donor>
    <city>Columbus</city>
    <state>Ohio</state>
    <organ>lungs</organ>
  <donor>
  <donor>
    <city>Columbus</city>
    <state>Ohio</state>
    <organ>lungs</organ>
  <donor>
  <donor>
    <city>Columbus</city>
    <state>Ohio</state>
    <organ>lungs</organ>
  <donor>
  <donor>
    <city>Cleveland</city>
    <state>Ohio</state>
    <organ>liver</organ>
  <donor>
  <donor>
    <city>Cleveland</city>
    <state>Ohio</state>
    <organ>heart</organ>
  <donor>
</donors>

<table>
  <row> <-- Header Row -->
    <td>State</td>
    <td>City</td>
    <td>Organ</td>
    <td>Count</td>
  </row>
  <row> <-- First appearance of "Ohio" -->
    <td>Ohio</td>
    <td></td>
    <td></td>
    <td></td>
  </row>
  <row> <-- First appearance of "Akron" in "Ohio" -->
    <td></td>
    <td>Akron</td>
    <td></td>
    <td></td>
  </row>
  <row> <-- First appearance of "kidney" in "Akron" in "Ohio" -->
    <td></td>
    <td></td>
    <td>kidney</td> <-- count of all kidneys from Akron, Ohio -->
    <td>2</td>
  </row>
  <row> <-- First appearance of "Columbus" in "Ohio" -->
    <td></td>
    <td>Columbus</td>
    <td></td>
    <td></td>
  </row>
  <row> <-- First appearance of "lungs" in "Columbus" in "Ohio" -->
    <td></td>
    <td></td>
    <td>lungs</td> <-- count of all lungs from Columbus, Ohio -->
    <td>2</td>
  </row>
  <row> <-- First appearance of "Cleveland" in "Ohio" -->
    <td></td>
    <td>Cleveland</td>
    <td></td>
    <td></td>
  </row>
  <row> <-- First appearance of "liver" in "Cleveland" in "Ohio" -->
    <td></td>
    <td></td>
    <td>liver</td>
    <td>1</td> <-- count of all livers from Cleveland, Ohio -->
  </row>
  <row> <-- First appearance of "heart" in "Cleveland" in "Ohio" -->
    <td></td>
    <td></td>
    <td>liver</td>
    <td>1</td> <-- count of all hearts from Cleveland, Ohio -->
  </row>
  <row> <-- First appearance of "Pennsylvania" -->
    <td>Pennsylvania</td>
    <td></td>
    <td></td>
    <td></td>
  </row>
  <row> <-- First appearance of "Pittsburgh" in "Pennsylvania" -->
    <td></td>
    <td>Pittsburgh</td>
    <td></td>
    <td></td>
  </row>
  <row> <-- First appearance of "kidney" in "Pittsburgh" in "Pennsylvania" -->
    <td></td>
    <td></td>
    <td>kidney</td> <-- count of all kidneys from Pittsburgh, Pennsylvania -->
    <td>1</td>
  </row>
  <row> <-- First appearance of "liver" in "Pittsburgh" in "Pennsylvania" -->
    <td></td>
    <td></td>
    <td>liver</td>
    <td>2</td> <-- count of all livers from Pittsburgh, Pennsylvania -->
  </row>
  <row> <-- First appearance of "lungs" in "Pittsburgh" in "Pennsylvania" -->
    <td></td>
    <td></td>
    <td>lungs</td> <-- count of all lungs from Pittsburgh, Pennsylvania -->
    <td>2</td>
  </row>
</table>



--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--




--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--