Search code examples
xsltxslt-1.0xslt-2.0

How to combine 3 xsl transformations in one?


I have 3 xsl transformations which, when run seperatedly, work fine but I'd like all 3 in one xsl for simplicity.

Input xml:

<?xml version="1.0" encoding="UTF-8"?>
<SHPMNT05>
   <IDOC BEGIN="1">
      <EDI_DC40 SEGMENT="1">
         <TABNAM>EDI_DC40</TABNAM>
      </EDI_DC40>
      <E1EDT20 SEGMENT="1">
         <TKNUM>0000287203</TKNUM>
         <E1EDK33 SEGMENT="1">
            <TSNUM>0001</TSNUM>
            <E1EDT44 SEGMENT="1">
               <QUALI>001</QUALI>
            </E1EDT44>
            <E1EDT44 SEGMENT="1">
               <QUALI>002</QUALI>
               <ABLAD>1</ABLAD>
            </E1EDT44>
            <E1EDT01 SEGMENT="1">
               <VBELN>0081018666</VBELN>
            </E1EDT01>
            <E1EDT01 SEGMENT="1">
               <VBELN>0081018667</VBELN>
            </E1EDT01>
            <E1EDT01 SEGMENT="1">
               <VBELN>0081018668</VBELN>
            </E1EDT01>
         </E1EDK33>
         <E1EDK33 SEGMENT="1">
            <TSNUM>0002</TSNUM>
            <E1EDT44 SEGMENT="1">
               <QUALI>001</QUALI>
            </E1EDT44>
            <E1EDT44 SEGMENT="1">
               <QUALI>002</QUALI>
               <ABLAD>2</ABLAD>
            </E1EDT44>
            <E1EDT01 SEGMENT="1">
               <VBELN>0081018666</VBELN>
            </E1EDT01>
            <E1EDT01 SEGMENT="1">
               <VBELN>0081018668</VBELN>
            </E1EDT01>
         </E1EDK33>
         <E1EDK33 SEGMENT="1">
            <TSNUM>0002</TSNUM>
            <E1EDT44 SEGMENT="1">
               <QUALI>001</QUALI>
            </E1EDT44>
            <E1EDT44 SEGMENT="1">
               <QUALI>002</QUALI>
               <ABLAD>3</ABLAD>
            </E1EDT44>
            <E1EDT01 SEGMENT="1">
               <VBELN>0081018666</VBELN>
            </E1EDT01>            
         </E1EDK33>
         <E1EDL20 SEGMENT="1">
            <VBELN>0081018666</VBELN>
         </E1EDL20>
         <E1EDL20 SEGMENT="1">
            <VBELN>0081018667</VBELN>
         </E1EDL20>
         <E1EDL20 SEGMENT="1">
            <VBELN>0081018668</VBELN>
         </E1EDL20>
      </E1EDT20>
   </IDOC>
</SHPMNT05>

Required result:

