Search code examples
xmlxsltxslt-2.0xslt-grouping

XSLT2.0: How to use nested for-each-group to compress xml records


I am trying to use nested for-each-group and for-each with current-group() to compress like data into one xml record based on two keys. The first key being ID and second being Inv_Link

I am getting the expected results for the outer loop but the inner loop when I use //Record i get all keys in each result, with . i only get the first data element. Whats the proper selector to get all nested keys with in the parent key?

Thanks for any help!

XML Data Set

<Data>
    <Record>
        <ID>01_2019</ID>
        <Link>ICE2</Link>
        <Component_ID>DEBT</Component_ID>
        <Amt>1500</Amt>
    </Record>
    <Record>
        <ID>01_2019</ID>
        <Link>ICE1</Link>
        <Component_ID>EQT</Component_ID>
        <Amt>200</Amt>
    </Record>
    <Record>
        <ID>01_2019</ID>
        <Link>ICE1</Link>
        <Component_ID>CASH</Component_ID>
        <Amt>100</Amt>
    </Record>
    <Record>
        <ID>01_2020</ID>
        <Link>ICE3</Link>
        <Component_ID>CASH</Component_ID>
        <Amt>100</Amt>
    </Record>
</Data>

XSLT I'm using now:

   <xsl:template match="Data">
        <xsl:for-each-group select="Record" group-by="ID">
        <xsl:for-each select="current-group()">
          <Record>
            <groupkey><xsl:value-of select="current-grouping-key()"/></groupkey>
            <AssetEvent>
                <ID> <xsl:copy> <xsl:value-of select="ID/text()" /> </xsl:copy> </ID>
                <DecompositionSequence>
                    <xsl:for-each-group select="Record" group-by="Link">
                    <groupkey><xsl:value-of select="current-grouping-key()"/></groupkey>
                    <xsl:for-each select="current-group()">
                        <Decompositions>
                            <Link> 
                              <ID><xsl:copy><xsl:value-of select="Link/text()" /> </xsl:copy> <ID> 
                            </Link>
                            <DecompositionDataSequence>
                                <DecompositionData>
                                    <Component>
                                        <ID> <xsl:copy> <xsl:value-of select="Component_ID/text()" /> </xsl:copy> </ID> 
                                    </Component>
                                    <Amt> <xsl:copy> <xsl:value-of select="Amt/text()" /> </xsl:copy> </Amt>
                                </DecompositionData>
                            </DecompositionDataSequence>
                        </Decompositions>   
                    </xsl:for-each>
                    </xsl:for-each-group>
              </DecompositionSequence>
            </AssetEvent>
          </Record>
        </xsl:for-each>
        </xsl:for-each-group>
  </xsl:template>

