среда, 22 июля 2015 г.

XML-парсер

Пришла задача загружать данные из внешней системы, которая отдаёт XML файл.
В одном файле приходят сразу всё таблицы.

В BW есть встроенный инструмент, "Редактор трансформаций", транзакция STRANS, с ним и будем работать.

https://www.dropbox.com/s/oa1r6bc0xmffo0a/PROG%20%28XML%20from%20WEB-server%29.docx?dl=0
В сети гуляет куча гайдов и способов, мы воспользуемся xsl-преобразованиями.
Сам xsl-код называется таблицей стилей, который парсит xml-файл, а на выход даёт xml-файл, но уже нужной структурой.

Оператор вызова xsl-преобразования работает как функциональный модуль - на вход закинули файл, на выходе получили заполненные таблицы, или наоборот. Синтаксис вызова примерно такой:
      CALL TRANSFORMATION ztrans_logistic_center
      SOURCE XML l_str

      RESULT

        enz       = lt_enz

или такой:
CALL TRANSFORMATION id 
SOURCE
lt_tab 
lt_tab
RESULT XML lv_str.


Для начала определим структуру, которую нам нужно будет задать в xsl-преобразовании для преобразованного файла:


lt_enz =
lt_inzh
= lt_inzl =
VALUE #(
 
( record = 1 )
 
( record = 2 )
 
( record = 3 )
 
( record = 4 )
).
CALL TRANSFORMATION id
SOURCE
  enz
= lt_enz
  inzh
= lt_inzh
  inzl
= lt_inzl
RESULT XML lv_str
.

cl_demo_output=>display_xml( lv_str ).

Видим, что таблицы выходного xml-файла идут сразу от корневого узла, ок, повторяем такую же структуру в xsl:
<!--***************************************************************************-->
  <xsl:template match=
"/" >
    <asx:abap xmlns:asx=
"http://www.sap.com/abapxml" version="1.0">
      <asx:values>
          <xsl:apply-templates select=
"//ENZ"/>
          <xsl:apply-templates select=
"//INZ"/>
          <xsl:apply-templates select=
"//ZR"/>
          <xsl:apply-templates select=
"//PTE"/>
          <xsl:apply-templates select=
"//STE"/>
          <xsl:apply-templates select=
"//P"/>
          <xsl:apply-templates select=
"//PNZ"/>
          <xsl:apply-templates select=
"//NZKGMK"/>
          <xsl:apply-templates select=
"//NZNGMK"/>
          <xsl:apply-templates select=
"//PR"/>
          <xsl:apply-templates select=
"//NZ"/>
          <xsl:apply-templates select=
"//SPARNZ"/>
          <xsl:apply-templates select=
"//PARNZ"/>

          <SCHTY>
            <xsl:apply-templates select=
"//STRP"/>
            <xsl:apply-templates select=
"//SUSERS"/>
            <xsl:apply-templates select=
"//SPHP"/>
            <xsl:apply-templates select=
"//SCONTR"/>
            <xsl:apply-templates select=
"//SORG"/>
            <xsl:apply-templates select=
"//SCURKGMK"/>
            <xsl:apply-templates select=
"//SPCONTR"/>
            <xsl:apply-templates select=
"//SSPT"/>
            <xsl:apply-templates select=
"//SDSPT"/>
          </SCHTY>
      </asx:values>
    </asx:abap>
  </xsl:template>

<!--***************************************************************************-->

Немного об особенностях xsl.
<!--    --> - коментарии.
В версии 1.0 не работают функции, пришлось воспользоваться шаблонами с параметрами (смотрим ниже в коде).
Пустые значение дат\времени нужно переводить во внутренний формат с разделителями.
Не забываем про кодировку.
Получить атрибут родительского узла из дочернего можно так:

    <INZL>
      <xsl:for-each select="//INZ//record" >
        <ZINZL>
          <_-BIC_-SNROW>     <xsl:value-of select="parent::document/@REF" /> </_-BIC_-SNROW>
        </ZINZL>
      </xsl:for-each>

    </INZL>







Cам код (код длинный, оставил пару таблиц):

<xsl:transform  xmlns:xsl= "http://www.w3.org/1999/XSL/Transform"
                xmlns:sap=
"http://www.sap.com/sapxsl"
                xmlns:xs=
"http://www.w3.org/2001/XMLSchema"
                version=
"1.0"> <!--***************************************************************************-->
<xsl:output encoding=
"UTF-8" indent="yes" method= "xml" version="1.0" />
<xsl:strip-space elements=
"*" /><!--***************************************************************************-->
    <xsl:template name=