<?xml version="1.0" encoding="UTF-8"?>
<SHPMNT05>
   <IDOC BEGIN="1">
      <EDI_DC40 SEGMENT="1">
         <TABNAM>EDI_DC40</TABNAM>
      </EDI_DC40>
      <E1EDT20 SEGMENT="1">
         <TKNUM>0000287203</TKNUM>
         <E1EDK33 SEGMENT="1">
            <TSNUM>0001</TSNUM>
            <E1EDT44 SEGMENT="1">
               <QUALI>001</QUALI>
            </E1EDT44>
            <E1EDT44 SEGMENT="1">
               <QUALI>002</QUALI>
               <ABLAD>1</ABLAD>
            </E1EDT44>
            <E1EDT01 SEGMENT="1">
               <VBELN>0081018667</VBELN>
            </E1EDT01>
         </E1EDK33>
         <E1EDK33 SEGMENT="1">
            <TSNUM>0002</TSNUM>
            <E1EDT44 SEGMENT="1">
               <QUALI>001</QUALI>
            </E1EDT44>
            <E1EDT44 SEGMENT="1">
               <QUALI>002</QUALI>
               <ABLAD>2</ABLAD>
            </E1EDT44>
            <E1EDT01 SEGMENT="1">
               <VBELN>0081018668</VBELN>
            </E1EDT01>
         </E1EDK33>
         <E1EDK33 SEGMENT="1">
            <TSNUM>0002</TSNUM>
            <E1EDT44 SEGMENT="1">
               <QUALI>001</QUALI>
            </E1EDT44>
            <E1EDT44 SEGMENT="1">
               <QUALI>002</QUALI>
               <ABLAD>3</ABLAD>
            </E1EDT44>
            <E1EDT01 SEGMENT="1">
               <VBELN>0081018666</VBELN>
            </E1EDT01>
         </E1EDK33>
         <E1EDL20 SEGMENT="1">
            <VBELN>0081018667</VBELN>
            <ABLAD>1</ABLAD>
         </E1EDL20>
         <E1EDL20 SEGMENT="1">
            <VBELN>0081018668</VBELN>
            <ABLAD>2</ABLAD>
         </E1EDL20>
         <E1EDL20 SEGMENT="1">
            <VBELN>0081018666</VBELN>
            <ABLAD>3</ABLAD>
         </E1EDL20>
      </E1EDT20>
   </IDOC>
</SHPMNT05>

Steps

Step 1: Filter out the VBELN elements if they exist in the next sibling

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>

    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="/SHPMNT05/IDOC/E1EDT20/E1EDK33/E1EDT01[(VBELN= following-sibling::E1EDT01/VBELN) or (VBELN= ../following-sibling::E1EDK33/E1EDT01/VBELN)]"/> 
      
   <xsl:template match="text()">
        <xsl:value-of select="normalize-space(.)"/>
     </xsl:template> 

</xsl:stylesheet>

Step 2: Use the ABLAD values of the E1EDK33/E1EDT01 elements and add an extra ABLAD element to E1EDL20

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>

    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>

        <xsl:template match="/SHPMNT05/IDOC/E1EDT20/E1EDL20/VBELN"> 
           <xsl:copy-of select="."/>
        <xsl:variable name="stopno" select="/SHPMNT05/IDOC/E1EDT20/E1EDK33[E1EDT01/VBELN=current()]/E1EDT44/ABLAD"/>
      
        <ABLAD><xsl:value-of select="$stopno" /></ABLAD> 
      </xsl:template> 
        
   <xsl:template match="text()">
        <xsl:value-of select="normalize-space(.)"/>
     </xsl:template> 

</xsl:stylesheet>

Step 3: Sort the E1EDL20 elements on the in step 2 newly created E1EDL20\ABLAD elements

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>
           
  <xsl:template match="E1EDT20">
      <xsl:copy>
      <xsl:copy-of select="@*"/>
        
    <xsl:for-each select="*[not(self::E1EDL20)]">
         <xsl:copy-of select="."/>
    </xsl:for-each>
    
        <xsl:for-each select="E1EDL20">
            <xsl:sort select="ABLAD" data-type="number"/>
            <xsl:copy-of select="."/>
        </xsl:for-each>

      </xsl:copy>
    </xsl:template>

    <!--  Default Template -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Hope I made myself clear / this is possible :)

Best regards, Mike


