Tuesday, 7 June 2011

XSLT - Dynamic Grouping Using the Muenchian Method

This blog post describes a full example on how to group XML using the Muenchian Method, using: keys, generate-id() and external parameters [xsl:param] passed into the XSL stylesheet.

My original requirement was to pass in parameters to the XSL file using C# and transform the XML on-the-fly. Therefore, I used xsl:param tags to hold the input values and generate a portion of XML based on this. I had a few problems matching templates and keys based on parameters, as this is not valid.
I therefore, generated by keys based on all the data and filtered the data I required based on the parameters.


The following code describes a full example using: Parameters, Keys and grouping. The aim is to dynamically group events by year and sort them in a descending order.


Grouping.xml
<?xml version="1.0"?>
<?xml-stylesheet type="text/xml" href="stylesheet.xsl"?>
<theme>
    <styles>        
        <style id="style1">
        <title>Test Style 1</title>
            <elements>
                <element id="element1">
                    <title>Test Element 1</title>    
                    <events>
                        <event id="event11" date="2008-10-13">
                            <title>Test Event 1</title>
                        </event>                        
                        <event id="event12" date="2009-03-18">
                            <title>Test Event 2</title>
                        </event>
                        <event id="event13" date="2009-02-26">
                            <title>Test Event 3</title>
                        </event>
                        <event id="event14" date="2011-04-12">
                            <title>Test Event 4</title>
                        </event>
                        <event id="event15" date="2010-01-01">
                            <title>Test Event 5</title>
                        </event>
                        <event id="event16" date="2010-07-06">
                            <title>Test Event 6</title>
                        </event>
                    </events>                                            
                </element>
                <element id="element2">
                    <title>Test Element 2</title>    
                    <events>
                        <event id="event21" date="2001-10-13">
                            <title>Test Event 1</title>
                        </event>                        
                        <event id="event22" date="2001-03-18">
                            <title>Test Event 2</title>
                        </event>
                        <event id="event23" date="2007-02-26">
                            <title>Test Event 3</title>
                        </event>
                        <event id="event24" date="2008-04-12">
                            <title>Test Event 4</title>
                        </event>
                        <event id="event25" date="2010-01-01">
                            <title>Test Event 5</title>
                        </event>
                        <event id="event26" date="2010-07-06">
                            <title>Test Event 6</title>
                        </event>
                    </events>                                            
                </element>
            </elements>                
        </style>
        <style id="style2">
        <title>Test Style 2</title>
            <elements>
                <element id="element3">
                    <title>Test Element 3</title>    
                    <events>
                        <event id="event31" date="2003-10-13">
                            <title>Test Event 1</title>
                        </event>                        
                        <event id="event32" date="2007-03-18">
                            <title>Test Event 2</title>
                        </event>
                        <event id="event33" date="2007-02-26">
                            <title>Test Event 3</title>
                        </event>
                        <event id="event34" date="2010-04-12">
                            <title>Test Event 4</title>
                        </event>
                        <event id="event35" date="2010-01-01">
                            <title>Test Event 5</title>
                        </event>
                        <event id="event36" date="2010-07-06">
                            <title>Test Event 6</title>
                        </event>
                    </events>                                            
                </element>                
            </elements>                
        </style>
    </styles>
</theme>





Stylesheet.xsl
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ms="urn:schemas-microsoft-com:xslt">
 
  <!-- Parameters -->
  <xsl:param name="style">style1</xsl:param>
  <xsl:param name="element">element1</xsl:param>
 
  <!-- Create a key based on the YYYY portion of the start_date attribute -->
  <xsl:key
    name="keyYear"
    match="events/event"
    use="substring(@date, 1, 4)"
  />
  <xsl:key
    name="keyEventID"
    match="events/event"
    use="@id"
  />
 
 
  <!-- Match default template -->
  <xsl:template match="/">
    <xsl:apply-templates select="/theme/styles/style[@id=$style]/elements/element[@id=$element]/events" />
  </xsl:template>
 
 
  <!-- matches on events, so only outputs once [per stream] -->
  <xsl:template match="events">
 
    <table>
      <xsl:for-each select="//event[generate-id()=generate-id(key('keyYear', substring(@date, 1, 4))[1])]">
        <xsl:sort select="substring(@date, 1, 4)" order="descending" />
 
        <!-- Save the Year to a variable -->
        <xsl:variable name="varCurrentYear"><xsl:value-of select="substring(@date, 1, 4)" /></xsl:variable>
        
        <!-- Select all the events belonging to the year -->
        <xsl:variable name="lstEventPerYear" select="/theme/styles/style[@id=$style]/elements/element[@id=$element]/events/event[substring(@date, 1, 4)=$varCurrentYear]" />
        
        <!-- Store child count -->
        <xsl:variable name="varChildCount"><xsl:value-of select="count($lstEventPerYear[generate-id(.) = generate-id(key('keyEventID', @id)[1])])" /></xsl:variable>
 
        <!-- Output year only if there are sub elements -->
        <xsl:if test="$varChildCount > 0">
            <tr>
                <td>
                    <h3><xsl:value-of select="$varCurrentYear" /></h3>
                </td>
            </tr>
          
            <xsl:for-each select="$lstEventPerYear[generate-id(.) = generate-id(key('keyEventID', @id)[1])]">
            <xsl:sort select="title" />     
            <tr>
                <td>
                    <h4>
                        <xsl:value-of select="title" />
                          <xsl:text disable-output-escaping="yes">&amp;nbsp;</xsl:text>
                        [<xsl:value-of select="ms:format-date(@date, 'dd MMM yyyy')"/>]
                    </h4>
                </td>
            </tr>             
            </xsl:for-each>
        </xsl:if>
      </xsl:for-each>
    </table>
  </xsl:template>
    
</xsl:stylesheet>

No comments: