Excel Import/Export in ABAP Cloud

kategorie
ABAP Cloud
Veröffentlicht
autor
Johannes

Excel-Verarbeitung in ABAP Cloud unterscheidet sich von klassischem ABAP. Legacy-Klassen wie CL_ABAP_CONV_* mit Excel-spezifischen Formaten oder GUI-basierte Downloads stehen nicht zur Verfuegung. Stattdessen nutzt du Cloud-native APIs wie XCO, die XLSX-Bibliothek oder externe Services.

Verfuegbare Optionen im Ueberblick

In ABAP Cloud gibt es mehrere Wege fuer Excel-Verarbeitung:

OptionBeschreibungAnwendungsfall
XCO Spreadsheet APICloud-native SAP APIStandard-Excel-Export
XLSX BibliothekOpen-Source BibliothekErweiterte Formatierung
Externe ServicesHTTP-basierte APIsSpezielle Konvertierungen
CSV als AlternativeEinfaches TextformatMaximale Kompatibilitaet

Architektur-Uebersicht

┌─────────────────────────────────────────────────────────────────────────────┐
│ Excel-Verarbeitung in ABAP Cloud │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ RAP Business Object / Service │ │
│ │ │ │
│ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │
│ │ │ RAP Action │ │ RAP Action │ │ │
│ │ │ "Export Excel" │ │ "Import Excel" │ │ │
│ │ └──────────┬──────────┘ └──────────┬──────────┘ │ │
│ └─────────────┼────────────────────────┼──────────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Excel-Verarbeitungs-Optionen │ │
│ │ │ │
│ │ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │ │
│ │ │ XCO Spreadsheet │ │ XLSX Bibliothek │ │ CSV Fallback │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ - Export/Import │ │ - Formatierung │ │ - Einfach │ │ │
│ │ │ - Cloud-native │ │ - Multiple Sheets│ │ - Universell │ │ │
│ │ └───────────────────┘ └───────────────────┘ └───────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘

Excel-Export mit XCO

Die XCO (Extension Components) Library bietet eine Cloud-kompatible API fuer Spreadsheet-Operationen.

Grundlegender Export

" Excel-Export mit XCO
CLASS zcl_excel_exporter DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_sales_order,
order_id TYPE string,
customer TYPE string,
amount TYPE p LENGTH 10 DECIMALS 2,
currency TYPE string,
order_date TYPE d,
status TYPE string,
END OF ty_sales_order,
ty_sales_orders TYPE STANDARD TABLE OF ty_sales_order WITH KEY order_id.
METHODS:
export_to_excel
IMPORTING it_data TYPE ty_sales_orders
RETURNING VALUE(rv_xlsx) TYPE xstring
RAISING cx_xco_runtime_exception.
ENDCLASS.
CLASS zcl_excel_exporter IMPLEMENTATION.
METHOD export_to_excel.
" Spreadsheet-Dokument erstellen
DATA(lo_spreadsheet) = xco_cp_xlsx=>document->empty( )->spreadsheet.
" Erstes Worksheet (Standard: Sheet1)
DATA(lo_worksheet) = lo_spreadsheet->worksheet->at_position( 1 ).
" Kopfzeile schreiben
DATA(lo_cursor) = lo_worksheet->cursor(
io_column = xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )
io_row = xco_cp_xlsx=>coordinate->for_numeric_value( 1 )
).
lo_cursor->get_cell( )->value->write_from( 'Auftragsnummer' ).
lo_cursor->move_right( )->get_cell( )->value->write_from( 'Kunde' ).
lo_cursor->move_right( )->get_cell( )->value->write_from( 'Betrag' ).
lo_cursor->move_right( )->get_cell( )->value->write_from( 'Waehrung' ).
lo_cursor->move_right( )->get_cell( )->value->write_from( 'Bestelldatum' ).
lo_cursor->move_right( )->get_cell( )->value->write_from( 'Status' ).
" Datenzeilen schreiben
DATA(lv_row) = 2.
LOOP AT it_data INTO DATA(ls_order).
lo_cursor = lo_worksheet->cursor(
io_column = xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )
io_row = xco_cp_xlsx=>coordinate->for_numeric_value( lv_row )
).
lo_cursor->get_cell( )->value->write_from( ls_order-order_id ).
lo_cursor->move_right( )->get_cell( )->value->write_from( ls_order-customer ).
lo_cursor->move_right( )->get_cell( )->value->write_from( ls_order-amount ).
lo_cursor->move_right( )->get_cell( )->value->write_from( ls_order-currency ).
lo_cursor->move_right( )->get_cell( )->value->write_from(
|{ ls_order-order_date DATE = USER }|
).
lo_cursor->move_right( )->get_cell( )->value->write_from( ls_order-status ).
lv_row = lv_row + 1.
ENDLOOP.
" Als XLSX-Binary exportieren
rv_xlsx = lo_spreadsheet->get_file_content( ).
ENDMETHOD.
ENDCLASS.

Generischer Export aus beliebigen Tabellen

