Handling patterns and manipulating hierarchies through XSLT

XSLT can handle complex and tricky requirements specially when copying patterns and manipulating hierarchies. I came across a tricky requirement in my project. It was an IDoc to File scenario where source IDoc xml was to be converted into a third party specific format.

Here we have an invoice Idoc (INVOIC.INVOIC02) which needs to be converted into a specific format. This transformation is required for a correction invoice Idoc, which means that Idoc would have even number of E1EDP01 segments in it. For n number of original E1EDP01 segments, there will be another n number of correction E1EDP01 segments.  Source structure of the idoc is as follows:

             IDOC

                              EDI_DC40

                              E1EDKxx segments

                              E1EDP01 segments (Even number, n original, n correction)

                              E1EDSxx segments

Third party requires the Idoc xml but in a bit different format  as explained below, Let us introduce some notations:

E1EDKxx_Original  - These are E1EDKxx segments which are present in the source idoc.So lets call them the E1EDKxx_Original segments.

E1EDKxx_Changed - This is modified version of source E1EDKxx segments. One of the   segment in E1EDKxx segment would be changed.

Trailer      - Copy of E1EDSxx, this would be used as trailer record

Target structure required by third party --

            IDOC

                             EDI_DC40           

                             E1EDKxx_Original  (As is original E1EDKxx).

                             E1EDP01 (item number 1, original item)

                             E1EDSxx (Trailer)

                             E1EDKxx_Changed

                             E1EDP01 (item number 1, correction item)

                             E1EDSxx (Trailer)

                             E1EDKxx_Original  (As is original E1EDKxx)

                             E1EDP01 (item number 2, original item)

                             E1EDSxx (Trailer)

                             E1EDKxx_Changed

                             E1EDP01 (item number 2, correction item)

                             E1EDSxx (Trailer)

                            ……………………………..

                            ……………………………..

We have a relation here between original and correction item (E1EDP01).

Original E1EDP01-POSEX = Correction E1EDP01-HIPOS.

Given the above information, we need to convert the source IDoc xml into target structure as explained above. Achieving this transformation via Graphical, Java or ABAP mapping would be quite difficult. So we will be discussing XSLT transformation here.

We would approach the target xml as mentioned below:

      Loop on all E1EDP01

            If ( Current E1EDP01-POSEX = Any of other E1EDP01-HIPOS)

                         write E1EDKxx_Changed;

                         write current E1EDP01;(original item)

                         write trailer;

                         write E1EDKxx_Original;

                         write correction E1EDP01; (Correction Item)

                         write trailer;

            End If.

     Endloop

First we have to copy the mulitple E1EDKxx segments in a variable. We will do that using an XSLT function - starts-with(name(), 'E1EDK') in a loop.

        <!--Copying all the E1EDKxx segments from the idoc, these will work as header for original E1EDP01 -->
        <xsl:variable name="e1edkxx_original">
            <xsl:for-each select="//IDOC/*">
                <!--Copying the E1EDKxx segments as they are -->
                <xsl:if test="starts-with(name(), 'E1EDK')">
                <xsl:copy-of select="."/>
                </xsl:if>
            </xsl:for-each>
        </xsl:variable>

Note: For checking every segment name inside IDOC, we need to run a loop on * (<xsl:for-each select="//IDOC/*"> )

Now we need E1EDKxx segment same as in the source idoc but with a change E1EDK14-ORGID = 'G2O' (where qualifier = '015'). We will store this data in another variable e1edkxx_changed.

                         <!-- Copying all the E1EDKxx segments from the idoc with a change E1EDK14-ORGID = 'G2O', these will work as header for corrected E1EDP01 -->
        <xsl:variable name="e1edkxx_changed">
            <xsl:for-each select="//IDOC/*">
                <!--Copying other E1EDKxx segments as they are -->
                <xsl:if test="not (starts-with(name(), 'E1EDK14') and QUALF = '015') and (starts-with(name(), 'E1EDK'))">
                    <xsl:copy-of select="."/>
                </xsl:if>
                <!--Changing E1EDK14-ORGID where qualifier = '015' -->
                <xsl:if test="starts-with(name(), 'E1EDK14') and QUALF = '015'">
                    <E1EDK14>
                        <xsl:attribute name="SEGMENT"><xsl:value-of select="1"/></xsl:attribute>
                        <QUALF>015</QUALF>
                        <ORGID>
                            <xsl:value-of select="'G2O'"/>
                        </ORGID>
                    </E1EDK14>
                </xsl:if>
            </xsl:for-each>
        </xsl:variable>

We will store all the E1EDP01 segments in a variable all_e1edp01.

                <!--Copying the E1EDPxx segments as they are -->
        <xsl:variable name="all_e1edp01">
            <xsl:for-each select="//IDOC/*">
                <!--Copying the E1EDKxx segments as they are -->
                <xsl:if test="starts-with(name(), 'E1EDP') ">
                    <xsl:copy-of select="."/>
                </xsl:if>
            </xsl:for-each>
        </xsl:variable>

