Search code examples
xmlxsltxslt-1.0

For-each group in XSLT 1.0


I want to use for-each group in XSLT 1.0 which is quite comfortable in XSLT 2.0. However my application expects XSLT 1.0. Also some additional input text in the XML

Input XML of XSLT

<?xml version="1.0" ?>
<message:GenericData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:generic="http://www.sdmx.org/resources/sdmxml/schemas/v2_1/data/generic"
 xmlns:message="http://www.sdmx.org/resources/sdmxml/schemas/v2_1/message"
 xmlns:common="http://www.sdmx.org/resources/sdmxml/schemas/v2_1/common">
<message:Header>
    <message:ID>BBSDI</message:ID>
    <message:Test>false</message:Test>
    <message:Prepared>2023-06-30T10:18:43.607+02:00</message:Prepared>
    <message:Sender id="BBK">
        <common:Name xml:lang="de">Deutsche Bundesbank</common:Name>
        <message:Contact>
            <message:Email>[email protected]</message:Email>
        </message:Contact>
    </message:Sender>
    <message:Structure structureID="BBK_SEDI" dimensionAtObservation="TIME_PERIOD">
        <common:Structure>
            <URN>urn:sdmx:org.sdmx.infomodel.datastructure.DataStructure=BBK:BBK_SEDI(1.0)</URN>
        </common:Structure>
    </message:Structure>
</message:Header>
        <message:DataSet structureRef="BBK_SEDI" >
            <generic:Series> 
                <generic:SeriesKey>  
                    <generic:Value id="BBK_SEDI_CALCULATION" value="ABZINS7"></generic:Value>        
                </generic:SeriesKey>
                <generic:Attributes>
                    <generic:Value id="BBK_ID" value="BBSDI.M.ABZINS7.R01"></generic:Value>
                </generic:Attributes>
                <generic:Obs>
                    <generic:ObsDimension value="2015-01"></generic:ObsDimension>
                    <generic:ObsValue value="3.07"></generic:ObsValue>
                </generic:Obs>
                <generic:Obs>
                    <generic:ObsDimension value="2016-01"></generic:ObsDimension>
                    <generic:ObsValue value="3.05"></generic:ObsValue> 
                </generic:Obs>
            </generic:Series>
            <generic:Series>
                <generic:SeriesKey>       
                    <generic:Value id="BBK_SEDI_CALCULATION" value="ABZINS7"></generic:Value>                       
                </generic:SeriesKey>
                <generic:Attributes>
                    <generic:Value id="BBK_ID" value="BBSDI.M.ABZINS7.R02"></generic:Value>
                </generic:Attributes>
                <generic:Obs>
                    <generic:ObsDimension value="2015-01"></generic:ObsDimension>
                    <generic:ObsValue value="1.07"></generic:ObsValue>
                </generic:Obs>
                <generic:Obs>
                    <generic:ObsDimension value="2016-01"></generic:ObsDimension>
                    <generic:ObsValue value="1.05"></generic:ObsValue>
                </generic:Obs> 
            </generic:Series>
        </message:DataSet>
    </message:GenericData>

Using XSLT 2.0 which is running quite well, it seems my application is not supporting XSLT 2.0

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://www.sdmx.org/resources/sdmxml/schemas/v2_1/data/generic">
<xsl:output indent="yes"/>

<xsl:template match="/">
    <ns1:DiscountInterestRatePercent xmlns:xslt-version="MonthlyDiscounts_XSLT_26/06/2022">
        <xsl:for-each-group select="//Series[SeriesKey/Value/@value='ABZINS7']/Obs" group-by="ObsDimension/@value">
            <Data>
                <Year>
                    <xsl:value-of select="substring-before(current-grouping-key(), '-')"/>
                </Year>
                <Month>
                    <xsl:value-of select="substring-after(current-grouping-key(), '-')"/>
                </Month>
                <RatePercentList>
                    <xsl:for-each select="current-group()">
                        <RatePercent>
                            <xsl:value-of select="ObsValue/@value"/>
                        </RatePercent>
                    </xsl:for-each>
                </RatePercentList>
            </Data>
        </xsl:for-each-group>
    </ns1:DiscountInterestRatePercent>
</xsl:template>

</xsl:stylesheet>

I want to use this logic in XSLT 1.0.


Solution

  • For XSLT 1.0 you need to use Muenchian Grouping.

    You'll notice it's fairly similar, you're just using xsl:key and key() instead of xsl:for-each-group...

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:g="http://www.sdmx.org/resources/sdmxml/schemas/v2_1/data/generic"
        xmlns:m="http://www.sdmx.org/resources/sdmxml/schemas/v2_1/message"
        exclude-result-prefixes="g m">
        <xsl:output indent="yes"/>
        <xsl:strip-space elements="*"/>
        
        <xsl:key name="obs" match="g:Obs" use="g:ObsDimension/@value"/>
        
        <xsl:template match="/*/m:DataSet">
            <DiscountInterestRatePercent xmlns:xslt-version="MonthlyDiscounts_XSLT_26/06/2022">
                <xsl:for-each select="g:Series/g:Obs[count(.|key('obs',g:ObsDimension/@value)[1])=1]">
                    <xsl:variable name="obsdim" select="g:ObsDimension/@value"/>
                    <Data>
                        <Year>
                            <xsl:value-of select="substring-before($obsdim,'-')"/>
                        </Year>
                        <Month>
                            <xsl:value-of select="substring-after($obsdim,'-')"/>
                        </Month>
                        <RatePercentList>
                            <xsl:for-each select="key('obs',$obsdim)">
                                <RatePercent>
                                    <xsl:value-of select="g:ObsValue/@value"/>
                                </RatePercent>
                            </xsl:for-each>
                        </RatePercentList>
                    </Data>
                </xsl:for-each>
            </DiscountInterestRatePercent>
        </xsl:template>
        
    </xsl:stylesheet>
    

    Fiddle: http://xsltfiddle.liberty-development.net/3Nzb5jQ

    Note: I didn't do any filtering based on ABZINS7 and I didn't bother with namespaces in the output. Those are left up to the reader.