" Generischer Excel-Exporter fuer beliebige Daten
CLASS zcl_generic_excel_export DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
export_table
IMPORTING it_data TYPE ANY TABLE
it_column_names TYPE string_table OPTIONAL
RETURNING VALUE(rv_xlsx) TYPE xstring
RAISING cx_xco_runtime_exception.
PRIVATE SECTION.
METHODS:
get_field_names
IMPORTING it_data TYPE ANY TABLE
RETURNING VALUE(rt_names) TYPE string_table.
ENDCLASS.
CLASS zcl_generic_excel_export IMPLEMENTATION.
METHOD export_table.
DATA(lo_spreadsheet) = xco_cp_xlsx=>document->empty( )->spreadsheet.
DATA(lo_worksheet) = lo_spreadsheet->worksheet->at_position( 1 ).
" Feldnamen ermitteln
DATA(lt_fields) = COND #(
WHEN it_column_names IS NOT INITIAL
THEN it_column_names
ELSE get_field_names( it_data )
).
" Kopfzeile
DATA(lo_cursor) = lo_worksheet->cursor(
io_column = xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )
io_row = xco_cp_xlsx=>coordinate->for_numeric_value( 1 )
).
LOOP AT lt_fields INTO DATA(lv_field_name).
lo_cursor->get_cell( )->value->write_from( lv_field_name ).
IF sy-tabix < lines( lt_fields ).
lo_cursor->move_right( ).
ENDIF.
ENDLOOP.
" Datenzeilen
DATA(lv_row) = 2.
DATA lv_value TYPE string.
LOOP AT it_data ASSIGNING FIELD-SYMBOL(<ls_row>).
lo_cursor = lo_worksheet->cursor(
io_column = xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )
io_row = xco_cp_xlsx=>coordinate->for_numeric_value( lv_row )
).
LOOP AT lt_fields INTO lv_field_name.
ASSIGN COMPONENT lv_field_name OF STRUCTURE <ls_row> TO FIELD-SYMBOL(<lv_value>).
IF sy-subrc = 0.
lv_value = <lv_value>.
lo_cursor->get_cell( )->value->write_from( lv_value ).
ENDIF.
IF sy-tabix < lines( lt_fields ).
lo_cursor->move_right( ).
ENDIF.
ENDLOOP.
lv_row = lv_row + 1.
ENDLOOP.
rv_xlsx = lo_spreadsheet->get_file_content( ).
ENDMETHOD.
METHOD get_field_names.
" Feldnamen aus Tabellenstruktur ermitteln
DATA(lo_table_descr) = CAST cl_abap_tabledescr(
cl_abap_typedescr=>describe_by_data( it_data )
).
DATA(lo_struct_descr) = CAST cl_abap_structdescr(
lo_table_descr->get_table_line_type( )
).
LOOP AT lo_struct_descr->get_components( ) INTO DATA(ls_component).
APPEND ls_component-name TO rt_names.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Excel-Import mit Parsing

Der Import von Excel-Dateien erfordert das Parsen der XLSX-Struktur.

XLSX-Import Grundlagen