Current Results, I get a total of 2 records but with none of the inner groupings (if i use //Record then i get all for both result records):

    <Record>
      <groupkey>ICE 01_2019</groupkey>
      <AssetEvent>
        <ID>ICE 01_2019</ID>
        <DecompositionSequence />
      </AssetEvent>
    </Record>
    <Record>
      <groupkey>01_2020</groupkey>
      <AssetEvent>
        <ID>01_2020</ID>
        <DecompositionSequence />
      </AssetEvent>
    </Record>


What I'm expecting:

    <Record>
      <groupkey>01_2019</groupkey>
      <AssetEvent>
        <ID>01_2019</ID>
        <DecompositionSequence>
          <groupkey>ICE2</groupkey>
          <Decompositions>
            <InvestmentLink>ICE2</InvestmentLink>
            <DecompositionDataSequence>
              <DecompositionData>
                <Component>
                  <ID>DEBT</ID>
                </Component>
                <Amt>150</Amt>
              </DecompositionData>
            </DecompositionDataSequence>
          </Decompositions>
          <groupkey>ICE1</groupkey>
          <Decompositions>
            <InvestmentLink>ICE1</InvestmentLink>
            <DecompositionDataSequence>
              <DecompositionData>
                <Component>
                  <ID>EQT</ID>
                </Component>
                <Amt>150</Amt>
              </DecompositionData>
              <DecompositionData>
                <Component>
                  <ID>CASH</ID>
                </Component>
                <Amt>150</Amt>
              </DecompositionData>
            </DecompositionDataSequence>
          </Decompositions>
        </DecompositionSequence>
      </AssetEvent>
    </Record>
    <Record>
      <groupkey>01_2020</groupkey>
      <AssetEvent>
        <ID>01_2020</ID>
        <DecompositionSequence>
          <groupkey>ICE3</groupkey>
          <Decompositions>
            <InvestmentLink>ICE3</InvestmentLink>
            <DecompositionDataSequence>
              <DecompositionData>
                <Component>
                  <ID>CASH</ID>
                </Component>
                <Amt>100</Amt>
              </DecompositionData>
            </DecompositionDataSequence>
          </Decompositions>
        </DecompositionSequence>
      </AssetEvent>
    </Record>


Solution

  • Assuming your input sample is

    <Data>
        <Record>
            <ID>01_2019</ID>
            <Link>ICE2</Link>
            <Component_ID>DEBT</Component_ID>
            <Amt>1500</Amt>
        </Record>
        <Record>
            <ID>01_2019</ID>
            <Link>ICE1</Link>
            <Component_ID>EQT</Component_ID>
            <Amt>200</Amt>
        </Record>
        <Record>
            <ID>01_2019</ID>
            <Link>ICE1</Link>
            <Component_ID>CASH</Component_ID>
            <Amt>100</Amt>
        </Record>
        <Record>
            <ID>01_2020</ID>
            <Link>ICE3</Link>
            <Component_ID>CASH</Component_ID>
            <Amt>100</Amt>
        </Record>
    </Data>
    

    then the code

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        exclude-result-prefixes="#all"
        version="3.0">
    
      <xsl:output indent="yes"/>
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:template match="Data">
        <xsl:for-each-group select="Record" group-by="ID">
            <xsl:copy>
                <groupkey>
                    <xsl:value-of select="current-grouping-key()"/>
                </groupkey>
                <AssetEvent>
                    <xsl:copy-of select="ID"/>
                    <DecompositionSequence>
                      <xsl:for-each-group select="current-group()" group-by="Link">
                          <groupkey>
                              <xsl:value-of select="current-grouping-key()"/>
                          </groupkey>
                          <Decompositions>
                              <InvestmentLink>
                                  <xsl:value-of select="current-grouping-key()"/>
                              </InvestmentLink>
                              <DecompositionDataSequence>
                                  <xsl:apply-templates select="current-group()"/>
                              </DecompositionDataSequence>
                          </Decompositions>
                      </xsl:for-each-group>
                    </DecompositionSequence>
                </AssetEvent>
    
            </xsl:copy>
          </xsl:for-each-group>
        </xsl:template>
    
        <xsl:template match="Record">
            <DecompositionData>
                <xsl:apply-templates select="* except (ID, Link)"/>
            </DecompositionData>
        </xsl:template>
    
        <xsl:template match="Component_ID">
            <Component>
                <ID>
                    <xsl:value-of select="."/>
                </ID>
            </Component>
        </xsl:template>
    
    </xsl:stylesheet>
    

    gives the output

    <?xml version="1.0" encoding="UTF-8"?>
    <Record>
       <groupkey>01_2019</groupkey>
       <AssetEvent>
          <ID>01_2019</ID>
          <DecompositionSequence>
             <groupkey>ICE2</groupkey>
             <Decompositions>
                <InvestmentLink>ICE2</InvestmentLink>
                <DecompositionDataSequence>
                   <DecompositionData>
                      <Component>
                         <ID>DEBT</ID>
                      </Component>
                      <Amt>1500</Amt>
                   </DecompositionData>
                </DecompositionDataSequence>
             </Decompositions>
             <groupkey>ICE1</groupkey>
             <Decompositions>
                <InvestmentLink>ICE1</InvestmentLink>
                <DecompositionDataSequence>
                   <DecompositionData>
                      <Component>
                         <ID>EQT</ID>
                      </Component>
                      <Amt>200</Amt>
                   </DecompositionData>
                   <DecompositionData>
                      <Component>
                         <ID>CASH</ID>
                      </Component>
                      <Amt>100</Amt>
                   </DecompositionData>
                </DecompositionDataSequence>
             </Decompositions>
          </DecompositionSequence>
       </AssetEvent>
    </Record>
    <Record>
       <groupkey>01_2020</groupkey>
       <AssetEvent>
          <ID>01_2020</ID>
          <DecompositionSequence>
             <groupkey>ICE3</groupkey>
             <Decompositions>
                <InvestmentLink>ICE3</InvestmentLink>
                <DecompositionDataSequence>
                   <DecompositionData>
                      <Component>
                         <ID>CASH</ID>
                      </Component>
                      <Amt>100</Amt>
                   </DecompositionData>
                </DecompositionDataSequence>
             </Decompositions>
          </DecompositionSequence>
       </AssetEvent>
    </Record>
    

    That uses the XSLT 3 declaration <xsl:mode on-no-match="shallow-copy"/> but if you use an XSLT 2 processor you can replace it with the identity transformation template.