Here's an XSLT 1.0 option that uses xsl:key
that is very similar to the currently accepted answer.
The difference is that if you change from XSLT 1.0 to 2.0, this answer won't break. It will work in both versions. (The other answer will fail with the error "A sequence of more than one item is not allowed as the first argument of generate-id()
" if run as version 2.0.)
Another difference is that I use name()
instead of local-name()
. By doing this I treat elements with namespace prefixes as different (which they are). If you want to treat them the same, use local-name()
. (Also, if you want to treat elements in different default namespaces (unprefixed), you could create a compound key using namespace-uri()
. Let me know if you'd like an example.)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="firsts" match="/*/*" use="name()"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:for-each select="*[count(.|key('firsts',name())[1])=1]">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If you're already using 2.0, here's another 2.0 only option using xsl:for-each-group
...
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:for-each-group select="*" group-by="name()">
<xsl:apply-templates select="current-group()[1]"/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that I use xsl:apply-templates
in both options to make the stylesheet easier to extend in the future.