"zDateCheck" >
      <xsl:param name=
"Dt" />
        <xsl:if test=
"string($Dt)" >
            <xsl:value-of select=
"concat(substring($Dt, 1, 4), '-', substring($Dt, 5, 2), '-', substring($Dt, 7, 2))"/>
        </xsl:if>
    </xsl:template>

    <xsl:template name=
"zTimeCheck" >
      <xsl:param name=
"Tm" />
        <xsl:if test=
"string($Tm)" >
            <xsl:value-of select=
"concat(substring($Tm, 1, 2), ':', substring($Tm, 3, 2), ':', substring($Tm, 5, 2))"/>
        </xsl:if>
    </xsl:template>

    <xsl:variable name=
"MsgNo" select="/lcdata/@НомерСообщения" />
    <xsl:variable name=
"MsgDate" >
        <xsl:call-template name=
"zDateCheck">
            <xsl:with-param name=
"Dt" select= "/lcdata/@ДатаСообщения" />
        </xsl:call-template>
    </xsl:variable>
<!--    select="/lcdata/@ДатаСообщения"/>-->
    <xsl:variable name=
"MsgTime" >
        <xsl:call-template name=
"zTimeCheck">
            <xsl:with-param name=
"Tm" select= "/lcdata/@ВремяСообщения" />
        </xsl:call-template>
    </xsl:variable>
<!--    select="/lcdata/@ВремяСообщения"/>-->
<!--***************************************************************************-->
  <xsl:template match=
"/" >
    <asx:abap xmlns:asx=
"http://www.sap.com/abapxml" version="1.0">
      <asx:values>
          <xsl:apply-templates select=
"//ENZ"/>
          <xsl:apply-templates select=
"//INZ"/>
          <xsl:apply-templates select=
"//ZR"/>
          <xsl:apply-templates select=
"//PTE"/>
          <xsl:apply-templates select=
"//STE"/>
          <xsl:apply-templates select=
"//P"/>
          <xsl:apply-templates select=
"//PNZ"/>
          <xsl:apply-templates select=
"//NZKGMK"/>
          <xsl:apply-templates select=
"//NZNGMK"/>
          <xsl:apply-templates select=
"//PR"/>
          <xsl:apply-templates select=
"//NZ"/>
          <xsl:apply-templates select=
"//SPARNZ"/>
          <xsl:apply-templates select=
"//PARNZ"/>

          <SCHTY>
            <xsl:apply-templates select=
"//STRP"/>
            <xsl:apply-templates select=
"//SUSERS"/>
            <xsl:apply-templates select=
"//SPHP"/>
            <xsl:apply-templates select=
"//SCONTR"/>
            <xsl:apply-templates select=
"//SORG"/>
            <xsl:apply-templates select=
"//SCURKGMK"/>
            <xsl:apply-templates select=
"//SPCONTR"/>
            <xsl:apply-templates select=
"//SSPT"/>
            <xsl:apply-templates select=
"//SDSPT"/>
          </SCHTY>
      </asx:values>
    </asx:abap>
  </xsl:template>
<!--***************************************************************************-->
<xsl:template match=
"PARNZ" >
    <PARNZH>
      <xsl:for-each select=
"//PARNZ/document" >
        <ZPARNZH>
          <_-BIC_-SRECORD>     <xsl:value-of select=
"position()"/>               </_-BIC_-SRECORD>
          <_-BIC_-SNMESSAGE>   <xsl:value-of select=
"$MsgNo"/>                   </_-BIC_-SNMESSAGE>
          <_-BIC_-SMDATE>      <xsl:value-of select=
"$MsgDate"/>                 </_-BIC_-SMDATE>
          <_-BIC_-SMTIME>      <xsl:value-of select=
"$MsgTime"/>                 </_-BIC_-SMTIME>
          <_-BIC_-SNROW>       <xsl:value-of select=
"@REF"/>                     </_-BIC_-SNROW>
          <_-BIC_-SDOCTYPE>    <xsl:value-of select=
"@doctype"/>                 </_-BIC_-SDOCTYPE>
          <_-BIC_-SDELETE>     <xsl:value-of select=
"@ПризнакУдаления" />         </_-BIC_-SDELETE>
          <_-BIC_-SFNAME>      <xsl:value-of select=
"@ПредставлениеДокумента" />  </_-BIC_-SFNAME>
        </ZPARNZH>
      </xsl:for-each>
    </PARNZH>

    <PARNZL>
      <xsl:for-each select=
"//PARNZ//record" >
        <ZPARNZL>
          <_-BIC_-SRECORD>   <xsl:value-of select=
