How to select the first node for each value of a given attribute using XSLT?
I have a document that looks like this:
<template>
<otherstuff>
<group name="foo">
...stuff...
</group>
</template>
<template>
<otherstuff>
<group name="bar">
...different stuff...
</group>
</template>
<template>
<otherstuff>
<group name="foo">
...same stuff as first foo group...
</group>
</template>
What I want to do is to call a particular template for each instance of a given group name - so once for "foo" and once for "bar". Since they're identical, I don't care which of them is used to call this template.
Following the answers to Select unique node开发者_运维技巧s based on a combination of two attribute values, I've produced this code:
<xsl:key name="groupsByName" match="//group" use="@name"/>
<xsl:template match="/">
<xsl:for-each select="//group[count(.|key('groupsByName',@name)[1])!=1]">
<xsl:call-template name="template-or-group"/>
</xsl:for-each>
</xsl:template>
The result, however, is that the template isn't called. I also suspect that since my case is simpler than the question I based my code on, there's probably a simpler way. My XSLT isn't strong enough to work out what it might be, though.
I would suggest to replace your template with:
<xsl:template match="group[generate-id(.) = generate-id(key('groupsByName',@name)[1])]">
....
</xsl:template>
Or just change your xpath expression to //group[generate-id(.) = generate-id(key('groupsByName',@name)[1])]
.
count(.|some-node-set)
is usually used to check whether some node belongs to the set or not. Here you need to compare current node with the first some unique node for each group. Also it might be useful to improve an Xpath expression and match only those group elements which have @name
attributes. Having said all this we get: //group[@name][generate-id(.) = generate-id(key('groupsByName', @name)[1])]
.
Just replace:
//group[count(.|key('groupsByName',@name)[1])!=1]
with:
//group[count(.|key('groupsByName',@name)[1])=1]
Here is a complete solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="groupsByName" match="//group"
use="@name"/>
<xsl:template match="/">
<xsl:for-each select=
"//group[count(.|key('groupsByName',@name)[1])=1]">
<xsl:call-template name="template-or-group"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="template-or-group">
<xsl:value-of select="@name"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document (fixed to be well-formed):
<t>
<template>
<otherstuff/>
<group name="foo"> ...stuff... </group>
</template>
<template>
<otherstuff/>
<group name="bar"> ...different stuff... </group>
</template>
<template>
<otherstuff/>
<group name="foo"> ...same stuff as first foo group... </group>
</template>
</t>
the wanted, correct result is produced:
foo
bar
精彩评论