Generic mapping to convert nested XML to flat - Receiver file adatper

In this weblog, I am presenting a generic way to convert any nested complex XML structure to flat using ABAP mapping. This is especially useful for interfaces using receiver file adapter with content conversion.

We know that the receiver file adapter with content conversion can only handle flat XML structure. That is, the resulting XML file should be of structure

<root>

  <nameA>

     <value1>value</value1>

     <value2>value</value2>

     <value3>value</value3>

  </nameA>

  <nameB>

     <value4>value</value4>

  </nameB>

</root>

However, in most cases, we might have to handle complex nested structures in message mapping. This is especially true when the target flat file has complex structure with multiple layouts per line.

For example, consider the below target XML message and flat file structure

Target XML structure (input to receiver file adapter)

<root>

<ORDERS>

  <HEADER>

    <HDR></HDR>

      <FH1></FH1>

      <FH2></FH2>

  </HEADER>

<DETAIL1>

  <DET1></DET1>

  <DF11></DF11>

  <DF12></DF12>

  </DETAIL1>

<DETAIL2>

  <DF21></DF21>

  <DF22></DF22>

  </DETAIL2>

</ORDERS>

</root>

Target flat file layout

HDR,FH1,FH2…
DET1,DF11,DF12
DET2,DF21,DF22
….
HDR,FH1,FH2
DET1,DF11,DF12
DET2,DF21,DF22

The file contains a group of three record types namely, Header, Detail1 and Detail2. Each record type has is own structure and the group repeats itself. For e.g., if this file should contain sales order information, the header contains order header information, Detail 1 contains line item information and Detail 2 contains schedule line information. There could be multiple sales orders as well

We can derive the flat structure in graphical mapping but not without the risk of disturbing the context especially if the source structure is complex nested (for e.g. IDOC). In which case, all HDR lines would be grouped together followed by all DET1 lines, then DET2 line etc.

We had many interfaces with such complex target structures. Instead of repeatedly complicating the graphical mapping, I decided to come up with a generic mapping which will convert any complex structure in to flat structure. This way, the graphical mapping is free of complex context handling functions. I chose to use ABAP mapping to achieve this as described below

Input to the ABAP mapping has two parts in the XML document.
  1. One contains the actual target structure, which is complex nested
  2. Other part contains the nodes which need to be flattened. This also contains elements for target message name and namespace. The idea of this part is to make the ABAP mapping generic so as to use it with any interface

In our example the input to the ABAP mapping will look like as below

<FLATFILE>
  <Z_FILTERS>
    <NAME>HEADER</NAME>
    <NAME>DETAIL1</NAME>
    <NAME>DETAIL2</NAME>
  </Z_FILTERS>
  <Z_TMSG_TYPE></Z_TMSG_TYPE>
  <ORDERS>
    <HEADER>
      <HDR></HDR>
      <FH1></FH1>
      <FH2></FH2>
      ..
    </HEADER>
    <DETAIL1>
      <DET1></DET1>
      <DF11></DF11>
      <DF12></DF12>
      ..
    </DETAIL1>
    <DETAIL2>
      <DF21></DF21>
      <DF22></DF22>
      ..
    </DETAIL2>
  </ORDERS>
</FLATFILE>

The code for the ABAP mapping is provided below.

Method: IF_MAPPING~EXECUTE
In this method we parse the input XML using DOM and read through to get only the nodes which need to flattened out.

Method: GET_LAST_NODE
This method is used to recursively to call itself till reference to the last child node is obtained.

***************************************************