"position()"/>                </_-BIC_-SRECORD>
          <_-BIC_-SNMESSAGE> <xsl:value-of select=
"$MsgNo"/>                    </_-BIC_-SNMESSAGE>
          <_-BIC_-SMDATE>    <xsl:value-of select=
"$MsgDate"/>                  </_-BIC_-SMDATE>
          <_-BIC_-SMTIME>    <xsl:value-of select=
"$MsgTime"/>                  </_-BIC_-SMTIME>
          <_-BIC_-SNROW>     <xsl:value-of select=
"parent::document/@REF" />     </_-BIC_-SNROW>
          <_-BIC_-SNREC>     <xsl:value-of select=
"@НомерСтроки" />              </_-BIC_-SNREC>
          <_-BIC_-SINDATE>
              <xsl:call-template name=
"zDateCheck">
                  <xsl:with-param name=
"Dt" select="@ПериодД" />
              </xsl:call-template>
          </_-BIC_-SINDATE>
          <_-BIC_-SINTIME>
              <xsl:call-template name=
"zTimeCheck">
                  <xsl:with-param name=
"Tm" select="@ПериодВ" />
              </xsl:call-template>
          </_-BIC_-SINTIME>
          <_-BIC_-SPARNZ>     <xsl:value-of select=
"@ПараметрыНарядЗаказа" />    </_-BIC_-SPARNZ>
          <_-BIC_-SIMPDATE>
              <xsl:call-template name=
"zDateCheck">
                  <xsl:with-param name=
"Dt" select="@ДатаРеализацииД" />
              </xsl:call-template>
          </_-BIC_-SIMPDATE>
          <_-BIC_-SIMPTIME>
              <xsl:call-template name=
"zTimeCheck">
                  <xsl:with-param name=
"Tm" select="@ДатаРеализацииВ" />
              </xsl:call-template>
          </_-BIC_-SIMPTIME>
          <_-BIC_-SUNITCO>   <xsl:value-of select=
"@ПодразделениеКонтрагента" /> </_-BIC_-SUNITCO>
        </ZPARNZL>
      </xsl:for-each>
    </PARNZL>
  </xsl:template>
<!--***************************************************************************-->
  <xsl:template match=
"STRP" >
    <xsl:for-each select=
"//STRP/element" >
      <ITEM>
        <_-BIC_-SNMESSAGE>  <xsl:value-of select=
"$MsgNo"/>         </_-BIC_-SNMESSAGE>
        <_-BIC_-SMDATE>     <xsl:value-of select=
"$MsgDate"/>       </_-BIC_-SMDATE>
        <_-BIC_-SMTIME>     <xsl:value-of select=
"$MsgTime"/>       </_-BIC_-SMTIME>
        <_-BIC_-SRECORD>    <xsl:value-of select=
"position()"/>     </_-BIC_-SRECORD>
        <_-BIC_-SCHTYPE>    <xsl:value-of select=
"../local-name()" /></_-BIC_-SCHTYPE>
        <_-BIC_-SNROW>      <xsl:value-of select=
"@Ref"/>           </_-BIC_-SNROW>
        <_-BIC_-SFNAME>     <xsl:value-of select=
"@Наименование" />  </_-BIC_-SFNAME>
      </ITEM>
    </xsl:for-each>
  </xsl:template>
<!--***************************************************************************-->

</xsl:transform>

Переменные:
TYPES BEGIN OF ty_resp ,
    message_num 
TYPE string ,
    status      
TYPE string ,
  
END OF ty_resp. 
DATA :
  l_url         
TYPE string ,
  lo_client     
TYPE REF TO if_http_client ,
  lo_request    
TYPE REF TO if_http_request ,
  lo_zip        
TYPE REF TO cl_abap_zip ,
  l_str         
TYPE string ,
  l_status      
TYPE string VALUE 'ok' , 
  l_xstr        
TYPE xstring ,
  l_offset      
TYPE i , 
  l_ts_cur      
TYPE timestamp ,
  l_ts_lim      
TYPE timestamp ,
  l_i           
TYPE i, 
  l_bool        
TYPE abap_bool ,
  l_name        
TYPE string ,

  lt_resp       
TYPE TABLE OF ty_resp ,
  l_resp        
TYPE ty_resp ,

  lt_itab       
TYPE STANDARD TABLE OF char2048 ,
  ls_itab       
LIKE LINE OF lt_itab ,

  ls_result_xml 
TYPE abap_trans_resbind ,
  lt_result_xml 
