You have already received good answers. In the pursuit of brevity, I present my 16-line solution based on Dimitris's answer :
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:template match="/*"> <choices> <xsl:for-each-group select="option" group-by="title"> <choice> <xsl:sequence select="title"/> <xsl:for-each-group select="current-group()/desc" group-by="."> <xsl:sequence select="."/> </xsl:for-each-group> </choice> </xsl:for-each-group> </choices> </xsl:template> </xsl:stylesheet>
Note that the current node context inside for-each-group is the first element in the current group, and current-group() returns a list of all the elements in the current group. I use the fact that the title element is identical for input and output and copies the first title from each group.
And for completeness, the XSLT 1.0 solution using Muenchian grouping (20 lines):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:key name="title" match="option/title" use="."/> <xsl:key name="desc" match="option/desc" use="."/> <xsl:template match="/*"> <choices> <xsl:for-each select="option/title[count(.|key('title',.)[1]) = 1]"> <choice> <xsl:copy-of select="."/> <xsl:for-each select="key('title',.)/../desc [count(.|key('desc', .)[../title=current()][1]) = 1]"> <xsl:copy-of select="."/> </xsl:for-each> </choice> </xsl:for-each> </choices> </xsl:template> </xsl:stylesheet>
source share