* All data declarations
  TYPE-POOLS: IXML.
  CLASS CL_IXML DEFINITION LOAD.

  DATA: BEGIN OF WA_FILTERS,
          NAME TYPE STRING,
        END OF WA_FILTERS.

  DATA: R_XML_FACTORY TYPE REF TO IF_IXML,
        R_STREAM_FACTORY TYPE REF TO IF_IXML_STREAM_FACTORY,
        R_STREAM TYPE REF TO IF_IXML_ISTREAM,
        R_OSTREAM TYPE REF TO IF_IXML_OSTREAM,
        R_IN_DOC TYPE REF TO IF_IXML_DOCUMENT,
        R_OUT_DOC TYPE REF TO IF_IXML_DOCUMENT,
        R_PARSER TYPE REF TO IF_IXML_PARSER,
        R_ENCODING TYPE REF TO IF_IXML_ENCODING,
        R_RENDERER TYPE REF TO IF_IXML_RENDERER,
        R_ROOT TYPE REF TO IF_IXML_ELEMENT,
        R_OUT_ROOT TYPE REF TO IF_IXML_ELEMENT,
        R_FILTER_ELEMENT TYPE REF TO IF_IXML_ELEMENT,
        R_TMSG_ELEMENT TYPE REF TO IF_IXML_ELEMENT,
        R_FILTER_MAIN TYPE REF TO IF_IXML_NODE_FILTER,
        R_FILTER TYPE REF TO IF_IXML_NODE_FILTER,
        R_ITER TYPE REF TO IF_IXML_NODE_ITERATOR,
        R_CHILD TYPE REF TO IF_IXML_NODE,
        R_NODE TYPE REF TO IF_IXML_NODE,
        R_CLONED_NODE TYPE REF TO IF_IXML_NODE,
        T_FILTERS LIKE TABLE OF WA_FILTERS,
        V_RC TYPE I,
        V_ROOT_NAME TYPE STRING,
        V_ROOT_PREFIX TYPE STRING,
        V_ROOT_NS TYPE STRING,
        V_ROOT_URI TYPE STRING,
        V_SOURCE TYPE STRING.

* Create Main factory class
  R_XML_FACTORY = CL_IXML=>CREATE( ).
* Create stream fatory
  R_STREAM_FACTORY = R_XML_FACTORY->CREATE_STREAM_FACTORY( ).
  V_SOURCE = SOURCE.
  R_STREAM = R_STREAM_FACTORY->CREATE_ISTREAM_XSTRING( SOURCE ).
* initialize input/output document
  R_IN_DOC = R_XML_FACTORY->CREATE_DOCUMENT( ).
  R_OUT_DOC = R_XML_FACTORY->CREATE_DOCUMENT( ).
* parse input document
  R_PARSER = R_XML_FACTORY->CREATE_PARSER( STREAM_FACTORY = R_STREAM_FACTORY
                                           ISTREAM = R_STREAM
                                           DOCUMENT = R_IN_DOC ).
  R_PARSER->PARSE( ).
* Get the encoding
  R_ENCODING = R_IN_DOC->GET_ENCODING( ).
* Get the root element
  R_ROOT = R_IN_DOC->GET_ROOT_ELEMENT( ).
* Get the filter objects first

  R_FILTER_ELEMENT = R_IN_DOC->FIND_FROM_NAME( 'Z_FILTERS' ).
  IF NOT R_FILTER_ELEMENT IS INITIAL.
  R_CHILD = R_FILTER_ELEMENT->GET_FIRST_CHILD( ).
  WHILE NOT R_CHILD IS INITIAL.
    CLEAR: WA_FILTERS.
    WA_FILTERS-NAME = R_CHILD->GET_VALUE( ).
    APPEND WA_FILTERS TO T_FILTERS.
    R_CHILD = R_CHILD->GET_NEXT( ).
  ENDWHILE.
  ENDIF.
** Throw error if no filters are available.

  CLEAR: R_FILTER, R_FILTER_MAIN.
  LOOP AT T_FILTERS INTO WA_FILTERS.
    IF SY-TABIX = 1.
      R_FILTER_MAIN = R_IN_DOC->CREATE_FILTER_NAME( NAME = WA_FILTERS-NAME ).
    ELSE.
      R_FILTER = R_IN_DOC->CREATE_FILTER_NAME( NAME = WA_FILTERS-NAME ).
      R_FILTER_MAIN  = R_IN_DOC->CREATE_FILTER_OR( FILTER1 = R_FILTER_MAIN
                                                   FILTER2 = R_FILTER ).
    ENDIF.
  ENDLOOP.

