Reputation: 5670
I have a variable named filt
holding an xml like this:
<filters>
<ISP_WebItem FILTER="Farve" FILTERNAME="Transparent" UNITCODE="" />
<ISP_WebItem FILTER="Antal" FILTERNAME="10" UNITCODE="mapper" />
<ISP_WebItem FILTER="Indpakning" FILTERNAME="Æske" UNITCODE="" />
<ISP_WebItem FILTER="Materiale" FILTERNAME="PP" UNITCODE="" />
<ISP_WebItem FILTER="Bredde" FILTERNAME="35.6" UNITCODE="cm" />
<ISP_WebItem FILTER="Farve" FILTERNAME="blue" UNITCODE="" />
<ISP_WebItem FILTER="Dybde" FILTERNAME="5" UNITCODE="mm" />
<ISP_WebItem FILTER="Farve" FILTERNAME="red" UNITCODE="" />
</filters>
I want to group these elements by its 'FILTER' attribute. That is I want an output xml like this (please note that xml elements are rearranged by filter atribute i.e. all elements with FILTER as farve is at adjacent position now)
<filters>
<ISP_WebItem FILTER="Farve" FILTERNAME="Transparent" UNITCODE="" />
<ISP_WebItem FILTER="Farve" FILTERNAME="blue" UNITCODE="" />
<ISP_WebItem FILTER="Farve" FILTERNAME="red" UNITCODE="" />
<ISP_WebItem FILTER="Antal" FILTERNAME="10" UNITCODE="mapper" />
<ISP_WebItem FILTER="Indpakning" FILTERNAME="Æske" UNITCODE="" />
<ISP_WebItem FILTER="Materiale" FILTERNAME="PP" UNITCODE="" />
<ISP_WebItem FILTER="Bredde" FILTERNAME="35.6" UNITCODE="cm" />
<ISP_WebItem FILTER="Dybde" FILTERNAME="5" UNITCODE="mm" />
</filters>
I have tried something like this:
<xsl:variable name="grouped_filt" select="$filt//ISP_WebItem[@FILTER = preceding-sibling::*[1]/@FILTER ]"></xsl:variable>
but of no use. I am unable to find anything wrong in this. Can anyone help?
Upvotes: 0
Views: 146
Reputation: 122364
The standard approach to this in XSLT 1.0 is called Muenchian Grouping:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes" />
<xsl:key name="itemByFilter" match="ISP_WebItem" use="@FILTER" />
<xsl:template match="filters">
<filters>
<xsl:apply-templates select="ISP_WebItem[
generate-id() = generate-id(key('itemByFilter', @FILTER)[1])]" />
</filters>
</xsl:template>
<xsl:template match="ISP_WebItem">
<xsl:copy-of select="key('itemByFilter', @FILTER)" />
</xsl:template>
</xsl:stylesheet>
The generate-id() = generate-id(key('itemByFilter', @FILTER)[1])
is a way to select just the first ISP_WebItem
element with each FILTER
value and apply the ISP_WebItem
template to that element. In the template we then copy all elements that have the same FILTER
value.
Edit:
You say the <filters>
element is in a "variable named filt" rather than being something you're matching directly from the input document. In that case, you can use the same key definition
<xsl:key name="itemByFilter" match="ISP_WebItem" use="@FILTER" />
but instead of <xsl:template match="filters">
you use <xsl:for-each select="$filt/filters">
. If the filt
variable is a result tree fragment rather than a node set - i.e. it was created as
<xsl:variable name="filt">
<filters>
<!-- ... -->
</filters>
</xsl:variable>
the you will need an extension function to turn it back into a node set
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:exslt="http://exslt.org/common"
exclude-result-prefixes="exslt">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes" />
<xsl:key name="itemByFilter" match="ISP_WebItem" use="@FILTER" />
<xsl:variable name="filt">
<filters>
<ISP_WebItem FILTER="Farve" FILTERNAME="Transparent" UNITCODE="" />
<ISP_WebItem FILTER="Antal" FILTERNAME="10" UNITCODE="mapper" />
<ISP_WebItem FILTER="Farve" FILTERNAME="blue" UNITCODE="" />
</filters>
</xsl:variable>
<xsl:variable name="grouped_filt">
<xsl:for-each select="exslt:node-set($filt)/filters">
<filters>
<xsl:apply-templates select="ISP_WebItem[
generate-id() = generate-id(key('itemByFilter', @FILTER)[1])]" />
</filters>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<!-- Demonstrate that the grouping did the right thing -->
<xsl:copy-of select="$grouped_filt" />
</xsl:template>
<xsl:template match="ISP_WebItem">
<xsl:copy-of select="key('itemByFilter', @FILTER)" />
</xsl:template>
</xsl:stylesheet>
This style sheet, when run over any input document (e.g. <foo/>
) will output
<?xml version="1.0"?>
<filters>
<ISP_WebItem FILTER="Farve" FILTERNAME="Transparent" UNITCODE=""/>
<ISP_WebItem FILTER="Farve" FILTERNAME="blue" UNITCODE=""/>
<ISP_WebItem FILTER="Antal" FILTERNAME="10" UNITCODE="mapper"/>
</filters>
Upvotes: 1