Proqb
Proqb

Reputation: 335

XSL 1.0 Double Grouping

This is my first question here.

I want to transform this xml using XSL 1.0 :

<RESULTS>
    <RES>
      <GROUP>1</GROUP>
      <SUBGROUP>A</SUBGROUP>
      <NAME>Alice</NAME>
    </RES>

    <RES>
      <GROUP>1</GROUP>
      <SUBGROUP>A</SUBGROUP>
      <NAME>Bart</NAME>
    </RES>

    <RES>
      <GROUP>1</GROUP>
      <SUBGROUP>B</SUBGROUP>
      <NAME>Keira</NAME>
    </RES>

    <RES>
      <GROUP>2</GROUP>
      <SUBGROUP>A</SUBGROUP>
      <NAME>Mike</NAME>
    </RES>

    <RES>
      <GROUP>2</GROUP>
      <SUBGROUP>B</SUBGROUP>
      <NAME>Peter</NAME>
    </RES>

    <RES>
      <GROUP>2</GROUP>
      <SUBGROUP>B</SUBGROUP>
      <NAME>Olaf</NAME>
    </RES>

</RESULTS>

Into this:

    <h1> 1 </h1>
       <h2>A</h2>
         <p>Alice</p>
         <p>Bart</p>

       <h2>B</h2>
         <p>Keira</p>

    <h1> 2 </h1>
       <h2>A</h2>
         <p>Mike</p>

       <h2>B</h2>
         <p>Peter</p>
         <p>Olaf</p>

I already tried using Muenchian Method, however this only allowed me to sort by GROUP, and I could not sort the sorted results by SUBGROUP. Note that I have to view the header only once per group/subgroup.

@ C. M. Sperberg-McQueen

I did not want to post a wall of text, but if it might help I do it:

This is one of the solutions I have tried:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="byGROUP" match="RESULTS/RES" use="GROUP" />
    <xsl:template match="RESULTS">
        <xsl:for-each select="RES[count(. | key('byGROUP', GROUP)[1]) = 1]">
            <xsl:sort select="GROUP" order="descending" />
            <h1>
                <xsl:value-of select="GROUP" />
            </h1>

            <xsl:for-each select="key('byGROUP', GROUP)">
                <xsl:sort select="SUBGROUP" order="descending" />
                <h2>
                    <xsl:value-of select="SUBGROUP" />
                </h2>

                <p>
                    <xsl:value-of select="NAME" />
                </p>

            </xsl:for-each>

        </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>

I tried using preceiding-sibling to test whether to view the SUBGROUP but I found it imposible to iterate through the nodes, so perhaps it is not a good approach.

Upvotes: 2

Views: 762

Answers (1)

JLRishe
JLRishe

Reputation: 101738

The typical way to do multiple groupings is to use the concatenation of the current level's value with all of the parent values as the key value:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" omit-xml-declaration="yes"/>
  <xsl:key name="byGROUP" match="RESULTS/RES" use="GROUP" />
  <xsl:key name="bySUBGROUP" match="RESULTS/RES"
           use="concat(GROUP, '+', SUBGROUP)" />  

  <xsl:template match="RESULTS">
    <xsl:apply-templates
      select="RES[count(. | key('byGROUP', GROUP)[1]) = 1]
                 /GROUP">
      <xsl:sort select="." order="ascending" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="GROUP">
    <h1>
      <xsl:value-of select="." />
    </h1>
    <xsl:variable name="thisGroup" select="key('byGROUP', .)" />
    <xsl:apply-templates
      select="$thisGroup[count(. | 
                          key('bySUBGROUP', concat(GROUP, '+', SUBGROUP))[1])
                         = 1]
                        /SUBGROUP">
      <xsl:sort select="." order="ascending" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="SUBGROUP">
    <h2>
      <xsl:value-of select="." />
    </h2>
    <xsl:apply-templates select="key('bySUBGROUP', concat(../GROUP, '+', .))"/>
  </xsl:template>

  <xsl:template match="RES">
    <p>
      <xsl:value-of select="NAME" />
    </p>
  </xsl:template>
</xsl:stylesheet>

When run on your sample input, this produces:

<h1>1</h1>
<h2>A</h2>
<p>Alice</p>
<p>Bart</p>
<h2>B</h2>
<p>Keira</p>
<h1>2</h1>
<h2>A</h2>
<p>Mike</p>
<h2>B</h2>
<p>Peter</p>
<p>Olaf</p>

Upvotes: 2

Related Questions