" Excel-Import Parser
CLASS zcl_excel_importer DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_sales_order_import,
order_id TYPE string,
customer TYPE string,
amount TYPE string,
currency TYPE string,
order_date TYPE string,
status TYPE string,
END OF ty_sales_order_import,
ty_sales_order_imports TYPE STANDARD TABLE OF ty_sales_order_import
WITH KEY order_id.
METHODS:
import_from_excel
IMPORTING iv_xlsx TYPE xstring
iv_skip_header TYPE abap_bool DEFAULT abap_true
RETURNING VALUE(rt_data) TYPE ty_sales_order_imports
RAISING cx_xco_runtime_exception.
METHODS:
validate_import
IMPORTING it_data TYPE ty_sales_order_imports
EXPORTING et_errors TYPE string_table
RETURNING VALUE(rv_valid) TYPE abap_bool.
ENDCLASS.
CLASS zcl_excel_importer IMPLEMENTATION.
METHOD import_from_excel.
" XLSX-Dokument laden
DATA(lo_document) = xco_cp_xlsx=>document->for_file_content( iv_xlsx ).
DATA(lo_spreadsheet) = lo_document->spreadsheet.
" Erstes Worksheet lesen
DATA(lo_worksheet) = lo_spreadsheet->worksheet->at_position( 1 ).
" Verwendeten Bereich ermitteln
DATA(lo_used_area) = lo_worksheet->used_area( ).
DATA(lo_row_selection) = lo_used_area->row_selection.
DATA(lv_first_row) = lo_row_selection->first_row->position->value.
DATA(lv_last_row) = lo_row_selection->last_row->position->value.
" Startzeile (Header ueberspringen)
DATA(lv_start_row) = COND #(
WHEN iv_skip_header = abap_true THEN lv_first_row + 1
ELSE lv_first_row
).
" Zeilen durchlaufen
DATA lv_row TYPE i.
DO ( lv_last_row - lv_start_row + 1 ) TIMES.
lv_row = lv_start_row + sy-index - 1.
DATA(lo_cursor) = lo_worksheet->cursor(
io_column = xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )
io_row = xco_cp_xlsx=>coordinate->for_numeric_value( lv_row )
).
DATA ls_order TYPE ty_sales_order_import.
" Zellwerte lesen
lo_cursor->get_cell( )->value->read_to( REF #( ls_order-order_id ) ).
lo_cursor->move_right( )->get_cell( )->value->read_to( REF #( ls_order-customer ) ).
lo_cursor->move_right( )->get_cell( )->value->read_to( REF #( ls_order-amount ) ).
lo_cursor->move_right( )->get_cell( )->value->read_to( REF #( ls_order-currency ) ).
lo_cursor->move_right( )->get_cell( )->value->read_to( REF #( ls_order-order_date ) ).
lo_cursor->move_right( )->get_cell( )->value->read_to( REF #( ls_order-status ) ).
" Leere Zeilen ueberspringen
IF ls_order-order_id IS NOT INITIAL.
APPEND ls_order TO rt_data.
ENDIF.
ENDDO.
ENDMETHOD.
METHOD validate_import.
rv_valid = abap_true.
CLEAR et_errors.
LOOP AT it_data INTO DATA(ls_order).
DATA(lv_row) = sy-tabix + 1. " +1 fuer Header
" Pflichtfelder pruefen
IF ls_order-order_id IS INITIAL.
APPEND |Zeile { lv_row }: Auftragsnummer fehlt| TO et_errors.
rv_valid = abap_false.
ENDIF.
IF ls_order-customer IS INITIAL.
APPEND |Zeile { lv_row }: Kunde fehlt| TO et_errors.
rv_valid = abap_false.
ENDIF.
" Betrag validieren
TRY.
DATA(lv_amount) = CONV decfloat34( ls_order-amount ).
IF lv_amount < 0.
APPEND |Zeile { lv_row }: Betrag darf nicht negativ sein| TO et_errors.
rv_valid = abap_false.
ENDIF.
CATCH cx_sy_conversion_no_number.
APPEND |Zeile { lv_row }: Ungueliger Betrag '{ ls_order-amount }'| TO et_errors.
rv_valid = abap_false.
ENDTRY.
" Waehrung validieren
IF ls_order-currency NOT IN VALUE #(
( sign = 'I' option = 'EQ' low = 'EUR' )
( sign = 'I' option = 'EQ' low = 'USD' )
( sign = 'I' option = 'EQ' low = 'CHF' )
).
APPEND |Zeile { lv_row }: Ungueltige Waehrung '{ ls_order-currency }'| TO et_errors.
rv_valid = abap_false.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Generischer Import mit dynamischer Struktur

" Generischer Excel-Import in interne Tabelle
CLASS zcl_generic_excel_import DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_cell,
column TYPE i,
row TYPE i,
value TYPE string,
END OF ty_cell,
ty_cells TYPE STANDARD TABLE OF ty_cell WITH KEY row column.
METHODS:
read_worksheet_raw
IMPORTING iv_xlsx TYPE xstring
iv_worksheet_pos TYPE i DEFAULT 1
RETURNING VALUE(rt_cells) TYPE ty_cells
RAISING cx_xco_runtime_exception.
METHODS:
read_as_table
IMPORTING iv_xlsx TYPE xstring
iv_worksheet_pos TYPE i DEFAULT 1
iv_has_header TYPE abap_bool DEFAULT abap_true
CHANGING ct_data TYPE ANY TABLE
RAISING cx_xco_runtime_exception.
ENDCLASS.
CLASS zcl_generic_excel_import IMPLEMENTATION.
METHOD read_worksheet_raw.
DATA(lo_document) = xco_cp_xlsx=>document->for_file_content( iv_xlsx ).
DATA(lo_worksheet) = lo_document->spreadsheet->worksheet->at_position( iv_worksheet_pos ).
DATA(lo_used_area) = lo_worksheet->used_area( ).
" Bereich ermitteln
DATA(lv_first_row) = lo_used_area->row_selection->first_row->position->value.
DATA(lv_last_row) = lo_used_area->row_selection->last_row->position->value.
DATA(lv_first_col) = lo_used_area->column_selection->first_column->position->value.
DATA(lv_last_col) = lo_used_area->column_selection->last_column->position->value.
" Alle Zellen lesen
DO ( lv_last_row - lv_first_row + 1 ) TIMES.
DATA(lv_row) = lv_first_row + sy-index - 1.
DO ( lv_last_col - lv_first_col + 1 ) TIMES.
DATA(lv_col) = lv_first_col + sy-index - 1.
DATA(lo_cursor) = lo_worksheet->cursor(
io_column = xco_cp_xlsx=>coordinate->for_numeric_value( lv_col )
io_row = xco_cp_xlsx=>coordinate->for_numeric_value( lv_row )
).
DATA lv_value TYPE string.
lo_cursor->get_cell( )->value->read_to( REF #( lv_value ) ).
APPEND VALUE #(
column = lv_col
row = lv_row
value = lv_value
) TO rt_cells.
ENDDO.
ENDDO.
ENDMETHOD.
METHOD read_as_table.
" Rohdaten lesen
DATA(lt_cells) = read_worksheet_raw(
iv_xlsx = iv_xlsx
iv_worksheet_pos = iv_worksheet_pos
).
" Spaltenstruktur ermitteln
DATA(lo_table_descr) = CAST cl_abap_tabledescr(
cl_abap_typedescr=>describe_by_data( ct_data )
).
DATA(lo_struct_descr) = CAST cl_abap_structdescr(
lo_table_descr->get_table_line_type( )
).
DATA(lt_components) = lo_struct_descr->get_components( ).
" Header-Mapping erstellen (wenn vorhanden)
DATA lt_column_mapping TYPE TABLE OF ty_cell.
IF iv_has_header = abap_true.
lt_column_mapping = VALUE #(
FOR ls_cell IN lt_cells WHERE ( row = 1 )
( ls_cell )
).
ENDIF.
" Startzeile bestimmen
DATA(lv_start_row) = COND #(
WHEN iv_has_header = abap_true THEN 2
ELSE 1
).
" Eindeutige Zeilen ermitteln
DATA lt_rows TYPE SORTED TABLE OF i WITH UNIQUE KEY table_line.
LOOP AT lt_cells INTO DATA(ls_cell) WHERE row >= lv_start_row.
INSERT ls_cell-row INTO TABLE lt_rows.
ENDLOOP.
" Daten einlesen
LOOP AT lt_rows INTO DATA(lv_row).
DATA ls_line TYPE REF TO data.
CREATE DATA ls_line LIKE LINE OF ct_data.
ASSIGN ls_line->* TO FIELD-SYMBOL(<ls_line>).
LOOP AT lt_cells INTO ls_cell WHERE row = lv_row.
" Spalte zu Feldname mappen
DATA(lv_field_index) = ls_cell-column.
IF iv_has_header = abap_true.
" Feldname aus Header
READ TABLE lt_column_mapping INTO DATA(ls_header)
WITH KEY column = ls_cell-column.
IF sy-subrc = 0.
READ TABLE lt_components WITH KEY name = to_upper( ls_header-value )
TRANSPORTING NO FIELDS.
IF sy-subrc = 0.
lv_field_index = sy-tabix.
ENDIF.
ENDIF.
ENDIF.
IF lv_field_index <= lines( lt_components ).
ASSIGN COMPONENT lv_field_index OF STRUCTURE <ls_line>
TO FIELD-SYMBOL(<lv_field>).
IF sy-subrc = 0.
<lv_field> = ls_cell-value.
ENDIF.
ENDIF.
ENDLOOP.
APPEND <ls_line> TO ct_data.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Formatierung (Farben, Spaltenbreite)

Fuer erweiterte Formatierung kannst du Zellenstile definieren.

Formatierter Export

" Excel-Export mit Formatierung
CLASS zcl_formatted_excel_export DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
export_with_formatting
IMPORTING it_data TYPE zcl_excel_exporter=>ty_sales_orders
RETURNING VALUE(rv_xlsx) TYPE xstring
RAISING cx_xco_runtime_exception.
PRIVATE SECTION.
METHODS:
apply_header_style
IMPORTING io_cursor TYPE REF TO if_xco_xlsx_wa_cursor.
METHODS:
apply_amount_style
IMPORTING io_cursor TYPE REF TO if_xco_xlsx_wa_cursor
iv_amount TYPE p.
METHODS:
apply_status_style
IMPORTING io_cursor TYPE REF TO if_xco_xlsx_wa_cursor
iv_status TYPE string.
ENDCLASS.
CLASS zcl_formatted_excel_export IMPLEMENTATION.
METHOD export_with_formatting.
DATA(lo_spreadsheet) = xco_cp_xlsx=>document->empty( )->spreadsheet.
DATA(lo_worksheet) = lo_spreadsheet->worksheet->at_position( 1 ).
" Worksheet-Namen setzen
lo_worksheet->properties( )->name->set( 'Auftraege' ).
" Kopfzeile mit Formatierung
DATA(lo_cursor) = lo_worksheet->cursor(
io_column = xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )
io_row = xco_cp_xlsx=>coordinate->for_numeric_value( 1 )
).
DATA(lt_headers) = VALUE string_table(
( `Auftragsnummer` )
( `Kunde` )
( `Betrag` )
( `Waehrung` )
( `Bestelldatum` )
( `Status` )
).
LOOP AT lt_headers INTO DATA(lv_header).
lo_cursor->get_cell( )->value->write_from( lv_header ).
apply_header_style( lo_cursor ).
IF sy-tabix < lines( lt_headers ).
lo_cursor->move_right( ).
ENDIF.
ENDLOOP.
" Datenzeilen mit bedingter Formatierung
DATA(lv_row) = 2.
LOOP AT it_data INTO DATA(ls_order).
lo_cursor = lo_worksheet->cursor(
io_column = xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )
io_row = xco_cp_xlsx=>coordinate->for_numeric_value( lv_row )
).
" Auftragsnummer
lo_cursor->get_cell( )->value->write_from( ls_order-order_id ).
" Kunde
lo_cursor->move_right( )->get_cell( )->value->write_from( ls_order-customer ).
" Betrag mit Formatierung
lo_cursor->move_right( ).
lo_cursor->get_cell( )->value->write_from( ls_order-amount ).
apply_amount_style( io_cursor = lo_cursor iv_amount = ls_order-amount ).
" Waehrung
lo_cursor->move_right( )->get_cell( )->value->write_from( ls_order-currency ).
" Datum
lo_cursor->move_right( )->get_cell( )->value->write_from(
|{ ls_order-order_date DATE = USER }|
).
" Status mit Farbcodierung
lo_cursor->move_right( ).
lo_cursor->get_cell( )->value->write_from( ls_order-status ).
apply_status_style( io_cursor = lo_cursor iv_status = ls_order-status ).
lv_row = lv_row + 1.
ENDLOOP.
rv_xlsx = lo_spreadsheet->get_file_content( ).
ENDMETHOD.
METHOD apply_header_style.
" Kopfzeile: Fett und Hintergrundfarbe
" Hinweis: Die XCO API bietet begrenzte Style-Optionen
" Fuer erweiterte Formatierung siehe XLSX-Bibliothek
" Beispiel: Zellbreite anpassen (wo verfuegbar)
" io_cursor->get_cell( )->style->set_bold( abap_true ).
ENDMETHOD.
METHOD apply_amount_style.
" Betraege > 10000 hervorheben
IF iv_amount > 10000.
" Hier wuerde Formatierung angewendet werden
ENDIF.
ENDMETHOD.
METHOD apply_status_style.
" Statusabhängige Farbcodierung
CASE iv_status.
WHEN 'COMPLETED'.
" Gruen
WHEN 'PENDING'.
" Gelb
WHEN 'CANCELLED'.
" Rot
ENDCASE.
ENDMETHOD.
ENDCLASS.

Erweiterte Formatierung mit XLSX-Struktur

Fuer komplexere Formatierungen kannst du direkt mit der XLSX-Struktur arbeiten:

" XLSX-Manipulator fuer erweiterte Formatierung
CLASS zcl_xlsx_formatter DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_column_format,
column_letter TYPE c LENGTH 3,
width TYPE i,
format TYPE string, " z.B. '#,##0.00' fuer Zahlen
bold_header TYPE abap_bool,
END OF ty_column_format,
ty_column_formats TYPE STANDARD TABLE OF ty_column_format WITH KEY column_letter.
METHODS:
create_formatted_export
IMPORTING it_data TYPE ANY TABLE
it_column_formats TYPE ty_column_formats OPTIONAL
iv_title TYPE string OPTIONAL
RETURNING VALUE(rv_xlsx) TYPE xstring
RAISING cx_xco_runtime_exception.
PRIVATE SECTION.
METHODS:
column_index_to_letter
IMPORTING iv_index TYPE i
RETURNING VALUE(rv_letter) TYPE string.
ENDCLASS.
CLASS zcl_xlsx_formatter IMPLEMENTATION.
METHOD create_formatted_export.
DATA(lo_spreadsheet) = xco_cp_xlsx=>document->empty( )->spreadsheet.
DATA(lo_worksheet) = lo_spreadsheet->worksheet->at_position( 1 ).
" Titel setzen wenn angegeben
IF iv_title IS NOT INITIAL.
lo_worksheet->properties( )->name->set( iv_title ).
ENDIF.
" Tabellenstruktur ermitteln
DATA(lo_table_descr) = CAST cl_abap_tabledescr(
cl_abap_typedescr=>describe_by_data( it_data )
).
DATA(lo_struct_descr) = CAST cl_abap_structdescr(
lo_table_descr->get_table_line_type( )
).
DATA(lt_components) = lo_struct_descr->get_components( ).
" Kopfzeile
DATA(lo_cursor) = lo_worksheet->cursor(
io_column = xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )
io_row = xco_cp_xlsx=>coordinate->for_numeric_value( 1 )
).
LOOP AT lt_components INTO DATA(ls_component).
lo_cursor->get_cell( )->value->write_from( ls_component-name ).
IF sy-tabix < lines( lt_components ).
lo_cursor->move_right( ).
ENDIF.
ENDLOOP.
" Datenzeilen
DATA(lv_row) = 2.
DATA lv_value TYPE string.
LOOP AT it_data ASSIGNING FIELD-SYMBOL(<ls_row>).
lo_cursor = lo_worksheet->cursor(
io_column = xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )
io_row = xco_cp_xlsx=>coordinate->for_numeric_value( lv_row )
).
LOOP AT lt_components INTO ls_component.
ASSIGN COMPONENT ls_component-name OF STRUCTURE <ls_row>
TO FIELD-SYMBOL(<lv_field>).
IF sy-subrc = 0.
" Typabhaengige Formatierung
CASE ls_component-type->type_kind.
WHEN cl_abap_typedescr=>typekind_date.
lv_value = |{ CONV d( <lv_field> ) DATE = USER }|.
WHEN cl_abap_typedescr=>typekind_time.
lv_value = |{ CONV t( <lv_field> ) TIME = USER }|.
WHEN cl_abap_typedescr=>typekind_packed
OR cl_abap_typedescr=>typekind_decfloat
OR cl_abap_typedescr=>typekind_decfloat16
OR cl_abap_typedescr=>typekind_decfloat34.
lv_value = |{ <lv_field> }|.
WHEN OTHERS.
lv_value = <lv_field>.
ENDCASE.
lo_cursor->get_cell( )->value->write_from( lv_value ).
ENDIF.
IF sy-tabix < lines( lt_components ).
lo_cursor->move_right( ).
ENDIF.
ENDLOOP.
lv_row = lv_row + 1.
ENDLOOP.
rv_xlsx = lo_spreadsheet->get_file_content( ).
ENDMETHOD.
METHOD column_index_to_letter.
" 1 -> A, 2 -> B, ..., 26 -> Z, 27 -> AA
DATA(lv_remaining) = iv_index.
WHILE lv_remaining > 0.
DATA(lv_mod) = ( lv_remaining - 1 ) MOD 26.
rv_letter = |{ CONV c( lv_mod + 65 ) }{ rv_letter }|.
lv_remaining = ( lv_remaining - lv_mod - 1 ) / 26.
ENDWHILE.
ENDMETHOD.
ENDCLASS.

