Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
TudorRiscutia
Active Participant

I'm not entirely sure if a similar solution exists; I've tried searching through blog posts in the ABAP Development space and couldn't find any match (although there are over 1000 results for "abap xml").

Basically, more out of boredom and willing to learn more about the capabilities of the iXML Library, I made an ABAP/4 class that can transform any data (simple data, structure, table type) at any depth into XML format and vice versa. Even though, bringing XML to ABAP/4 only matches elements with the same name, ignoring extra data.

For example:

Given the following nested-structured local transparent table:

DATA :
  BEGIN OF ls_data.
        INCLUDE STRUCTURE spfli.
DATA :
      scarr   TYPE scarr,
      sflight TYPE sflight_tab1,
    END OF ls_data,
  lt_data LIKE TABLE OF ls_data.

Filled with test data:

SELECT *
       UP TO 10 ROWS
       FROM spfli
       INTO CORRESPONDING FIELDS OF TABLE lt_data.

FIELD-SYMBOLS : <fs_data> LIKE LINE OF lt_data.
LOOP AT lt_data ASSIGNING <fs_data>.
  SELECT SINGLE *
                FROM scarr
                INTO CORRESPONDING FIELDS OF <fs_data>-scarr
                WHERE carrid = <fs_data>-carrid.

  SELECT *
         FROM sflight
         INTO CORRESPONDING FIELDS OF TABLE <fs_data>-sflight
         WHERE carrid = <fs_data>-carrid
           AND connid = <fs_data>-connid.
ENDLOOP.

The result is:

The class looks as follows (I will also attach it as text at the bottom together with the demo report):

class ZCL_XML_UTIL definition
  public
  create public .

public section.
*"* public components of class ZCL_XML_UTIL
*"* do not include other source files here!!!

  methods CONSTRUCTOR .
  methods ABAP_TO_XML
    importing
      !IM_DATA type ANY
    exporting
      !EX_CONTENT type XSTRING
    raising
      ZCX_TYPE_NOT_SUPPORTED .
  methods XML_TO_ABAP
    importing
      !IM_CONTENT type XSTRING
    exporting
      !EX_DATA type ANY
    raising
      ZCX_TYPE_NOT_SUPPORTED .
protected section.
*"* protected components of class ZCL_XML_UTIL
*"* do not include other source files here!!!

  data MO_IXML type ref to IF_IXML .
  data MO_DOCUMENT type ref to IF_IXML_DOCUMENT .
private section.
*"* private components of class ZCL_XML_UTIL
*"* do not include other source files here!!!

  methods PROCESS
    importing
      !IM_NODE type ref to IF_IXML_NODE
      !IM_NAME type STRING
      !IM_DATA type ANY
    raising
      ZCX_TYPE_NOT_SUPPORTED .
  methods CREATE_ELEMENT
    importing
      !IM_NAME type STRING
      !IM_VALUE type STRING optional
    returning
      value(RE_ELEMENT) type ref to IF_IXML_ELEMENT .
  methods TRAVERSE
    importing
      !IM_CURRENT_NODE type ref to IF_IXML_NODE
    changing
      !CH_DATA type ANY
    raising
      ZCX_TYPE_NOT_SUPPORTED .
  class-methods AS_STRING
    importing
      !IM_DATA type ANY
    returning
      value(RE_DATA_STRING) type STRING .
ENDCLASS.



CLASS ZCL_XML_UTIL IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_XML_UTIL->ABAP_TO_XML
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_DATA                        TYPE        ANY
* | [<---] EX_CONTENT                     TYPE        XSTRING
* | [!CX!] ZCX_TYPE_NOT_SUPPORTED
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD abap_to_xml.

  CLEAR : ex_content.

* create .xml document
  mo_document = mo_ixml->create_document( ).

* set encoding to UTF-8 (Unicode Transformation Format)
* 8-bit variable-width encoding maximizes compatibility with ASCII
  mo_document->set_encoding( mo_ixml->create_encoding(
      byte_order    = 0
      character_set = 'UTF-8' ) ).

  IF NOT im_data IS INITIAL.
*   use mo_document as root
    process( im_node = mo_document
             im_name = 'data'
             im_data = im_data ).
  ENDIF.

