ABAP in Praxis - String Verarbeitung
In diesem praktischen Beispiel schauen wir uns die String Verarbeitung zur Ermittlung der CDS Namen in CamelCase an und wie du das mit ABAP umsetzen kannst.
Inhaltsverzeichnis
In den folgenden Abschnitten werden wir auf die einfache und effiziente Verarbeitung von Strings eingehen. Wir nutzen dabei Modern ABAP, um an die Namen der Core Data Services zu gelangen.
Einleitung
In manchen Situationen fehlen uns die richtigen Schnittstellen, um an direkte Informationen zu kommen. So zum Beispiel hatten wir keine Klasse und keinen Funktionsbaustein gefunden, um an die CamelCase Namen der Core Data Services zu gelangen. Diese werden auf klassischem Weg in Tabellen gespeichert und die Informationen in Großbuchstaben konvertiert. Damit sind sie leider nicht so leicht für uns nutzbar.
In diesem Blog zerlegen wir den Quellcode der Core Data Services in verarbeitbare Bestandteile und ermitteln für uns den Namen mit Groß- und Kleinschreibung, wie er im Quellcode definiert wurde.
Vorbereitung
Bevor du mit der eigentlichen Aufgabe beginnen kannst, benötigen wir eine Grundlage, die du weiterentwickeln kannst. Dazu legen wir im System eine ausführbare Klasse an. Wir arbeiten dabei außerhalb von ABAP Cloud, da wir auf eine Tabelle zugreifen wollen, die nicht freigegeben ist. Dazu legen wir uns drei Typen an, die wir für die Verarbeitung benötigen, eine Range für die Selektion, eine Struktur für das Mapping und einen Tabellen typ für die Rückgabe der Daten.
TYPES tt_r_name TYPE RANGE OF ddddlsrc-ddlname.
TYPES: BEGIN OF ts_mapping,
ddlname TYPE ddddlsrc-ddlname,
cds_name TYPE string,
END OF ts_mapping.
TYPES tt_mapping TYPE SORTED TABLE OF ts_mapping WITH UNIQUE KEY ddlname.
Als nächsten Schritt legen wir eine Methode an, die die Ermittlung durchführen soll. Dabei wollen wir sie wiederverwendbar gestalten und übergeben die CDS Views, für die wir Namen ermitteln wollen, um nicht alle Objekte zu lesen.
METHODS extract_cds_name
IMPORTING it_r_name TYPE tt_r_name
RETURNING VALUE(rt_result) TYPE tt_mapping.
Die Methode befüllen wir nun mit einigen Daten, um schnell ein Ergebnis aus der Logik zu erhalten. Bei der Befüllung wählen wir eine Inline-Deklaration und befüllen die Range Tabelle. Da SIGN und OPTION nur einmal gesetzt werden müssen, lassen wir sie außerhalb der Datensätze und sparen uns die weitere Aufzählung.
DATA(lt_r_names) = VALUE tt_r_name( sign = 'I'
option = 'EQ'
( low = 'I_COMPANYCODE' )
( low = '/1BS/SADL_CDS_EXP' )
( low = 'A_CHANGEMASTEROBJECTTYPETEXT' )
( low = 'C_BUDGETPERIODCHILDGROUP' )
( low = 'I_ABOPCHECKINGRULE' )
( low = 'I_JOBSTATUS' )
( low = 'SADL_CDS_RS_SO_ROOT_W_DB_HINT' )
( low = 'SADL_GW_V_AUNIT_V2_VH_WRONG_AN' )
( low = 'SEPM_SDDL_EXTENSIONS' ) ).
Zum Abschluss lesen wir auf der Tabelle DDDDLSRC die gewählten Core Data Services und den abgelegten Quellcode.
SELECT FROM ddddlsrc
FIELDS ddlname, source
WHERE ddlname IN @it_r_name
INTO TABLE @DATA(lt_views).
Damit kannst du die folgende Klasse für die Implementierung verwenden. Möchtest du noch weiter gehen, dann kannst du die Umsetzung im Test Driven Development machen und dir vor der Implementierung noch die Testklasse aufbauen.
CLASS zcl_bs_demo_cds_names DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PRIVATE SECTION.
TYPES tt_r_name TYPE RANGE OF ddddlsrc-ddlname.
TYPES: BEGIN OF ts_mapping,
ddlname TYPE ddddlsrc-ddlname,
cds_name TYPE string,
END OF ts_mapping.
TYPES tt_mapping TYPE SORTED TABLE OF ts_mapping WITH UNIQUE KEY ddlname.
METHODS extract_cds_name
IMPORTING it_r_name TYPE tt_r_name
RETURNING VALUE(rt_result) TYPE tt_mapping.
ENDCLASS.
CLASS zcl_bs_demo_cds_names IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lt_r_names) = VALUE tt_r_name( sign = 'I'
option = 'EQ'
( low = 'I_COMPANYCODE' )
( low = '/1BS/SADL_CDS_EXP' )
( low = 'A_CHANGEMASTEROBJECTTYPETEXT' )
( low = 'C_BUDGETPERIODCHILDGROUP' )
( low = 'I_ABOPCHECKINGRULE' )
( low = 'I_JOBSTATUS' )
( low = 'SADL_CDS_RS_SO_ROOT_W_DB_HINT' )
( low = 'SADL_GW_V_AUNIT_V2_VH_WRONG_AN' )
( low = 'SEPM_SDDL_EXTENSIONS' ) ).
out->write( extract_cds_name( lt_r_names ) ).
ENDMETHOD.
METHOD extract_cds_name.
SELECT FROM ddddlsrc
FIELDS ddlname, source
WHERE ddlname IN @it_r_name
INTO TABLE @DATA(lt_views).
" Implement here
ENDMETHOD.
ENDCLASS.
Aufgabe
Die Aufgabe besteht nun darin, die Rückgabetabelle zu befüllen und die echten CamelCase Namen aus dem Code abzuleiten. In der internen Tabelle LT_VIEWS findest du den Namen des Views und das Coding:
Hinweis: Im nächsten Abschnitt werden wir auf die Lösung eingehen, wenn du die Aufgabe erst einmal selbstständig machen möchtest, solltest du hier pausieren.
Lösung
Schauen wir uns in diesem Abschnitt die verschiedenen Schritte zum Aufbau der Tabelle an.
Schleife
Im ersten Schritt wollen wir die verschiedenen Views verarbeiten, dazu legen wir eine Schleife an und arbeiten dabei mit einer Referenz. Mehr Informationen zur Arbeit damit, findest du in einem älteren Artikel von uns. Im zweiten Schritt fügen wir eine neue Zeile an unsere Ergebnistabelle und befüllen den Viewnamen. Die erzeugte Zeile weisen wir einer neuen Referenz zu, um später den Namen zu ergänzen.
LOOP AT lt_views REFERENCE INTO DATA(lr_view).
INSERT VALUE #( ddlname = lr_view->ddlname ) INTO TABLE rt_result REFERENCE INTO DATA(lr_result).
ENDLOOP.
Wir übernehmen direkt den Namen des Views, da es sich um eine sortierte Tabelle handelt und wir den Schlüssel befüllen müssen, da wir sonst einen Fehler erhalten. Da wir keine Ausschlusskriterien haben, die einzelne Zeilen ausschließt, sondern wir alles ins Ergebnis übernehmen, macht dieser Schritt auch Sinn.
Aufteilung
Als nächstes wollen wir die einzelnen Statements der Quelle teilen, dazu verwenden wir einen klassischen SPLIT Befehl und erhalten eine String Tabelle mit den einzelnen Elementen aus dem Quellcode.
SPLIT lr_view->source AT ` ` INTO TABLE DATA(lt_split).
Suche
Wir können nun also mit der Verarbeitung der einzelnen Statements beginnen. Die Tabelle würde im Moment nach dem Split so aussehen:
Nun müssen wir nur die Tabelle mit Elementen durchsuchen und nach dem gesuchten CDS View filtern. Haben wir den Views gefunden, übernehmen wir diesen und verlassen die Routine. Damit die Werte miteinander vergleichbar sind, wandeln wir den CamelCase Namen per TO_UPPER um und vergleichen das Ergebnis. Da wir hier mit einer Referenz arbeiten, müssen wir für den Zugriff "->*" verwenden.
LOOP AT lt_split REFERENCE INTO DATA(lr_split).
IF to_upper( lr_split->* ) = lr_view->ddlname.
lr_result->cds_name = lr_split->*.
EXIT.
ENDIF.
ENDLOOP.
Zum Abschluss verlassen wir die Logik mit EXIT und gehen zum nächsten Core Data Service.
Performance
Um die Performance etwas zu verbessern, müssen wir nicht immer bei der ersten Zeile beginnen, vor allem wenn viele Annotationen am Anfang sind. Im Grunde können wir nach dem Schlüsselwort DEFINE suchen, da dieses den View einleitet und definiert. Dazu suchen wir in der Tabelle nach "define", da diese aber keine Felder besitzt, verwenden wir den Zugriff über TABLE_LINE. Mit der Funktion LINE_INDEX lassen wir uns die aktuelle Zeile zurückgeben. Die Schleife ergänzen wir dann noch um den Zusatz FROM, um ab dieser Position zu starten.
DATA(ld_start) = line_index( lt_split[ table_line = `define` ] ).
LOOP AT lt_split REFERENCE INTO DATA(lr_split) FROM ld_start.
ENDLOOP.
Sollte LINE_INDEX kein Statement finden, dann wird die Position auf 0 gesetzt und kann dann weiterhin für den LOOP verwendet werden, um mit der ersten Position zu beginnen.
Zeilensprünge
In einigen Fällen kann es vorkommen, dass "define" nicht gefunden werden kann. Schauen wir uns diese Fälle genauer an, werden wir erkennen, dass das Statement nicht sauber getrennt wurde.
Bei Backslash & "n" handelt es sich um einen Zeilenumbruch, der ebenfalls in der Tabelle gespeichert wird. In so einem Fall sollten wir alle Zeilenumbrüche im Quellcode vor dem SPLIT ersetzen, damit wir ein sauberes Ergebnis erhalten. Dafür verwenden wir die Replace Funktion und ersetzen alle Vorkommen des Zeilenumbruchs durch ein Leerzeichen.
lr_view->source = replace( val = lr_view->source
sub = cl_abap_char_utilities=>cr_lf
with = ` `
occ = 0 ).
Schauen wir uns nun die Tabelle nach dem SPLIT an, dann sind die Statements sauber getrennt, um im Anschluss nach dem Anfang suchen zu können.
Namen
In unseren Beispielen gibt es auch einen Fall, der einen leeren Core Data Service zurückliefert. Schauen wir uns hier das Mapping im Detail an, werden wir feststellen, dass der Name im Quellcode nicht gleich dem Namen des Objekts ist.
Im einfachsten Fall übernehmen wir dann den Namen des gesuchten Core Data Services als Ergebnis. Hier gibt es dann keinen CamelCase Namen für das Objekt.
IF lr_result->cds_name IS INITIAL.
lr_result->cds_name = lr_view->ddlname.
ENDIF.
Vollständiges Beispiel
In diesem Abschnitt findest du die vollständige Klasse mit der kompletten Implementierung, um das Beispiel nachstellen zu können. Grundsätzlich kannst du die Eingrenzung der einzelnen Views auch entfernen, um alle Views als Ergebnis zu erhalten. Dabei wird aber die Laufzeit sehr stark ansteigen. Die Ressource findest du bei uns im GitHub Repository für die Extraktion der CDS Informationen.
Fazit
In diesem praktischen Beispiel wollten wir dir die Arbeit mit Strings etwas näherbringen und wie du die verschiedenen Techniken nutzen kannst, um an das Ergebnis zu kommen. Hast du eine interessante Lösung für das Problem, gern in die Kommentare.