Search code examples
xsltxslt-grouping

XSLT 1.0: grouping positive and negative values with an xsl:key


I have this source XML and I have to group the <SpinRecord> elements by <IndirectCust> and <SalesVolume>:

<?xml version="1.0" encoding="UTF-8"?>
<SI_CSV_SPIN_OUT>
    <Recordset>
        <SpinRecord>
            <IndirectCust>1080360</IndirectCust>
            <SalesVolume>-2</SalesVolume>
        </SpinRecord>
        <SpinRecord>
            <IndirectCust>1080360</IndirectCust>
            <SalesVolume>-1</SalesVolume>
        </SpinRecord>
        <SpinRecord>
            <IndirectCust>1080360</IndirectCust>
            <SalesVolume>10</SalesVolume>
        </SpinRecord>
        <SpinRecord>
            <IndirectCust>123</IndirectCust>
            <SalesVolume>10</SalesVolume>
        </SpinRecord>
        <SpinRecord>
            <IndirectCust>123</IndirectCust>
            <SalesVolume>-9</SalesVolume>
        </SpinRecord>
        <SpinRecord>
            <IndirectCust>123</IndirectCust>
            <SalesVolume>8</SalesVolume>
        </SpinRecord>
    </Recordset>
</SI_CSV_SPIN_OUT>

I came up with this XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
   exclude-result-prefixes="xd"
   version="1.0">   
   <xsl:output indent="yes" method="xml"/>
   
   <!-- define the key to define unique elements --> 
   <xsl:key name="keyCustGroup" match="SpinRecord" use="concat(IndirectCust, '|', self::*[SalesVolume &gt; 0])"/>
   
   <!-- define which elements are unique -->
   <xsl:template match="/*/*">
       <xsl:variable name="uniqueTransactions" select="SpinRecord[generate-id()=generate-id(key('keyCustGroup',concat(IndirectCust, '|', self::*[SalesVolume &gt; 0]))[1])]"/>
       <SI_CSV_SPIN_OUT>
           <xsl:apply-templates select="$uniqueTransactions" mode="group"/>
       </SI_CSV_SPIN_OUT>
   </xsl:template>
   
   <!-- create the unique groups -->
   <xsl:template match="SpinRecord" mode="group">
       <SpinRecords>
           <xsl:apply-templates select="key('keyCustGroup',concat(IndirectCust, '|', self::*[SalesVolume &gt; 0]))" mode="item" />
       </SpinRecords>    
   </xsl:template>
   
   <!-- write the item content into each group -->
   <xsl:template match="SpinRecord" mode="item">
       <SpinRecord>
           <xsl:copy-of select="child::*"/>
       </SpinRecord>
   </xsl:template>
   
</xsl:stylesheet>

But it is not working 100%, I am getting this output and for <IndirectCust> 123 the positive values are not grouped together:

<?xml version="1.0" encoding="UTF-8"?>
<SI_CSV_SPIN_OUT>
    <SpinRecords>
        <SpinRecord>
            <IndirectCust>1080360</IndirectCust>
            <SalesVolume>-2</SalesVolume>
        </SpinRecord>
        <SpinRecord>
            <IndirectCust>1080360</IndirectCust>
            <SalesVolume>-1</SalesVolume>
        </SpinRecord>
    </SpinRecords>
    <SpinRecords>
        <SpinRecord>
            <IndirectCust>1080360</IndirectCust>
            <SalesVolume>10</SalesVolume>
        </SpinRecord>
    </SpinRecords>
    <SpinRecords>
        <SpinRecord>
            <IndirectCust>123</IndirectCust>
            <SalesVolume>10</SalesVolume>
        </SpinRecord>
    </SpinRecords>
    <SpinRecords>
        <SpinRecord>
            <IndirectCust>123</IndirectCust>
            <SalesVolume>-9</SalesVolume>
        </SpinRecord>
    </SpinRecords>
    <SpinRecords>
        <SpinRecord>
            <IndirectCust>123</IndirectCust>
            <SalesVolume>8</SalesVolume>
        </SpinRecord>
    </SpinRecords>
</SI_CSV_SPIN_OUT>

My expected output would be the following:

<?xml version="1.0" encoding="UTF-8"?>
<SI_CSV_SPIN_OUT>
    <SpinRecords>
        <SpinRecord>
            <IndirectCust>1080360</IndirectCust>
            <SalesVolume>-2</SalesVolume>
        </SpinRecord>
        <SpinRecord>
            <IndirectCust>1080360</IndirectCust>
            <SalesVolume>-1</SalesVolume>
        </SpinRecord>
    </SpinRecords>
    <SpinRecords>
        <SpinRecord>
            <IndirectCust>1080360</IndirectCust>
            <SalesVolume>10</SalesVolume>
        </SpinRecord>
    </SpinRecords>
    <SpinRecords>
        <SpinRecord>
            <IndirectCust>123</IndirectCust>
            <SalesVolume>10</SalesVolume>
        </SpinRecord>
        <SpinRecord>
            <IndirectCust>123</IndirectCust>
            <SalesVolume>8</SalesVolume>
        </SpinRecord>
    </SpinRecords>
    <SpinRecords>
        <SpinRecord>
            <IndirectCust>123</IndirectCust>
            <SalesVolume>-9</SalesVolume>
        </SpinRecord>
    </SpinRecords>
</SI_CSV_SPIN_OUT>

I am struggling with the <xsl:key>. Do you have any idea how to make this work? Do I have to use two keys, one for positive and one for negative <SalesVolume>?

Thank you, Peter


Solution

  • Could you not do simply:

    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="k1" match="SpinRecord" use="concat(IndirectCust, '|', SalesVolume > 0)"/>
    
    <xsl:template match="/SI_CSV_SPIN_OUT">
        <xsl:copy>
            <xsl:for-each select="Recordset/SpinRecord[count(. | key('k1', concat(IndirectCust, '|', SalesVolume > 0))[1]) = 1]">
                <SpinRecords>
                    <xsl:copy-of select="key('k1', concat(IndirectCust, '|', SalesVolume > 0))"/>
                </SpinRecords>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>