* render .xml document with output stream
  mo_document->render(
    ostream = mo_ixml->create_stream_factory( )->create_ostream_xstring(
      string = ex_content ) ).
  FREE : mo_document.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_XML_UTIL=>AS_STRING
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_DATA                        TYPE        ANY
* | [<-()] RE_DATA_STRING                 TYPE        STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD as_string.

  MOVE im_data TO re_data_string.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_XML_UTIL->CONSTRUCTOR
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD CONSTRUCTOR.

* get iXML library instance
  mo_ixml = cl_ixml=>create( ).

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_XML_UTIL->CREATE_ELEMENT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_NAME                        TYPE        STRING
* | [--->] IM_VALUE                       TYPE        STRING(optional)
* | [<-()] RE_ELEMENT                     TYPE REF TO IF_IXML_ELEMENT
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD CREATE_ELEMENT.

  DATA :
    lv_name TYPE string VALUE IS INITIAL.

* element names look cooler in lower case :smile:
  lv_name = to_lower( im_name ).

* create element with given name
  re_element = mo_document->create_element( name = lv_name ).

* if element is leaf, set corresponding value
  IF im_value IS SUPPLIED.
    re_element->set_value( im_value ).
  ENDIF.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_XML_UTIL->PROCESS
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_NODE                        TYPE REF TO IF_IXML_NODE
* | [--->] IM_NAME                        TYPE        STRING
* | [--->] IM_DATA                        TYPE        ANY
* | [!CX!] ZCX_TYPE_NOT_SUPPORTED
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD process.

  DATA :
    lo_type    TYPE REF TO cl_abap_typedescr,
    lo_struct  TYPE REF TO cl_abap_structdescr,
    lo_table   TYPE REF TO cl_abap_tabledescr,
    lo_data    TYPE REF TO data,
    lo_element TYPE REF TO if_ixml_element,
    lv_name    TYPE string VALUE IS INITIAL.

  FIELD-SYMBOLS :
    <ft_data>      TYPE STANDARD TABLE,
    <fs_component> TYPE abap_compdescr,
    <fs_data>      TYPE any,
    <fv_value>     TYPE any.

* determine ABAP/4 data type, support only:
* - simple type
* - structure
* - table type
  lo_type = cl_abap_typedescr=>describe_by_data( im_data ).

  CASE lo_type->kind.
    WHEN cl_abap_typedescr=>kind_elem.
*     create element
      lo_element = create_element(
          im_name  = im_name
          im_value = as_string( im_data ) ).

    WHEN cl_abap_typedescr=>kind_struct.
*     create parent element
      lo_element = create_element( im_name = im_name ).

*     process each structure component independently
      lo_struct ?= cl_abap_structdescr=>describe_by_data( im_data ).
      LOOP AT lo_struct->components ASSIGNING <fs_component>.
        ASSIGN COMPONENT <fs_component>-name OF STRUCTURE im_data TO <fv_value>.
        CHECK sy-subrc IS INITIAL.

        lv_name = <fs_component>-name.

        process( im_node = lo_element
                 im_name = lv_name
                 im_data = <fv_value> ).
      ENDLOOP.

    WHEN cl_abap_typedescr=>kind_table.
      lo_table ?= cl_abap_tabledescr=>describe_by_data( im_data ).
      CREATE DATA lo_data TYPE HANDLE lo_table.
      ASSIGN lo_data->* TO <ft_data>.
      <ft_data> = im_data.

*     create parent element
      lo_element = create_element( im_name = im_name ).

*     process each table line independently
      LOOP AT <ft_data> ASSIGNING <fs_data>.
       
process( im_node = lo_element
                 im_name = im_name
                 im_data = <fs_data> ).
     
ENDLOOP.

    WHEN OTHERS.
      TRY.
        RAISE EXCEPTION TYPE zcx_type_not_supported.
      ENDTRY.
  ENDCASE.

  IF lo_element IS BOUND.
    im_node->append_child( lo_element ).
  ENDIF.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_XML_UTIL->TRAVERSE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_CURRENT_NODE                TYPE REF TO IF_IXML_NODE
* | [<-->] CH_DATA                        TYPE        ANY
* | [!CX!] ZCX_TYPE_NOT_SUPPORTED
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD traverse.

  DATA :
    lo_type      TYPE REF TO cl_abap_typedescr,
    lo_struct    TYPE REF TO cl_abap_structdescr,
    lo_table     TYPE REF TO cl_abap_tabledescr,
    lo_data      TYPE REF TO data,
    lo_iterator  TYPE REF TO if_ixml_node_iterator,
    lo_node_list TYPE REF TO if_ixml_node_list,
    lo_node      TYPE REF TO if_ixml_node,
    lv_node_name TYPE string VALUE IS INITIAL,
    lv_match     TYPE boolean.

  FIELD-SYMBOLS :
    <ft_data>      TYPE STANDARD TABLE,
    <fs_component> TYPE abap_compdescr,
    <fs_data>      TYPE any,
    <fv_value>     TYPE any.