E1EDSxx segments would be used as trailer. A variable trailer will hold all the E1EDSxx segments.

        <xsl:variable name="trailer">
            <!--Trailer Record E1EDSxx-->
            <xsl:for-each select="//IDOC/*">
                <!--Copying the E1EDSxx segments as they are -->
                <xsl:if test="starts-with(name(), 'E1EDS') ">
                    <xsl:copy-of select="."/>
                </xsl:if>
            </xsl:for-each>
            <!--    Finishing the trailer part here-->
        </xsl:variable>

We now have E1EDKxx_original, E1EDKxx_changed, all_e1edp01 and trailer records ready. Other requirement remains for relating the original E1EDP01 segments and correction E1EDP01 segments. We have been given following condition for relating original  E1EDP01 segments and correction E1EDP01 segments.

HIPOS of correction E1EDP01 segments = POSEX of original E1EDP01 segments

This can be done using two for loops. But we will be using an XSLT key() function. The key() function returns a node-set from the document, using the index specified by an <xsl:key> element.

For using the key function, we first need to declare a definition of key function in the beginning of XSLT code.

     <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="urn:test:xi:Sales:100">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <!--Defining key for HIPOS, HIPOS of corrected E1EDP01 = POSEX of original E1EDP01 -->
    <xsl:key name="E1EDPXX_HIPOS" match="E1EDP01" use="HIPOS"/>

Declaration of key in the beginning of XSLT, informs the processor that we would be using key() function somewhere in the code.Now we just need to run a loop on POSEX of the E1EDP01 and compare the key function output if:

E1EDP01-HIPOS from Key Function = Current E1EDP01-POSEX.

And then place these segments in the required format using copy-of function.

           <!-- Forming the required structure here-->
            <IDOC>
                <xsl:copy-of select="$controlRecord"/>    <!-- Control Record -->
                <xsl:for-each select="$all_e1edp01/*">     <!-- Manupulating the E1EDPxx segments here -->
                    <!-- Checking if the value of Posex in the current E1EDP01 segment is equal to the HIPOS of any of E1EDP01 segment  -->
                    <!-- Assumption is that HIPOS is populated only in case of corrected E1EDP01 items and is equal to POSEX of the original E1EDP01 -->
                    <xsl:if test="POSEX = key('E1EDPXX_HIPOS', POSEX)/HIPOS ">
                        <xsl:copy-of select="$e1edkxx_changed"/>    <!-- Copying E1EDKxx Changed  -->
                        <xsl:copy-of select="."/>                    <!-- Copying E1EDP01 segment (original)-->                   
                        <xsl:copy-of select="$trailer"/>                <!--Copying E1EDSxx Segment-->
                        <xsl:copy-of select="$e1edkxx_original"/>     <!-- Copying E1EDKxx Original-->
                        <xsl:copy-of select="key('E1EDPXX_HIPOS', POSEX)"/>    <!-- Copying E1EDP01 segment for correction-->
                        <xsl:copy-of select="$trailer"/>
                    </xsl:if>
                </xsl:for-each>
                </IDOC>

Here is the complete code which will transform the IDoc into required format.

       <?xml version="1.0" encoding="UTF-8"?>
