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:
XML, using GeSHi 1.0.8.8
<xsl:key
name="ym"
match="*[(@isDoc or name()='node') and
BlogLibrary:IsType('BlogPost', @id)]"
use="umbraco.library:FormatDateTime(./postDate,
'MMMM yyyy')" />
Parsed in 0.001 seconds at 219.43 KB/s
(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:
XML, using GeSHi 1.0.8.8
<xsl:if
test="generate-id()=generate-id(key('ym',umbraco.library:FormatDateTime(./postDate,
'MMMM yyyy')))">
...
</xsl:if>
Parsed in 0.001 seconds at 145.70 KB/s
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
given expression - in this case, passing in the current node's
postDate for evaluation;
- Returns true if the two IDs match.
If it matches, great, we render results.
Hope this is useful!