Mittwoch, 25. März 2009

Ein einfacher Parser für SAPscript

Im letzten Blog Ereignisbasierter XML-Parser in ABAP beschrieb ich anhand eines Beispiels, wie der in der ABAP-Objektfamilie iXML implementierte SAX-Parser verwendet werden kann. In diesem Beitrag soll es um einen ereignisbasierten Parser für SAPscript gehen, das SAP-Dateiformat für Langtexte. Wir haben einen solchen Parser benötigt, um in SAPscript verfasste Entwicklerdokumentation (zum Beispiel von Funktionsbausteinen oder Klassen) dynamisch in TML zu übersetzen, die von der TWiki-Plattform verwendete Auszeichnungssprache.

SAPscript (auch als ITF, internes Textformat bezeichnet) ist ein einfaches textbasiertes Datenformat für Texte mit Markup. Es wird im SAP-System für die verschiedensten Arten von Langtexten verwendet — keineswegs nur für Formulare, wie auf manchen Webseiten behauptet wird, sondern beispielsweise auch für lange Hilfetexte zu Meldungen, F1-Hilfetexte für Datenelemente oder für ganze Programme, Informationstexte im Workflow, Entwicklerdokumentation von Programmobjekten und vieles mehr. Auch den meisten Anwendungsbelegen können SAPscript-Texte als Kopf- oder Positionstexte zugeordnet werden, auf die man dann in Folgebelegen oder im Belegdruck Bezug nehmen kann.

Aufgrund dieser vielfältigen Verwendung bietet SAPscript eine sehr komplexe Grammatik mit einer Fülle von Steuerungsmöglichkeiten. In den Grundzügen ist sie jedoch leicht zu beschreiben: Jede Zeile besteht aus zwei Zeichen für das zu verwendende Absatzformat und einer Textzeile mit bis zu 132 Zeichen. Ist kein Absatzformat angegeben, so bleibt das Absatzformat der vorhergehenden Zeile gültig. Der Text selbst kann auch noch Zeichenformate enthalten, z.B. für Kursiv- oder Fettschrift, die mit speziellen, an XML erinnernden Symbolen eingeleitet und geschlossen werden.

Hier ein Screenshot des textbasierten Editors — es gibt wahlweise auch einen WYSIWYG-Editor, der das Format vor dem Anwender verbirgt und ihm gleich den Text so präsentiert, wie er nachher aufbereitet werden wird.


SAPscript-Editor


Als Entwickler bin ich mit dem textbasierten Editor meist glücklicher, weil ich das tatsächlich generierte Format unter Kontrolle habe. Um flüssige, längere Texte zu schreiben, die relativ wenig Markup enthalten, bin ich allerdings mit dem WYSIWYG-Editor schneller.

Wer schon länger in der IT-Branche arbeitet, kennt sicher den Texteditor XEDIT, der gern auf den VM/CMS Betriebssystemen eingesetzt wurde. Es folgt ein XEDIT-Screenshot, der eine verdächtige Ähnlichkeit mit dem SAPscript-Editor aufweist (die Ähnlichkeit würde noch deutlicher, wenn man XEDIT mit einem früher eingesetzten Editormodus für SAPscript vergleicht: Wie in XEDIT gab es auch dort einen fünf Zeichen breiten sogenannte Präfixbereich, in dem man Kommandos eingeben konnte, zum Beispiel zum Löschen, Kopieren oder Verschieben von einzelnen Zeilen oder Zeilenbereichen. Dieser Editormodus ist m.W. mittlerweile abgeschafft).


Screenshot XEDIT


Einen Parser für eine derart komplexe Sprache wie SAPscript zu schreiben, konnte für uns von vorneherein nur eine 80%-Lösung bedeuten: Die vielen, aber jeweils nur selten benutzten Feinheiten von SAPscript ignorieren wir, versuchen aber die am häufigsten verwendeten Features zu erkennen.

Für einen Übersetzer zwischen verschiedenen Dateiformaten ist das Erbauer-Entwurfsmuster ein typisches Einsatzfeld. Die Idee ist, den Vorgang des Parsens von der eigentlichen Konvertierung zu entkoppeln, so dass der Parser für sich in mehreren Kontexten verwendbar ist. Es gibt also zwei Objekte, den Parser und den Converter, die über Ereignisse miteinander kommunizieren (wofür das Entwurfsmuster Beobachter verwendet werden kann). Eine Implementierung auf diesem Weg ist umso günstiger, da das Entwurfsmuster Beobachter dank der ABAP-Objects-Events bereits in den Sprachkern von ABAP integriert ist.

