Reputation: 789
I would like to get the distinct values inside of a for loop, or within some group. Since the xsl:key can only be declared at the top level, how would I be able to make a xsl:key for each group? In the example below, the group would be the most outer fruit tags. Note that there's also a xsl:sort. If there is a way to accomplish this by just xpaths (preceding-sibling), I would love to know this solution as well. I'm not sure if I would need to use the Muenchian method to accomplish this, but this is what I have:
Input.xml
<root>
<fruits>
<fruit>
<fruit id="2">
<banana><taste>Yummy</taste></banana>
<banana><taste>Disgusting</taste></banana>
</fruit>
<fruit id="1">
<banana><taste>Eh</taste></banana>
<banana><taste>Disgusting</taste></banana>
</fruit>
</fruit>
<fruit>
<fruit id="2">
<banana><taste>Yummy</taste></banana>
<banana><taste>Disgusting</taste></banana>
</fruit>
<fruit id="1">
<banana><taste>Amazing</taste></banana>
<banana><taste>Disgusting</taste></banana>
</fruit>
</fruit>
</fruits>
</root>
Transform.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:key name="taste" use="." match="taste" />
<xsl:template match="root">
<xsl:apply-templates select="fruits" />
</xsl:template>
<xsl:template match="fruits">
<xsl:element name="newFruits">
<xsl:call-template name="test" />
</xsl:element>
</xsl:template>
<xsl:template name="test">
<xsl:for-each select="fruit">
<xsl:sort select="fruit/@id" />
<xsl:element name="newFruit">
<!-- xsl:for-each select="fruit/banana/taste[not(.=preceding::taste)]/.." /> -->
<xsl:for-each select="fruit/banana/taste[generate-id() = generate-id(key('taste',.)[1])]/..">
<xsl:element name="fruit">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output (comments in the output is the desired tags that should appear)
<?xml version="1.0" encoding="UTF-8"?>
<newFruits>
<newFruit>
<fruit>Yummy</fruit>
<fruit>Disgusting</fruit>
<fruit>Eh</fruit>
</newFruit>
<newFruit>
<!-- <fruit>Yummy</fruit> -->
<!-- <fruit>Disgusting</fruit> -->
<fruit>Amazing</fruit>
</newFruit>
</newFruits>
Upvotes: 1
Views: 1500
Reputation: 70598
The issue is that you want your taste
elements to be distinct per each top-level fruit
element. Your current grouping is getting the distinct elements for the whole document.
If you can't update to XSLT 2.0 then shed a tear, as you have to then use a concatenated key in XSLT 1.0, to include a unique identifier for the relevant fruit
element, which can be achieved by using generate-id()
<xsl:key name="taste" use="concat(generate-id(../../..), '|', .)" match="taste" />
Then, in your "test" template, define a variable to hold the id for the relevant fruit...
<xsl:variable name="id" select="generate-id()" />
And your expression to get the distinct tastes becomes this...
<xsl:for-each select="fruit/banana/taste[generate-id() = generate-id(key('taste', concat($id, '|', .))[1])]">
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes" />
<xsl:key name="taste" use="concat(generate-id(../../..), '|', .)" match="taste" />
<xsl:template match="root">
<xsl:apply-templates select="fruits" />
</xsl:template>
<xsl:template match="fruits">
<newFruits>
<xsl:call-template name="test" />
</newFruits>
</xsl:template>
<xsl:template name="test">
<xsl:for-each select="fruit">
<xsl:variable name="id" select="generate-id()" />
<newFruit>
<!-- xsl:for-each select="fruit/banana/taste[not(.=preceding::taste)]/.." /> -->
<xsl:for-each select="fruit/banana/taste[generate-id() = generate-id(key('taste', concat($id, '|', .))[1])]">
<fruit>
<xsl:value-of select="."/>
</fruit>
</xsl:for-each>
</newFruit>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note, you don't really need the first template, and I can't see the point of a named template, so you can simplify the above XSLT to this...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes" />
<xsl:key name="taste" use="concat(generate-id(../../..), '|', .)" match="taste" />
<xsl:template match="fruits">
<newFruits>
<xsl:apply-templates select="fruit" />
</newFruits>
</xsl:template>
<xsl:template match="fruit">
<xsl:variable name="id" select="generate-id()" />
<newFruit>
<!-- xsl:for-each select="fruit/banana/taste[not(.=preceding::taste)]/.." /> -->
<xsl:for-each select="fruit/banana/taste[generate-id() = generate-id(key('taste', concat($id, '|', .))[1])]">
<fruit>
<xsl:value-of select="."/>
</fruit>
</xsl:for-each>
</newFruit>
</xsl:template>
</xsl:stylesheet>
Upvotes: 3