ABAP和外部系统做接口的时候,XML和JSON是两种常用的数据传输格式,它们各自有一些优缺点,如下所述:
XML:
优点:XML具有较好的可扩展性和灵活性,可以通过定义DTD或XSD等文档类型定义来定义和验证数据结构。XML的标记语言具有很强的可读性,易于人们理解和处理,对于需要手动编辑的文件而言,这是一个重要的优势。
缺点:XML语法比JSON复杂,需要更多的字符和标签来表示相同的信息,这导致XML的文件大小较大。在处理大型数据时,会带来较高的处理负载和内存占用。与JSON相比,XML处理速度较慢。
JSON:
优点:JSON比XML更轻量级,因此在传输和处理大量数据时更有效率。语法简单,易于阅读和编写,并且可以通过各种编程语言和平台进行处理。
缺点:JSON缺乏XML的严格数据类型,无法通过DTD或XSD等文档类型定义来验证数据结构的正确性。不支持注释,这使得JSON文件不像XML那样容易被人类读取和编辑,对于需要手动编辑的文件而言,JSON的可读性不如XML。
实事求是的讲,就ABAP做的接口而言,大多都是固定的数据格式,不需要人工解读,DTD和XSD在接口使用场景下更是毫无用处,所以XML的优点在这儿无任何卵用,反而是文件大、效率低、处理起来恶心,相比JSON一无是处。
所以建议使用JSON来代替XML。
正是因为XML处理起来太麻烦,所以给大家提供几个有用的函数,有了这几个函数足矣应付90+%的XML格式接口场景了。
1、SAP数据转为XML文件
FUNCTION zxml_data_to_string .
*"----------------------------------------------------------------------
*"*"局部接口:
*" IMPORTING
*" REFERENCE(DATA) TYPE ANY
*" REFERENCE(CASE) TYPE C OPTIONAL
*" REFERENCE(PRETTY) TYPE C OPTIONAL
*" REFERENCE(ROOTNAME) TYPE STRING DEFAULT 'BOOT'
*" REFERENCE(CHARACTER_SET) TYPE STRING DEFAULT 'UTF-8'
*" REFERENCE(DCXMLINIT) TYPE DCXMLINIT DEFAULT 'N'
*" EXPORTING
*" REFERENCE(XMLSTR) TYPE STRING
*" REFERENCE(XMLXSTR) TYPE XSTRING
*" TABLES
*" MAPTAB STRUCTURE ZXML_NODEN_MAPPING OPTIONAL
*" EXCEPTIONS
*" DATA_TO_DOM_ERROR
*"----------------------------------------------------------------------
DATA ifxml TYPE REF TO if_ixml.
DATA factory TYPE REF TO if_ixml_stream_factory.
DATA document TYPE REF TO if_ixml_document.
DATA node TYPE REF TO if_ixml_node.
DATA iterator TYPE REF TO if_ixml_node_iterator.
DATA ostream TYPE REF TO if_ixml_ostream.
DATA encoding TYPE REF TO if_ixml_encoding .
DATA element TYPE REF TO if_ixml_element.
DATA retval TYPE i.
DATA str TYPE string.
DATA control TYPE dcxmlsercl.
ifxml = cl_ixml=>create( ).
document = ifxml->create_document( ).
factory = ifxml->create_stream_factory( ).
encoding = ifxml->create_encoding( byte_order = 0 character_set = character_set ).
control-init_treat = dcxmlinit .
CALL FUNCTION 'SDIXML_DATA_TO_DOM'
EXPORTING
name = rootname
dataobject = data
control = control
IMPORTING
data_as_dom = element
CHANGING
document = document
EXCEPTIONS
illegal_name = 1
OTHERS = 2.
IF element IS INITIAL.
RAISE data_to_dom_error.
ELSE.
retval = document->append_child( new_child = element ).
ENDIF.
IF case <> '' OR maptab[] IS NOT INITIAL.
iterator = document->create_iterator( ).
DO.
node = iterator->get_next( ).
IF node IS INITIAL.
EXIT.
ENDIF.
IF node->get_type( ) = if_ixml_node=>co_node_element.
READ TABLE maptab WITH KEY frstr = node->get_name( ).
IF sy-subrc = 0.
str = maptab-tostr.
node->set_name( str ).
ELSE.
CASE case.
WHEN 'U'.
node->set_name( to_upper( node->get_name( ) ) ).
WHEN 'L'.
node->set_name( to_lower( node->get_name( ) ) ).
WHEN OTHERS.
ENDCASE.
ENDIF.
ENDIF.
ENDDO.
ENDIF.
ostream = factory->create_ostream_xstring( string = xmlxstr ).
ostream->set_encoding( encoding = encoding ).
ostream->set_pretty_print( pretty_print = pretty ).
document->render( ostream = ostream ).
xmlstr = cl_abap_codepage=>convert_from( source = xmlxstr codepage = character_set ).
ENDFUNCTION.
2、XML转为SAP数据
FUNCTION zxml_string_to_data .
*"----------------------------------------------------------------------
*"*"局部接口:
*" IMPORTING
*" REFERENCE(XMLSTR) TYPE STRING
*" REFERENCE(ICDATA) TYPE CHAR1 OPTIONAL
*" EXPORTING
*" REFERENCE(DATA) TYPE ANY
*" REFERENCE(SUBRC) TYPE SY-SUBRC
*"----------------------------------------------------------------------
***如果要取CDATA数据
***程序:LSDIXMLF04,FORM get_cdata,做如下修改:
* ...
* DATA: icdata.
*
* CLEAR value.
* IMPORT icdata FROM MEMORY ID 'ZXML_STRING_TO_DATA_ICDATA'.
* IF icdata IS NOT INITIAL .
* filter = element->create_filter_node_type( 48 ).
* ELSE.
* filter = element->create_filter_node_type(
* if_ixml_node=>co_node_text ).
* ENDIF.
* iterator = element->create_iterator( 1 ).
* ...
DATA: go_xml TYPE REF TO cl_xml_document .
IF icdata IS NOT INITIAL.
EXPORT icdata TO MEMORY ID 'ZXML_STRING_TO_DATA_ICDATA'. "LSDIXMLF04
ENDIF.
IF go_xml IS INITIAL.
CREATE OBJECT go_xml.
ENDIF.
CALL METHOD go_xml->parse_string
EXPORTING
stream = xmlstr
RECEIVING
retcode = subrc.
CALL METHOD go_xml->get_data
IMPORTING
retcode = subrc
CHANGING
dataobject = data.
FREE MEMORY ID 'ZXML_STRING_TO_DATA_ICDATA'.
ENDFUNCTION.
3、对XML重新处理,比如标签名的批量更改、字符集的转换等
FUNCTION zxml_string_rebuild .
*"----------------------------------------------------------------------
*"*"局部接口:
*" IMPORTING
*" REFERENCE(XMLIN) TYPE STRING
*" REFERENCE(CASE) TYPE C OPTIONAL
*" REFERENCE(PRETTY) TYPE C OPTIONAL
*" REFERENCE(CHARACTER_SET) TYPE STRING DEFAULT 'UTF-8'
*" EXPORTING
*" VALUE(XMLOUT) TYPE STRING
*" VALUE(XMLOUTX) TYPE XSTRING
*" VALUE(CDATA) TYPE RSTT_T_STRINGS
*" TABLES
*" MAPTAB STRUCTURE ZXML_NODEN_MAPPING OPTIONAL
*" EXCEPTIONS
*" XML_ERROR
*"----------------------------------------------------------------------
DATA ifxml TYPE REF TO if_ixml.
DATA factory TYPE REF TO if_ixml_stream_factory.
DATA document TYPE REF TO if_ixml_document.
DATA iterator TYPE REF TO if_ixml_node_iterator.
DATA node TYPE REF TO if_ixml_node.
DATA parser TYPE REF TO if_ixml_parser.
DATA istream TYPE REF TO if_ixml_istream.
DATA ostream TYPE REF TO if_ixml_ostream.
DATA encoding TYPE REF TO if_ixml_encoding .
* DATA retval TYPE i.
DATA xmlxstr TYPE xstring.
DATA str TYPE string.
DATA wa_cdata TYPE rstt_s_string.
ifxml = cl_ixml=>create( ).
document = ifxml->create_document( ).
factory = ifxml->create_stream_factory( ).
xmlxstr = cl_abap_codepage=>convert_to( source = xmlin ).
istream = factory->create_istream_xstring( string = xmlxstr ).
parser = ifxml->create_parser( document = document
stream_factory = factory
istream = istream
).
IF parser->parse( ) <> 0.
RAISE xml_error.
ENDIF.
iterator = document->create_iterator( ).
DO.
node = iterator->get_next( ).
IF node IS INITIAL.
EXIT.
ENDIF.
IF node->get_type( ) = if_ixml_node=>co_node_element.
READ TABLE maptab WITH KEY frstr = node->get_name( ).
IF sy-subrc = 0.
str = maptab-tostr.
node->set_name( str ).
ELSE.
CASE case.
WHEN 'U'.
node->set_name( to_upper( node->get_name( ) ) ).
WHEN 'L'.
node->set_name( to_lower( node->get_name( ) ) ).
ENDCASE.
ENDIF.
ELSEIF node->get_type( ) = if_ixml_node=>co_node_cdata_section.
wa_cdata-string = node->get_value( ).
APPEND wa_cdata TO cdata.
ENDIF.
ENDDO.
ostream = factory->create_ostream_xstring( string = xmloutx ).
IF character_set IS NOT INITIAL.
encoding = ifxml->create_encoding( byte_order = 0 character_set = character_set ).
ostream->set_encoding( encoding = encoding ).
ENDIF.
IF pretty IS NOT INITIAL.
ostream->set_pretty_print( pretty_print = pretty ).
ENDIF.
document->render( ostream = ostream ).
xmlout = cl_abap_codepage=>convert_from( xmloutx ).
ENDFUNCTION.
4、XML表示数组(内表)有两种格式,分别是
和:
SAP默认生成是第二种格式,但是如果外部系统只认第一种格式呢?
这个时候就需要有把两个格式相互转换的方法,也就是下面两个函数:
FUNCTION ZXML_TRANSFER_ITEMTAG1 .
*"----------------------------------------------------------------------
*"*"局部接口:
*" IMPORTING
*" VALUE(XMLIN) TYPE STRING
*" EXPORTING
*" VALUE(XMLSTR) TYPE STRING
*" TABLES
*" TAGTAB
*"----------------------------------------------------------------------
DATA: maptab TYPE TABLE OF zxml_noden_mapping WITH HEADER LINE.
DATA: tagc(100).
DATA: moff TYPE i .
CHECK xmlin IS NOT INITIAL.
FIND FIRST OCCURRENCE OF '<item>' IN xmlin .
IF sy-subrc = 0.
MESSAGE e000(oo) WITH '原始XML已经包含<item>标签,无法转换'.
ENDIF.
xmlstr = xmlin.
LOOP AT tagtab.
CLEAR: maptab,maptab[].
maptab-frstr = tagtab.
maptab-tostr = 'item'.
APPEND maptab.
CALL FUNCTION 'ZXML_STRING_REBUILD'
EXPORTING
xmlin = xmlstr
character_set = ''
IMPORTING
xmlout = xmlstr
TABLES
maptab = maptab
EXCEPTIONS
xml_error = 1.
IF sy-subrc <> 0.
MESSAGE e000(oo) WITH '转换出错'.
ELSE.
FIND FIRST OCCURRENCE OF '<item>' IN SECTION OFFSET moff OF xmlstr
MATCH OFFSET moff.
IF sy-subrc = 0.
tagc = '<' && tagtab && '>'.
CONCATENATE xmlstr(moff) tagc xmlstr+moff INTO xmlstr.
ENDIF.
FIND ALL OCCURRENCES OF '</item>' IN SECTION OFFSET moff OF xmlstr
MATCH OFFSET moff.
IF sy-subrc = 0.
moff = moff + 7.
tagc = '</' && tagtab && '>'.
CONCATENATE xmlstr(moff) tagc xmlstr+moff INTO xmlstr.
ENDIF.
ENDIF.
ENDLOOP.
ENDFUNCTION.
FUNCTION zxml_transfer_itemtag2 .
*"----------------------------------------------------------------------
*"*"局部接口:
*" IMPORTING
*" VALUE(XMLIN) TYPE STRING
*" EXPORTING
*" VALUE(XMLSTR) TYPE STRING
*" TABLES
*" TAGTAB
*"----------------------------------------------------------------------
DATA: regex(100).
DATA: tagb(100),
tage(100),
tags(100).
DATA: moff TYPE i ,
mlen TYPE i .
CHECK xmlin IS NOT INITIAL.
FIND FIRST OCCURRENCE OF '<item>' IN xmlin .
IF sy-subrc <> 0.
MESSAGE e000(oo) WITH '原始XML不包含<item>标签,无法转换'.
ENDIF.
xmlstr = xmlin.
LOOP AT tagtab.
tagb = '<' && tagtab && '>'.
tage = '</' && tagtab && '>'.
regex = tagb && '.*' && tage.
FIND FIRST OCCURRENCE OF REGEX regex IN xmlstr MATCH OFFSET moff MATCH LENGTH mlen .
IF sy-subrc = 0.
REPLACE ALL OCCURRENCES OF '<item>' IN SECTION OFFSET moff LENGTH mlen OF xmlstr WITH tagb.
ENDIF.
FIND FIRST OCCURRENCE OF REGEX regex IN xmlstr MATCH OFFSET moff MATCH LENGTH mlen .
IF sy-subrc = 0.
REPLACE ALL OCCURRENCES OF '</item>' IN SECTION OFFSET moff LENGTH mlen OF xmlstr WITH tage.
ENDIF.
tags = tagb && tagb.
REPLACE ALL OCCURRENCES OF tags IN xmlstr WITH tagb.
tags = tage && tage.
REPLACE ALL OCCURRENCES OF tags IN xmlstr WITH tage.
ENDLOOP.
ENDFUNCTION.
测试样例:
REPORT z_barry_testxml NO STANDARD PAGE HEADING.
DATA: BEGIN OF wa_appaysavz ,
errcod TYPE string,
errmsg TYPE string,
paytim TYPE string,
recnum TYPE string,
refnbr TYPE string,
remark TYPE string,
END OF wa_appaysavz.
DATA: BEGIN OF wa_appaysavy ,
busnbr TYPE string,
exttx1 TYPE string,
sqrnbr TYPE string,
END OF wa_appaysavy.
DATA: BEGIN OF wa_info ,
erptyp TYPE string,
errmsg TYPE string,
funnam TYPE string,
retcod TYPE string,
END OF wa_info.
DATA: BEGIN OF wa_sycomretz,
errcod TYPE string,
errdtl TYPE string,
errmsg TYPE string,
END OF wa_sycomretz.
DATA: BEGIN OF wa_cbserppgk ,
info LIKE wa_info,
appaysavy LIKE TABLE OF wa_appaysavy,
appaysavz LIKE TABLE OF wa_appaysavz,
sycomretz LIKE wa_sycomretz,
END OF wa_cbserppgk.
DATA moff TYPE i .
DATA xmlstr TYPE string.
DATA maptab TYPE TABLE OF zxml_noden_mapping WITH HEADER LINE.
DATA tagtab TYPE TABLE OF char100.
START-OF-SELECTION.
CONCATENATE '<?xml version="1.0" encoding="UTF-8"?>'
'<CBSERPPGK>'
' <INFO>'
' <ERPTYP>H</ERPTYP>'
' <ERRMSG/>'
' <FUNNAM>ERPAYSAV</FUNNAM>'
' <RETCOD>0000000</RETCOD>'
' </INFO>'
' <APPAYSAVY>'
' <BUSNBR>0000001</BUSNBR>'
' <EXTTX1>0000001</EXTTX1>'
' <SQRNBR>0000001</SQRNBR>'
' </APPAYSAVY>'
' <APPAYSAVY>'
' <BUSNBR>0000002</BUSNBR>'
' <EXTTX1>0000002</EXTTX1>'
' <SQRNBR>0000002</SQRNBR>'
' </APPAYSAVY>'
' <APPAYSAVY>'
' <BUSNBR>0000003</BUSNBR>'
' <EXTTX1>0000003</EXTTX1>'
' <SQRNBR>0000003</SQRNBR>'
' </APPAYSAVY>'
' <APPAYSAVZ>'
' <ERRCOD>0000001</ERRCOD>'
' <ERRMSG>业务参考号重复!</ERRMSG>'
' <PAYTIM/>'
' <RECNUM>1</RECNUM>'
' <REFNBR>gp:FK21092321147</REFNBR>'
' <REMARK/>'
' </APPAYSAVZ>'
' <APPAYSAVZ>'
' <ERRCOD>0000001</ERRCOD>'
' <ERRMSG>业务参考号重复!</ERRMSG>'
' <PAYTIM/>'
' <RECNUM>1</RECNUM>'
' <REFNBR>gp:FK21092321148</REFNBR>'
' <REMARK/>'
' </APPAYSAVZ>'
' <SYCOMRETZ>'
' <ERRCOD>0000000</ERRCOD>'
' <ERRDTL/>'
' <ERRMSG/>'
' </SYCOMRETZ>'
'</CBSERPPGK>'
INTO xmlstr.
"转换内表的标签格式
APPEND 'APPAYSAVY' TO tagtab.
APPEND 'APPAYSAVZ' TO tagtab.
CALL FUNCTION 'ZXML_TRANSFER_ITEMTAG1'
EXPORTING
xmlin = xmlstr
IMPORTING
xmlstr = xmlstr
TABLES
tagtab = tagtab.
"反序列化
CALL FUNCTION 'ZXML_STRING_TO_DATA'
EXPORTING
xmlstr = xmlstr
IMPORTING
data = wa_cbserppgk.
BREAK-POINT.
几个特别重要的说明:
1、使用ZXML_STRING_TO_DATA反序列化XML的时候,SAP对象的元素定义和XML的格式并不需要一一对应。比如银行接口,针对不同的业务会返回不同格式的XML,这个时候只要定义一个包含这些格式元素全集的深层结构就OK了。
2、有时候需要直接发送二进制的XML给外部系统或者是下载到本地,ZXML_DATA_TO_STRING和ZXML_STRING_REBUILD都提供XSTRING格式的XML,在需要的时候,可以直接使用而不必再转换一次。
3、ZXML_TRANSFER_ITEMTAG1和2本来应该写成一个单独的函数,但这玩意用得少,没动力,不改了。
4、有时候外部系统会把数据写到CDATA里面,ZXML_STRING_TO_DATA函数是不能直接取到的,需要改一下源码才可,改源码的位置和代码写到ZXML_STRING_TO_DATA的注释里面了。