TYPE abap_trans_resbind_tab ,

  lcl_rif_ex    
TYPE REF TO cx_root ,
  l_var_text    
TYPE string ,

  lt_enz        
TYPE TABLE OF /bic/azsenz000 ,
  lt_inzh       
TYPE TABLE OF /bic/azsinzh000 ,
  lt_inzl       
TYPE TABLE OF /bic/azsinzl000 ,
  lt_zrh        
TYPE TABLE OF /bic/azszrh000 ,
  lt_zrl        
TYPE TABLE OF /bic/azszrl000 ,
  lt_pteh       
TYPE TABLE OF /bic/azspteh000 ,
  lt_ptel       
TYPE TABLE OF /bic/azsptel000 ,
  lt_steh       
TYPE TABLE OF /bic/azssteh000 ,
  lt_stel       
TYPE TABLE OF /bic/azsstel000 ,
  lt_picelisth  
TYPE TABLE OF /bic/azsplh000 ,
  lt_picelistl  
TYPE TABLE OF /bic/azspll000 ,
  lt_pnzh       
TYPE TABLE OF /bic/azspnzh000 ,
  lt_pnzl       
TYPE TABLE OF /bic/azspnzl000 ,
  lt_nzkgmkh    
TYPE TABLE OF /bic/azsnzkgh000 ,
  lt_nzkgmkl    
TYPE TABLE OF /bic/azsnzkgl000 ,
  lt_nzngmkh    
TYPE TABLE OF /bic/azsnzngh000 ,
  lt_nzngmkl    
TYPE TABLE OF /bic/azsnzngl000 ,
  lt_prh        
TYPE TABLE OF /bic/azsprh000 ,
  lt_prl        
TYPE TABLE OF /bic/azsprl000 ,
  lt_nzak       
TYPE TABLE OF /bic/azsnzak000 ,
  lt_sparnz     
TYPE TABLE OF /bic/azsspnz000 ,
  lt_parnzh     
TYPE TABLE OF /bic/azspanzh000 ,
  lt_parnzl     
TYPE TABLE OF /bic/azspanzl000, 
  lt_schty      
TYPE TABLE OF /bic/azschty000 .
CONSTANTS :
  c_url 
TYPE string VALUE 'http://xxx.xx.xx.xxx:1080' ,
  c_uri 
TYPE string VALUE '/lc/service/send_dwh_data' .
*-----------------------------------------------------------------------

Вызов трансформации в Abap:
*-----------------------------------------------------------------------
FORM call_trans.
 
TRY.
     
CALL TRANSFORMATION ztrans_logistic_center
      SOURCE XML l_str

      RESULT
        enz      
= lt_enz
        inzh     
= lt_inzh
        inzl     
= lt_inzl
        zrh      
= lt_zrh
        zrl      
= lt_zrl
        pteh     
= lt_pteh
        ptel     
= lt_ptel
        steh     
= lt_steh
        stel     
= lt_stel
        picelisth
= lt_picelisth
        picelistl
= lt_picelistl
        pnzh     
= lt_pnzh
        pnzl     
= lt_pnzl
        nzkgmkh  
= lt_nzkgmkh
        nzkgmkl  
= lt_nzkgmkl
        nzngmkh  
= lt_nzngmkh
        nzngmkl  
= lt_nzngmkl
        prh      
= lt_prh
        prl      
= lt_prl
        nz       
= lt_nzak
        sparnz   
= lt_sparnz
        parnzh   
= lt_parnzh
        parnzl   
= lt_parnzl
        schty    
= lt_schty .

   
CATCH cx_root INTO lcl_rif_ex .

      l_var_text = lcl_rif_ex->get_text ( ).
     
MESSAGE l_var_text TYPE 'E' .
 
ENDTRY.
ENDFORM.

*-----------------------------------------------------------------------


Ах, да, файл забираем из папки, примонтированной к серверу:


  CONSTANTS:
    l_file      
TYPE rlgrap-filename  VALUE '/mnt/dms00236_SAP_resource_file_upload/lc20150609.xml' .

 
FIELD-SYMBOLS:
                 <lf_itab>
LIKE LINE OF lt_itab .
*-----------------------------------------------------------------------
*"  Get file and get the XML file
 
OPEN DATASET l_file FOR INPUT IN TEXT MODE ENCODING DEFAULT .
 
IF sy- subrc EQ 0 .
   
DO.
     
READ DATASET l_file INTO ls_itab .
     
IF sy-subrc EQ 0.
       
REPLACE ALL OCCURRENCES OF '¶' IN ls_itab WITH ` `.
       
