05-11-2016 4:57 PM
Hey, we are currently working on an ICF Service, which calls some function modules.
The result of those calls (i.e. any export parameter) will be converted to an XML.
Unfortunately, the development of the function module is beyond our control. Therefore we do not know the type of the individual parameters.
Our goal is the following XML structure:
Any simple type is mapped to a single XML tag.
Example:
e_result TYPE string VALUE 'hello'.
will be mapped to:
<E_RESULT>hello<E_RESULT>
Structures are mapped as following (the given structure type will be in data dictionary, as it is used as export parameter):
BEGIN OF my_ddic_str
result TYPE string
result2 TYPE i
END OF my_ddic_str.
DATA bla TYPE my_ddic_str.
my_ddic_str-result = 'hello'.
my_ddic_str-result = 'hello2'.
will be mapped to:
<BLA result="hello" result2="hello2"/>
If any component of the structure is deep, it will be added as child tag.
Example:
BEGIN OF internal_str
result2 TYPE string
result3 TYPE string
END OF internal_str
BEGIN OF my_ddic_str
result TYPE string
internal TYPE internal_str
END OF my_ddic_str.
DATA bla TYPE my_ddic_str.
my_ddic_str-result = 'hello'.
DATA bla2 TYPE internal_str
bla2-result2 = 'hello2'.
bla2-result3 = 'hello3'.
will be mapped to:
<BLA result="hello">
<INTERNAL result2="hello2" result3="hello3"/>
</BLA>
Any table is mapped to a main table tag, whose lines are children tags. Those children tags have "item" as name and look the same, as the structure tags (as table lines are structures).
Is there any way in XSLT or ST (Simple Transformation) to detect the type of the given data, so that we can build the XML, based on that type?
Unfortunately the identity transformation does not fit our needs, as it does not add structure's components as attributes.
Kind regards
Tobias
05-11-2016 7:18 PM
In case you want to know technical attributes of a table/type/field, here's the code you looking for:
*&- Output Workarea Type for Settings/Metadata for Output
TYPES: BEGIN OF type_mdata,
tabname TYPE tabname. " Table Name
INCLUDE TYPE abap_compdescr. " Field Names
TYPES: outlength TYPE i, " Output Length
rollname TYPE dd04t-rollname, " Data Element
reptext TYPE dd04t-reptext, " Header
scrtext_s TYPE dd04t-scrtext_s, " Short Text
scrtext_m TYPE dd04t-scrtext_m, " Medium Text
scrtext_l TYPE dd04t-scrtext_l, " Long Text
colpos TYPE i, " Column Position
no_out TYPE lvc_noout, " No Out
END OF type_mdata.
*&- Local Types
*&- Types for Elements
TYPES: BEGIN OF type_elems,
fldnm TYPE abap_compname, " Field Name
fldid TYPE dd04t-rollname, " Data Element
fldln TYPE i, " Output Length
END OF type_elems.
*&- Local Data Declarations for Internal Tables
DATA:
lt_comps TYPE abap_compdescr_tab, " Table Components
lt_elems TYPE TABLE OF type_elems. " Field Mappings
*&- Local Data Declarations for Objects
DATA :
lo_structdescr TYPE REF TO cl_abap_structdescr, " Structure Description
lo_typedescr TYPE REF TO cl_abap_typedescr, " Type Description
lo_tabledescr TYPE REF TO cl_abap_tabledescr, " Table Description
lo_elemdescr TYPE REF TO cl_abap_elemdescr. " Element Description
*&- Local Data Declarations for Error Hash
DATA: lx_root_error TYPE REF TO cx_root. " Error for Object References
*&- Get Table Component Reference (IT_TABLE can be any table here)
TRY.
lo_tabledescr ?= cl_abap_typedescr=>describe_by_data( it_table ).
CATCH cx_root INTO lx_root_error ##catch_all.
CLEAR: lw_msgtx.
lw_msgtx = lx_root_error->get_text( ).
MESSAGE lw_msgtx TYPE c_msgty_s DISPLAY LIKE c_msgty_e .
EXIT.
ENDTRY.
*&- Get Type Description for Table Type
TRY.
lo_typedescr = lo_tabledescr->get_table_line_type( ).
CATCH cx_root INTO lx_root_error ##catch_all.
CLEAR: lw_msgtx.
lw_msgtx = lx_root_error->get_text( ).
MESSAGE lw_msgtx TYPE c_msgty_s DISPLAY LIKE c_msgty_e.
EXIT.
ENDTRY.
*&- Get Line Type by Type Casting to Structure Type
lo_structdescr ?= lo_typedescr.
*&- Get Fields from the Table Components
lt_comps = lo_structdescr->components[].
*&- Loop through the Fields and Create Field Catalog
LOOP AT lt_comps INTO ls_comps.
*&- Get Element Details (Data Element)
lo_elemdescr ?= lo_structdescr->get_component_type( p_name = ls_comps-name ).
*&- Store Data in Texts Table
ls_elems-fldnm = ls_comps-name.
ls_elems-fldid = lo_elemdescr->help_id.
ls_elems-fldln = lo_elemdescr->output_length.
APPEND ls_elems TO lt_elems.
CLEAR: ls_elems.
ENDLOOP. " LOOP AT lt_comps INTO ls_comps.
*&- Get Data Elements Description Texts from Table DD04T
IF lt_elems IS NOT INITIAL.
SELECT * FROM dd04t
INTO TABLE lt_dd04t
FOR ALL ENTRIES IN lt_elems
WHERE rollname = lt_elems-fldid
AND ddlanguage EQ sy-langu
AND as4local = c_as4locl.
IF sy-subrc EQ 0.
*&- Sort All Table Data Found
SORT lt_dd04t BY rollname.
ENDIF. " IF sy-subrc EQ 0.
ENDIF. " IF lt_elems IS NOT INITIAL.
*&- Sort ALV Header Texts Table
SORT t_heads BY name.
*&- Prepare Metadata
LOOP AT lt_comps INTO ls_comps.
*&- First Store Table Index
lw_index = sy-tabix.
*&- Get Data Element Name for Field
READ TABLE lt_elems INTO ls_elems
WITH KEY fldnm = ls_comps-name
BINARY SEARCH.
IF sy-subrc EQ 0.
READ TABLE lt_dd04t INTO ls_dd04t
WITH KEY rollname = ls_elems-fldid
BINARY SEARCH.
IF sy-subrc NE 0.
*&- If Nothing Found, Temporary Fill Missing Texts
ls_dd04t-rollname = ls_elems-fldid.
ls_dd04t-reptext = 'Missing Text'.
ls_dd04t-scrtext_s = ls_dd04t-reptext.
ls_dd04t-scrtext_m = ls_dd04t-reptext.
ls_dd04t-scrtext_l = ls_dd04t-reptext.
ENDIF. " IF
*&- Prepare Final Output Metadata Table
ls_metadata-tabname = ls_control-keynm.
ls_metadata-length = ls_comps-length.
ls_metadata-decimals = ls_comps-decimals.
ls_metadata-type_kind = ls_comps-type_kind.
ls_metadata-name = ls_comps-name.
ls_metadata-outlength = ls_elems-fldln.
ls_metadata-rollname = ls_dd04t-rollname.
ls_metadata-reptext = ls_dd04t-reptext.
ls_metadata-scrtext_s = ls_dd04t-scrtext_s.
ls_metadata-scrtext_m = ls_dd04t-scrtext_m.
ls_metadata-scrtext_l = ls_dd04t-scrtext_l.
ls_metadata-colpos = lw_index.
ls_metadata-no_out = ls_heads-no_out.
*&- Clear Variables
CLEAR: ls_metadata, ls_elems, ls_dd04t.
ENDLOOP. " LOOP AT lt_comps INTO ls_comps.
By end of this loop you would have all technical information to create an XML tree. You may be required to modify the code a bit to fit your requirement. However, the interesting feature is you can get all type/technical/semantic information of an table/field.
Check if this transformation can be helpful in your case.
*&- Local Data Declarations for References
DATA: lo_xml_writer TYPE REF TO cl_sxml_string_writer. " XML Writer Object
*&- Create an XML Object for Internal Table
lo_xml_writer = cl_sxml_string_writer=>create( type = if_sxml=>co_xt_xml10 ).
*&- Call Transformation to Convert the String to XML Object (IT_INPUT can be any table)
CALL TRANSFORMATION id SOURCE headers = it_input RESULT XML lo_xml_writer.
Adding to this, the following classes might be helpful in creating tags elements.
if_ixml_document
if_ixml_element,
if_ixml_attribute
Cheers,
VS
05-12-2016 7:51 AM
Hey VS, thank you very mutch for your answer.
Currently (and I should have mentioned that) we are actually using the IXML library to build the XML tree programmatically, where we use the tabledescriptors and structdescriptors to determine the technical types of our data elements. Everything works fine, but we are running into some performance issues.
When our ICF Service has to create a big XML it consumes approximately 120 ms of ressources.
When switching to the XSLT/ST approach and using the identity transformation this overall time is reduced to 60 ms.
Unfortunately I am very new to XSLT/ST. Is there a way to detect (directly within XSLT or ST), if the source object is a table or a deep structure?.
If not, I will try your approach, by using the ID transformation and then using table and struct descriptors to adjust the XML result to my needs.
05-11-2016 10:06 PM
I don't understand how your ICF service handler can call function modules without knowing the parameter types. If you don't pass compatible types, that will dump!
That said, if you have a data object (structured or whatever) containing the parameter value, I think it's possible to automatically map the structure components to attributes of its upper element, whatever you do it in XSLT or ST (which would be preferrable over XSLT because of performance). But if you don't master ST/XSLT, I recommend to do it in iXML as suggested by VS, after having called the identity transformation (as I said, to call the function modules, you must have data objects compatible with parameters, so you may apply the identity transformation).
05-12-2016 8:25 AM
Hey Sandra, thanks for your response,
as you can see in my response to VS's answer, we are actually using the IXML library to parse the received XML and then use the information about the function module, we get from database table fupararef, to convert the strings from the XML to the correct type.
Unfortunately, we have some performance issues, which are not a big problem, when parsing an XML, but during XML creation (based on the result of the function module).
And yes, XSLT / ST is very new to me, indeed. But, as this is so much faster, than our IXML handling, my hope is, that it is possible to do the whole abap - xml transformation solely in XSLT (or ST, as it is faster - thanks for this information!). But I do not know, if it is possible to detect (in XSLT / ST), if the source object is a table or a deep structure (like I could use
DESCRIBE FIELD l_value TYPE l_type
to get the type in ABAP).
05-12-2016 9:51 AM
You say "so fast", but there's not so much difference between 60/120 ms! Why making a Ferrari for one program when the rest of SAP is "so" slow? Personally, I would keep your current code.
Another point is that ID is an XSLT, so it's basically slow (although it doesn't do many things). With an ST, you may expect a faster processing.
In ST/XSLT, you may call ABAP (tt:call-method for ST, and sap:call-external and sap:external-function for XSLT)
By the way, after thinking, I'm not so sure that ST could fit your needs, because I don't know if it may work on elements with a variable name (yes for attributes). Well, that's pure theory, I'm not expert, so the best way is to try (personally I would first try with XSLT).
05-12-2016 10:32 AM
Yes, you are right. 120ms is not that much. Unfortunately we do not know, how this duration scales, when more devices connect to our ICF Service and invoke the handler method.
We know, that sometime there will be about 500 devices connected to the ICF Service. Each device will send approximately 1000 requests a day.
We fear, that this will result in an inappropriate high load on the SAP system. Therefore, we are happy with every saved millisecond.
Thank you very much for mentioning the tt:call-method (sap:call-external) functionality.
I will have a look at that.
05-12-2016 12:13 PM
I digged a little bit to see whether ST can be used.
I think it's not possible to use ST because of 2 reasons: the element names cannot be dynamic, and it's not possible to loop at structure components (tt:loop is only for a data node corresponding to an internal table).
About XSLT, I had done some highly dynamic transformations in the past (with XSLT for PHP, not with XSLT for ABAP), so I'm confident you could use it (xsl:element for dynamic element name, xsl:for-each may be used for structure components, and name(.) as xpath expression for getting the element name of current node)
05-12-2016 12:26 PM
Thank you Sandra for giving these hints and xslt-statements I can use.
I will now continue diving deeper into this topic and hopefully will come up with an appropriate solution 🙂
07-01-2016 1:40 PM
Hi Sandra,
ID can be seen as an XSLT program in the repository, but that is not necessarily called.
In dependence from source and result, there are optimized ID-engines called instead.
Best
Horst
05-12-2016 10:16 AM
Hi Tobias,
I don't remember where I found the basics, I created a very simple and straightforward method to convert any kind of abap data object into xml. The result is written as a file to the users desktop. I used methods parse_string and export_to_file of class cl_xml_document but you can also use method GET_DATA and continue as you need.
METHOD xml_to_desktop." importing IS_ANY type ANY
DATA:
lv_xml TYPE string,
lo_xml_doc TYPE REF TO cl_xml_document,
lv_get_desktop_directory TYPE string,
lv_localfile TYPE localfile,
lo_typedescr TYPE REF TO cl_abap_typedescr,
ls_x030l TYPE x030l.
lo_typedescr = cl_abap_datadescr=>describe_by_data( is_any ).
ls_x030l = lo_typedescr->get_ddic_header( ).
CALL TRANSFORMATION id
SOURCE data_node = is_any
RESULT XML lv_xml.
CREATE OBJECT lo_xml_doc.
lo_xml_doc->parse_string( lv_xml ).
cl_gui_frontend_services=>get_desktop_directory(
CHANGING
desktop_directory = lv_get_desktop_directory ).
cl_gui_cfw=>flush( ).
lv_get_desktop_directory =
lv_get_desktop_directory &&
'\' &&
ls_x030l-tabname &&
sy-datum &&
sy-uzeit &&
'.xml'(xml) .
lv_localfile = lv_get_desktop_directory.
lo_xml_doc->export_to_file( lv_localfile ).
ENDMETHOD.
I hope the code will do what you expect it to do - I love ist for the simplicity
Regards Clemens