* determine ABAP/4 data type, support only:
* - simple type
* - structure
* - table type
  lo_type = cl_abap_typedescr=>describe_by_data( ch_data ).

  CASE lo_type->kind.
    WHEN cl_abap_typedescr=>kind_elem.
      ch_data = im_current_node->get_value( ).

    WHEN cl_abap_typedescr=>kind_struct.
      lo_node_list = im_current_node->get_children( ).

*     process each structure component independently
      lo_struct ?= cl_abap_structdescr=>describe_by_data( ch_data ).
      LOOP AT lo_struct->components ASSIGNING <fs_component>.
        ASSIGN COMPONENT <fs_component>-name OF STRUCTURE ch_data TO <fv_value>.
        CHECK sy-subrc IS INITIAL.

*       create new iterator with each step
        lo_iterator = lo_node_list->create_iterator( ).
        lo_node = lo_iterator->get_next( ).

        WHILE lo_node  IS BOUND
           OR lv_match EQ abap_true.

*         xml element names can contain lower case characters
          lv_node_name = to_upper( lo_node->get_name( ) ).

          IF lv_node_name EQ <fs_component>-name.
            lv_match = abap_true.
            EXIT.
          ENDIF.

          lo_node = lo_iterator->get_next( ).
        ENDWHILE.

        CHECK lv_match EQ abap_true.
        traverse( EXPORTING im_current_node = lo_node
                  CHANGING  ch_data         = <fv_value> ).
        CLEAR : lv_match.
      ENDLOOP.

    WHEN cl_abap_typedescr=>kind_table.
      lo_table ?= cl_abap_tabledescr=>describe_by_data( ch_data ).

      CREATE DATA lo_data TYPE HANDLE lo_table.
      ASSIGN lo_data->* TO <ft_data>.

      lo_node_list = im_current_node->get_children( ).
      lo_iterator = lo_node_list->create_iterator( ).
      lo_node = lo_iterator->get_next( ).

      WHILE lo_node IS BOUND.
        APPEND INITIAL LINE TO <ft_data> ASSIGNING <fs_data>.

        traverse( EXPORTING im_current_node = lo_node
                  CHANGING  ch_data         = <fs_data> ).

        lo_node = lo_iterator->get_next( ).
      ENDWHILE.

      IF NOT <ft_data> IS INITIAL.
        ch_data = <ft_data>.
      ENDIF.

    WHEN OTHERS.
      TRY.
        RAISE EXCEPTION TYPE zcx_type_not_supported.
      ENDTRY.
  ENDCASE.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_XML_UTIL->XML_TO_ABAP
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_CONTENT                     TYPE        XSTRING
* | [<---] EX_DATA                        TYPE        ANY
* | [!CX!] ZCX_TYPE_NOT_SUPPORTED
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD xml_to_abap.

  CLEAR : ex_data.

  DATA :
    lo_istream        TYPE REF TO if_ixml_istream,
    lo_stream_factory TYPE REF TO if_ixml_stream_factory,
    lo_iterator       TYPE REF TO if_ixml_node_iterator.

  ASSERT NOT im_content IS INITIAL.

  IF NOT ex_data IS REQUESTED.
    RETURN.
  ENDIF.

* create .xml document
  mo_document = mo_ixml->create_document( ).

  lo_stream_factory = mo_ixml->create_stream_factory( ).
  lo_istream = lo_stream_factory->create_istream_xstring( im_content ).

* parse .xml document with given input stream
  IF mo_ixml->create_parser(
      document       = mo_document
      istream        = lo_istream
      stream_factory = lo_stream_factory )->parse( ) IS INITIAL.

*   create iterator
    lo_iterator = mo_document->create_iterator( 0 ).

*   skip #document element
    lo_iterator->get_next( ).

*   start with root
    traverse( EXPORTING im_current_node = lo_iterator->get_next( )
              CHANGING  ch_data         = ex_data ).
  ENDIF.
  FREE : mo_document.

ENDMETHOD.
ENDCLASS.

Feedback is most welcomed!

Tudor

6 Comments