Re: [xsl] Muench method for two or three keys? (Sorting and Grouping)

Subject: Re: [xsl] Muench method for two or three keys? (Sorting and Grouping)
From: "G. Ken Holman" <gkholman@xxxxxxxxxxxxxxxxxxxx>
Date: Sun, 16 Jan 2005 09:47:04 -0500
At 2005-01-15 21:00 -0700, Daniel O'Donnell wrote:
What I am interested in is sorting and grouping input on two or three keys. I am building an xml document containing the index for print book. The index was written in a 4-field spreadsheet. The following shows some of the allowable permutations:

head-a, subfield1-a, subfield2-a, locater1
head-a, subfield1-b, subfield2-g, locater2
head-a, subfield1-b, subfield2-h, locater3
head-b, subfield1-x, ---, locater4
head-c, ---, ---, ---

It would have helped us to help you if you had given us XML for your data.


What I want to produce is a hierarchical list of the following type:

<list>
  <item>head-a
    <list>
      <item>subfield1-a
        <list>
          <item>subfield2-a, <seg type="locater">locater1</item>
        </list>
      </item>
      <item>subfield1-b
        <list>
          <item>subfield2-g, <seg type="locater">locater2</item></item>
          <item>subfield2-h, <seg type="locater">locater3</item></item>
        </list>
      </item>
    </list>
  </item>
  <item>head-b
    <list>
      <item>subfield1-x, <seg type="locater">locater4</item></item>
    </list>
  </item>
  <item>head-c
  </item>
</list>

Your use of mixed content for <item> seems strange ... but I've followed the example. I've assumed the end tag for seg is the mistyped initial end tag for item in order for the above to be well-formed.


So what I need is a sheet that does the following:
a) sorts records by head
b) groups records so that if two subfield1's are children of heads with the same content they are put in the same record
c) repeats the same for subfield2's

This is an example of multi-level grouping.


For those of you who are interested in what I have been doing, I've been using the "Muench Method"

I far prefer using the variable-based method than the Muenchian method when doing multi-level grouping. While it can be done using the Muenchian method, keeping track of the unique id's of common apex points can be a real pain.


Here are some examples of multi-level grouping that I've posted in the past:

  http://www.biglist.com/lists/xsl-list/archives/200401/msg00340.html
  http://www.biglist.com/lists/xsl-list/archives/200207/msg01418.html
  http://www.biglist.com/lists/xsl-list/archives/200205/msg00487.html

Following those examples, I've coded below a solution to your problem. Comments in the code show how the variable-based method of multi-level grouping is done.

I hope this helps.

............................... Ken


T:\ftemp>type daniel1.xml <?xml version="1.0" encoding="iso-8859-1"?> <all> <data><h>head-a</h><s1>subfield1-a</s1><s2>subfield2-a</s2><l>locator1</l></data> <data><h>head-a</h><s1>subfield1-b</s1><s2>subfield2-g</s2><l>locator2</l></data> <data><h>head-a</h><s1>subfield1-b</s1><s2>subfield2-h</s2><l>locator3</l></data> <data><h>head-b</h><s1>subfield1-x</s1><s2>---</s2><l>locator4</l></data> <data><h>head-c</h><s1>---</s1><s2>---</s2><l>---</l></data> </all>

T:\ftemp>saxon daniel1.xml daniel.xsl
<?xml version="1.0" encoding="utf-8"?>
<list>
   <item>head-a<list>
         <item>subfield1-a<list>
               <item>subfield2-a, <seg>locator1</seg>
               </item>
            </list>
         </item>
         <item>subfield1-b<list>
               <item>subfield2-g, <seg>locator2</seg>
               </item>
               <item>subfield2-h, <seg>locator3</seg>
               </item>
            </list>
         </item>
      </list>
   </item>
   <item>head-b<list>
         <item>subfield1-x, <seg>locator4</seg>
         </item>
      </list>
   </item>
   <item>head-c</item>
</list>
T:\ftemp>type daniel.xsl
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
                version="1.0">

<xsl:output indent="yes"/>