Multiple Worksheets

Fuer komplexere Reports mit mehreren Tabellenblaettern.

Export mit mehreren Worksheets

" Multi-Worksheet Excel Export
CLASS zcl_multi_worksheet_export DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_worksheet_data,
name TYPE string,
data TYPE REF TO data, " Referenz auf ANY TABLE
END OF ty_worksheet_data,
ty_worksheets TYPE STANDARD TABLE OF ty_worksheet_data WITH KEY name.
METHODS:
export_multiple_worksheets
IMPORTING it_worksheets TYPE ty_worksheets
RETURNING VALUE(rv_xlsx) TYPE xstring
RAISING cx_xco_runtime_exception.
PRIVATE SECTION.
METHODS:
write_table_to_worksheet
IMPORTING io_worksheet TYPE REF TO if_xco_xlsx_wa_worksheet
it_data TYPE ANY TABLE.
ENDCLASS.
CLASS zcl_multi_worksheet_export IMPLEMENTATION.
METHOD export_multiple_worksheets.
DATA(lo_spreadsheet) = xco_cp_xlsx=>document->empty( )->spreadsheet.
DATA(lv_sheet_pos) = 1.
LOOP AT it_worksheets INTO DATA(ls_worksheet).
" Worksheet erstellen/holen
DATA(lo_worksheet) = lo_spreadsheet->worksheet->at_position( lv_sheet_pos ).
" Name setzen
lo_worksheet->properties( )->name->set( ls_worksheet-name ).
" Daten schreiben
ASSIGN ls_worksheet-data->* TO FIELD-SYMBOL(<lt_data>).
write_table_to_worksheet(
io_worksheet = lo_worksheet
it_data = <lt_data>
).
lv_sheet_pos = lv_sheet_pos + 1.
ENDLOOP.
rv_xlsx = lo_spreadsheet->get_file_content( ).
ENDMETHOD.
METHOD write_table_to_worksheet.
" Struktur ermitteln
DATA(lo_table_descr) = CAST cl_abap_tabledescr(
cl_abap_typedescr=>describe_by_data( it_data )
).
DATA(lo_struct_descr) = CAST cl_abap_structdescr(
lo_table_descr->get_table_line_type( )
).
DATA(lt_components) = lo_struct_descr->get_components( ).
" Kopfzeile
DATA(lo_cursor) = io_worksheet->cursor(
io_column = xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )
io_row = xco_cp_xlsx=>coordinate->for_numeric_value( 1 )
).
LOOP AT lt_components INTO DATA(ls_component).
lo_cursor->get_cell( )->value->write_from( ls_component-name ).
IF sy-tabix < lines( lt_components ).
lo_cursor->move_right( ).
ENDIF.
ENDLOOP.
" Daten
DATA(lv_row) = 2.
DATA lv_value TYPE string.
LOOP AT it_data ASSIGNING FIELD-SYMBOL(<ls_row>).
lo_cursor = io_worksheet->cursor(
io_column = xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' )
io_row = xco_cp_xlsx=>coordinate->for_numeric_value( lv_row )
).
LOOP AT lt_components INTO ls_component.
ASSIGN COMPONENT ls_component-name OF STRUCTURE <ls_row>
TO FIELD-SYMBOL(<lv_field>).
IF sy-subrc = 0.
lv_value = <lv_field>.
lo_cursor->get_cell( )->value->write_from( lv_value ).
ENDIF.
IF sy-tabix < lines( lt_components ).
lo_cursor->move_right( ).
ENDIF.
ENDLOOP.
lv_row = lv_row + 1.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Praktisches Beispiel: Reporting mit mehreren Sheets

