Home > Back-end >  XSLT Group By Attribute and position of element
XSLT Group By Attribute and position of element

Time:10-02

I am trying to transform data using XSLT with element name used as an attribute and then group by it. I also need to reset the position value based on the occurrence of the element. I can't figure out how to group by element name when performing for-each Request:

 <SOH>
 <SOH3_4>
    <DIE>A</DIE>
    <NAMDIE>string</NAMDIE>
    <CCE>string</CCE>
</SOH3_4>
<SOH3_4>
    <DIE>AB</DIE>
    <NAMDIE>string</NAMDIE>
    <CCE>string</CCE>
</SOH3_4>
    <SOH3_5>
    <SHO>string</SHO>
    <INVDTAAMT>1873960.2349058</INVDTAAMT>
    <INVDTATYP>03:ENG:%</INVDTATYP>
    <SFISSTCOD>string</SFISSTCOD>
</SOH3_5>
<SOH3_5>
    <SHO>string</SHO>
    <INVDTAAMT>2280630.2349058</INVDTAAMT>
    <INVDTATYP>01:ENG:Tax excluded</INVDTATYP>
    <SFISSTCOD>string</SFISSTCOD>
</SOH3_5>

Result:

<PARAM>
<TAB ID="SOH3_4">
<LIN ID="1">
        <FLD NAME="DIE">A</FLD>
        <FLD NAME="NAMDIE">string</FLD>
        <FLD NAME="CCE">string</FLD>
        
</LIN>
<LIN ID="2">
        <FLD NAME="DIE">AB</FLD>
        <FLD NAME="NAMDIE">string</FLD>
        <FLD NAME="CCE">string</FLD>
</LIN>
</TAB>
<TAB ID="SOH3_5">
<LIN ID="1">
        <FLD NAME="SHO">string</FLD>
        <FLD NAME="INVDTAAMT">1873960.2349058</FLD>
        <FLD NAME="INVDTATYP">03:ENG:%</FLD>
        <FLD NAME="INVDTAAMT">string</FLD>
</LIN>
<LIN ID="2">
        <FLD NAME="SHO">string</FLD>
        <FLD NAME="INVDTAAMT">2280630.2349058</FLD>
        <FLD NAME="INVDTATYP">01:ENG:Tax excluded</FLD>
        <FLD NAME="SFISSTCOD">string</FLD>
</LIN>
</TAB>

CodePudding user response:

Your question is a bit unclear about the sort order, but you could use the following template to get the desired output:

<xsl:template match="SOH">
  <xsl:element name="TAB">
    <xsl:attribute name="ID"><xsl:value-of select="name(*[1])" /></xsl:attribute>
    <xsl:for-each select="*">
      <LIN ID="{position()}">
        <xsl:for-each select="*">
          <xsl:element name="FLD">
            <xsl:attribute name="NAME"><xsl:value-of select="name()" /></xsl:attribute>
            <xsl:value-of select="." />
          </xsl:element>
        </xsl:for-each>
      </LIN>
    </xsl:for-each>
  </xsl:element>
</xsl:template>

The output is as desired

<TAB ID="SOH3_5">
    <LIN ID="1">
        <FLD NAME="SHO">string</FLD>
        <FLD NAME="INVDTAAMT">1873960.2349058</FLD>
        <FLD NAME="INVDTATYP">03:ENG:%</FLD>
        <FLD NAME="SFISSTCOD">string</FLD>
    </LIN>
    <LIN ID="2">
        <FLD NAME="SHO">string</FLD>
        <FLD NAME="INVDTAAMT">2280630.2349058</FLD>
        <FLD NAME="INVDTATYP">01:ENG:Tax excluded</FLD>
        <FLD NAME="SFISSTCOD">string</FLD>
    </LIN>
</TAB>

CodePudding user response:

Your edit suggests you could benefit from using XSLT 2 or 3 and for-each-group select="*" group-adjacent="node-name()" e.g. in XSLT 3 (supported with SaxonJS2, Saxon 9.8 HE or later/higher, SaxonC, SaxonCS, AltovaXML 2017 R3 and later) it boils down to:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="SOH">
    <PARAM>
      <xsl:for-each-group select="*" group-adjacent="node-name()">
        <TAB ID="{current-grouping-key()}">
          <xsl:apply-templates select="current-group()"/>
        </TAB>
      </xsl:for-each-group>
    </PARAM>
  </xsl:template>
  
  <xsl:template match="SOH/*">
    <LIN ID="{position()}">
      <xsl:apply-templates/>
    </LIN>
  </xsl:template>
  
  <xsl:template match="SOH/*/*">
    <FLD NAME="{local-name()}">{.}</FLD>
  </xsl:template>
  
</xsl:stylesheet>
  • Related