In den letzten Artikeln haben wir uns damit beschäftigt alphanumerische Konstanten, Variablen und Spalten in Tabellen, die mit unterschiedlichen CCSIDs definiert wurden, zu verarbeiten. Im Besonderen haben wir uns mit der Verarbeitung von UTF-8 Daten auseinandergesetzt. Bei UTF-8-Daten kann ein Zeichen 1, 2, 3, oder 4 Byte lang sein. Damit entspricht die Länge eines Strings nicht mehr zwangsläufig der Anzahl Zeichen. In diesem Artikel werden wir uns anschauen, wie sich die alphanumerischen Built-In-Funktionen in Verbindung mit UTF-8-Daten verhalten.
Bei den Built-In-Funktionen werden die Längen-Angaben oder der Startpunkt, an dem in einem String aufgesetzt werden soll, per Default in Byte ermittelt oder verlangt. Ebenso sind die Rückgabe-Werte (z.B. an welcher Position sich der gesuchte Text befindet), in Anzahl Byte ausgegeben. Solange man nur mit reinem EBCDIC-Code oder mit Doppel-Byte Character Sets (z.B. UCS2) arbeitet, ist das auch kein Problem. Wenn jedoch der String UTF-8-Daten beinhaltet, bei denen ein Zeichen zwischen 1 und 4 Byte einnehmen kann, wird es etwas komplizierter, da ein Zeichen nicht mehr einem Byte entspricht. Wie lang sind denn nun unsere Daten?
Ermitteln String-Länge
Die Länge einer Zeichenkette kann mit der Built-In-Funktion %LEN() ermittelt werden.
Built-in-Function %LEN() – Ermitteln Länge in Anzahl Byte
Die Länge wird allerdings (historisch bedingt) immer in Anzahl an Bytes ermittelt. Solange der Unicode-String nur aus den Buchstaben A-Z und Ziffern besteht entspricht die Anzahl der Anzahl an Bytes. Sobald jedoch ein Umlaut (ä,ö,ü), ß oder irgendein akzentuierter Buchstabe (z.B. é) in dem String enthalten ist, gibt es Abweichungen, da für diese Zeichen 2-Byte verwendet werden.
Die folgende Datenstruktur, die durch eine Feldgruppe (Array) überlagert wird, enthält 5 Unterfelder à 50 Byte! (nicht Zeichen) die mit CCSID 1208, also UTF-8 definiert wurden. Die Unterfelder wurden mit diversen Namen initialisiert. Lediglich der erste Name (Peter Hausberg) besteht nur aus den Buchstaben A-Z. Alle anderen Namen beinhalten Umlaute, akzentuierte Buchstaben und/oder ein ß.
Quelle: HauserIn dem folgenden Code-Beispiel wird die Feldgruppe in einer Schleife gelesen und die Länge der UTF-8-Daten in den Unter-Feldern mit der Built-In-Funktion %LEN() bestimmt und angezeigt.
Quelle: HauserFührt man die Prozedur aus, werden die folgenden Werte angezeigt.
Quelle: HauserVergleicht man die ermittelten Werte mit der Anzahl Zeichen, so wird man feststellen, dass lediglich bei Peter Hausberg die ermittelte Länge mit der Anzahl Zeichen übereinstimmt. Bei Anna Weißenberg wird zusätzlich das ß verwendet, deshalb weicht die Länge nur um ein Byte von der Anzahl Zeichen ab. Bei Günther Roßberg und Renée Lébergus sind es jeweils 2 (üß bzw. 2 x é) und bei Käthe Küßbergen sind es sogar 3 (äüß) Zeichen, die jeweils 2 Byte benötigen. Dadurch erhöht sich die Länge (im Vergleich zu der Anzahl an Zeichen) um jeweils ein Byte pro Umlaut, ß oder akzentuiertem Buchstaben.
Mit Ausnahme der alphanumerischen String-Funktionen, bei denen die Position und Länge per Default in Byte angegeben werden muss, ist die Anzahl Byte nicht unbedingt hilfreich, zumindest nicht bei UTF-8-Daten. Interessanter wäre es die Länge in Anzahl Zeichen zu ermitteln.
Built-In-Funktion %CHARCOUNT() – Ermitteln Anzahl Zeichen
Über die Built-In-Funktion %CHARCOUNT() kann die Anzahl an Zeichen in Single-Byte oder Double-Byte Character Sets und natürlich auch für UTF-8 oder UTF-16 ermittelt werden.
Wie die Built-In-Funktion %LEN() erwartet auch die Built-In-Funktion %CHARCOUNT() genau einen alphanumerischen Parameter (den Text) und gibt dann die Anzahl der Zeichen zurück.
In dem folgenden Beispiel wird wiederum die zuvor gezeigte Datenstruktur mit dem überlagernden Array verarbeitet. Dieses Mal wird sowohl die Länge in Anzahl Byte als auch die Anzahl Zeichen ermittelt, gegenübergestellt und ausgegeben.
Quelle: HauserDas Ergebnis zeigt die bereits zuvor festgestellten Abweichungen:
Quelle: HauserAnmerkung: %CHARCOUNT() bringt grundsätzlich die Anzahl Zeichen zurück, während %LEN() grundsätzlich die Anzahl Byte zurückbringt! Je nachdem welcher Wert benötigt wird, muss entweder für die eine oder andere Funktion verwendet werden.
Alphanumerische Built-In-Funktionen
Neben der Längen-Berechnungen gibt es eine ganze Anzahl von alphanumerischen String-Funktionen, z.B. um einen Text zu durchsuchen (%SCAN()), um einen Text zu ersetzen (%REPLACE() oder %SCANRPL() etc.), bei denen eine Start-Position oder eine Länge angegeben werden muss und die z.T. auch wieder einen numerischen Wert (z.B. Position) zurückgeben.
Die alphanumerischen Built-In-Funktionen wurden ursprünglich so angelegt, dass alle Angaben für eine (Start-)Position und die Länge, sowie die entsprechenden Rückgabe-Wert die Anzahl an Bytes repräsentiert. Das war auch nie ein Problem, solange kein UTF-8 verwendet wurde.
Die folgende Liste zeigt die alphanumerischen Built-In-Funktionen:
Quelle: HauserWas passiert nun, wenn UTF-8-Daten mit einer String-Funktion z.B. %SUBST() verarbeitet werden müssen?
Verarbeitung von UTF-8 Daten mit Built-In-Functions: Default: Anzahl Byte
Schauen wir uns einfach ein paar Beispiele an:
Built-In-Funktion %SUBSTR() und hardcodierte Start-Positionen
In dem folgenden Beispiel wird wiederum die zuvor gezeigte Datenstruktur mit den UTF-8-Unterfeldern und der überlagernden Feldgruppe(Array) mit dem gleichen Inhalt wie zuvor verarbeitet.
In einer zweiten Daten-Struktur ebenfalls mit überlagernder Feldgruppe sind die Positionen des Textes ‘berg‘ in den einzelnen Unterfeldern hinterlegt (allerdings nicht in Byte, sondern in Anzahl Zeichen). Mit Hilfe der Built-In-Function %SUBST() soll der Teil des Namens, beginnend mit ‘berg‘ und allen folgenden Zeichen ausgegeben werden.
Sollte in diesem Beispiel bei der Aufbereitung bzw. bei der Built-In-Funktion %SUBST() ein Fehler auftreten, wird dieser durch eine Monitor-Group abgefangen und die Fehlermeldung (aus der Programm-Status-Datenstruktur) angezeigt.
Quelle: HauserFührt man diese Prozedur aus, bekommt man die folgende Anzeige:
Quelle: HauserDa die Start-Position in Anzahl Zeichen und nicht in Anzahl Byte angegeben wurde, wird bei Anna Weißenberg nberg ausgegeben, da das ß 2 Byte braucht. Ähnliches gilt bei Günther Roßberg und Renée Lébergus. Hier sind es 2 Zeichen, die 2 Byte benötigen (üß bzw. 2xé). Da das ß bzw. é unmittelbar vor ‘berg‘ stehen und ebenfalls 2 Byte benötigen wird ßberg bzw. ébergus ausgegeben. Bei Käthe Küßbergen kommt es zum Abbruch, weil hier versucht wird in der Mitte eines Zeichens aufzusetzen.
Verwendet man alphanumerische Built-In-Funktionen in Verbindung mit UTF-8, muss man darauf achten, dass man bei der Positions- oder Längen-Angabe, die Anzahl Byte und nicht die Anzahl Zeichen verwendet. Ebenso muss einem klar sein, dass es sich bei den numerischen Rückgabe-Werten ebenfalls um Byte-Angaben handelt. Wie sieht es nun aus, wenn die Position mit einer anderen Built-In-Function z.B. %SCAN() ermittelt wird?
Built-In-Funktion %SUBSTR() und Ermitteln der Start-Position mit %SCAN()
In unserem Beispiel hatten wir die Start-Positionen von ‘berg‘ innerhalb des Strings hardcodiert, was vielleicht auch ein bisschen ungeschickt und unrealistisch war.
Mit Hilfe der Built-In-Function %SCAN() kann man zumindest in diesem Fall zunächst die Start-Position ermitteln und diese dann in der Built-In-Function %SUBST() einsetzen.
In dem folgenden Beispiel wir wiederum die schon bekannte Datenstruktur mit überlagernder Feldgruppe verarbeitet.
Die Start-Position wird mit Hilfe der Built-In-Function %SCAN ermittelt.
Anmerkung: Der konstante Wert ‘berg‘ kann nicht direkt in der Built-In-Function %SCAN() angegeben werden. RPG kann die Konstante nicht in UTF-8 umsetzen, was zu einem Compile-Fehler führt! Eine Konstante jedoch kann nicht mit CCSID definiert werden. Definiert man jedoch eine Variable mit CCSID 1208 und initialisiert diese Variable mit dem Wert ‘berg‘, kann man diese Variable in der Built-In-Function %SCAN() verwenden.
Die über %SCAN() ermittelte Start-Position wird anschließend in der Built-In-Funktion %SUBST() eingesetzt.
Quelle: HauserFührt man die Prozedur aus, erhält man das folgende Ergebnis:
Quelle: Hauser
Der String wurde korrekt ermittelt und ausgegeben, da %SCAN() die Position in Byte ermittelt und %SUBST() die Start-Position in Byte erwartet.
Das ist so weit ja alles schön und gut, aber doch eine ziemlich heikle Angelegenheit. Einfacher wäre es auf alle Fälle, wenn man die Positionen und die Längen nicht in Byte, sondern in Anzahl Zeichen ermitteln und auch verwenden könnte!
Und genau das ist möglich! … Wie das geht, zeigen wir im nächsten Artikel. Bis dahin schon einmal viel Spaß beim Arbeiten mit alphanumerischen Built-In-Functions und Unicode-Daten.
Die Autorin Birgitta Hauser schreibt regelmäßig für den MIDRANGE Deep Dive.