" Monatsreport mit Summary und Details
CLASS zcl_monthly_report_export DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_summary,
month TYPE string,
total_orders TYPE i,
total_revenue TYPE p LENGTH 10 DECIMALS 2,
avg_order_size TYPE p LENGTH 10 DECIMALS 2,
END OF ty_summary,
ty_summaries TYPE STANDARD TABLE OF ty_summary WITH KEY month.
TYPES:
BEGIN OF ty_order_detail,
order_id TYPE string,
order_date TYPE d,
customer TYPE string,
amount TYPE p LENGTH 10 DECIMALS 2,
status TYPE string,
END OF ty_order_detail,
ty_order_details TYPE STANDARD TABLE OF ty_order_detail WITH KEY order_id.
METHODS:
generate_report
IMPORTING it_summary TYPE ty_summaries
it_details TYPE ty_order_details
RETURNING VALUE(rv_xlsx) TYPE xstring
RAISING cx_xco_runtime_exception.
ENDCLASS.
CLASS zcl_monthly_report_export IMPLEMENTATION.
METHOD generate_report.
" Worksheets vorbereiten
DATA lt_worksheets TYPE zcl_multi_worksheet_export=>ty_worksheets.
" Summary-Daten
DATA lr_summary TYPE REF TO ty_summaries.
CREATE DATA lr_summary.
lr_summary->* = it_summary.
APPEND VALUE #(
name = 'Zusammenfassung'
data = lr_summary
) TO lt_worksheets.
" Detail-Daten
DATA lr_details TYPE REF TO ty_order_details.
CREATE DATA lr_details.
lr_details->* = it_details.
APPEND VALUE #(
name = 'Auftragsdetails'
data = lr_details
) TO lt_worksheets.
" Export durchfuehren
DATA(lo_exporter) = NEW zcl_multi_worksheet_export( ).
rv_xlsx = lo_exporter->export_multiple_worksheets( lt_worksheets ).
ENDMETHOD.
ENDCLASS.

