I'm trying to produce an XML from the original one that will be grouping the same Item/@Id
while skipping the child duplicates within item
Source XML:
<products>
<item id="1">
<content desc="laptop"/>
<content desc="charger"/>
</item>
<item id="1">
<content desc="laptop"/>
<content desc="charger"/>
<content desc="mouse"/>
</item>
<item id="2">
<content desc="laptop"/>
<content desc="charger"/>
</item>
</products>
I've been checking several posts with similar question, but I'm stuck with below XSLT 1.0 code I'm trying my own:
<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="itemKey" match="item" use="@id"/>
<xsl:template match="products">
<output>
<xsl:for-each select="item[generate-id() = generate-id(key('itemKey', @id)[1])]">
<product>
<id>
<xsl:value-of select="@id"/>
</id>
<content>
<xsl:for-each select="content">
<desc>
<xsl:value-of select="@desc"/>
</desc>
</xsl:for-each>
</content>
</product>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
From the source XML, there are in total 2 item ids (1 and 2) and the group with item id="1"
has laptop
and charger
repeated in both, so those should appear only once, and mouse
will complete that group. The expected output is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<product>
<id>1</id>
<detail>
<item>laptop</item>
<item>charger</item>
<item>mouse</item>
</detail>
</product>
<product>
<id>2</id>
<detail>
<item>laptop</item>
<item>charger</item>
</detail>
</product>
</output>
I was reading that I probably have to first do a group by item/@id
and then for each group do another group by content/@desc
but not sure how to accomplish that. Can you please let me know what changes do I need to do to in my XSLT in order to achieve the expected result? Solution is expected in XSLT 1.0, but I'd also consider XSLT 2.0.
Using XSLT 2.0 again I'm not sure how to capture the unique content
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="/*">
<output>
<xsl:for-each-group select="item" group-by="@id">
<product>
<id>
<xsl:value-of select="@id"/>
</id>
<content>
</content>
</product>
</xsl:for-each-group>
</output>
</xsl:template>
</xsl:stylesheet>
The adaption of the answer I linked to earlier is straightforward; I don't see why you had a problem with it:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="item" match="item" use="@id" />
<xsl:key name="content" match="content" use="concat(@desc, '|', ../@id)" />
<xsl:template match="/products">
<output>
<!-- a group for each unique item id -->
<xsl:for-each select="item[count(. | key('item', @id)[1]) = 1]">
<xsl:variable name="id" select="@id"/>
<product>
<id>
<xsl:value-of select="$id"/>
</id>
<detail>
<!-- for each unique content in this group -->
<xsl:for-each select="key('item', @id)/content[count(. | key('content', concat(@desc, '|', $id))[1]) = 1]">
<item>
<xsl:value-of select="@desc"/>
</item>
</xsl:for-each>
</detail>
</product>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
As also mentioned in the comments, doing this in XSLT 2.0 is much easier:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/products">
<output>
<!-- a group for each unique item id -->
<xsl:for-each-group select="item" group-by="@id">
<product>
<id>
<xsl:value-of select="@id"/>
</id>
<detail>
<!-- for each unique content in this group -->
<xsl:for-each select="distinct-values(current-group()/content/@desc)">
<item>
<xsl:value-of select="."/>
</item>
</xsl:for-each>
</detail>
</product>
</xsl:for-each-group>
</output>
</xsl:template>
</xsl:stylesheet>