XSLT is fantastic for simple data structure queries - like WHERE and ORDER BY clauses. But what about the XSLT equivalent of DISTINCT?

Fortunately, there's a great answer to that - <xsl:key />.

Why would you want to use it? Well, I've been working on Blog4Umbraco a lot lately, and I've implemented custom datefoldering strategies. Now, that's great for flexibility on SEO and human-readable URLs, but if I don't have date folders set up, then the Blog Archive and Full Archive macros break, cause they rely on the unique nature of datefoldering to work - by iterating over the year folder in yyyy format, then month in mm format, then date in dd format, then the posts themselves - four levels of nested for-each's. I would be using the Exslt.ExsltSets:distinct function to retrieve all postDate values of all grandchildren, were it not broken with a nice YSOD stacktrace in Umbraco 4.5.2.

So the question is: how do we retrieve every post, and then tell XSLT only to iterate over the unique values, without resorting to .NET? Answer: <xsl:key />.

The <xsl:key /> element is designed to generate unique IDs in-memory for matching nodes based on their properties. In this instance I used:

<xsl:key name="ym"
match="*[(@isDoc or name()='node') and BlogLibrary:IsType('BlogPost', @id)]"
use="umbraco.library:FormatDateTime(./postDate, 'MMMM yyyy')" />

(P.S. main reason for using (@isDoc or name()='node') is compatibility between Umbraco < 4.0 and 4.5+ schemas - it'll work with both!)

So now I have a key called "ym", which will be created for every Umbraco content node, where the key's value for that node will be calculated from the use attribute.

This now means that I can do:

<xsl:if test="generate-id()=generate-id(key('ym',umbraco.library:FormatDateTime(./postDate, 'MMMM yyyy')))">
...
</xsl:if>

This does a few things:

  1. Calls generate-id() for the given node, which creates an ID;
  2. Calls generate-id() for any node where the "ym" key matches the expression - in this case, passing in the current node's for evaluation;
  3. Returns true if the two IDs match. If it matches, great, we render results.

Hope this is useful!