Integration in RAP Action

Die nahtlose Integration in RAP-Business-Objects ermoeglicht Excel-Download direkt aus Fiori-Apps.

Behavior Definition

managed implementation in class ZBP_I_SALESORDER unique;
strict ( 2 );
define behavior for ZI_SalesOrder alias SalesOrder
persistent table zsalesorder
lock master
authorization master ( instance )
{
create;
update;
delete;
// Excel-Export als Action
static action exportToExcel
result [1] ZA_ExcelDownload;
// Excel-Import als Action mit Parameter
static action importFromExcel
parameter ZA_ExcelUpload
result [*] $self;
field ( readonly ) OrderUUID, CreatedAt, CreatedBy;
}

Action Parameter und Result

-- Excel-Download Result
@EndUserText.label: 'Excel Download Result'
define abstract entity ZA_ExcelDownload
{
FileName : abap.char(100);
MimeType : abap.char(50);
FileContent : abap.rawstring(0);
}
-- Excel-Upload Parameter
@EndUserText.label: 'Excel Upload Parameter'
define abstract entity ZA_ExcelUpload
{
FileName : abap.char(100);
MimeType : abap.char(50);
FileContent : abap.rawstring(0);
}

Behavior Implementation

" RAP Behavior Implementation mit Excel-Actions
CLASS lhc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS exportToExcel FOR MODIFY
IMPORTING keys FOR ACTION SalesOrder~exportToExcel RESULT result.
METHODS importFromExcel FOR MODIFY
IMPORTING keys FOR ACTION SalesOrder~importFromExcel RESULT result.
ENDCLASS.
CLASS lhc_salesorder IMPLEMENTATION.
METHOD exportToExcel.
" Alle Auftraege laden
SELECT order_id, customer, amount, currency, order_date, status
FROM zsalesorder
INTO TABLE @DATA(lt_orders).
" In Export-Format konvertieren
DATA lt_export TYPE zcl_excel_exporter=>ty_sales_orders.
lt_export = CORRESPONDING #( lt_orders ).
TRY.
" Excel generieren
DATA(lo_exporter) = NEW zcl_excel_exporter( ).
DATA(lv_xlsx) = lo_exporter->export_to_excel( lt_export ).
" Dateiname mit Timestamp
DATA(lv_filename) = |Auftraege_{ sy-datum }_{ sy-uzeit }.xlsx|.
" Result zurueckgeben
APPEND VALUE #(
%cid = keys[ 1 ]-%cid
%param = VALUE #(
FileName = lv_filename
MimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
FileContent = lv_xlsx
)
) TO result.
CATCH cx_xco_runtime_exception INTO DATA(lx_error).
APPEND VALUE #(
%cid = keys[ 1 ]-%cid
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = lx_error->get_text( )
)
) TO reported-salesorder.
ENDTRY.
ENDMETHOD.
METHOD importFromExcel.
" Parameter auslesen
DATA(ls_param) = keys[ 1 ]-%param.
TRY.
" Excel parsen
DATA(lo_importer) = NEW zcl_excel_importer( ).
DATA(lt_imported) = lo_importer->import_from_excel(
iv_xlsx = ls_param-FileContent
iv_skip_header = abap_true
).
" Validierung
DATA lt_errors TYPE string_table.
DATA(lv_valid) = lo_importer->validate_import(
EXPORTING it_data = lt_imported
IMPORTING et_errors = lt_errors
).
IF lv_valid = abap_false.
" Fehler melden
LOOP AT lt_errors INTO DATA(lv_error).
APPEND VALUE #(
%cid = keys[ 1 ]-%cid
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = lv_error
)
) TO reported-salesorder.
ENDLOOP.
RETURN.
ENDIF.
" Daten importieren
DATA lt_create TYPE TABLE FOR CREATE zi_salesorder.
LOOP AT lt_imported INTO DATA(ls_imported).
APPEND VALUE #(
%cid = |IMPORT_{ sy-tabix }|
OrderID = ls_imported-order_id
Customer = ls_imported-customer
Amount = CONV #( ls_imported-amount )
Currency = ls_imported-currency
OrderDate = CONV d( ls_imported-order_date )
Status = ls_imported-status
) TO lt_create.
ENDLOOP.
" Entitaeten erstellen
MODIFY ENTITIES OF zi_salesorder IN LOCAL MODE
ENTITY SalesOrder
CREATE SET FIELDS WITH lt_create
MAPPED DATA(lt_mapped)
FAILED DATA(lt_failed)
REPORTED DATA(lt_reported).
" Ergebnis zurueckgeben
result = CORRESPONDING #( lt_mapped-salesorder ).
CATCH cx_xco_runtime_exception INTO DATA(lx_error).
APPEND VALUE #(
%cid = keys[ 1 ]-%cid
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = lx_error->get_text( )
)
) TO reported-salesorder.
ENDTRY.
ENDMETHOD.
ENDCLASS.