<!-- This XSLT converts invoice IDoc into a required format as requested by third party.
    In case of correction invoice, Invoice IDoc would have even number of E1EDP01 (Item) segments since for each correction there will be an original E1EDP01   
      segment. Apart from this there will be standard header E1EDKxx segments and E1EDSxx segments in the INVOIC02 IDoc.
    The E1EDKxx segments are treated as header segment for the corrected E1EDP01 segments.
    The same E1EDKxx segments would be repeated for origin items E1EDP01 with a slight change that ORGID will be set as G2O for E1EDK14 segment where qualifier = 015.
    The original IDoc structure received from SAP ECC in case of correction invoice is :
    IDOC
        EDI_DC40
        E1EDKxx segments
        (will be used as header for corrected E1EDP01 segment, and will be used as header (with a slight change in E1EDK14 segment) for original E1EDP01
        E1EDPxx segments ( even number of E1EDPxx segments)
        E1EDSxx segments ( will be used as trailer record in the output)
*****************************************************************************************************************************************************************************************
Structure as required by Third Party
        IDOC
            EDI_DC40
            E1EDKxx_changed
            E1EDP01 (item number 1, original item)
            E1EDSxx ( trailer)
            E1EDKxx_original
            E1EDP01 (item number 1, corrected item)
            E1EDSxx ( trailer)
            E1EDKxx_changed
            E1EDP01 (item number 2, original item)
            E1EDSxx ( trailer)
            E1EDKxx_original
            E1EDP01 (item number 2, corrected item) PSTYV=ZL2N
            E1EDSxx ( trailer)
            ................................................
            ................................................
    Original E1EDP01 segments would be related to corrected E1EDP01 segments by E1EDP01-POSEX and E1EDP01-HIPOS.
    POSEX of original E1EDP01 = HIPOS of corrected E1EDP01. HIPOS is populated by custom coding in ECC only for correction line items.
    This XSLT does the required transformation.
-->
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="urn:test:xi:Sales:100">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <!--Defining key for HIPOS, HIPOS of corrected E1EDP01 = POSEX of original E1EDP01 -->
    <xsl:key name="E1EDPXX_HIPOS" match="E1EDP01" use="HIPOS"/>
    <xsl:template match="/">
                <!--Copying the EDI_DC40 segment -->
        <xsl:variable name="controlRecord" select="//IDOC/EDI_DC40"/>
        <!--Copying all the E1EDKxx segments from the idoc, these will work as header for original E1EDP01 -->
        <xsl:variable name="e1edkxx_original">
            <xsl:for-each select="//IDOC/*">
                <!--Copying the E1EDKxx segments as they are -->
                <xsl:if test="starts-with(name(), 'E1EDK')">
                <xsl:copy-of select="."/>
                </xsl:if>
            </xsl:for-each>
        </xsl:variable>
        <!-- Copying all the E1EDKxx segments from the idoc with a change E1EDK14-ORGID = 'G2O', these will work as header for corrected E1EDP01 -->
        <xsl:variable name="e1edkxx_changed">
            <xsl:for-each select="//IDOC/*">
                <!--Copying other E1EDKxx segments as they are -->
                <xsl:if test="not (starts-with(name(), 'E1EDK14') and QUALF = '015') and (starts-with(name(), 'E1EDK'))">
                    <xsl:copy-of select="."/>
                </xsl:if>
                <!--Changing E1EDK14-ORGID where qualifier = '015' -->
                <xsl:if test="starts-with(name(), 'E1EDK14') and QUALF = '015'">
                    <E1EDK14>
                        <xsl:attribute name="SEGMENT"><xsl:value-of select="1"/></xsl:attribute>
                        <QUALF>015</QUALF>
                        <ORGID>
                            <xsl:value-of select="'G2O'"/>
                        </ORGID>
                    </E1EDK14>
                </xsl:if>
            </xsl:for-each>
        </xsl:variable>
        <xsl:variable name="trailer">
            <!--Trailer Record E1EDSxx-->
            <xsl:for-each select="//IDOC/*">
                <!--Copying the E1EDSxx segments as they are -->
                <xsl:if test="starts-with(name(), 'E1EDS') ">
                    <xsl:copy-of select="."/>
                </xsl:if>
            </xsl:for-each>
            <!--    Finishing the trailer part here-->
        </xsl:variable>
        <!--Copying the E1EDPxx segments as they are -->
        <xsl:variable name="all_e1edp01">
            <xsl:for-each select="//IDOC/*">
                <!--Copying the E1EDKxx segments as they are -->
                <xsl:if test="starts-with(name(), 'E1EDP') ">
                    <xsl:copy-of select="."/>
                </xsl:if>
            </xsl:for-each>
        </xsl:variable>
        <!-- Forming the required structure here-->
            <IDOC>
                <xsl:copy-of select="$controlRecord"/>    <!-- Control Record -->
                <xsl:for-each select="$all_e1edp01/*">     <!-- Manupulating the E1EDPxx segments here -->
                    <!-- Checking if the value of Posex in the current E1EDP01 segment is equal to the HIPOS of any of E1EDP01 segment  -->
                    <!-- Assumption is that HIPOS is populated only in case of corrected E1EDP01 items and is equal to POSEX of the original E1EDP01 -->
                    <xsl:if test="POSEX = key('E1EDPXX_HIPOS', POSEX)/HIPOS ">
                        <xsl:copy-of select="$e1edkxx_changed"/>    <!-- Copying E1EDKxx Changed  -->
                        <xsl:copy-of select="."/>                    <!-- Copying E1EDP01 segment (original)-->                   
                        <xsl:copy-of select="$trailer"/>                <!--Copying E1EDSxx Segment-->
                        <xsl:copy-of select="$e1edkxx_original"/>     <!-- Copying E1EDKxx Original-->
                        <xsl:copy-of select="key('E1EDPXX_HIPOS', POSEX)"/>    <!-- Copying E1EDP01 segment for correction-->
                        <xsl:copy-of select="$trailer"/>
                    </xsl:if>
                </xsl:for-each>
                </IDOC>
    </xsl:template>
</xsl:stylesheet>

The source file in Altova

Source IDoc data in Altova

On executing XSLT, target file is generated

Target xml data in Altova

Important Points to Note:

1) For running a loop on IDoc data, we choose //IDoc/* since we have to copy multiple segments.

2) Use of xslt function starts-with(name(), 'Pattern') to copy data.

3) Copying the required segments in variables and then putting them in desired position.

4) Using Key Function to relate two segments. Key function is quite efficient when dealing with large XSLT.

SAP Developer Network SAP Weblogs: SAP Process Integration (PI)