Grouping nodes by date in XSL

Hello,

a while ago I stumbled upon a task to display a list of entries (let’s call them events) grouped by date. The requirement was to transform the data which I got from a web service via XSL. The source data was not grouped in any way, each node had its own sub-element with the date information (let’s call the field EventDate). So the desired output had ought to look like this:

  • 15.03.2009
    • Event A
    • Event B
  • 21.03.2009
    • Event C
    • Event D

…and so forth. There is a technique in XSL which allowed me to achieve the goal. First of all, we have to define a key for the style sheet, which will include the dates, by which we want to group our data:

<xsl:key name="dateGroup" match="Event"
                          use="substring-before(EventDate,'T')"/>

So, we name the key dateGroup, it has to match nodes of type Event, and its value should be set to a part of the EventDate value (the substring-before function). Since our date format looks like that:

2009-03-15T15:30:00

, and we don’t want to take exact time into consideration (only the date), we shall use the sub-string of the value. So far, so good. Note that the key declaration is a top-level element in XSL.

In the next step, we shall select only the nodes with unique dates:

<xsl:template match="/ArrayOfEvent">
  <xsl:for-each select="Event[generate-id(.)=generate-id(
    key('dateGroup',substring-before(EventDate,'T')))]">
    <ul>
      <li>
        <xsl:value-of select="substring-before(EventDate,'T')" />
      </li>
    </ul>
  </xsl:for-each>
</xsl:template>

A couple of words of explanation: two additional XSL functions have been used here. The key() function uses the key defined before in order to find a node (an event in this case) with a given date. The generate-id() function creates and returns an unique identifier for a node. The dot (.) represents a current item in the for-each loop. Hence, the loop goes only over those Event-nodes, for which the equation is true, therefore for those which have an unique date.

OK, but that’s the first part. We already got first level of our desired list – the dates. Still we have to get a list of events for each of those unique dates. And again, the key() function comes to rescue. Within the previous loop, for example after displaying the date as in the previous snippet, we use another for-each loop and go over the nodes/events which have in fact the date:

<ul>
  <xsl:for-each select="key('dateGroup',substring-before(EventDate,'T'))">
    <li>
      <xsl:value-of select="Title" />
    </li>
  </xsl:for-each>
</ul>

Assuming that our event nodes have an element called Title, we display here its value. The function used here selects only the events which have the date equal to the date in the first level’s loop current iteration. The final code could look like this:

<xsl:template match="/ArrayOfEvent">
  <xsl:for-each select="Event[generate-id(.)=generate-id(
    key('dateGroup',substring-before(EventDate,'T')))]">
    <ul>
      <li>
        <xsl:value-of select="substring-before(EventDate,'T')" />
        <ul>
          <xsl:for-each select="key('dateGroup',
             substring-before(EventDate,'T'))">
            <li>
              <xsl:value-of select="Title" />
            </li>
          </xsl:for-each>
        </ul>
      </li>
    </ul>
  </xsl:for-each>
</xsl:template>

Hope this helps. Happy XSLTransforming!