Search code examples
xslt

How to group different tag level in XML?


Here is source xml. We have 1 header level tag. Line level tags are contained under 1 header level.

<Tran>
    <Company>ABC corp</Company>
    <Country>IN</Country>
    <Line>
        <No>1</No>
        <Type>Item</Type>
        <Amount>100</Amount>
    </Line>
    <Line>
        <No>2</No>
        <Type>Item</Type>
        <Amount>200</Amount>
    </Line>
    <Line>
        <No>3</No>
        <Type>Freight</Type>
        <Amount>50</Amount>
    </Line>
    <Line>
        <No>4</No>
        <Type>Freight</Type>
        <Amount>60</Amount>
    </Line>
    <Line>
        <No>5</No>
        <Type>Tax</Type>
        <Amount>100</Amount>
    </Line>
<Tran>

I would like to group by header level <Company> and line level <Type>. But not sure how to group by xsl.

Expected result is splitting header level by header level and line level . and expected result is here.

<Pmtheader>
    <Comp>ABC corp</Comp>
    <Type>Item</Type>
    <CountLine>2</CountLine>
    <TotalAmt>300</TotalAmt>
    <Pmtline>
        <No>1</No>
        <Amt>100</Amt>
    </Pmtline>
    <Pmtline>
        <No>2</No>
        <Amt>200</Amt>
    </Pmtline>
</Pmtheader>
<Pmtheader>
    <Comp>ABC corp</Comp>
    <Type>Freight</Type>
    <CountLine>2</CountLine>  
    <TotalAmt>110</TotalAmt>
    <Pmtline>
        <No>3</No>
        <Amt>50</Amt>
    </Pmtline>
    <Pmtline>
        <No>4</No>
        <Amt>60</Amt>
    </Pmtline>
<Pmtheader>
    <Comp>ABC corp</Comp>
    <Type>Tax</Type>
    <CountLine>1</CountLine>  
    <TotalAmt>100</TotalAmt>
    <Pmtline>
        <No>5</No>
        <Amt>100</Amt>
    </Pmtline>
</Pmtheader>

I never use xsl:for-each-group and for-each function. So I am not sure where I start. Please give me any advise.


Solution

  • Here is an example using XSLT 3:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="3.0"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      exclude-result-prefixes="#all"
      expand-text="yes">
    
      <xsl:output method="xml" indent="yes"/>
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:template match="/" name="xsl:initial-template">
        <xsl:copy>
          <xsl:for-each-group select="//Tran/Line" composite="yes" group-by="../Company, Type">
            <Pmtheader>
                <Comp>{current-grouping-key()[1]}</Comp>
                <Type>{current-grouping-key()[2]}</Type>
                <CountLine>{count(current-group())}</CountLine>
                <TotalAmt>{sum(current-group()/Amount)}</TotalAmt>
                <xsl:apply-templates select="current-group()"/>
            </Pmtheader>
          </xsl:for-each-group>
        </xsl:copy>
      </xsl:template>
      
      <xsl:template match="Line">
        <Pmtline>
          <xsl:apply-templates/>
        </Pmtline>
      </xsl:template>
      
      <xsl:template match="Line/Type"/>
    
    </xsl:stylesheet>