[ABAP] CSV-Datei in interne Table einlesen

Variante 1 (cl_gui_frontend_services, freier Separator)

* Beispiel für die CSV-Eingabedaten:
* Mandant;Fluggesellschaft;Flugnummer;Land;Abflugstadt;Startflugh.;Land;Ankunftstadt;Zielflugh.;Flugdauer;Abflug;Ankunftszeit;Entfernung;Entfernung in;Charter;n Tag(e) später;
* 900;AA;0017;US;NEW YORK;JFK;US;SAN FRANCISCO;SFO;  6:01;11:00:00;14:01:00;2572;MI;;0;
* 900;AA;0064;US;SAN FRANCISCO;SFO;US;NEW YORK;JFK;  5:21;09:00:00;17:21:00;2572;MI;;0;

* Ausgabestruktur (Spalten) für die eingelesenen und gesplitteten CSV-Daten
* der Einfachheit halber hier alles erstmal als Strings abbilden
TYPES : BEGIN OF ty_s_csv,
          mandant          TYPE string,
          fluggesellschaft TYPE string,
          flugnummer       TYPE string,
          land_ab          TYPE string,
          abflugstadt	   TYPE string,
          startflughafen   TYPE string,
          land_an          TYPE string,
          ankunftstadt     TYPE string,
          zielflughafen    TYPE string,
          flugdauer        TYPE string,
          abflug           TYPE string,
          ankunftszeit     TYPE string,
          entfernung       TYPE string,
          einheit          TYPE string,
          charter          TYPE string,
          tage             TYPE string,
        END OF ty_s_csv.

* Tabellentypen
TYPES: ty_it_csv TYPE STANDARD TABLE OF ty_s_csv WITH DEFAULT KEY.

* Überschriften vorhanden
PARAMETERS: p_head AS CHECKBOX DEFAULT 'X'.
* Separator / Trennzeichen
PARAMETERS: p_sep TYPE char1 DEFAULT ';'.

TRY.
    DATA: lv_rc TYPE i.
    DATA: it_files TYPE filetable.
    DATA: lv_action TYPE i.

* FileOpen-Dialog aufrufen
    cl_gui_frontend_services=>file_open_dialog( EXPORTING
                                                  file_filter    = |csv (*.csv)\|*.csv\|{ cl_gui_frontend_services=>filetype_all }|
                                                  multiselection = abap_false
                                                CHANGING
                                                  file_table     = it_files
                                                  rc             = lv_rc
                                                  user_action    = lv_action ).

    IF lv_action = cl_gui_frontend_services=>action_ok.
* wenn mind. eine Dateie ausgewählt worden ist
      IF lines( it_files ) = 1.

* Tabelle für Einlesedaten
        DATA(it_strings) = VALUE string_table( ).

* eingelesene Datei zeilenweise als Stringdaten einlesen
        cl_gui_frontend_services=>gui_upload( EXPORTING
                                                filename = CONV #( it_files[ 1 ]-filename )
                                                filetype = 'ASC'             " Dateityp BIN, ASC, DAT
                                              CHANGING
                                                data_tab = it_strings ).           " Übergabetabelle für Datei-Inhalt

        cl_demo_output=>write_data( it_strings ).

* Wenn mit Header, dann ab Zeile 2, sonst gleich ab Zeile 1
        DATA(lv_startzeile) = COND i( WHEN p_head = abap_true THEN 2 ELSE 1 ).

* Je nach Vorhandensein des Headers prüfen, ob genug Zeilen in der Tabelle
        IF ( lines( it_strings ) > lv_startzeile - 1 ).
* Ausgabetabelle mit ausgesplitteten CSV-Daten
          DATA(it_csv) = VALUE ty_it_csv( ).

* Eingelesene Strings durchlaufen, Start bei Zeile 1 (mit Header) oder 2 (mit Header)
          LOOP AT it_strings ASSIGNING FIELD-SYMBOL(<z>) FROM lv_startzeile.
* neue Ausgabezeile
            DATA(lv_csv_line) = VALUE ty_s_csv( ).

* String anhand des Separators aufsplitten
            SPLIT <z> AT p_sep INTO TABLE DATA(it_columns).

* Wenn anzahl der Splitelemente == Anzahl Felder in der CSV-Struktur
            IF lines( it_columns ) = 16.
* gesplittete Daten in die neue CSV-Zeile übernehmen
              lv_csv_line-mandant = it_columns[ 1 ].
              lv_csv_line-fluggesellschaft = it_columns[ 2 ].
              lv_csv_line-flugnummer = it_columns[ 3 ].
              lv_csv_line-land_ab = it_columns[ 4 ].
              lv_csv_line-abflugstadt = it_columns[ 5 ].
              lv_csv_line-startflughafen = it_columns[ 6 ].
              lv_csv_line-land_an = it_columns[ 7 ].
              lv_csv_line-ankunftstadt = it_columns[ 8 ].
              lv_csv_line-zielflughafen = it_columns[ 9 ].
              lv_csv_line-flugdauer = it_columns[ 10 ].
              lv_csv_line-abflug = it_columns[ 11 ].
              lv_csv_line-ankunftszeit = it_columns[ 12 ].
              lv_csv_line-entfernung = it_columns[ 13 ].
              lv_csv_line-einheit = it_columns[ 14 ].
              lv_csv_line-charter = it_columns[ 15 ].
              lv_csv_line-tage = it_columns[ 16 ].
            ENDIF.

