Search code examples
xmlxsltxslt-2.0xslt-grouping

XSLT to combine Line Items with same ID into separate records


Trying to combine/ group line items(Header, Line, Charge) having same value for "invoice_id" under one 'Record' dynamically using XSLT.

In the input XML, there are multiple items such as Header, Line, Charge whose occurrences are unbounded.

In the output XML, we are trying to group Header, Line and Charges whose invoice id is equal under one parent node "Record". The parent node "Record" repeats as many times as the invoice id in the input XML.

Please find the input and expected output below:

Input XML Input XML is provided below:

    <?xml version="1.0" encoding="utf-8"?>
    <ns:MT_OkayToPay_Source xmlns:ns="http://kpmg.com/xi/Coupa/OkayToPay">
        <Header>
            <type>Header</type>
            <invoice_id>5</invoice_id>
        </Header>
        <Header>
            <type>Header</type>
            <invoice_id>6</invoice_id>
        </Header>
        <Line>
            <type>Line</type>
            <invoice_id>5</invoice_id>
        </Line>
        <Line>
            <type>Line</type>
            <invoice_id>6</invoice_id>
        </Line>
        <Charge>
            <type>Charge</type>
            <invoice_id>5</invoice_id>
            <invoice_charge_invoice_id>10</invoice_charge_invoice_id>
        </Charge>
        <Charge>
            <type>Charge</type>
            <invoice_id>5</invoice_id>
            <invoice_charge_invoice_id>11</invoice_charge_invoice_id>
        </Charge>
        <Charge>
            <type>Charge</type>
            <invoice_id>6</invoice_id>
            <invoice_charge_invoice_id>13</invoice_charge_invoice_id>
        </Charge>
        <Charge>
            <type>Charge</type>
            <invoice_id>6</invoice_id>
            <invoice_charge_invoice_id>14</invoice_charge_invoice_id>
        </Charge>
</ns:MT_OkayToPay_Source>

Output XML The output is expected as below

<?xml version="1.0" encoding="utf-8"?>
<ns:MT_OkayToPay_Source xmlns:ns="http://kpmg.com/xi/Coupa/OkayToPay">
    <Record>
    <Header>
        <type>Header</type>
        <invoice_id>5</invoice_id>
    </Header>
    <Line>
        <type>Line</type>
        <invoice_id>5</invoice_id>
   </Line>
    <Charge>
        <type>Charge</type>
        <invoice_id>5</invoice_id>
        <invoice_charge_invoice_id>10</invoice_charge_invoice_id>
    </Charge>
    <Charge>
        <type>Charge</type>
        <invoice_id>5</invoice_id>
        <invoice_charge_invoice_id>11</invoice_charge_invoice_id>   
    </Charge>
    </Record>
    <Record>
    <Header>
        <type>Header</type>
        <invoice_id>6</invoice_id>
    </Header>
    <Line>
        <type>Line</type>
        <invoice_id>6</invoice_id>  
    </Line>
    <Charge>
        <type>Charge</type>
        <invoice_id>6</invoice_id>
        <invoice_charge_invoice_id>13</invoice_charge_invoice_id>   
    </Charge>
    <Charge>
        <type>Charge</type>
        <invoice_id>6</invoice_id>
        <invoice_charge_invoice_id>14</invoice_charge_invoice_id>   
    </Charge>
 </Record>
</ns:MT_OkayToPay_Source>

Kindly help.


Solution

  • Found the answer:

    <xsl:stylesheet version="2.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output omit-xml-declaration="yes" indent="yes"/>
    
     <xsl:template match="/*">
         <root>
          <xsl:for-each-group select="*" group-by="invoice_id">
           <record>
            <xsl:copy-of select="current-group()"/>
           </record>
          </xsl:for-each-group>
     
          
         </root>
     
     </xsl:template>
    
    </xsl:stylesheet>
    

    To achieve the same in XSLT 1.0, please use the following:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
    
    <xsl:key name="Records_by_invoice_id" match="*" use="invoice_id"/>
    
    <xsl:template match="/*">
      <MT_OkayToPay_Source>
       <xsl:apply-templates/>
      </MT_OkayToPay_Source>
     </xsl:template>
    
    <xsl:template match=
       "MT_OkayToPay_Source/*[generate-id()=generate-id(key('Records_by_invoice_id',invoice_id)[1])]">
    
      <record>
       <xsl:copy-of select="key('Records_by_invoice_id',invoice_id)"/>
      </record>
     </xsl:template>
     
     <xsl:template match=
       "MT_OkayToPay_Source/*[not(generate-id()=generate-id(key('Records_by_invoice_id',invoice_id)[1]))]"/>
    
      </xsl:stylesheet>
    

    Thank you!

    Regards

    Vishakha Khona