Alternativen: CSV und OData

CSV als einfache Alternative

CSV ist universell kompatibel und einfacher zu generieren:

" CSV-Export als Excel-Alternative
CLASS zcl_csv_exporter DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
CONSTANTS:
gc_separator TYPE c LENGTH 1 VALUE ';',
gc_newline TYPE string VALUE cl_abap_char_utilities=>cr_lf.
METHODS:
export_to_csv
IMPORTING it_data TYPE ANY TABLE
iv_separator TYPE c DEFAULT gc_separator
RETURNING VALUE(rv_csv) TYPE string.
METHODS:
export_to_csv_binary
IMPORTING it_data TYPE ANY TABLE
iv_separator TYPE c DEFAULT gc_separator
iv_encoding TYPE string DEFAULT 'UTF-8'
RETURNING VALUE(rv_binary) TYPE xstring.
ENDCLASS.
CLASS zcl_csv_exporter IMPLEMENTATION.
METHOD export_to_csv.
" Struktur ermitteln
DATA(lo_table_descr) = CAST cl_abap_tabledescr(
cl_abap_typedescr=>describe_by_data( it_data )
).
DATA(lo_struct_descr) = CAST cl_abap_structdescr(
lo_table_descr->get_table_line_type( )
).
DATA(lt_components) = lo_struct_descr->get_components( ).
" Kopfzeile
LOOP AT lt_components INTO DATA(ls_component).
rv_csv = rv_csv && ls_component-name.
IF sy-tabix < lines( lt_components ).
rv_csv = rv_csv && iv_separator.
ENDIF.
ENDLOOP.
rv_csv = rv_csv && gc_newline.
" Datenzeilen
DATA lv_value TYPE string.
LOOP AT it_data ASSIGNING FIELD-SYMBOL(<ls_row>).
LOOP AT lt_components INTO ls_component.
ASSIGN COMPONENT ls_component-name OF STRUCTURE <ls_row>
TO FIELD-SYMBOL(<lv_field>).
IF sy-subrc = 0.
lv_value = <lv_field>.
" Escape fuer CSV (Anführungszeichen verdoppeln, bei Trennzeichen quotieren)
IF lv_value CS iv_separator OR lv_value CS '"' OR lv_value CS gc_newline.
REPLACE ALL OCCURRENCES OF '"' IN lv_value WITH '""'.
lv_value = |"{ lv_value }"|.
ENDIF.
rv_csv = rv_csv && lv_value.
ENDIF.
IF sy-tabix < lines( lt_components ).
rv_csv = rv_csv && iv_separator.
ENDIF.
ENDLOOP.
rv_csv = rv_csv && gc_newline.
ENDLOOP.
ENDMETHOD.
METHOD export_to_csv_binary.
DATA(lv_csv) = export_to_csv(
it_data = it_data
iv_separator = iv_separator
).
" BOM fuer UTF-8 (Excel-Erkennung)
DATA(lv_bom) = CONV xstring( 'EFBBBF' ).
" Konvertieren
rv_binary = lv_bom && cl_abap_conv_codepage=>create_out(
codepage = iv_encoding
)->convert( lv_csv ).
ENDMETHOD.
ENDCLASS.

OData-Export fuer Excel

Fiori Apps koennen Daten direkt ueber OData als Excel exportieren:

-- CDS View mit Excel-Export-Annotation
@EndUserText.label: 'Sales Orders for Excel Export'
@OData.publish: true
@UI.headerInfo: {
typeName: 'Auftrag',
typeNamePlural: 'Auftraege'
}
define view entity ZC_SalesOrderExcel
as projection on ZI_SalesOrder
{
@UI.lineItem: [{ position: 10, label: 'Auftragsnummer' }]
key OrderID,
@UI.lineItem: [{ position: 20, label: 'Kunde' }]
Customer,
@UI.lineItem: [{ position: 30, label: 'Betrag' }]
@Semantics.amount.currencyCode: 'Currency'
Amount,
@UI.lineItem: [{ position: 40, label: 'Waehrung' }]
@Semantics.currencyCode: true
Currency,
@UI.lineItem: [{ position: 50, label: 'Bestelldatum' }]
OrderDate,
@UI.lineItem: [{ position: 60, label: 'Status' }]
Status
}

Fiori Elements bietet dann automatisch einen Export-Button fuer Excel.

Fehlerbehandlung