* neue CSV-Zeile an Ausgabetabelle anfügen
            APPEND lv_csv_line TO it_csv.
          ENDLOOP.

          cl_demo_output=>write_data( it_csv ).

* HTML-Code vom Demo-Output holen
          DATA(lv_html) = cl_demo_output=>get( ).
* Daten im Inline-Browser im SAP-Fenster anzeigen
          cl_abap_browser=>show_html( EXPORTING
                                        title        = 'Daten aus CSV'
                                        html_string  = lv_html
                                        container    = cl_gui_container=>default_screen ).

* cl_gui_container=>default_screen erzwingen
          WRITE: space.
        ENDIF.
      ENDIF.
    ENDIF.
  CATCH cx_root INTO DATA(e_text).
    MESSAGE e_text->get_text( ) TYPE 'I'.
ENDTRY.

Variante 2 (cl_gui_frontend_services, freier Separator, dynamisches Einlesen von CSV-Daten)

* Überschriften vorhanden
PARAMETERS: p_head AS CHECKBOX DEFAULT 'X'.
* Separator / Trennzeichen
PARAMETERS: p_sep TYPE char1 DEFAULT ';'.

TRY.
    DATA: lv_rc TYPE i.
    DATA: it_files TYPE filetable.
    DATA: lv_action TYPE i.

* FileOpen-Dialog aufrufen
    cl_gui_frontend_services=>file_open_dialog( EXPORTING
                                                  file_filter    = |csv (*.csv)\|*.csv\|{ cl_gui_frontend_services=>filetype_all }|
                                                  multiselection = abap_false
                                                CHANGING
                                                  file_table     = it_files
                                                  rc             = lv_rc
                                                  user_action    = lv_action ).

    IF lv_action = cl_gui_frontend_services=>action_ok.
* wenn mind. eine Dateie ausgewählt worden ist
      IF lines( it_files ) = 1.

* Tabelle für Einlesedaten
        DATA(it_strings) = VALUE string_table( ).

* eingelesene Datei zeilenweise als Stringdaten einlesen
        cl_gui_frontend_services=>gui_upload( EXPORTING
                                                filename = CONV #( it_files[ 1 ]-filename )
                                                filetype = 'ASC'             " Dateityp BIN, ASC, DAT
                                              CHANGING
                                                data_tab = it_strings ).     " Übergabetabelle für Datei-Inhalt

        cl_demo_output=>write_data( it_strings ).

        DATA(it_errors) = VALUE string_table( ).

* Wenn mit Header, dann ab Zeile 2, sonst gleich ab Zeile 1
        DATA(lv_startzeile) = COND i( WHEN p_head = abap_true THEN 2 ELSE 1 ).

* Je nach Vorhandensein des Headers prüfen, ob genug Zeilen (für Erstellung des Headers) in der Tabelle
        IF ( lines( it_strings ) > lv_startzeile - 1 ).

* 1. verfügbaren String aus der eingelesenen Tabelle anhand des Separators aufsplitten
          SPLIT it_strings[ 1 ] AT p_sep INTO TABLE DATA(it_cnt_col).

* Komponenten (Spalten) der Tabelle
          DATA(it_components) = VALUE cl_abap_structdescr=>component_table( ).

* Komponententabelle füllen
          LOOP AT it_cnt_col ASSIGNING FIELD-SYMBOL(<c>).
* Standard-Spaltenbezeichner vorbelegen (COL1...COLn)
            DATA(lv_name) = |COL{ sy-tabix }|.

* Wenn Header vorhanden, dann Header übernehmen
            IF p_head = abap_true.
              lv_name = <c>.
* alle Vorkommen, die nicht [a-zA-Z0-9_] entsprechen, durch '_' ersetzen
              REPLACE ALL OCCURRENCES OF REGEX '([^\w]|[äöüÄÖÜß])+' IN lv_name WITH '_'.
            ENDIF.

* Spalte vom Typ String zur Komponententabelle hinzufügen
            APPEND VALUE #( name = lv_name
                            type = cl_abap_elemdescr=>get_string( )
                          ) TO it_components.
          ENDLOOP.

* Strukturdeskriptor für Komponententabelle
          DATA(o_struct_desc) = cl_abap_structdescr=>create( it_components ).

* Tabellendeskriptor erzeugen
          DATA(o_table_desc) = cl_abap_tabledescr=>create(
                                                           p_line_type  = o_struct_desc                          " Spalten
                                                           p_table_kind = cl_abap_tabledescr=>tablekind_std      " Tabellentyp STANDARD TABLE
                                                           p_unique     = abap_false                             " NON-UNIQUE KEY
                                                           p_key_kind   = cl_abap_tabledescr=>keydefkind_default " Benutzerdefinierter Schlüssel
                                                         ).

