Вы уже получили хорошие ответы. В погоне за краткостью я представляю 16-строчное решение, основанное на ответе Dimitres :
<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>
Обратите внимание, что текущий узел контекста внутри for-each-group
является первым элементом в текущей группе, а current-group()
возвращает список всех элементов в текущей группе. Я использую тот факт, что элемент title
идентичен для ввода и вывода, и копирую первый заголовок из каждой группы.
А для полноты, решение XSLT 1.0, использующее мюнхенскую группировку (20 строк):
<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>