Robuster Import mit Fehlerprotokoll

" Excel-Import mit detailliertem Fehlerprotokoll
CLASS zcl_excel_import_validator DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_import_error,
row TYPE i,
column TYPE string,
value TYPE string,
error_type TYPE string,
message TYPE string,
END OF ty_import_error,
ty_import_errors TYPE STANDARD TABLE OF ty_import_error WITH KEY row column.
TYPES:
BEGIN OF ty_import_result,
success TYPE abap_bool,
rows_total TYPE i,
rows_success TYPE i,
rows_error TYPE i,
errors TYPE ty_import_errors,
data TYPE REF TO data,
END OF ty_import_result.
METHODS:
import_and_validate
IMPORTING iv_xlsx TYPE xstring
ir_target_type TYPE REF TO cl_abap_tabledescr
RETURNING VALUE(rs_result) TYPE ty_import_result
RAISING cx_xco_runtime_exception.
ENDCLASS.
CLASS zcl_excel_import_validator IMPLEMENTATION.
METHOD import_and_validate.
" Ziel-Tabelle erstellen
CREATE DATA rs_result-data TYPE HANDLE ir_target_type.
ASSIGN rs_result-data->* TO FIELD-SYMBOL(<lt_data>).
" Excel parsen
DATA(lo_document) = xco_cp_xlsx=>document->for_file_content( iv_xlsx ).
DATA(lo_worksheet) = lo_document->spreadsheet->worksheet->at_position( 1 ).
DATA(lo_used_area) = lo_worksheet->used_area( ).
DATA(lv_first_row) = lo_used_area->row_selection->first_row->position->value.
DATA(lv_last_row) = lo_used_area->row_selection->last_row->position->value.
rs_result-rows_total = lv_last_row - lv_first_row. " Ohne Header
rs_result-success = abap_true.
" Struktur der Zieltabelle
DATA(lo_struct_descr) = CAST cl_abap_structdescr(
ir_target_type->get_table_line_type( )
).
DATA(lt_components) = lo_struct_descr->get_components( ).
" Zeilen verarbeiten (ab Zeile 2 = nach Header)
DO ( lv_last_row - lv_first_row ) TIMES.
DATA(lv_row) = lv_first_row + sy-index.
" Neue Zeile erstellen
DATA ls_line TYPE REF TO data.
CREATE DATA ls_line TYPE HANDLE lo_struct_descr.
ASSIGN ls_line->* TO FIELD-SYMBOL(<ls_line>).
DATA(lv_row_has_error) = abap_false.
" Spalten durchlaufen
LOOP AT lt_components INTO DATA(ls_component).
DATA(lv_col) = sy-tabix.
DATA(lo_cursor) = lo_worksheet->cursor(
io_column = xco_cp_xlsx=>coordinate->for_numeric_value( lv_col )
io_row = xco_cp_xlsx=>coordinate->for_numeric_value( lv_row )
).
DATA lv_value TYPE string.
lo_cursor->get_cell( )->value->read_to( REF #( lv_value ) ).
" Wert zuweisen mit Fehlerbehandlung
ASSIGN COMPONENT ls_component-name OF STRUCTURE <ls_line>
TO FIELD-SYMBOL(<lv_field>).
IF sy-subrc = 0.
TRY.
" Typkonvertierung versuchen
<lv_field> = lv_value.
CATCH cx_sy_conversion_no_number
cx_sy_conversion_overflow
cx_sy_conversion_error INTO DATA(lx_conv).
" Konvertierungsfehler protokollieren
APPEND VALUE #(
row = lv_row
column = ls_component-name
value = lv_value
error_type = 'CONVERSION'
message = lx_conv->get_text( )
) TO rs_result-errors.
lv_row_has_error = abap_true.
rs_result-success = abap_false.
ENDTRY.
ENDIF.
ENDLOOP.
" Zeile zur Ergebnistabelle hinzufuegen (auch mit Fehlern)
IF lv_row_has_error = abap_false.
INSERT <ls_line> INTO TABLE <lt_data>.
rs_result-rows_success = rs_result-rows_success + 1.
ELSE.
rs_result-rows_error = rs_result-rows_error + 1.
ENDIF.
ENDDO.
ENDMETHOD.
ENDCLASS.

Best Practices

Empfehlungen fuer Excel-Verarbeitung

AspektEmpfehlung
Kleine DatenmengenXCO Spreadsheet API direkt
Grosse DatenmengenStreaming/Chunking oder CSV
Komplexe FormatierungExterne XLSX-Bibliothek
Maximale KompatibilitaetCSV mit UTF-8 BOM
Fiori-IntegrationOData-Export nutzen

Performance-Tipps

  1. Batch-Verarbeitung: Grosse Dateien in Chunks verarbeiten
  2. Streaming: Bei sehr grossen Dateien Stream-basierte Verarbeitung
  3. Caching: Wiederholte Exporte cachen
  4. Asynchron: Grosse Exporte im Hintergrund (bgPF)

Sicherheits-Tipps

  1. Dateityp validieren: MIME-Type pruefen vor Verarbeitung
  2. Groessenlimit: Maximale Dateigroesse definieren
  3. Inhalt validieren: Daten vor Import validieren
  4. Berechtigungen: Export/Import-Berechtigung pruefen

Zusammenfassung

Excel-Verarbeitung in ABAP Cloud nutzt moderne APIs:

  1. XCO Spreadsheet API fuer Standard-Export/Import
  2. Generische Exporter fuer beliebige Tabellentypen
  3. Formatierung ueber Zellenstile und Eigenschaften
  4. Multiple Worksheets fuer komplexe Reports
  5. RAP Actions fuer nahtlose Fiori-Integration
  6. CSV als Alternative fuer maximale Kompatibilitaet

Die Wahl der richtigen Methode haengt von deinen Anforderungen ab: Datenvolumen, Formatierungskomplexitaet und Zielgruppe.

Verwandte Themen