* Tabellenobjekt anhand des Tabellendeskriptors erstellen
          DATA: o_table TYPE REF TO data.
          CREATE DATA o_table TYPE HANDLE o_table_desc.

* Feldsymbol auf das Tabellenobjekt
          FIELD-SYMBOLS <csv_table> TYPE STANDARD TABLE.
          ASSIGN o_table->* TO <csv_table>.

* Eingelesene Strings durchlaufen, Start bei Zeile 1 (ohne Header) oder 2 (mit Header)
          LOOP AT it_strings ASSIGNING FIELD-SYMBOL(<z>) FROM lv_startzeile.
* neue Ausgabezeile anfügen
            APPEND INITIAL LINE TO <csv_table>.
* neue Ausgabezeile holen
            DATA(lv_lc) = lines( <csv_table> ).
            ASSIGN <csv_table>[ lv_lc ] TO FIELD-SYMBOL(<row>).

* String anhand des Separators aufsplitten
            SPLIT <z> AT p_sep INTO TABLE DATA(it_column_data).

* Wenn anzahl der Splitelemente == Anzahl Felder in der CSV-Struktur
            IF lines( it_column_data ) = lines( it_components ).
* gesplittete Daten in die neue CSV-Zeile übernehmen
              LOOP AT it_components ASSIGNING FIELD-SYMBOL(<comp>).
* n-te Spalte <col> der akt. Tabellenzeile <row> ermitteln
                ASSIGN COMPONENT sy-tabix OF STRUCTURE <row> TO FIELD-SYMBOL(<col>).
* den n-ten gesplitteten Wert der n-ten Spalte <col> der akt. Tabellenzeile <row> zuordnen
                <col> = it_column_data[ sy-tabix ].
              ENDLOOP.
            ELSE.
* Wenn abweichende Spaltenanzahl vom Header
              APPEND |Zeile { sy-tabix }: Spaltenanzahl ({ lines( it_column_data ) }) weicht vom Header ({ lines( it_components ) }) ab.| TO it_errors.
            ENDIF.

          ENDLOOP.

          cl_demo_output=>write_data( it_errors ).

          cl_demo_output=>write_data( <csv_table> ).

* HTML-Code vom Demo-Output holen
          DATA(lv_html) = cl_demo_output=>get( ).
* Daten im Inline-Browser im SAP-Fenster anzeigen
          cl_abap_browser=>show_html( EXPORTING
                                        title        = 'Daten aus CSV'
                                        html_string  = lv_html
                                        container    = cl_gui_container=>default_screen ).

* cl_gui_container=>default_screen erzwingen
          WRITE: space.
        ENDIF.
      ENDIF.
    ENDIF.
  CATCH cx_root INTO DATA(e_text).
    MESSAGE e_text->get_text( ) TYPE 'I'.
ENDTRY.

Variante 3 (cl_gui_frontend_services, TAB-Zeichen als Separator)

* CSV-Datei mit drei Spalten
TYPES: BEGIN OF ty_s_type,
         col1 TYPE string,
         col2 TYPE string,
         col3 TYPE string,
       END OF ty_s_type.

TYPES: ty_it_csv TYPE STANDARD TABLE OF ty_s_type WITH DEFAULT KEY.

DATA: it_csv TYPE ty_it_csv.

TRY.
  * Setzt voraus, dass die CSV-Daten TAB-getrennt sind!
    cl_gui_frontend_services=>gui_upload( EXPORTING
                                            filename            = 'c:\temp\test.csv' " Eingabedatei
                                            filetype            = 'ASC'              " Dateityp BIN, ASC, DAT
                                            has_field_separator = abap_true          " Spalten durch TAB getrennt bei ASCII Upload?
                                          CHANGING
                                            data_tab            = it_csv ).          " Übergabetabelle für Datei-Inhalt

    cl_demo_output=>write_data( it_csv ).

  CATCH cx_root INTO DATA(e_text).
    MESSAGE e_text->get_text( ) TYPE 'I'.
ENDTRY.

Variante 4 (Funktionbaustein, obsolet)

* CSV-Datei mit drei Spalten
TYPES: BEGIN OF ty_s_type,
         col1 TYPE string,
         col2 TYPE string,
         col3 TYPE string,
       END OF ty_s_type.

TYPES: ty_it_csv TYPE STANDARD TABLE OF ty_s_type WITH DEFAULT KEY.

DATA: it_csv TYPE ty_it_csv.

* Funktion ruft eigenen File-Open-Dialog auf
* FuBa UPLOAD ist obsolet
CALL FUNCTION 'UPLOAD'
  EXPORTING
    filename                = 'c:\temp\data.txt' " Dateinamen vorbelegen
    filetype                = 'DAT'              " Eingabedatei-Typ: Datentabelle ASCII mit Spaltentabulator
  TABLES
    data_tab                = it_csv
  EXCEPTIONS
    conversion_error        = 1
    file_open_error         = 2
    file_read_error         = 3
    invalid_table_width     = 4
    invalid_type            = 5
    no_batch                = 6
    unknown_error           = 7
    gui_refuse_filetransfer = 8
    OTHERS                  = 9.
    
IF sy-subrc <> 0.
  MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
ENDIF.