* Get the root node to the outbound document.
* Get the target message name from the payload
  R_TMSG_ELEMENT = R_IN_DOC->FIND_FROM_NAME( 'Z_TMSG_TYPE' ).
  IF NOT R_TMSG_ELEMENT IS INITIAL.
    V_ROOT_NAME = R_TMSG_ELEMENT->GET_VALUE( ).
  ENDIF.

*  V_ROOT_NAME = R_ROOT->GET_NAME( ).
  V_ROOT_PREFIX = R_ROOT->GET_NAMESPACE_PREFIX( ).
  V_ROOT_URI = R_ROOT->GET_NAMESPACE_URI( ).

  R_OUT_ROOT = R_OUT_DOC->CREATE_SIMPLE_ELEMENT_NS( NAME = V_ROOT_NAME
                                                    PREFIX = V_ROOT_PREFIX
                                                    URI    = V_ROOT_URI
                                                    PARENT = R_OUT_DOC ).
  IF NOT V_ROOT_PREFIX IS INITIAL.
    CONCATENATE 'xmlns:' V_ROOT_PREFIX INTO V_ROOT_NS.

    V_RC = R_OUT_ROOT->SET_ATTRIBUTE( NAME   = V_ROOT_NS
                                      VALUE  = V_ROOT_URI ).
  ENDIF.

  V_RC = R_OUT_DOC->APPEND_CHILD( R_OUT_ROOT ).

  CLEAR: R_ITER.
  R_ITER = R_IN_DOC->CREATE_ITERATOR_FILTERED( R_FILTER_MAIN ).
  R_NODE = R_ITER->GET_NEXT( ).
  WHILE NOT R_NODE IS INITIAL.
    R_CLONED_NODE = R_NODE->CLONE( ).
    R_OUT_ROOT->APPEND_CHILD( R_CLONED_NODE ).
    R_NODE = R_ITER->GET_NEXT( ).
  ENDWHILE.

* render document ======================================================
* create output stream
  CLEAR: R_STREAM.
  R_OSTREAM = R_STREAM_FACTORY->CREATE_OSTREAM_XSTRING( RESULT ).
* Set encoding
  R_OUT_DOC->SET_ENCODING( ENCODING = R_ENCODING ).
* create renderer
  R_RENDERER = R_XML_FACTORY->CREATE_RENDERER( OSTREAM = R_OSTREAM
                                             DOCUMENT = R_OUT_DOC ).
  V_RC = R_RENDERER->RENDER( ).

***************************************************

Method: GET_LAST_NODE
This method is used to recursively to call itself till reference to the last child node is obtained.

***************************************************

DATA: CHILD TYPE REF TO IF_IXML_NODE,
      NODENAME TYPE STRING.

CHILD = IN_NODE->GET_FIRST_CHILD( ).

IF CHILD IS INITIAL.
  NODENAME = CHILD->GET_NAME( ).
  OUT_NODE->APPEND_CHILD( IN_NODE ).
ELSE.
  WHILE NOT CHILD IS INITIAL.
  NODENAME = CHILD->GET_NAME( ).
  CALL METHOD ME->GET_LAST_NODE
    EXPORTING
      IN_NODE  = CHILD
    CHANGING
      OUT_NODE = OUT_NODE.
  ENDWHILE.
ENDIF.

 

***************************************************

The input parameters to method GET_LAST_NODE are as below
image

Now, you can use this ABAP mapping as the last step in your mapping sequence to convert any complex structure to flat.

Note
The names of the filters should exactly match the node names which need to flattened out.
The namespace for the target message is assumed the same as the source in the mapping program

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