While working on some tweaks to our website I decided that for the archive I needed to be able to select a list of categories used. Should be easy, right? Here’s a sample of the XML:
<machines-archive section="weblog" section-id="3">
<entry id="78" handle="installing-oracle-10g-on-64-bit-centos-5" linked-count="0">
<date year="2007" month="06" date="28" weekday="4">2007-06-28</date>
<time hour="12" minute="35">12:35</time>
<author />
<fields>
<title handle="installing-oracle-10g-on-64-bit-centos-5">installing Oracle 10g on 64 bit centos 5</title>
<categories>
<item handle="ruby">Ruby</item>
<item handle="java">Java</item>
<item handle="programming">Programming</item>
<item handle="code">Code</item>
</categories>
</fields>
<comments count="0" spam="0" />
</entry>
<entry id="75" handle="compulsory-wearing-of-facial-hair" linked-count="0">
<date year="2007" month="06" date="14" weekday="4">2007-06-14</date>
<time hour="14" minute="15">14:15</time>
<author />
<fields>
<title handle="compulsory-wearing-of-facial-hair">compulsory wearing of facial hair</title>
<categories>
<item handle="programming">Programming</item>
</categories>
</fields>
<comments count="0" spam="0" />
</entry>
</machines-archive>
I’m trying to get at machines-archive//item so this ought to would work, right?
<xsl:template match="machines-archive">
<xsl:for-each select=".//item[not(@handle = preceding::item/@handle)]">
<xsl:sort select="@handle" order="ascending"/>
<xsl:value-of select="@handle" /><xsl:text>, </xsl:text>
</xsl:for-each>
</xsl:template>
All the forum posts and mailing list archives I searched assured me that this’d be just fine… Except it wasn’t. It just didn’t work. Operating on all items in the document (//item) generated a distinct, de-duped set as expected, but as soon as I added the ‘.’ to work on a subset (.//item) it didn’t. Eh?!
Head-scratching, template-tweaking and tedious Googling ensued. I tried everything I could think of and after several hours of a lot of searching I eventually stumbled across a solution: EXSLT and set:distinct.
I first needed to add: xmlns:set="http://exslt.org/sets" to my stylesheet’s namespace. Then I could use set:distinct on my items:
<xsl:for-each select="set:distinct(.//item)">
<xsl:sort select="@handle" order="ascending"/>
<xsl:value-of select="@handle" /><xsl:text>, </xsl:text>
</xsl:for-each>
Job done. No one was pointing out this most elegant of solutions anywhere I found on the World Wide Web but it’s easy when you know how…