Die Klasse ZCL_ITF_PARSER durchsucht einen SAPscript-Text auf das Vorkommen gewisser Sondersequenzen und legt alle reinen Textstrings auf einen Stack (eine interne Tabelle vom Zeilentyp string). Trifft der Parser auf eine Sondersequenz, so löst er ein Ereignis aus und sendet neben der aktuellen Textzeile ein Schreibobjekt mit, mit dem sich der Top of Stack manipulieren lässt. Folgende Ereignisse werden ausgelöst:


  • Beginn eines neuen Absatzformats

  • Ende eines Absatzformats

  • Beginn eines Zeichenformats (z.B. Fettdruck)

  • Ende eines Zeichenformats

  • Kommentare (beginnen mit Format /*)

  • SAPscript-Kommandozeilen (beginnen mit dem Format /:)

  • Textsymbole – hier wird Name und allfällig bereits ermittelter Wert des Symbols übergeben

  • Table Data – Daten einer einzelnen Tabellenzeile



Wenn man die Methode parse() aufruft, ohne sich für irgendein Ereignis registriert zu haben, erhält man schliesslich die reinen, von allen Formatierungen befreiten Textbestandteile des SAPscript-Texts. Dieser Aufrufer wirkt also wie ein Konverter ins Nurtextformat.

Üblicherweise wird man jedoch die Formatierungen berücksichtigen und auf eine bestimmte Weise übersetzen. Für einen Konverter nach HTML müsste man beispielsweise das Zeichenformat <ZH> in das Tag <b> übersetzen, das Absatzformat AS in das Tag <p> usf. Hierzu muss man mittels der Anweisung set handler die Parserklasse mit der Klasse verknüpfen, die die eigentliche Übersetzung leistet. Auf diese Weise kann derselbe Parser an ganz verschiedene Übersetzer angeschlossen werden.

Für unseren TWiki-Converter haben wir daher eine zweite Klasse zcl_itf_to_twiki eingeführt. Sie hat eine öffentliche Methode transform, in der eine Parserinstanz beschafft wird, die Registrierung für die verschiedenen Parser-Events erfolgt und schliesslich der gelesene (und veränderte) Text an den Aufrufer zurückgegeben wird:
method transform .

data: lo_parser type ref to zcl_itf_parser,
lv_text type string.

create object lo_parser.
set handler do_par_begin for lo_parser.
set handler do_par_end for lo_parser.
set handler do_tag_begin for lo_parser.
set handler do_tag_end for lo_parser.
set handler do_table_data for lo_parser.
lo_parser->parse( exporting it_text = it_text
importing ev_text = ev_text ).
do_at_end( changing cv_text = ev_text ).

endmethod.

Ein Beispiel möge verdeutlichen, wie in diesen Behandlermethoden nun die eigentliche Übersetzung durchgeführt wird: In SAPscript gibt es für Überschriften erster Ordnung das Absatzformat U1. Eine Überschrift schreibt man also in der Form
U1Überschrift erster Ordnung

Dieselbe Überschrift würde in TML, der Zielsprache für TWiki, wie folgt geschrieben:
---++ Überschrift erster Ordnung

Der Übersetzer muss also, wenn eine Überschrift erkannt wird, die Zeichenfolge ---++ ins Ergebnis schreiben, und bei Beendigung der Überschrift einen Umbruch schreiben. In der Methode do_par_begin steht daher:
method do_par_begin.
...
case iv_format.
...
when zitf_format-u1.
* Titelzeile 1
io_text->add( cl_abap_char_utilities=>cr_lf ).
io_text->add( gc_head1 ). " ---++
io_text->add( space ).
when ...
endcase.
endmethod.

Entsprechend wird, wenn der Parser das Ende einer Überschrift erkennt, ein Zeilenumbruch gerendert:
method do_par_end.
case iv_format.
when zitf_format-u1 or
zitf_format-u2 or
zitf_format-u3 or
zitf_format-u4.
io_text->add( cl_abap_char_utilities=>cr_lf ).
...
endcase.
endmethod.

Mit Hilfe dieser Klassen konnten wir alle im SAP-System vorliegenden Entwicklerdokumentationen dynamisch in die TWiki-Plattform einbinden. Da TWiki in Perl implementiert ist, war es leicht um einen SapConnectPlugin zu ergänzen, das sich mit einem HTTP-Request an das Entwicklungssystem den TML-Code für eine Dokumentation beschafft und in den aktuellen TWiki-Topic einmischt. Im TWiki-Topic selbst schreibt man zum Beispiel
%INCLSAPDOCU{"cl.zcl_xml_helper"}% 

Das Plugin ruft dann im Zielsystem (hier das Entwicklungssystem) einen Service auf, der die Dokumentation der Klasse zunächst einliest und dann an den Konverter übergibt. Der konvertierte Code wird dann an Stelle der %INCLSAPDOCU{}%-Anweisung in das Topic gesetzt.

Der grosse Vorteil, den wir davon haben, ist, dass Dokumentation nicht doppelt geschrieben werden muss: Alle Dokumentation, egal ob im SAP-System verfasst oder im TWiki selbst, ist im TWiki abrufbar. Da es keine Redundanzen gibt, ist die Dokumentation immer auf dem neuesten Stand.

Kommentare :

Tobias Topyla hat gesagt…

Hallo Rüdiger,

vielen Dank für Deinen Artikel! Es war mir bei einem konkreten Problem sehr hilfreich.

Viele Grüße
Tobias

Rüdiger Plantiko hat gesagt…

Hallo Tobias,

danke für die Rückmeldung! Freut mich, dass mein Blog hilfreich war.

Das (eigene) TWiki-Plugin mit dem oben beschriebenen Konverter haben wir übrigens nach wie vor im Einsatz...

Gruss,
Rüdiger

Rüdiger Plantiko hat gesagt…

Da ich kürzlich per Mail eine Anfrage nach dem Code erhielt - hier die Informationen. Die folgenden Programmobjekte sind nötig:

Klasse ZCL_ITF_PARSER: http://bsp.mits.ch/code/clas/zcl_itf_parser
Interface ZIF_WRITER: http://bsp.mits.ch/code/intf/zif_writer
Typgruppe ZITF: http://bsp.mits.ch/buch/abap/limu/reps/%25_CZITF

Egal wofür Sie den ITF-Parser einzusetzen planen: Auch wenn es nicht für die Konvertierung ins TWiki-Format sein sollte, zeigt die Klasse ZCL_ITF_TO_TWIKI ( http://bsp.mits.ch/code/clas/zcl_itf_to_twiki ) wenigstens, wie der Parser verwendet wird (siehe dort die Methode TRANSFORM).