<xsl:template match="/">
  <xsl:variable name="recs" select="/all/data"/>
  <list>
    <!--walk through all records-->
    <xsl:for-each select="$recs">
      <xsl:if test="generate-id(.)=
                    generate-id($recs[h=current()/h])">
        <!--found unique headings-->
        <item>
          <xsl:value-of select="h"/>
          <!--do all for the given heading-->
          <xsl:call-template name="heads">
            <xsl:with-param name="recs" select="$recs"/>
          </xsl:call-template>
        </item>
      </xsl:if>
    </xsl:for-each>
  </list>
</xsl:template>

<xsl:template name="heads">
  <xsl:param name="recs"/>
  <xsl:variable name="heads" select="$recs[h=current()/h]"/>
  <xsl:choose>
    <xsl:when test="$heads[s1='---']">
      <!--no more grouping once hit this signal-->
      <xsl:if test="l!='---'">
        <xsl:text/>, <seg><xsl:value-of select="l"/></seg>
      </xsl:if>
    </xsl:when>
    <xsl:otherwise>
      <list>
        <!--walk through all of given heading-->
        <xsl:for-each select="$heads">
          <xsl:if test="generate-id(.)=
                        generate-id($heads[s1=current()/s1])">
            <!--found unique s1-->
            <item>
              <xsl:value-of select="s1"/>
              <!--do all for the given s1-->
              <xsl:call-template name="s1">
                <xsl:with-param name="recs" select="$recs"/>
              </xsl:call-template>
            </item>
          </xsl:if>
        </xsl:for-each>
      </list>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="s1">
  <xsl:param name="recs"/>
  <xsl:variable name="s1s" select="$recs[s1=current()/s1]"/>
  <xsl:choose>
    <xsl:when test="$s1s[s2='---']">
      <!--no more grouping once hit this signal-->
      <xsl:if test="l!='---'">
        <xsl:text/>, <seg><xsl:value-of select="l"/></seg>
      </xsl:if>
    </xsl:when>
    <xsl:otherwise>
      <list>
        <!--walk through all of given s1s-->
        <xsl:for-each select="$s1s">
          <xsl:if test="generate-id(.)=
                        generate-id($s1s[s2=current()/s2])">
            <!--found unique s2-->
            <item>
              <xsl:value-of select="s2"/>
              <!--do all for the given s2-->
              <xsl:call-template name="s2">
                <xsl:with-param name="recs" select="$recs"/>
              </xsl:call-template>
            </item>
          </xsl:if>
        </xsl:for-each>
      </list>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="s2">
  <xsl:param name="recs"/>
  <xsl:variable name="s2s" select="$recs[s2=current()/s2]"/>
  <xsl:choose>
    <xsl:when test="$s2s[s2='---'] or count($s2s)=1">
      <!--no more grouping once hit this signal-->
      <xsl:if test="l!='---'">
        <xsl:text/>, <seg><xsl:value-of select="l"/></seg>
      </xsl:if>
    </xsl:when>
    <xsl:otherwise>
      <!--multiple locations for a given combination of head, s1, s2-->
      <xsl:text>
</xsl:text>
      <list>
        <xsl:for-each select="$s2s">
          <xsl:if test="generate-id(.)=
                        generate-id($s2s[l=current()/l])">
            <item>
              <xsl:text/>, <seg><xsl:value-of select="l"/></seg>
            </item>
          </xsl:if>
        </xsl:for-each>
      </list>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>

T:\ftemp>rem Done!



--
World-wide on-site corporate, govt. & user group XML/XSL training.
G. Ken Holman                 mailto:gkholman@xxxxxxxxxxxxxxxxxxxx
Crane Softwrights Ltd.          http://www.CraneSoftwrights.com/s/
Box 266, Kars, Ontario CANADA K0A-2E0    +1(613)489-0999 (F:-0995)
Male Breast Cancer Awareness  http://www.CraneSoftwrights.com/s/bc
Legal business disclaimers:  http://www.CraneSoftwrights.com/legal

Current Thread