APPEND ls_itab TO lt_itab .
       
CLEAR ls_itab.
     
ELSE .
       
EXIT .
     
ENDIF .
   
ENDDO .
 
ENDIF.

  CLOSE DATASET l_file.


А ещё:

REPORT znn_logistic_center_send_resp. 
*&---------------------------------------------------------------------* 
*&  Отправка статуса загрузки WEB-сервису 
*&---------------------------------------------------------------------* 
TYPES BEGIN OF ty_resp ,
         message_num 
TYPE string ,
         status      
TYPE string , 
       
END OF ty_resp .
DATA :
  lt_resp    
TYPE TABLE OF ty_resp ,
  lcl_rif_ex 
TYPE REF TO cx_root ,
  l_var_text 
TYPE string ,

  lo_client  
TYPE REF TO if_http_client ,
  lo_request 
TYPE REF TO if_http_request ,
  l_url      
TYPE string ,
  l_str      
TYPE string ,
  l_mess_num 
TYPE string .
*----------------------------------------------------------------------- 
SELECTION-SCREEN 
  
BEGIN OF BLOCK s_b_1 WITH FRAME TITLE text - b01. 
*"  Указание параметров для соединения 
PARAMETERS :
  p_status 
TYPE string LOWER CASE OBLIGATORY DEFAULT 'ok' , 
  p_url    
TYPE string .
SELECTION-SCREEN 
END OF BLOCK s_b_1. 
*----------------------------------------------------------------------- 
*"  Считываем актуальный номер сообщения 
SELECT low
  
INTO l_mess_num
  
FROM tvarvcWHERE name 'ZSV_LC_MASSAGE_NUM' .
ENDSELECT .
*"  Отправляем запрос со статусом веб-сервису 
CONCATENATE l_url '?request=' l_mess_num '&status=' p_status
       
INTO l_url .
CALL FUNCTION 'ZNN_GET_SERVICE_MESSAGE'
  
EXPORTING 
    iv_url  
p_url
  
IMPORTING 
    ev_resp 
lt_resp .
*-----------------------------------------------------------------------

И ещё:
FUNCTION znn_get_service_message. 
*"---------------------------------------------------------------------- 
*"*"Локальный интерфейс: 
*"  IMPORTING 
*"     REFERENCE(IV_URL) TYPE  STRING 
*"  EXPORTING 
*"     REFERENCE(EV_RESP) 
*"  EXCEPTIONS 
*"      XML_PARS 
*"---------------------------------------------------------------------- 
  
TYPES BEGIN OF ty_resp ,
           message_num 
TYPE string , 
           status      
TYPE string , 
         
END OF ty_resp .

  
DATA :
    lcl_rif_ex 
TYPE REF TO cx_root ,
    l_var_text 
TYPE string ,
    lo_client  
TYPE REF TO if_http_client ,
    lo_request 
TYPE REF TO if_http_request ,
    lt_resp    
TYPE TABLE OF ty_resp ,
    l_str      
TYPE string .
*----------------------------------------------------------------------- 
  cl_http_client
=> create_by_url (
    
EXPORTING 
      url 
iv_url
    
IMPORTING 
      
client lo_client
    
EXCEPTIONS 
      argument_not_found 
1 
      internal_error 
2 
      plugin_not_active 
3 
      
OTHERS 4
  
). 

  lo_client
-> request-> set_method (
    
method if_http_request=> co_request_method_get
  
). 

  lo_client
-> send( 
    
EXPORTING 
      timeout 
300 
    
EXCEPTIONS 
      http_communication_failure 
1 
      http_invalid_state 
2 
      http_processing_failed 
3 
      
OTHERS 4
  
). 

  lo_client
-> listen( ). 

  lo_client
-> receive( 
    
EXCEPTIONS 
      http_communication_failure 
1 
      http_invalid_state 
2 
      http_processing_failed 
3 
      
OTHERS 4
  
). 

  lo_client
-> response-> get_cdata (
    RECEIVING
      
data l_str
  
). 

  lo_client
-> close( ). 

  
TRY .
      
CALL TRANSFORMATION ztrans_logistic_center_fresp
      SOURCE XML l_str

      RESULT
        lt_tab 
lt_resp .

    
CATCH cx_root INTO lcl_rif_ex . 
      l_var_text 
lcl_rif_ex ->get_text ( ). 
      
MESSAGE l_var_text TYPE 'E' RAISING xml_pars. 
  
ENDTRY .

  ev_resp 
lt_resp. 
ENDFUNCTION .
*-----------------------------------------------------------------------

Комментариев нет:

Отправить комментарий