
RAP - Excel Datei laden
In diesem praktischen Beispiel schauen wir uns die Verarbeitung von Excel in ABAP mit den neuen XCO Klassen an und wie wir die Datei in unser RAP Objekt bekommen.
Inhaltsverzeichnis
In diesem Artikel wollen wir die Verarbeitung der Excel Datei in unsere Anwendung integrieren und verschiedene weitere Schritte implementieren.
Einleitung
Für die Verarbeitung von Excel Dateien stellt SAP eine eigene XCO API zur Verfügung, die du zum Lesen und mittlerweile auch zum Schreiben verwenden kannst. Im heutigen Beispiel erweitern wir unsere Report Pattern App um eine Aktion, um die hochgeladene Excel Datei zu lesen, zu parsen und damit die Daten zu aktualisieren. Im Anschluss wollen wir die Datei wieder entfernen, wenn die Verarbeitung so weit erfolgreich war.
Erweiterung
Bevor wir mit der Implementierung beginnen, müssen wir die App um eine Aktion erweitern, die wir dann für die Entwicklung verwenden können. Die Aktion soll ein Popup anzeigen, wo wir noch Einstellung vornehmen können, dazu legen wir eine Abstrakte Entität an.
@EndUserText.label: 'Excel Popup'
define abstract entity ZBS_S_DRPExcelPopup
{
@EndUserText.label: 'Test run'
TestRun : abap_boolean;
}
Im nächsten Schritt ergänzen wir die Aktion in der Verhaltensdefinition "ZBS_R_DRPCurrency". Dabei muss für die Aktion ein Datensatz markiert werden, wir möchten gern vorher ein Popup anzeigen (Parameter) und die Aktion soll eine Instanz von sich selbst zurückgeben, um die Daten nach der Verarbeitung zu aktualisieren.
action LoadExcelContent parameter ZBS_S_DRPExcelPopup result [1] $self;
Damit wir dann auch die Aktion in der App auch verwenden können, müssen wir sie in der Projektion "ZBS_C_DRPCurrency" bekannt geben.
use action LoadExcelContent;
Zum Abschluss erweitern wir noch die Metadaten "ZBS_C_DRPCurrency", um auf dem UI einen Platz für den Button zu finden. Wir möchten gern den Button auf dem Detailbild des Datensatzes anzeigen, deshalb binden wir diesen an die IDENTIFICATION.
@UI:{
lineItem: [{ position: 10 }],
selectionField: [{ position: 10 }],
identification: [{ type: #FOR_ACTION, dataAction: 'LoadExcelContent', label: 'Load Excel' }]
}
Currency;
Der Button sollte nun auf den Detailbildern jedes Datensatzes zu sehen sein. Wenn du ihn anklickst, sollte vor der Verarbeitung auch ein Popup erscheinen, wo der Testlauf abgefragt wird. Damit sind wir mit der Erweiterung durch und können die Aktion ausprägen.
Excel
In diesem Abschnitt möchten wir nun das hochgeladene Dokument einlesen und verarbeiten.
Datei
In diesem Tutorial werden wir die folgende Datei zum Testen verwenden. Dabei sind mehr Länder enthalten, als eigentlich an einem Datensatz verfügbar sind. Zusätzlich haben wir noch eine weitere Spalte, die wir für Auswertungen heranziehen könnten. Die Datei hat nur ein Arbeitsblatt, wenn wir sie einlesen wollen.
Die Excel Datei laden wir in unsere Entität hoch. Für unser Beispiel können wir die Datei nach EUR oder nach AED laden, da wir Datensätze für die beiden Währungen haben.
Implementierung
Um nun die Datei verarbeiten zu können, müssen wir die Aktion in RAP implementieren. Beginnen wir im ersten Schritt den aktuellen Datensatz zu lesen und alle zugeordneten Länder, um diese später abzugleichen. Da wir nur einen Datensatz verarbeiten, lesen wir den ersten Schlüssel und den ersten Datensatz, implementieren aber noch ein passendes Fehlerhandling.
READ ENTITIES OF ZBS_R_DRPCurrency IN LOCAL MODE
ENTITY Currency FIELDS ( Currency ExcelAttachement ) WITH CORRESPONDING #( keys )
RESULT DATA(lt_attachement)
ENTITY Currency BY \_Country ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_countries).
TRY.
DATA(ls_key) = keys[ 1 ].
DATA(ls_attachement) = lt_attachement[ 1 ].
CATCH cx_sy_itab_line_not_found.
INSERT new_message( id = 'ZBS_DEMO_RAP_PATTERN'
number = '003'
severity = if_abap_behv_message=>severity-error )
INTO TABLE reported-%other.
RETURN.
ENDTRY.
Im nächsten Schritt parsen wir die Excel Datei, um an die Daten zu kommen, die wir dann verarbeiten wollen. Dazu lagern wir die Logik in eine eigene Methode aus und erstellen eine eigene Exception, die wir im Fehlerfall direkt an das Log hängen können. Zum Abschluss erzeugen wir noch eine Meldung für den User, wie viele Datensätze wir in der Excel Datei gefunden haben.
TRY.
DATA(lt_excel) = convert_excel_file_to_table( ls_attachement-excelattachement ).
CATCH zcx_drp_excel_error INTO DATA(lo_excel_error).
INSERT lo_excel_error INTO TABLE reported-%other.
RETURN.
ENDTRY.
INSERT new_message( id = 'ZBS_DEMO_RAP_PATTERN'
number = '005'
severity = if_abap_behv_message=>severity-success
v1 = lines( lt_excel ) )
INTO TABLE reported-%other.
Hier findest du die Methode zum Parsen der Datei. Zuerst einmal überprüfen wir, ob ein Stream, also ein Upload, vorhanden ist. Im nächsten Schritt öffnen wir die Datei und laden das erste Arbeitsblatt, sollte dieses nicht vorhanden sein, erzeugen wir eine Fehlermeldung. Im Anschluss versuchen wir die Excel Datei ab Zeile zwei einzulesen und die Überschriften zu sparen.
IF id_stream IS INITIAL.
RAISE EXCEPTION NEW zcx_drp_excel_error( textid = VALUE #( msgid = 'ZBS_DEMO_RAP_PATTERN'
msgno = '001' ) ).
ENDIF.
DATA(lo_sheet) = xco_cp_xlsx=>document->for_file_content( id_stream
)->read_access( )->get_workbook(
)->worksheet->at_position( 1 ).
IF NOT lo_sheet->exists( ).
RAISE EXCEPTION NEW zcx_drp_excel_error( textid = VALUE #( msgid = 'ZBS_DEMO_RAP_PATTERN'
msgno = '002' ) ).
ENDIF.
DATA(lo_pattern) = xco_cp_xlsx_selection=>pattern_builder->simple_from_to(
)->from_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )
)->from_row( xco_cp_xlsx=>coordinate->for_numeric_value( 2 )
)->get_pattern( ).
lo_sheet->select( lo_pattern
)->row_stream(
)->operation->write_to( REF #( rt_result )
)->set_value_transformation( xco_cp_xlsx_read_access=>value_transformation->string_value
)->execute( ).
Befinden wir uns im Testlauf, dann endet hier die Verarbeitung und wir verlassen die Logik. Dem Anwender geben wir noch eine entsprechende Warnung mit.
IF ls_key-%param-TestRun = abap_true.
INSERT new_message( id = 'ZBS_DEMO_RAP_PATTERN'
number = '004'
severity = if_abap_behv_message=>severity-warning )
INTO TABLE reported-%other.
RETURN.
ENDIF.
Um nun die Änderungen durchführen zu können, bereiten wir drei Tabellen vor, die die verschiedenen Änderungen später dem Entity Manipulation Language (EML) Statement übergeben.
DATA lt_currency_modify TYPE TABLE FOR UPDATE ZBS_R_DRPCurrencyCurrency.
DATA lt_countries_create TYPE TABLE FOR CREATE ZBS_R_DRPCurrency\_Country.
DATA lt_countries_modify TYPE TABLE FOR UPDATE ZBS_R_DRPCurrencyCountry.
Um den Anhang zu entfernen, setzen wir die Felder auf leer. Du solltest auch nicht vergessen, die entsprechenden %CONTROL Strukturen zu aktivieren, damit die Änderungen durchgeführt werden können.
INSERT VALUE #( %tky = ls_attachement-%tky
excelattachement = ''
excelmimetype = ''
excelfilename = ''
%control-excelattachement = if_abap_behv=>mk-on
%control-excelmimetype = if_abap_behv=>mk-on
%control-excelfilename = if_abap_behv=>mk-on )
INTO TABLE lt_currency_modify.
Im nächsten Code-Abschnitt verarbeiten wir alle Datensätze in der Excel Datei, die die gleiche Währung haben, also zu unserem Datensatz passen. Dann prüfen wir gegen die Ländertabelle, die wir zuvor gelesen haben. Ist ein Land bereits vorhanden, aktualisieren wir die zusätzlichen Felder, in diesem Fall das Ranking. Ist ein Laden noch nicht vorhanden, übernehmen wir den neuen Datensatz.
INSERT VALUE #( currency = ls_attachement-Currency )
INTO TABLE lt_countries_create REFERENCE INTO DATA(lr_new).
LOOP AT lt_excel INTO DATA(ls_excel) WHERE currency = ls_attachement-Currency.
TRY.
DATA(ls_country) = lt_countries[ Country = ls_excel-country ].
INSERT VALUE #( currency = ls_country-Currency
country = ls_country-Country
countryranking = ls_excel-ranking
%control-countryranking = if_abap_behv=>mk-on )
INTO TABLE lt_countries_modify.
CATCH cx_sy_itab_line_not_found.
INSERT VALUE #( %cid = xco_cp=>uuid( )->value
currency = ls_excel-currency
country = ls_excel-country
countryranking = ls_excel-ranking
%control-currency = if_abap_behv=>mk-on
%control-country = if_abap_behv=>mk-on
%control-countryranking = if_abap_behv=>mk-on )
INTO TABLE lr_new->%target.
ENDTRY.
ENDLOOP.
Nach der Verarbeitung können wir unsere drei Tabellen dem EML Statement übergeben und die Änderungen im transaktionalen Puffer des RAP Objekts registrieren. Der CREATE für Country ist nur über Currency möglich, da wir es im RAP Objekt in der Verhaltensdefinition so definiert haben. Dort ist der Create an der Assoziation aktiviert.
MODIFY ENTITIES OF ZBS_R_DRPCurrency IN LOCAL MODE
ENTITY Currency UPDATE FROM lt_currency_modify
ENTITY Country UPDATE FROM lt_countries_modify
ENTITY Currency CREATE BY \_Country FROM lt_countries_create.
Zum Abschluss der Verarbeitung geben wir dem Anwender noch Meldungen mit, wie viele Datensätze neu aufgenommen wurden und wie viele sich geändert haben. Damit wäre die Implementierung der Methode auch soweit fertig.
INSERT new_message( id = 'ZBS_DEMO_RAP_PATTERN'
number = '007'
severity = if_abap_behv_message=>severity-success
v1 = lines( lt_countries_create[ 1 ]-%target ) )
INTO TABLE reported-%other.
INSERT new_message( id = 'ZBS_DEMO_RAP_PATTERN'
number = '006'
severity = if_abap_behv_message=>severity-success
v1 = lines( lt_countries_modify ) )
INTO TABLE reported-%other.
Test
In diesem Abschnitt wollen wir nun einmal das neue Feature testen. Dazu laden wir die Excel Datei in die Währung EUR. Aktuell haben wir hier auch Deutschland und Dänemark mit einem schlechten Ranking angelegt.
Nachdem wir die Aktion ausgeführt haben, erhalten wir eine Erfolgsmeldung, dass zwei Länder neu angelegt wurden und ein Land geändert wurde.
Nach der Aktualisierung der Oberfläche sollten nun die Änderungen aktiv sein. Die Datei ist nun verschwunden und die Länderliste ist wieder aktuell.
Vollständiges Beispiel
Alle Änderungen findest du wie immer am entsprechenden Commit des GitHub Repositorys. Zusätzlich findest du hier noch die Verhaltensimplementierung, um den aktuellen Stand der Implementierung nachvollziehen zu können.
CLASS lhc_Currency DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
TYPES: BEGIN OF ts_excel,
currency TYPE string,
country TYPE string,
ranking TYPE string,
flag TYPE string,
END OF ts_excel.
TYPES tt_excel TYPE STANDARD TABLE OF ts_excel WITH EMPTY KEY.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR Currency RESULT result.
METHODS loadexcelcontent FOR MODIFY
IMPORTING keys FOR ACTION currency~loadexcelcontent.
METHODS convert_excel_file_to_table
IMPORTING id_stream TYPE xstring
RETURNING VALUE(rt_result) TYPE tt_excel
RAISING zcx_drp_excel_error.
ENDCLASS.
CLASS lhc_Currency IMPLEMENTATION.
METHOD get_instance_authorizations.
ENDMETHOD.
METHOD LoadExcelContent.
DATA lt_currency_modify TYPE TABLE FOR UPDATE ZBS_R_DRPCurrencyCurrency.
DATA lt_countries_create TYPE TABLE FOR CREATE ZBS_R_DRPCurrency\_Country.
DATA lt_countries_modify TYPE TABLE FOR UPDATE ZBS_R_DRPCurrencyCountry.
READ ENTITIES OF ZBS_R_DRPCurrency IN LOCAL MODE
ENTITY Currency FIELDS ( Currency ExcelAttachement ) WITH CORRESPONDING #( keys )
RESULT DATA(lt_attachement)
ENTITY Currency BY \_Country ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_countries).
TRY.
DATA(ls_key) = keys[ 1 ].
DATA(ls_attachement) = lt_attachement[ 1 ].
CATCH cx_sy_itab_line_not_found.
INSERT new_message( id = 'ZBS_DEMO_RAP_PATTERN'
number = '003'
severity = if_abap_behv_message=>severity-error )
INTO TABLE reported-%other.
RETURN.
ENDTRY.
TRY.
DATA(lt_excel) = convert_excel_file_to_table( ls_attachement-excelattachement ).
CATCH zcx_drp_excel_error INTO DATA(lo_excel_error).
INSERT lo_excel_error INTO TABLE reported-%other.
RETURN.
ENDTRY.
INSERT new_message( id = 'ZBS_DEMO_RAP_PATTERN'
number = '005'
severity = if_abap_behv_message=>severity-success
v1 = lines( lt_excel ) )
INTO TABLE reported-%other.
IF ls_key-%param-TestRun = abap_true.
INSERT new_message( id = 'ZBS_DEMO_RAP_PATTERN'
number = '004'
severity = if_abap_behv_message=>severity-warning )
INTO TABLE reported-%other.
RETURN.
ENDIF.
INSERT VALUE #( %tky = ls_attachement-%tky
excelattachement = ''
excelmimetype = ''
excelfilename = ''
%control-excelattachement = if_abap_behv=>mk-on
%control-excelmimetype = if_abap_behv=>mk-on
%control-excelfilename = if_abap_behv=>mk-on )
INTO TABLE lt_currency_modify.
INSERT VALUE #( currency = ls_attachement-Currency )
INTO TABLE lt_countries_create REFERENCE INTO DATA(lr_new).
LOOP AT lt_excel INTO DATA(ls_excel) WHERE currency = ls_attachement-Currency.
TRY.
DATA(ls_country) = lt_countries[ Country = ls_excel-country ].
INSERT VALUE #( currency = ls_country-Currency
country = ls_country-Country
countryranking = ls_excel-ranking
%control-countryranking = if_abap_behv=>mk-on )
INTO TABLE lt_countries_modify.
CATCH cx_sy_itab_line_not_found.
INSERT VALUE #( %cid = xco_cp=>uuid( )->value
currency = ls_excel-currency
country = ls_excel-country
countryranking = ls_excel-ranking
%control-currency = if_abap_behv=>mk-on
%control-country = if_abap_behv=>mk-on
%control-countryranking = if_abap_behv=>mk-on )
INTO TABLE lr_new->%target.
ENDTRY.
ENDLOOP.
MODIFY ENTITIES OF ZBS_R_DRPCurrency IN LOCAL MODE
ENTITY Currency UPDATE FROM lt_currency_modify
ENTITY Country UPDATE FROM lt_countries_modify
ENTITY Currency CREATE BY \_Country FROM lt_countries_create.
INSERT new_message( id = 'ZBS_DEMO_RAP_PATTERN'
number = '007'
severity = if_abap_behv_message=>severity-success
v1 = lines( lt_countries_create[ 1 ]-%target ) )
INTO TABLE reported-%other.
INSERT new_message( id = 'ZBS_DEMO_RAP_PATTERN'
number = '006'
severity = if_abap_behv_message=>severity-success
v1 = lines( lt_countries_modify ) )
INTO TABLE reported-%other.
ENDMETHOD.
METHOD convert_excel_file_to_table.
IF id_stream IS INITIAL.
RAISE EXCEPTION NEW zcx_drp_excel_error( textid = VALUE #( msgid = 'ZBS_DEMO_RAP_PATTERN'
msgno = '001' ) ).
ENDIF.
DATA(lo_sheet) = xco_cp_xlsx=>document->for_file_content( id_stream
)->read_access( )->get_workbook(
)->worksheet->at_position( 1 ).
IF NOT lo_sheet->exists( ).
RAISE EXCEPTION NEW zcx_drp_excel_error( textid = VALUE #( msgid = 'ZBS_DEMO_RAP_PATTERN'
msgno = '002' ) ).
ENDIF.
DATA(lo_pattern) = xco_cp_xlsx_selection=>pattern_builder->simple_from_to(
)->from_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )
)->from_row( xco_cp_xlsx=>coordinate->for_numeric_value( 2 )
)->get_pattern( ).
lo_sheet->select( lo_pattern
)->row_stream(
)->operation->write_to( REF #( rt_result )
)->set_value_transformation( xco_cp_xlsx_read_access=>value_transformation->string_value
)->execute( ).
ENDMETHOD.
ENDCLASS.
CLASS lsc_ZBS_R_DRPCURRENCY DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS
save_modified REDEFINITION.
METHODS
cleanup_finalize REDEFINITION.
ENDCLASS.
CLASS lsc_ZBS_R_DRPCURRENCY IMPLEMENTATION.
METHOD save_modified.
LOOP AT update-currency INTO DATA(ls_new_currency).
DATA(ls_modify_currency) = CORRESPONDING zbs_drp_addcurr( ls_new_currency MAPPING FROM ENTITY ).
ls_modify_currency-last_editor = cl_abap_context_info=>get_user_technical_name( ).
MODIFY zbs_drp_addcurr FROM @ls_modify_currency.
ENDLOOP.
LOOP AT create-country INTO DATA(ls_create_country).
INSERT zbs_drp_country FROM @( CORRESPONDING zbs_drp_country( ls_create_country MAPPING FROM ENTITY ) ).
ENDLOOP.
LOOP AT update-country INTO DATA(ls_update_country).
UPDATE zbs_drp_country FROM @( CORRESPONDING zbs_drp_country( ls_update_country MAPPING FROM ENTITY ) ).
ENDLOOP.
LOOP AT delete-country INTO DATA(ls_delete_country).
DELETE zbs_drp_country FROM @( CORRESPONDING zbs_drp_country( ls_delete_country MAPPING FROM ENTITY ) ).
ENDLOOP.
ENDMETHOD.
METHOD cleanup_finalize.
ENDMETHOD.
ENDCLASS.
Upload von Dateien
Die Methode der Attachements eignet sich aktuell gut für die Ablage von kleinen Dateien, die direkt für den Anwender zur Verfügung stehen sollen. Möchtest du den Upload allerdings nur nutzen, um damit die aktuelle Entität zu versorgen und eigentlich brauchst du danach die Datei nicht mehr, dann empfiehlt sich eine Aktion, in der du direkt die Datei mitgeben kannst. Hier gibt es bereits Open Source Projekte, wie zum Beispiel den Spreadsheet Importer, um so eine Funktion zu implementieren.
Fazit
Mit der neuen API kannst du auch einfache Excel Dokumente lesen und verarbeiten. Wie du die API richtig nutzt, haben wir dir in diesem Artikel nähergebracht.