The <xsl:key /> element (and why it rocks)By Benjamin Howarth on
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:
- Calls generate-id() for the given node, which creates an ID;
- Calls generate-id() for any node where the "ym" key matches the expression - in this case, passing in the current node's for evaluation;
- Returns true if the two IDs match. If it matches, great, we render results.
Hope this is useful!