Grouping nested elements in xslt 1.0

864 views Asked by At

I've been looking at examples of Muenchian grouping in XSLT 1.0, specifically this example here. However I'm unable to get it working on a more complex XML structure.

My XML currently looks like this:

<?xml version="1.0" encoding="utf-8"?>
<ContestResults>
  <Contests>
    <Contest sportId="35">
      <Sport>Beach Volleyball</Sport>
      <Event>Men's</Event>
      <Ranks>
        <Rank position="1" eventId="1">
          <Athlete>Athlete 1a / Athlete 2a [GER]</Athlete>
          <Result>2</Result>
        </Rank>
        <Rank position="2" eventId="1">
          <Athlete>Athlete 1b / Athlete 2b [NED]</Athlete>
          <Result>0</Result>
        </Rank>
      </Ranks>
    </Contest>
    <Contest sportId="32">
      <Sport>Tennis</Sport>
      <Event>Women's Singles</Event>
      <Ranks>
        <Rank position="1" eventId="2">
          <Athlete>Tennis Athlete 1</Athlete>
          <Result>2</Result>
        </Rank>
        <Rank position="2" eventId="2">
          <Athlete>Tennis Athlete 2</Athlete>
          <Result>1</Result>
        </Rank>
      </Ranks>
    </Contest>
    <Contest sportId="35">
      <Sport>Beach Volleyball</Sport>
      <Event>Men's</Event>
      <Ranks>
        <Rank position="1" eventId="3">
          <Athlete>Athlete 3a / Athlete 4a [AUT]</Athlete>
          <Result>2</Result>
        </Rank>
        <Rank position="2" eventId="3">
          <Athlete>Athlete 3b / Athlete 4b [SUI]</Athlete>
          <Result>0</Result>
        </Rank>
      </Ranks>
    </Contest>
    </Contests>
</ContestResults>

However I want to group the Rank nodes under the same Ranks parent when they have the same Sport and Event. So I want the result to look like this:

<?xml version="1.0" encoding="utf-8"?>
<ContestResults>
  <Contests>
    <Contest sportId="35">
      <Sport>Beach Volleyball</Sport>
      <Event>Men's</Event>
      <Ranks>
        <Rank position="1" eventId="1">
          <Athlete>Athlete 1a / Athlete 2a [GER]</Athlete>
          <Result>2</Result>
        </Rank>
        <Rank position="2" eventId="1">
          <Athlete>Athlete 1b / Athlete 2b [NED]</Athlete>
          <Result>0</Result>
        </Rank>
        <Rank position="1" eventId="3">
          <Athlete>Athlete 3a / Athlete 4a [AUT]</Athlete>
          <Result>2</Result>
        </Rank>
        <Rank position="2" eventId="3">
          <Athlete>Athlete 3b / Athlete 4b [SUI]</Athlete>
          <Result>0</Result>
        </Rank>
      </Ranks>
    </Contest>
    <Contest sportId="32">
      <Sport>Tennis</Sport>
      <Event>Women's Singles</Event>
      <Ranks>
        <Rank position="1" eventId="2">
          <Athlete>Tennis Athlete 1</Athlete>
          <Result>2</Result>
        </Rank>
        <Rank position="2" eventId="2">
          <Athlete>Tennis Athlete 2</Athlete>
          <Result>1</Result>
        </Rank>
      </Ranks>
    </Contest>
  </Contests>
</ContestResults>

I'm just a little lost how to do this as the only other examples are dealing with a much simpler structure and I'm not sure if it's possible or how my key and templates need to be structured to do this. Can anyone provide some examples how this might be achieved?

Any advice would be appreciated.

1

There are 1 answers

2
Dimitre Novatchev On BEST ANSWER

This transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kContestById" match="Contest" use="@sportId"/>

 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="Contests">
      <Contests>
       <xsl:apply-templates/>
      </Contests>
 </xsl:template>

 <xsl:template match=
  "Contest
    [not(generate-id()
    =
     generate-id(key('kContestById', @sportId)[1]))
     ]"/>
 <xsl:template match="Ranks">
  <Ranks>
    <xsl:apply-templates select="key('kContestById', ../@sportId)/Ranks/Rank"/>
  </Ranks>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<ContestResults>
  <Contests>
    <Contest sportId="35">
      <Sport>Beach Volleyball</Sport>
      <Event>Men's</Event>
      <Ranks>
        <Rank position="1" eventId="1">
          <Athlete>Athlete 1a / Athlete 2a [GER]</Athlete>
          <Result>2</Result>
        </Rank>
        <Rank position="2" eventId="1">
          <Athlete>Athlete 1b / Athlete 2b [NED]</Athlete>
          <Result>0</Result>
        </Rank>
      </Ranks>
    </Contest>
    <Contest sportId="32">
      <Sport>Tennis</Sport>
      <Event>Women's Singles</Event>
      <Ranks>
        <Rank position="1" eventId="2">
          <Athlete>Tennis Athlete 1</Athlete>
          <Result>2</Result>
        </Rank>
        <Rank position="2" eventId="2">
          <Athlete>Tennis Athlete 2</Athlete>
          <Result>1</Result>
        </Rank>
      </Ranks>
    </Contest>
    <Contest sportId="35">
      <Sport>Beach Volleyball</Sport>
      <Event>Men's</Event>
      <Ranks>
        <Rank position="1" eventId="3">
          <Athlete>Athlete 3a / Athlete 4a [AUT]</Athlete>
          <Result>2</Result>
        </Rank>
        <Rank position="2" eventId="3">
          <Athlete>Athlete 3b / Athlete 4b [SUI]</Athlete>
          <Result>0</Result>
        </Rank>
      </Ranks>
    </Contest>
    </Contests>
</ContestResults>

produces the wanted, correct result:

<ContestResults>
   <Contests>
      <Contest sportId="35">
         <Sport>Beach Volleyball</Sport>
         <Event>Men's</Event>
         <Ranks>
            <Rank position="1" eventId="1">
               <Athlete>Athlete 1a / Athlete 2a [GER]</Athlete>
               <Result>2</Result>
            </Rank>
            <Rank position="2" eventId="1">
               <Athlete>Athlete 1b / Athlete 2b [NED]</Athlete>
               <Result>0</Result>
            </Rank>
            <Rank position="1" eventId="3">
               <Athlete>Athlete 3a / Athlete 4a [AUT]</Athlete>
               <Result>2</Result>
            </Rank>
            <Rank position="2" eventId="3">
               <Athlete>Athlete 3b / Athlete 4b [SUI]</Athlete>
               <Result>0</Result>
            </Rank>
         </Ranks>
      </Contest>
      <Contest sportId="32">
         <Sport>Tennis</Sport>
         <Event>Women's Singles</Event>
         <Ranks>
            <Rank position="1" eventId="2">
               <Athlete>Tennis Athlete 1</Athlete>
               <Result>2</Result>
            </Rank>
            <Rank position="2" eventId="2">
               <Athlete>Tennis Athlete 2</Athlete>
               <Result>1</Result>
            </Rank>
         </Ranks>
      </Contest>
   </Contests>
</ContestResults>

Explanation:

Proper use of the Muenchian grouping method and overriding the identity rule.