Solution

  • Chaining using fold-left and transform (XPath 3.1, XQuery 3.1):

    let $xslt1 := document {
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    
        <xsl:template match="node()|@*">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="/SHPMNT05/IDOC/E1EDT20/E1EDK33/E1EDT01[(VBELN= following-sibling::E1EDT01/VBELN) or (VBELN= ../following-sibling::E1EDK33/E1EDT01/VBELN)]"/> 
          
       <xsl:template match="text()">
            <xsl:value-of select="normalize-space(.)"/>
         </xsl:template> 
    
    </xsl:stylesheet>  
        },
        $xslt2 := document {
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    
        <xsl:template match="node()|@*">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*"/>
            </xsl:copy>
        </xsl:template>
    
            <xsl:template match="/SHPMNT05/IDOC/E1EDT20/E1EDL20/VBELN"> 
               <xsl:copy-of select="."/>
            <xsl:variable name="stopno" select="/SHPMNT05/IDOC/E1EDT20/E1EDK33[E1EDT01/VBELN=current()]/E1EDT44/ABLAD"/>
          
            <ABLAD><xsl:value-of select="$stopno" /></ABLAD> 
          </xsl:template> 
            
       <xsl:template match="text()">
            <xsl:value-of select="normalize-space(.)"/>
         </xsl:template> 
    
    </xsl:stylesheet>      
        },
        $xslt3 := document {
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:strip-space elements="*"/>
        <xsl:output indent="yes"/>
               
      <xsl:template match="E1EDT20">
          <xsl:copy>
          <xsl:copy-of select="@*"/>
            
        <xsl:for-each select="*[not(self::E1EDL20)]">
             <xsl:copy-of select="."/>
        </xsl:for-each>
        
            <xsl:for-each select="E1EDL20">
                <xsl:sort select="ABLAD" data-type="number"/>
                <xsl:copy-of select="."/>
            </xsl:for-each>
    
          </xsl:copy>
        </xsl:template>
    
        <!--  Default Template -->
        <xsl:template match="@*|node()">
            <xsl:copy>
                <xsl:apply-templates select="@*|node()" />
            </xsl:copy>
        </xsl:template>
    
    </xsl:stylesheet>      
        },
        $xslts := ($xslt1, $xslt2, $xslt3)
    return
        fold-left(
          $xslts, 
          ., 
          function($xml, $xslt) { 
            transform( 
              map { 
                'source-node' : $xml, 
                'stylesheet-node' : $xslt 
              }
            )?output
          }
        )
    

    Using three transformation steps with three modes in one XSLT:

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="2.0"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      exclude-result-prefixes="#all">
    
        <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    
        <xsl:template match="node()|@*" mode="#all">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="#current"/>
            </xsl:copy>
        </xsl:template>
        
        <xsl:variable name="step1">
          <xsl:apply-templates mode="step1"/>
        </xsl:variable>
        
        <xsl:template mode="step1" match="/SHPMNT05/IDOC/E1EDT20/E1EDK33/E1EDT01[(VBELN= following-sibling::E1EDT01/VBELN) or (VBELN= ../following-sibling::E1EDK33/E1EDT01/VBELN)]"/> 
          
        <xsl:template mode="step1" match="text()">
            <xsl:value-of select="normalize-space(.)"/>
        </xsl:template> 
         
         <xsl:variable name="step2">
           <xsl:apply-templates select="$step1/node()" mode="step2"/>
         </xsl:variable>
         
           <xsl:template mode="step2" match="/SHPMNT05/IDOC/E1EDT20/E1EDL20/VBELN"> 
               <xsl:copy-of select="."/>
            <xsl:variable name="stopno" select="/SHPMNT05/IDOC/E1EDT20/E1EDK33[E1EDT01/VBELN=current()]/E1EDT44/ABLAD"/>
          
            <ABLAD><xsl:value-of select="$stopno" /></ABLAD> 
          </xsl:template> 
            
       <xsl:template mode="step2" match="text()">
            <xsl:value-of select="normalize-space(.)"/>
         </xsl:template> 
         
        <xsl:template match="/">
          <xsl:apply-templates select="$step2/node()" mode="step3"/>
        </xsl:template>
    
      <xsl:template mode="step3" match="E1EDT20">
          <xsl:copy>
          <xsl:copy-of select="@*"/>
            
        <xsl:for-each select="*[not(self::E1EDL20)]">
             <xsl:copy-of select="."/>
        </xsl:for-each>
        
            <xsl:for-each select="E1EDL20">
                <xsl:sort select="ABLAD" data-type="number"/>
                <xsl:copy-of select="."/>
            </xsl:for-each>
    
          </xsl:copy>
        </xsl:template>
    </xsl:stylesheet>