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
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