Reguläre Ausdrücke

Richard Feynman, einer der bekanntesten Physiker des 20. Jahrhunderts, war überzeugt, dass niemand die Quantentheorie versteht. Über reguläre Ausdrücke, auch reguläre Expressionen genannt, könnte ähnliches behauptet werden. Vielleicht liegt es daran, dass diese Ausdrücke auf den ersten Blick einschüchternd aussehen können:

#?([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})

Allerdings sind reguläre Ausdrücke nicht ganz so kontraintuitiv wie besagte Theorie und sind in der Entwicklung sowie im IT-Alltag ein Werkzeug, welches in vielen Fällen hilfreich zur Seite stehen kann, aber auch Grenzen hat.

Definition

Aus Sicht der theoretischen Informatik ist ein regulärer Ausdruck eine Methode, um Mengen von Zeichenketten mit gemeinsamen Mustern zu beschreiben. Reguläre Ausdrücke werden durch die Verwendung von verschiedenen Operatoren und Konventionen definiert, die es ermöglichen, komplexe Suchmuster zu erstellen.

Die Theorie der regulären Ausdrücke basiert auf den Konzepten der endlichen Automaten und der regulären Sprachen. Vereinfacht gesehen, kann die Funktionalität eines regulären Ausdrucks als Musterabgleich gesehen werden. Oft werden diese Ausdrücke als Regex oder Rexexp abgekürzt.

Ein einfacher regulärer Ausdruck könnte wie folgt aussehen:

[abc]

Dieser Ausdruck würde in einem Text die Zeichen a, b, und c finden. Über einen solchen regulären Ausdruck können Zeichenfolgen in Texten identifiziert, extrahiert und verarbeitet werden.

Geschichte

Die Geschichte regulärer Ausdrücke geht zurück auf das Jahr 1951, in welchem der Mathematiker Stephen Cole Kleene den Begriff prägte.

Praktische Anwendung fanden solche Ausdrücke in den 1960er-Jahren, als Ken Thompson, diese im Editor QED implementierte, als er diesen für das Compatible Time-Sharing System (CTSS) neuschrieb.

Über die Jahre wurde die Funktionalität regulärer Ausdrücke in weitere Werkzeuge wie grep und sed integriert. Im Rahmen der Unix-Philosophie, in welcher ein Werkzeug eine Aufgabe gut beherrschen soll, boten sich hiermit noch mächtigere Werkzeuge für die Textverarbeitung. Die Ausdrücke fanden nicht nur in der Textverarbeitung, sondern auch in der lexikalischen Analyse im Compiler-Design Anwendung.

Neben der Integration in solche Werkzeuge wurde vor allem beginnend mit den 1980er-Jahren die Integration regulärer Ausdrücke in Programmiersprachen wie Perl vorangetrieben. Einhergehend mit dieser Entwicklung wurde die Ausdrücke mächtiger und mehr Anwendungsfälle konnten mit diesen bearbeitet werden. Larry Wall, der Schöpfer von Perl, erweiterte die Fähigkeiten regulärer Ausdrücke erheblich und machte sie zu einem zentralen Bestandteil seiner Sprache.

In den 1990er-Jahren kam es zu einer Standardisierung von Syntax und Verhalten regulärer Ausdrücke, was die Entwicklung der Bibliothek der Perl-kompatiblen regulären Ausdrücke (PCRE) vorantrieb. Diese Bibliothek wurde in vielen Anwendungen verwendet und ist bis heute eine der am weitesten verbreiteten Implementierungen von regulären Ausdrücken.

Grundlagen

Doch wie genau werden reguläre Ausdrücke erstellt? Solche Ausdrücke können aus vielen unterschiedlichen Elementen bestehen, wie Literalen, Zeichenklassen, Quantifizierer und Gruppen.

So wäre ein regulärer Ausdruck bestehend aus einem einzigen Literal gültig:

a

Dieser Ausdruck würde hierbei auf die Vorkommen von a in einem Text matchen.
In regulären Ausdrücken können Oder-Verknüpfungen gebildet werden. So würde der Ausdruck:

a|b

entweder auf das Zeichen a oder auf das Zeichen b in einem Text matchen.

In den meisten Fällen wird allerdings nicht nach einem einzelnen Literal gematcht, sondern mit Zeichenklassen, Quantifizierern und Gruppen gearbeitet. Im Grundsatz würde allerdings nichts dagegen sprechen, einen regulären Ausdruck komplett, als Literal zu definieren:

Supercalifragilisticexpialigetisch

Diese Definition würde dazu führen, dass jedes Auftreten des Wortes Supercalifragilisticexpialigetisch gematcht werden würde. Allerdings wäre der reguläre Ausdruck in diesem Fall nicht mehr als eine einfache Suche.

Da es in regulären Ausdrücken eine Reihe von Zeichen mit spezieller Bedeutung gibt, müssen diese in bestimmten Fällen maskiert werden. Dies geschieht mit einem Backslash und dem sich anschließenden Zeichen:

\.

In diesem Beispiel würde der Punkt als normaler Punkt behandelt werden und nicht als Zeichen mit spezieller Bedeutung betrachtet werden.

Zeichenklassen

Zeichenklassen in regulären Ausdrücken erlauben es, eine Menge von Zeichen zu definieren, von denen jedes ein potenzielles Match für ein Zeichen aus dem Eingabetext darstellen kann. So würde der reguläre Ausdruck:

[abcdefghijklmnopqrstuvwxyz]

auf alle Zeichen zwischen a und z matchen. Die Zeichen werden hierbei in eckige Klammern eingefasst. Obiger Ausdruck kann allerdings sinnvoller gestaltet werden:

[a-z]

Der Bindestrich führt dazu, dass der Ausdruck als a bis z gelesen werden kann und damit einen Bereich definiert. Auch können mehrere Zeichenklassen in einem Block definiert werden:

[a-zA-Z]

Diese Zeichenklassen würde bei Buchstaben in Klein- und Großschreibung anschlagen. Bei dieser Notation ist darauf zu achten, dass die Definitionen ohne Leerzeichen aneinander gehangen werden.

Um auch deutsche Umlaute und das Eszett zu berücksichtigen, müssen diese Zeichen explizit zur Zeichenklasse hinzugefügt werden:

[a-zA-ZäöüÄÖÜß]

Standard-Zeichenklassen

Neben den selbstdefinierten Zeichenklassen existieren eine Reihe von vordefinierten Zeichenklassen, welche ebenfalls genutzt werden können.

So existiert die Zeichenklasse \d, welche allen Dezimalziffern entspricht, also 0 bis 9; dies entspricht der selbstdefinierten Zeichenklasse:

[0-9]

Die Zeichenklasse \D definiert das Gegenteil der vorherigen Klasse und matcht auf alle Nichtziffern und entspricht damit folgender Zeichenklasse:

[^0-9]

Während der Zirkumflex innerhalb einer Zeichenklasse normal genutzt werden kann, negiert er, am Anfang der Zeichenklasse stehend, diese. Eine Erweiterung dieser Standardklassen sind die Klassen \w und \W, welche für alle Wortzeichen stehen und folgenden selbstdefinierten Zeichenklassen entsprechen würden:

[a-zA-Z0-9_]
[^a-zA-Z0-9_]

Daneben existieren, abhängig von der Implementierung weitere vordefinierte Zeichenklassen, wie \s für Whitespace-Zeichen oder die Klasse \S für alle Nicht-Whitespace-Zeichen.

Metazeichen

Im Rahmen regulärer Ausdrücke sind einige Metazeichen definiert, welche unterschiedlichste Bedeutungen haben und nicht als die Zeichen selbst interpretiert werden. Sie werden verwendet, um Muster für die Textsuche und -manipulation zu definieren. Einige dieser Zeichen sollen nachfolgend vorgestellt werden.

Der Punkt ist als Metazeichen so definiert, dass er für jedes beliebige Zeichen, bis auf einen Zeilenumbruch steht. Damit würde der Ausdruck:

a.c

unter anderem auf folgende Zeichenketten matchen:

aac
abc
acc

Solange ein a gefolgt von einem beliebigen Zeichen, wiederum gefolgt von einem c im Text vorkommt, wird dieses gematcht.

Der Zirkumflex markiert den Anfang einer Zeile bzw. eines Textes. Damit würde der reguläre Ausdruck:

^Lorem Ipsum

in einem gegebenen Beispieltext:

Lorem Ipsum
	
abcdefghijklmnopqrstuvwxyz ABCDEFGHIKLMNOPQRSTUWXYZ
	
Lorem Ipsum

das Auftreten von Lorem Ipsum am Anfang des Textes matchen. Die gegenteilige Operation kann mit dem Metazeichen $ erreicht werden:

Lorem Ipsum$

In diesem Fall würde das Lorem Ipsum am Ende des Beispieltextes gefunden werden.

Es ist wichtig zu beachten, dass innerhalb einer benutzerdefinierten Zeichenklasse die meisten Metazeichen, wie der Punkt oder der Stern, ihre spezielle Bedeutung verlieren und als normale Zeichen behandelt werden. Ausnahmen sind der Zirkumflex, wenn er als erstes Zeichen in der Klasse verwendet wird, um die Klasse zu negieren, der Bindestrich, um einen Bereich anzugeben, und der Backslash, um Escape-Sequenzen zu ermöglichen.

Quantifizierer

Quantifizierer (engl. Quantifiers) in regulären Ausdrücken sind spezielle Metazeichen, die angeben, wie oft das vorangehende Element in einem Textmuster vorkommen muss, um eine Übereinstimmung zu erzielen. Sie sind entscheidend, um die Flexibilität der Mustererkennung zu erhöhen.

Gegeben sei folgende Telefonnummer:

0176/04069015

Für diese Nummer könnte ein regulärer Ausdruck erstellt werden:

[0-9][0-9][0-9][0-9]/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]

In dem Beispiel wurde auf Standardzeichenklassen verzichtet. Stattdessen wurden eigene Zeichenklassen definiert. Durch diesen Ausdruck werden die ersten vier Ziffern, dann ein Slash und anschließend die folgenden Ziffern gematcht.

Allerdings ist dieser Ausdruck weder sonderlich elegant, noch deckt er das Problem vollständig ab. Immerhin können Telefonnummern unterschiedlich lang sein und auch der Slash könnte optional sein. Hier kommen Quantifizierer zum Einsatz, von denen es eine Vielzahl mit diversen Anwendungsoptionen gibt.

Der Stern-Quantifizierer definiert, dass das vorangehende Element null- oder mehrmals vorkommt. So würde der Ausdruck:

a*b

unter anderem auf folgende Zeichenketten matchen:

ab
aab
aaab
b

Die Anzahl der a’s vor dem Zeichen b sind hierbei unerheblich.

Das Plus-Quantifizierer definiert, dass das vorangehende Element mindestens einmal vorkommen muss. Damit würde der Ausdruck:

a+b

unter anderem auf folgende Zeichenketten matchen:

ab
aab
aaab

Ein Match auf eine Zeichenkette nur bestehend aus einem b wäre bei diesem Quantifizierer ausgeschlossen.

Zu dieser Gruppe von Quantifizierern gehört auch das Fragezeichen. Bei diesem kann das vorangehende Element null- oder einmal vorkommen. Damit würde der Ausdruck:

a?b

unter anderem auf folgende Zeichenketten matchen:

ab
b

Ein Match auf eine Zeichenkette wie aab wäre bei dieser Variante ausgeschlossen.

Daneben existieren eine Reihe von komplexeren Quantifizierer wie den geschweiften Klammern, mit denen eine bestimmte Anzahl von Wiederholungen definiert werden kann. Der Ausdruck:

a{3}b

wurde spezifizieren, dass drei a’s hintereinander folgend von einem b gesucht werden. Damit würde dieser Ausdruck auf folgende Zeichenkette matchen:

aaab

Eine Erweiterung dieser Variante ist die Nutzung von Bereichen:

a{2,4}b

Hiermit würden alle Zeichenketten matchen bei denen zwischen zwei und vier a’s enthalten sind:

aab
aaab
aaaab

Eine weitere Abwandlung dieser Notationen ist es festzulegen, wie oft ein Element mindestens vorkommen muss:

a{2,}b

In diesem Fall müsste das Zeichen a mindestens zweimal vorkommen.

Mithilfe dieser Quantifizierer könnte der Ausdruck zum Match obiger Telefonnummer nun wesentlich vereinfacht werden:

[0-9]+/?[0-9]+

Damit ist der Ausdruck so definiert, dass eine beliebige Anzahl an Ziffern vorkommen können, gefolgt von einem optionalen Slash, wiederum gefolgt von einer beliebigen Anzahl an Ziffern.

Allerdings zeigt sich hier auch eine der Grenzen regulärer Ausdrücke: die Vielfalt möglicher Telefonnummernformate weltweit. Je nach Land und Region gelten unterschiedliche Regeln für den Aufbau einer Telefonnummer. Ein umfassender regulärer Ausdruck, der all diese Varianten abdeckt, könnte schnell sehr komplex werden und schwer zu warten sein.

In solchen Fällen kann es sinnvoll sein, zusätzliche Validierungslogik zu implementieren und nicht zu versuchen, die komplette Logik über einen regulären Ausdruck zu implementieren.

Gierige und faule Quantifizierer

Bei den beschriebenen Quantifizierern existieren gierige (engl. greedy) und faule (engl. lazy) Varianten. Gierige Quantifizierer versuchen, so viel wie möglich von der Zeichenkette zu erfassen, während sie den Regeln des regulären Ausdrucks folgen. Damit greifen sie den längstmöglichen Teil der Zeichenkette, der mit dem Muster übereinstimmt.

Faulen Quantifizierern hingegen geht es darum, so wenig wie möglich zu erfassen, während sie immer noch eine Übereinstimmung finden. Damit greifen sie den kürzestmöglichen Teil der Zeichenkette, der mit dem Muster übereinstimmt.

Quantifizierer sind im Normalfall gierig. Der Ausdruck:

a.*b

würde hiermit die Zeichenfolge:

ababab

als Ganzes matchen. Um diesen Ausdruck in der Lazy-Konfiguration zu betreiben, muss ein Fragezeichen nachgestellt werden:

a.*?b

In diesem Fall würden die ab-Blöcke jeweils einzeln gematcht werden.

Neben diesen beiden Varianten existieren noch possessive Quantifizierer. Solche Quantifizierer verhalten sich wie gierige Quantifizierer, aber sie geben einmal erfasste Zeichen nicht mehr frei.

Das kann das Matching verhindern, wenn der Rest des Musters nicht mehr passt. Possessive Quantifizierer können in bestimmten Situationen die Effizienz der Auswertung verbessern, da sie das aufwendige Backtracking unterbinden, können aber auch zu nicht intuitiven Ergebnissen führen, wenn sie nicht mit Bedacht eingesetzt werden.

Gruppierungen

Eine weitere Möglichkeit bei der Entwicklung regulärer Ausdrücke sind Gruppierungen. So würde der Ausdruck:

abc

nur auf die Zeichenkette abc matchen. Sollen hier jetzt auch Zeichenketten wie abcabc gematcht werden, können Klammern zur Gruppierung im Zusammenhang mit einem Quantifizierer genutzt werden:

(abc)+

Damit würden unter anderem folgende Zeichenketten auf den Ausdruck passen:

abc
abcabc
abcabcabc

Eine weitere Art von Gruppierung ist die Bildung von sogenannten Erfassungsgruppen. Mit diesen Gruppen, welche einen Teil des Ausdrucks ausmachen, kann später weitergearbeitet werden, z. B. in Form von Rückreferenzen:

(abc).*\1

In diesem Beispiel wird die Zeichenkette abc gesucht, welcher beliebige Zeichen folgen, bis schlussendlich wieder abc folgt. Dies wird über den Rückverweis auf die erste Erfassungsgruppe (\1) gelöst. Damit würde dieser Ausdruck unter anderem auf folgende Zeichenketten matchen:

abcabc
abcloremabc
abcipsumabc

Eine weitere Möglichkeit zur Nutzung von Gruppierung sind Oder-Verknüpfungen in einer Gruppe:

(a|b)c

Mit einer solchen Verknüpfung würden Zeichenketten wie ac und bc gematcht, allerdings nicht die Zeichenkette abc.

Daneben existieren Varianten wie verschachtelte Gruppen, benannte Gruppen oder nicht erfassende Gruppen, welche hier nicht weiter im Detail behandelt werden sollen.

Lookaround-Assertions

Eine weitere Möglichkeit zur Verfeinerung von regulären Ausdrücken sind Lookahead– und Lookbehind-Assertions. Im Deutschen könnte man diese Begrifflichkeiten mit vorwärts bzw. rückwärtsgerichtete Bedingungen grob übersetzen. Mit diesen kann die Umgebung eines Matches definiert werden.

So würde der Ausdruck:

Redaktion(?=skonferenz)

in einem Text auf das Wort Redaktion matchen, wenn es in der Zeichenkette Redaktionskonferenz enthalten wäre. Würde das Wort Redaktion alleine im Text stehen, so würde hier kein Match stattfinden. Bei dieser Variante handelt es sich um einen positiven Lookahead, da überprüft wird, ob das Muster nach dem Match vorkommt.

Das Gegenteil ist ein negativer Lookahead:

Redaktion(?!skonferenz)

Bei diesem würde auf das einzelne Wort Redaktion gematcht werden, auf das Wort Redaktion in der Zeichenkette Redaktionskonferenz allerdings nicht.

Neben den Lookahead-Assertions existieren analog dazu Lookbehind-Assertions welche prüfen, ob das Muster vor dem Match vorhanden ist:

(?<=Schluss)redaktion
(?<!Schluss)redaktion

Ein komplexeres Beispiel für die Anwendung von Lookarounds könnte die Suche nach verschiedenen Schreibweisen des Wortes Hauptstraße sein. Ein regulärer Ausdruck, der Hauptstr., Hauptstraße und Hauptstrasse matcht, könnte wie folgt aussehen:

Hauptstr(aße|asse|\.)(?=\s|\b)

Hierbei sorgt die Lookahead-Assertion (?=\s|\b) dafür, dass das Match nur dann stattfindet, wenn das gesuchte Wort gefolgt von einem Whitespace (\s) oder einem Wortgrenzenzeichen (\b) steht. Diese Vorgehensweise verhindert, dass unerwünschte Matches wie Hauptstrasseneinmündung entstehen.

Flags

Neben den eigentlichen regulären Ausdrücken existieren eine Reihe von Optionen, welche das Verhalten der Engine zur Auswertung der regulären Ausdrücke anpassen.

So existiert mit der Global-Option (g) die Möglichkeit die Suche im gesamten Text durchzuführen und nicht nur bis zur ersten Übereinstimmung. Mit der Option i ignoriert die Engine die Groß- und Kleinschreibung im Rahmen der Mustererkennung.

Die Option Multiline (m) verändert das Verhalten für die Zeichen ^ und $, sodass sie nicht mehr nur den Start und das Ende des gesamten Textes markieren, sondern den Anfang und das Ende jeder einzelnen Zeile analysieren. Über die Singleline– bzw. Dotall-Direktive (s) kann der Punkt als Wildcard-Zeichen auch über die Grenzen der Zeilenumbrüche hinweg arbeiten.

Mit der Unicode-Option (u) kann bei einigen Engines die Auswertung von Unicode-Zeichen aktiviert werden.

Gesetzt werden können diese Flags als Teil des regulären Ausdrucks, so z. B. für das Global-Flag in Verbindung mit der Ignorierung der Groß- und Kleinschreibung:

(abc)/gi

Je nach verwendeter Engine werden diese Flags auch anders gesetzt, z. B. in Java beim Anlegen eines Pattern:

Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(text);

Daneben existieren in einigen Engines bestimmte Flags nicht, z. B. da Java bereits so konzipiert ist, dass die Engine alle Übereinstimmungen in einem gegebenen Text finden kann. Damit entspricht dieses Verhalten dem Global-Flag.

Unterschiede

Obwohl die grundlegenden Konzepte von regulären Ausdrücken über verschiedene Sprachen hinweg ähnlich sind, gibt es Unterschiede in der Syntax und Funktionalität, die Entwickler beachten müssen.

Einige Engines bieten erweiterte Funktionen wie Lookahead– und Lookbehind-Assertions, andere unterstützen benannte Gruppen. Die Unterstützung für Unicode ist ebenfalls ein Unterscheidungsmerkmal, da manche Engines in der Lage sind, mit einer Vielzahl von Zeichensätzen und Sprachen umzugehen, während andere auf ASCII beschränkt sind.

Trotz der Vielfalt unter den Engines haben sich viele Gemeinsamkeiten herausgebildet. Dazu gehören Metazeichen wie den Punkt für jedes Zeichen, den Stern für null oder mehr Wiederholungen, das Plus für eine oder mehr Wiederholungen und das Fragezeichen für null oder eine Wiederholung. Ebenso sind Zeichenklassen, Negationen, Anker für Anfang und Ende einer Zeichenkette, und einfache Quantifizierer weitgehend identisch.

Reguläre Ausdrücke in der Entwicklung

Bei der Anwendung regulärer Ausdrücke in der Entwicklung sind die jeweiligen Gegebenheiten der Programmiersprachen zu berücksichtigen. Ein Beispiel hierfür ist die Java-Methode zum Matchen in der Stringklasse:

String text = "3";
boolean matches = text.matches("[123]");

Hier wird die matches-Methode genutzt, um zu überprüfen, ob der gesamte String text dem regulären Ausdruck entspricht. Diese Herangehensweise ist für einmalige Überprüfungen einfach und direkt. Allerdings ist es ineffizient, wenn der gleiche Ausdruck in einer Schleife oder mehrfach im Code verwendet wird, da bei jedem Aufruf der Ausdruck neu kompiliert wird.

Alternativ kann diese Operation anders implementiert werden:

Pattern pattern = Pattern.compile("[123]");
boolean matches = pattern.matcher(text).matches();

Diese Variante ist effizienter, wenn der gleiche Ausdruck mehrfach genutzt werden soll. Hier wird das Pattern einmal kompiliert und kann anschließend mehrfach verwendet werden, ohne dass es jedes Mal neu kompiliert werden muss.

Reguläre Ausdrücke in Anwendungen

Neben der direkten Nutzung regulärer Ausdrücke in der Entwicklung, existieren unzählige Tools, wie Texteditoren oder Kommandozeilenwerkzeuge wie grep, welche ebenfalls Unterstützung für reguläre Ausdrücke liefern.

Für grep könnte dies wie folgt aussehen:

grep -E "G{1}N" gpl3.txt

Auch Texteditioren und IDEs enthalten Suchmethodiken, um über reguläre Ausdrücke zu finden.

Die Suche mittels regulärer Ausdrücke in IntelliJ IDEA

Damit ist es möglich, Textstellen zu finden, die komplexeren Mustern entsprechen und mit einer einfachen Suche nicht ohne weiteres gefunden werden können.

Nutzung

Reguläre Ausdrücke sind ein mächtiges Werkzeug, wenn es darum geht, Benutzereingaben zu validieren. Sie werden häufig verwendet, um sicherzustellen, dass E-Mail-Adressen, Telefonnummern und andere Formen von Daten bestimmten Mustern entsprechen.

So könnte mit einem solchen Ausdruck z. B. die Validität einer E-Mail-Adresse überprüft werden:

[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?

Daneben können sie beim Parsen von Zeichenketten genutzt werden, z. B. um spezifische Informationen aus einem Text wie einer Logdatei herauszufiltern.

In der Compiler-Konstruktion werden reguläre Ausdrücke verwendet, um Tokens zu identifizieren, indem sie Muster definieren, die Schlüsselwörter, Operatoren oder andere syntaktische Elemente erkennen. Auch bei der Textbearbeitung, wie dem massenhaften Ersetzen, können reguläre Ausdrücke ihre Stärke ausspielen.

Trotzdem sollte nicht vergessen werden, dass sich reguläre Ausdrücke nicht für alle Zwecke eignen. So sollte z. B. davon abgesehen werden, nicht formale Sprachen mit regulären Ausdrücken zu parsen. Für das Parsen von HTML mittels regulärer Ausdrücke existiert dazu ein geradezu legendärer Post bei Stack Overflow, der sich dieses Problems annimmt.

Stack Overflow beantwortet die Frage nach dem Sinn von regulären Ausdrücken in Verbindung mit HTML

Best Practices

Werden reguläre Ausdrücke genutzt, so sollten einige Best Practices berücksichtigt werden. Bei unsachgemäßer Verwendung kann ein regulärer Ausdruck schnell unübersichtlich und schwer wartbar werden kann.

So sollten genutzte reguläre Ausdrücke kommentiert werden und ihre Fachlichkeit darlegen, um das Verständnis zu erhöhen.

Anstelle von nummerierten Rückreferenzen lassen sich, wenn unterstützt, benannte Gruppen nutzen. Einige Engines erlauben auch die Verwendung von Leerzeichen und Zeilenumbrüchen in den Ausdrücken, was hilft, sie besser zu strukturieren. Zudem kann es vorteilhaft sein, lange und komplexe Muster in kleinere, wiederverwendbare Teile zu zerlegen, was die Wartung und das Verständnis des Ausdrucks erleichtert.

Es ist ratsam, in regulären Ausdrücken so spezifisch wie möglich zu sein. Anstatt generische Zeichen wie den Punkt, der jedes Zeichen repräsentieren kann, zu verwenden, sollten präzisere Zeichen oder Zeichenklassen gewählt werden. Dies hilft, unnötiges Backtracking zu vermeiden, das die Performance beeinträchtigen kann.

Obwohl Lookaround-Assertions in bestimmten Situationen sehr nützlich sein können, sollten sie mit Bedacht eingesetzt werden, da sie bei komplexen Mustern und großen Textmengen die Performance negativ beeinflussen können. Auch bei der Verwendung von Gruppierungen gilt gleiches, denn jede zusätzliche Gruppierung bedeutet mehr Aufwand bei der Verarbeitung des Ausdrucks.

Schließlich ist es unerlässlich, dass reguläre Ausdrücke gründlich getestet werden. Idealerweise sollte dies mit einer breiten Palette von Testfällen geschehen, um sicherzustellen, dass sie in jeder erwarteten Situation korrekt funktionieren.

Unter der Haube

In der alltäglichen Anwendung werden regulären Ausdrücke von einer Engine ausgeführt. Ein kurzer Blick in eine solche Engine, kann sich lohnen, da es hilft zu verstehen, wie reguläre Ausdrücke sinnvoll gestaltet werden können. Eine Engine, die diese Ausdrücke verarbeitet, kann entweder textorientiert oder regex-orientiert sein.

Eine textorientierte Engine, implementiert als deterministischer endlicher Automat (DFA), analysiert den Eingabetext sequenziell. Diese Methode ist schnell und effizient, da sie keine alternativen Pfade verfolgt und somit kein Backtracking benötigt. DFAs liefern stets die längste Übereinstimmung und sind aufgrund ihrer deterministischen Natur in der Performance vorhersehbar.

Im Gegensatz dazu steht die regex-orientierte Engine, die auf einem nichtdeterministischen endlichen Automaten (NFA) basiert. Eine solche Engine ist in der Lage, mehrere Pfade gleichzeitig zu verfolgen und bei Bedarf mittels Backtracking alternative Wege zu untersuchen. Dies ermöglicht eine flexible Mustererkennung, kann jedoch bei komplexen Ausdrücken zu einer erhöhten Rechenlast führen. NFAs priorisieren die am weitesten links stehende Übereinstimmung und können bei mehreren möglichen Matches zu einer kürzeren Übereinstimmung führen, selbst wenn weiter rechts im Text eine längere vorhanden wäre.

Moderne Regex-Engines sind meist regex-orientiert und nutzen einen Preprozessor, um den regulären Ausdruck vorzuverarbeiten, etwa um Makros in Zeichenklassen umzuwandeln. Anschließend wird der Ausdruck kompiliert, wobei er in eine effiziente Form überführt wird, die entweder als Reihe von Instruktionen oder als Zustandsautomat von der Engine verarbeitet werden kann.

Die Wahl der Engine hängt von den spezifischen Anforderungen der Aufgabe ab. Während DFAs für einfache, vorhersehbare Suchvorgänge geeignet sind, bieten NFAs die notwendige Flexibilität für komplexere Mustererkennungen.

Risiken und Nebenwirkungen

Neben der Möglichkeit reguläre Ausdrücke für Aufgaben zu nutzen, für die sie nicht geeignet sind, existieren auch andere Probleme, die mit diesen Ausdrücken zusammenhängen.

So gibt es den Regular expression Denial of Service
-Angriff (ReDoS), welcher ausnutzt, dass viele Engines für reguläre Ausdrücke bei bestimmten Ausdrücken extrem langsam werden und viele Systemressourcen beanspruchen können.

Ein schönes Beispiel für eine solche Anfälligkeit, war der Ausfall von Stack Overflow im Jahre 2016. Dieser wurde durch einen Post mit zu vielen Leerzeichen verursacht, welcher dazu führte, dass die auf Backtracking basierte Engine über 199 Millionen Überprüfungen durchführen musste.

Allerdings gibt es Alternativen zu Backtracking nutzenden Engines, wie RE2 von Google. Diese Engine garantiert, basierend auf einem endlichen Automaten, eine lineare Ausführungszeit, bezogen auf die Eingabedaten und ist trotzdem mit den Features moderner Engines ausgestattet.

Werkzeuge

Für die Nutzung und Erstellung von regulären Ausdrücken existieren hilfreiche Werkzeugen. Zu diesen Werkzeugen gehören eine Reihe von Online-Testern. Dies sind interaktive Werkzeuge, die es dem Nutzer ermöglicht, regulären Ausdrücke in Echtzeit zu testen und zu debuggen. Diese Werkzeuge bieten oft farblich hervorgehobene Übereinstimmungen und Erklärungen für jedes Element des Ausdrucks.

Einer dieser Tester ist RegExr, welcher unter regexr.com zu finden ist.

Mit RegExr können reguläre Ausdrücke schnell ausprobiert werden

Neben der interaktiven Oberfläche bietet RegExr eine Referenz und eine große Anzahl von Community Patterns, die viele Probleme bereits abdecken und so die Entwicklung eines eigenen Ausdrucks beschleunigen können.

Ein weiterer Tester für reguläre Ausdrücke ist regular expressions 101, welcher unter regex101.com zu finden ist.

regular expressions 101 verfügt über einen Debugger

Eine Besonderheit dieses Dienstes ist der integrierte Debugger, mit welchem regulären Ausdrücke analysiert werden können. Einen umgekehrten Weg geht der Regex Generator von Olaf Neumann.

Der Regex Generator von Olaf Neumann

Mithilfe dieses Werkzeuges können reguläre Ausdrücke anhand eines Datenbeispieles erzeugt werden. Kommandozeilenwerkzeuge wie rgxg arbeiten nach ähnlichen Prinzipien und können auch offline genutzt werden.

Fazit

Insgesamt sind reguläre Ausdrücke ein mächtiges Werkzeug, um Textmuster zu durchsuchen und zu manipulieren. Sie ermöglichen eine effiziente Verarbeitung von Texten und sind daher ein wichtiges Werkzeug für Entwickler und Anwender. In der Praxis werden sie in verschiedenen Bereichen eingesetzt, um Texte zu durchsuchen, zu filtern und zu manipulieren.

Durch die Einhaltung von Best Practices und dem damit verbundenen Vermeiden häufiger Fehler können Entwickler sicherstellen, dass ihre regulären Ausdrücke sowohl leistungsfähig als auch wartbar sind.

Dieser Artikel erschien ursprünglich auf Golem.de und ist hier in einer alternativen Variante zu finden.

Alle Schriftfamilien des Noto-Fonts in einem Paket

Mit Noto existiert ein Font, mit welchem perspektivisch alle Unicode-Zeichen abgebildet werden sollen. Lizenziert ist dieser unter SIL Open Font License und damit frei verfügbar. Leider gibt es keine einfache Möglichkeit alle verfügbaren Noto-Schriftfamilien am Stück herunterzuladen.

fonts.google.com/noto

Aus diesem Grund habe ich ein Paket erstellt, in welchem alle 194 Schriftfamilien des Noto-Fonts enthalten sind. In manchen Paketen der Noto-Schriftfamilien sind statische und variable Font-Dateien enthalten. In einem solchen Fall wurden die statischen Daten in das Paket gepackt. Der Download des Paketes ist entsprechend verlinkt. Einzelne Schriftfamilie können direkt über die offizielle Noto-Webseite heruntergeladen werden.

w.org im Seitenquelltext unter WordPress

Auf einer Webseite, welche ich betreibe, fand sich folgende Abhängigkeit im Quelltext der Seite:

<link rel='dns-prefetch' href='//s.w.org' />

Hier wurde ein DNS Prefetch durchgeführt, das bedeutet der Browser angewiesen wird, besagte Domain bereits per DNS aufzulösen, bevor sich eigentlich benötigt wird. Grundsätzlich achte ich darauf das meine Webseiten ohne externe Abhängigkeiten auskommen. Eine Ausnahme ist z.B. der Zählschnipsel der VG Wort, welcher für die Abrechnung entsprechender Texte benötigt wird.

Bei der Ursachenforschung stellte ich fest das WordPress hier Emojis nachlädt, obwohl dies in den Einstellungen in der Sektion Schreiben deaktiviert wurde.

In den Einstellungen kann die automatische Umwandlung von Emojis abgeschaltet werden

Ursächlich für das Problem war ein Unicode-Zeichen, welches in einem Widget der Seite genutzt wurde und dazu führte, dass das entsprechende Zeichen extern als SVG-Datei geladen wird. Die einfachste Lösung ist es das entsprechende Zeichen zu entfernen, allerdings kann dieses Verhalten auch generell abgeschaltet werden. Dazu sind in der Datei functions.php des benutzen Themes folgende Zeilen hinzuzufügen:

add_filter( 'emoji_svg_url', '__return_false' );
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'wp_print_styles', 'print_emoji_styles' );

Damit ist die automatische Umwandlung von Emojis abgeschaltet und auch der entsprechende DNS Prefetch sollte nicht mehr im Quelltext auftauchen.

EPC-QR-Code mittels qrencode erstellen

Vor etlichen Jahren hatte ich mit qrencode ein Werkzeug vorgestellt, um QR-Codes auf der Kommandozeile zu generieren. Neben normalen QR-Code können mit dem Werkzeug auch andere Codes wie EPC-QR-Codes erstellt werden. Dazu muss im ersten Schritt eine Textdatei erzeugt und entsprechend befüllt werden:

BCD
001
1
SCT
BIC12345678
Ada Lovelace
DE07123412341234123412
EUR3.14


Verwendungszweck

Der Wert in der ersten Zeile ist der Service Tag, welcher immer BCD ist und in der nächsten Zeile von einer Versionsnummer ergänzt wird. Anschließend folgt die Zeichenkodierung, in diesem Fall ist es UTF-8. Daraufhin folgt die Identifikation für den SEPA Credit Transfer, anschließend die BIC und der Name des Zahlungsempfängers. Mit dem Zahlungsbeitrag folgen die optionalen Werte, welche bei Bedarf ausgelassen werden dürfen, indem eine Leerzeile genutzt wird.

Der generierte EPC-QR-Code

Anschließend kann aus der Datei der entsprechende QR-Code erstellt werden:

cat epc.txt | qrencode -o epc.png -l M -s 24

Gedacht sind diese Codes, um Überweisung auf mobilen Endgeräten schnell vorzunehmen, indem die entsprechenden Daten über den QR-Code eingelesen werden.

Von Codierungen und Vereinheitlichung

Unicode erblickte vor knapp dreißig Jahren das Licht der Welt und brachte ein wenig Ordnung in die babylonische Vielfalt der Kodierungsstandards. Auch wenn er oft genutzt wird, gibt es doch viel Halbwissen rund um diesen Standard.

Wenn eine Datei mit einem Text eingelesen wird, so besteht diese für den Computer nur aus einer Abfolge von Daten, ohne eine wirkliche Bedeutung. Dass wir am Ende in dieser Datei Eine Geschichte zweier Städte von Charles Dickens finden, ist dem Umstand geschuldet, dass sich auf die Codierung dieser Daten geeinigt wurde.

Für die Codierung von Text wurden viele Codierungen erdacht und in der Praxis eingesetzt, vom Morsecode über den Baudot-Murray-Code, welcher als 5-Bit-Code die Tastenstellung eines Telegrafengerät kodierte, bis zum American Standard Code for Information Interchange, kurz ASCII, der für viele Jahre das Fundament der Textkodierung in der IT darstellte.

Eine Telegraphen-Tastatur

Mit Unicode, welcher vor etwa 30 Jahren das Licht der Welt erblickte, wurde ein wenig Ordnung in die babylonische Vielfalt der Codierungsstandards gebracht, was wegen der Internationalisierung der IT dringend nötig war. Und damit ist dieser Standard aus der Vergangenheit gleichzeitig die Zukunft.

ASCII und EBCDIC

Wie vieles in der IT, baut auch Unicode auf einem historischen Erbe auf, in diesem Fall dem ASCII-Standard. In diesem 1963 verabschiedeten Standard waren in einer 7-Bit-Codierung 128 Zeichen kodiert. Neben dem lateinischen Alphabet, also den Zeichen von A bis Z, jeweils in Groß- und Kleinschreibung, den Ziffern und einigen Sonderzeichen, befanden sich in diesem auch etliche Steuerzeichen.

Zu diesen Steuerzeichen gehören bekannte Zeichen wie der Tabulator oder der Zeilenvorschub, als auch weniger bekannte Zeichen wie die Glocke (Bell). Genutzt wurden diese Zeichen zur Steuerung der Geräte, welche mit dem ASCII-Code umgehen sollten.

Technisch betrachtet wurden im ASCII-Code eigentlich nur 126 Zeichen kodiert. Der Hintergrund hierfür ist historisch begründet. Das Zeichen 0 (kodiert als Bits 0000000) wird genutzt, um die Nichtexistenz von Daten anzuzeigen. Im Kontext einer Lochkarte werden keine Löcher im entsprechenden Bereich gestanzt.

Das Gegenteil hiervon ist das Zeichen 127 (1111111), welches festlegt, dass die Daten auf diesem Feld als gelöscht gelten; auch hier wieder der Lochkarten-Hintergrund, bei welchem im Fehlerfalle bestehende Löcher nicht mehr geschlossen werden können bzw. sollten und somit alle Löcher gelocht werden, um ein gelöschtes Datum zu symbolisieren.

Eine ASCII-Tabelle in einem Drucker von General Electric aus den 70er Jahren

Da sie in dieser Form nicht mehr benötigt wurden, wurden diese Zeichen teilweise anders genutzt. In vielen Programmiersprachen stellt das Zeichen 0 das Ende einer Zeichenkette dar. Somit können in diesen Sprachen, wie z. B. C, Zeichenketten abgespeichert werden, ohne dass die entsprechende Länge bekannt sein muss, im Gegensatz zu Sprachen wie z. B. Pascal.

In seiner heutigen Form wurde der ASCII-Code nach einigen kleineren Änderungen schließlich 1968 verabschiedet. Neben dem ASCII-Code wurde von IBM der Extended Binary Coded Decimal Interchange Code (EBCDIC) entwickelt, welcher vorwiegend auf Großrechnern genutzt wurde. Diese Entwicklung fand zwischen 1963 und 1964 statt und wurde mit dem System/360 der Öffentlichkeit vorgestellt.

Im Westen nichts Neues

Aus Sicht der westlichen Welt war der ASCII-Code für viele Dinge gut genug. So können Umlaute im Deutschen leicht ersetzt werden, z. B. das Zeichen ä durch ae. Allerdings wurde dies nicht immer gemacht und war auch nicht immer gewünscht.

Um andere Zeichen wie Umlaute oder Akzentzeichen abzubilden, entstanden 8-Bit Codes basierend auf dem ASCII-Code, welche somit die doppelte Zeichenanzahl unterbringen konnten.

In Westeuropa hat sich hierbei insbesondere der Standard ISO/IEC 8859-1 etabliert, besser bekannt unter dem Namen Latin-1. Ziel dieses Standards war es, möglichst viele Zeichen westeuropäischer Sprachen abzubilden. Unter Windows wurde eine abgewandelte Form dieses Zeichensatzes unter dem Namen Windows-1252 genutzt.

Durch die ISO standardisiert sind daneben Zeichensätze für weitere europäische Regionen wie Griechisch, aber auch Kyrillisch, Arabisch, Thai und Hebräisch.

Daneben existieren andere 8-Bit-Codierungen, welche teilweise nur mit ihren jeweiligen Anwendungen bzw. Betriebssystemen kompatibel waren und nicht standardisiert sind. Ein Beispiel für eine solche Codierungen ist CBM-ASCII, in welcher unter anderem Zeichen für Blockgrafik enthalten waren und welche bei vielen Heimcomputern von Commodore zum Einsatz kam.

Zeichensalat

Die Nutzung unterschiedlicher Codierungen führt aber auch zu Problemen. Wird ein Dokument z. B. mit der falschen Codierung geöffnet, so erhält der Nutzer in solchen Fällen sogenannten Zeichensalat:

Falsches Üben von Xylophonmusik quält jeden größeren Zwerg.

Auch Texte, in denen mehrere Sprachen vorhanden sind, ließen sich mit diesen bestehenden Codierungen nicht einfach umsetzen.

Noch ein Standard?

Mit der Globalisierung und dem Austausch von Dokumenten über Sprach- und Systemgrenzen hinweg entwickelte sich die Vielfalt an Codierungen zu einem Problem.

Die ISO/IEC 2022 versuchte dieses Problem zu lösen. Bei diesem Standard kann unter Zuhilfenahme verschiedener Escape-Sequenzen zwischen den unterschiedlichen Zeichensätzen umgeschaltet werden. Durchgesetzt hat sich dieser Standard nur in Japan, Korea und China, in erster Linie im Kontext E-Mail; da der Standard auch entsprechende 7-Bit-Codierungen definiert.

Schlussendlich kam es zur Entwicklung von Unicode. Wie so viele Entwicklungen, die die IT weiter brachten, hatten sie etwas mit Xerox zu tun. Vom PARC Universal Packet, welches maßgeblich das Design von TCP/IP beeinflusste, bis zum WIMP-Paradigma (Windows, Icons, Menus, Pointer), dem wir unsere modernen Desktop-Oberflächen und Interface-Konzepte verdanken.

Dort entstand der Xerox Character Code Standard, welcher als inoffizieller Vorgänger des Unicode-Standards betrachtet werden kann. Joseph D. Becker, welcher sich bei Xerox schon länger mit multilingualer Software befasste und unter anderem 1984 das Paper Multilingual Word Processing dazu verfasste, ist einer der Erfinder und Gestalter von Unicode.

Die eigentliche Entwicklung von Unicode begann 1987. Neben Joseph D. Becker arbeiteten Lee Collins und Mark Davis, damals bei Apple angestellt, ebenfalls an dem neuen Standard, welcher ein universelles Set von Zeichen darstellen sollte, in welchem die aktuellen Schriftsysteme der Zeit enthalten sein sollten. In der damaligen Entwurfsphase war es noch nicht das erklärte Ziel historische Schriftsysteme abzubilden:

Unicode gives higher priority to ensuring utility for the future than to preserving past antiquities. Unicode aims in the first instance at the characters published in modern text (e.g. in the union of all newspapers and magazines printed in the world in 1988), whose number is undoubtedly far below 2^14 = 16,384. Beyond those modern-use characters, all others may be defined to be obsolete or rare; these are better candidates for private-use registration than for congesting the public list of generally-useful Unicodes.

Im Laufe des Jahres 1989 stießen zur Gruppe um Becker, Mitarbeiter von Metaphor, RLG und Sun Microsystems hinzu. 1990 wurde das Team um Mitstreiter von Microsoft und NEXT erweitert.

Ende desselben Jahres war der Standard dann so weit gediehen, dass am 3. Januar 1991 das Unicode Consortium gegründet wurde. Dieses setzte einige Monate später im Oktober des Jahres den ersten Unicode-Standard in die Welt.

Allerdings wurde dies nicht überall so verstanden. In der TrueType Spezifikation in Version 1.0 für das entsprechende Font-Format erhielt der Standard die Plattform-ID Apple Unicode, was allerdings ein Irrtum war, welcher mittlerweile korrigiert wurde.

Beim Unicode Consortium selbst handelt es sich um eine gemeinnützige Organisation, mit Sitz in Mountain View in Kalifornien. Sie sorgt für die Weiterentwicklung des Standards und die Aufnahme weiterer Zeichen. Zu den Mitgliedern gehören unter anderem Adobe, Apple, Google und Netflix, aber auch Institutionen wie das Bangladesh Computer Council.

Aufbau

Grundsätzlich definiert der Unicode-Standard einen sogenannten Codespace. Jedem Zeichen wird eine Nummer innerhalb dieses Codespace zugewiesen. Ein vergebener Wert für ein Zeichen innerhalb dieses Codespace wird als Code Point bezeichnet und stellt die Grundlage von Unicode dar. So entspricht z. B. der Code Point 2126 dem Omega-Zeichen (Ω). Der Umfang dieses Codespace erstreckt sich von 0 bis 10FFFF.

Genau betrachtet stellt ein Code Point aber nicht unbedingt ein Zeichen dar, da es Zeichen gibt, welche sich aus mehreren Code Points zusammensetzen. So könnte ein Ä als A mit einem Trema (die Punkte über dem Ä), also mit zwei Code Points, dargestellt werden.

Auch werden keine Repräsentationen der Zeichen durch den Unicode-Standard vorgegeben. Stattdessen handelt es sich um abstrakte Zeichen, welche definiert werden. Ihre jeweilige Ausgestaltung ist z. B. entsprechenden Fonts vorbehalten.

Allerdings ist der Adressraum von Unicode nicht flach, sondern in sogenannte Planes unterteilt. Eine Plane entspricht hierbei 2^16, also 65.536 Code Points. Insgesamt sind 17 Planes (Plane 0 bis 16) im Standard definiert. Damit ist das aktuelle Limit an Zeichen in Unicode auf 1.114.112 festgelegt. Nach dem Abzug von Regionen für die private Nutzung bleiben am Ende in etwa 970.000 Code Points für die öffentliche Nutzung übrig.

Die Planes sind nach bestimmten Kriterien unterteilt und definiert. Aktuell genutzt respektive definiert sind sieben dieser Planes, wobei zwei davon für die private Nutzung definiert sind und somit im Unicode-Standard keinerlei Zeichen zugewiesen bekommen.

Die wichtigste und zuerst definierte Plane ist die Basic Multilingual Plane (BMP) mit der ID 0. In dieser sind die Zeichen für die meisten aktuell genutzten Sprachen, definiert. Grundsätzlich sollten in dieser Plane alle Zeichen definiert werden, welche in modernen Schriftsystemen rund um die Welt Verwendung finden. Hier finden sich neben Symbolen und lateinischen Buchstaben hauptsächlich die Zeichen aus der chinesischen, japanischen und koreanischen Sprache.

Die Basic Multilingual Plane

Innerhalb der Planes werden die Code Points in sogenannten Blöcken gruppiert. So existieren in der BMP 164 solcher Blöcke. In diesen Blöcken werden Schriftzeichen thematisch gruppiert, so z. B. der Block für lateinische Buchstaben, für Thai, für mathematische Operatoren oder geometrische Formen. Die Größe eines Blockes hängt von der Anzahl der zu kodierenden Code Points ab, ist aber immer ein Vielfaches von 16.

Neben dieser Plane existieren noch die Supplementary Multilingual Plane mit der ID 1, die Supplementary Ideographic Plane mit der ID 2, die Tertiary Ideographic Plane mit der ID 3 und die Supplementary Special-purpose Plane mit der ID 14.

In der Supplementary Multilingual Plane finden sich weitere Schriftsysteme, auch historischer Natur, wie ägyptische Hieroglyphen oder Zeichen zur Notation von Musik. Auf der Supplementary Ideographic Plane finden sich weitere sogenannte CJK-Zeichen also Schriftzeichen aus dem Chinesischen, Japanischen, Koreanischen und Vietnamesischen. Neben einigem historischen Schriftsystemen wie dem Oracle bone script, einem Vorgänger der chinesischen Schrift, welcher auf Orakelknochen gefunden wurde, ist die Tertiary Ideographic Plane größtenteils leer. Die Supplementary Special-purpose Plane wird für bestimmte Spezialzeichen genutzt, die z. B. im Zusammenhang mit Emojis zum Tragen kommen.

Die Planes 4 bis 13 sind im Moment nicht belegt und somit für zukünftige Erweiterungen verfügbar. Die Planes 15 und 16 sind sogenannte Private Use Area Planes, mit welchen Zeichen kodiert werden können, welche nicht im Unicode-Standard definiert sind. Hierbei müssen sich die Anwendungen über die Bedeutung der einzelnen Zeichen bewusst sein, um diese korrekt darstellen zu können.

Teilweise sind die Zeichen innerhalb einer Plane und entsprechender Blöcke fragmentiert, da Blöcke bereits komplett belegt waren und Zeichen erst später dazukamen.

Altlasten

Neben besagter Fragmentierung befinden sich auch historische Altlasten im Unicode.

Damit bestehende Zeichensätze wie die Latin-Familie, aber auch asiatische Systeme, sinnvoll übernommen werden und einfach konvertiert werden konnten, wurden diese als Ganzes in den Standard übernommen. So entsprechen die ersten 256 Zeichen im Unicode-Standard exakt dem Standard ISO/IEC 8859-1, besser bekannt als Latin-1.

Dies führt stellenweise zu doppelten Zeichen im Unicode-Standard und auch Zeichen wie dem Ä, welches eigentlich ein zusammengesetztes Zeichen ist und als solches nach den heutigen Kriterien wohl nicht im Standard aufgenommen würde. Die meisten dieser Zeichensätze sind in der Basic Multilingual Plane gelandet.

Im Standard selbst mag dies zu einigen Problemen geführt haben und wie ein Makel wirken, aber es war eine pragmatische Entscheidung, welche die Umstellung auf Unicode vereinfachen sollte.

Im Reich der Codierungen

Um ein Unicode-Zeichen zu codieren, existieren unterschiedliche Unicode-Codierungen, sogenannte Unicode Transformation Formats (UTF). Diese unterscheiden sich in grob in Codierungen mit fester Länge wie UTF-32 und Codierungen variabler Länge wie UTF-8.

Im Standard existieren drei Geschmacksrichtungen dieser UTFs: UTF-8, UTF-16 und UTF-32. Die Zahl gibt die Minimalbits an, welche zur Codierung eines Zeichens benötigt werden. Auch wenn dies historisch betrachtet nicht immer der Fall war, da es Codierungen wie UTF-1 oder UTF-7 gab.

Im Falle von Buchstaben aus dem ASCII-Zeichensatz benötigt ein Zeichen in der Codierung UTF-8 ein Byte, kann aber bis zu 4 Byte beanspruchen, je nach dem auf welches Unicode-Zeichen verwiesen werden soll. Somit können alle Zeichen des Unicode-Standards entsprechend kodiert werden. Werden nur ASCII-Zeichen genutzt, so ist diese Codierung kompatibel mit ASCII.

Die Geschichte von UTF-8 ist eng mit der des Betriebsystems Plan-9 verbunden, welches in den 80er-Jahren in den Bell Laboratories entwickelt wurde. Die ursprüngliche Umsetzung sah vor 16-Bit breite Zeichen in Plan 9 zu nutzen. Allerdings waren die Entwickler damit unzufrieden. Ein Anruf von IBM, zu einem bevorstehenden Meeting des X/Open Komitees, führte dazu, dass Rob Pike und Ken Thompson an einem Mittwochabend die UTF-8 Codierung entwickelten und sie Plan 9 von Mittwoch zu Freitag auf UTF-8 umstellten. Ein weiterer Anruf beim X/Open Komitee und dem Eingeständnis, dass der Vorschlag von Pike und Thompson wesentlich besser war als der eigene, begann UTF-8 seinen Siegeszug.

Doch wie wird das Ganze technisch gelöst? Da es sich bei ASCII um eine 7-Bit-Codierung handelt, wird das Bit mit dem Index 7 nicht genutzt. Es ist immer Null. So wäre das Zeichen F wie folgt kodiert:

[0 1 0 0 0 1 1 0]
 7 6 5 4 3 2 1 0

Dieser Umstand wird nun für UTF-8 genutzt. Ist das Bit (Index 7) Null, so handelt es sich um ein ASCII-Zeichen, ist es Eins, so liegt eine UTF-8 Codierung vor. Das Zeichen Ä (196) sähe in der UTF-8-Codierung wie folgt aus:

[11 00 00 11] [10 00 01 00]

Die Bitfolge 11 im ersten Byte zeigt dabei an, dass es sich um das Startbyte des Zeichens handelt. Die Anzahl der Einsen am Anfang gibt hierbei an, in wie vielen Bytes das Zeichen kodiert ist. Das Yen-Zeichen ¥ würde in UTF-8 wie folgt kodiert werden:

[11 10 11 11] [10 11 11 11] [10 10 01 01]

Hier zeigt das Startbyte drei Einsen am Anfang. Damit ist klar, dass dieses Zeichen in drei Byte kodiert wird. Die folgenden Bytes sind die sogenannten Folgebytes und sind an der Bitfolge 10 am Anfang zu erkennen.

Nach den aktuellen Unicode-Regeln darf ein UTF-8-Zeichen maximal 4 Byte in Anspruch nehmen, auch wenn die Art der Codierung in der Theorie mehr Bytes nutzen könnte. Zu den jeweiligen Codierungen zählen weiterhin bestimmte Feinheiten, die im Rahmen dieses Artikels nicht im Detail besprochen werden sollen, da sie für das Grundprinzip der Codierung unerheblich sind. So dürfen unter anderem bestimmte Bitfolgen nicht genutzt werden, da sie im Rahmen der UTF-8 Codierung als ungültig angesehen werden. Ein Vorteil dieser Codierung ist, dass immer klar ist, wo ein neues Zeichen beginnt und daher defekte Daten in der UTF-8-Codierung problemlos übersprungen werden können.

Bei UTF-16 wird ein Zeichen durch 16 Bit abgebildet, wenn es sich um ein Zeichen aus der Basic Multilingual Plane handelt. Bei Codierungen außerhalb der BMP werden jeweils zwei 16-Bit-Pärchen genutzt. Dies sollte allerdings nicht mit der UTF-32 Codierung verwechselt werden, da es sich trotzdem um zwei UTF-16 codierte Entitäten handelt.

UTF-32 kodiert jeden Code Point immer in vier Byte und stellt damit die speicherintensivste Codierung dar. Im gewissen Rahmen, mit Ausnahme zusammengesetzter Zeichen, bietet sie einen wahlfreien Zugriff auf die Zeichen einer Zeichenkette an. Damit kann auf Zeichen innerhalb der Zeichenkette zugegriffen werden, indem ihre Position berechnet wird, anstatt die komplette Zeichenkette von Anfang an decodieren zu müssen.

Im Zusammenhang mit den Codierungen fallen gelegentlich auch die Begriffe UCS-2 und UCS-4. Bei UCS-2 handelt es sich um eine obsolete Codierung, welche nur die Code Points der Basic Multilingual Plane codieren konnte. UCS-4 hingegen ist gleichbedeutend mit UTF-32. Die Begrifflichkeit UCS kommt vom Universal Coded Character Set aus der Norm ISO/IEC 10646.

Daneben gibt es Codierungen wie UTF-EBCDIC, welche dafür gedacht waren, Unicode auf entsprechende Mainframe-Rechnern, welche mit der EBCDIC-Codierung arbeiten, zu bringen. In der Praxis wird auf solchen Systemen, wie z/OS, aber meist UTF-16 genutzt.

Der Unterschied zwischen Codierungen fester Breite gegenüber denen variabler Breite liegt im Aufwand, die entsprechende Codierung auszuwerten. Bei den Codierungen variabler Breiten, muss ein entsprechender Rechenaufwand in das Lesen der Codierungen gesteckt werden. Dieser entfällt bei Codierungen fester Breite, allerdings ist hier der Speicherbedarf höher als bei den Codierungen variabler Breite.

Byte Order Mark

Doch wie wird erkannt, in welcher Codierung ein Dokument vorliegt? Dafür existiert das Byte Order Mark, kurz BOM. Dieses steht am Anfang einer Datei und teilt die genutzte Codierung mit.

Für UTF-8 haben viele ein solches BOM sicherlich schon einmal gesehen:



Als hexadezimale Repräsentation sieht dieses dann wie folgt aus:

EF BB BF

Bei den UTF-16 und UTF-32 Codierungen muss die Endianness, also die Reihenfolge der Bytes berücksichtigt werden. Dies wird mit dem Byte Order Mark, wie der Name es andeutet, bewerkstelligt. Ein Beispiel eines UTF-16 BOM:

 FE FF (Big Endian)
 FF FE (Little Endian)

Die Nutzung des Byte Order Mark ist optional. Ist keines gesetzt, so wird automatisch davon ausgegangen, dass die Byte-Reihenfolge Big-Endian ist.

ISO und Unicode

Mit dem Standard ISO/IEC 10646, dem Universal Coded Character Set sind die Zeichen des Unicodes auch in einem ISO-Standard verewigt. Mittlerweile werden der ISO-Standard und Unicode synchronisiert; das bedeutet, sie definieren die gleichen Zeichen.

Allerdings existieren Unterschiede zwischen diesem Standard und Unicode. Während die Namen und Code Points in beiden Standards gleich sind, spezifiziert der Unicode Standard Regeln zur Interoperabilität und liefert weitere Dinge wie entsprechende Algorithmen mit. Im Vergleich dazu stellt der ISO-Standard nur eine Zeichentabelle dar.

Im Laufe der Zeit

Während im Oktober 1991 die Unicode-Version 1.0.0, mit 7129 Code Points, veröffentlicht wurde, ist die aktuelle Version 14.0 im September 2021 erschienen und definiert ein Vielfaches an Code Points im Standard.

Dabei sind von Version zu Version immer wieder neue Zeichen hinzugekommen. Der Unicode-Standard legt fest, dass einmal eingebrachte Zeichen nie wieder aus dem Standard entfernt werden dürfen. Das bedeutet, dass sich gut überlegt werden muss, ob ein Zeichen wirklich dem Standard zugeschlagen wird.

Allerdings ist dies nicht immer so gewesen. In der Frühzeit des Standards bis einschließlich Version 2.0, welche im Juli 1996 erschien, wurden unter anderem das koreanische Alphabet in Version 2.0 entfernt und durch neue Zeichen an einer anderen Stelle in der Codeplane ersetzt. Auch in den vorherigen Versionen 1.1 und 1.0.1 wurden Zeichen entfernt.

Ab der Version 2.1 des Standards wurde sich von dieser Praxis gelöst und seitdem wird der Standard nur noch erweitert. Wird nun von der Nutzung eines Zeichens abgeraten, so wird dieses im Unicode-Standard als deprecated gekennzeichnet.

Im Oktober 2010 wurden mit dem Unicode-Standard 5.2 nicht nur Symbole für Spielkarten und weitere Schriftsysteme hinzugefügt, sondern auch Emojis feierten ihr Debüt in dieser Version. Gab es ursprünglich 722 definierte Emojis, sind diese mittlerweile angewachsen und verfügen auch über die Unterstützung unterschiedliche Hautfarben anzeigen zu können.

Mit Version 1.40 sind 144.697 Code Points definiert. Damit ist in etwa ein Siebtel des Codespace belegt. Dass das klingonische Schriftsystem darunter ist, ist im Übrigen ein Gerücht. Allerdings werden klingonische Zeichen über die privaten Bereiche des Unicode-Codespaces genutzt. Für diese privaten Bereiche innerhalb des Codespace gibt es mit der Under-ConScript Unicode Registry eine Art informelle Registry für diese.

Umsetzung in der IT

Standards und damit auch der Unicode-Standard sind nur dann sinnvoll, wenn sie umgesetzt und genutzt werden. Aktuelle Windows-Versionen und macOS-Versionen nutzen für die interne Repräsentation von Unicode UTF-16, bei Linux ist es UTF-8. Damit ist die grundlegende Nutzung in den entsprechenden Betriebssystemen gegeben.

In den Betriebssystemen selbst gibt es für den Nutzer unterschiedliche Möglichkeiten Unicode-Zeichen einzusetzen. Viele Varianten arbeiten mit der Eingabe der entsprechenden ID des Code Points in dezimaler oder hexadezimaler Repräsentation in Verbindung mit einer Taste. Unter Windows kann die Alt-Taste in Verbindung mit der entsprechenden ID zur Eingabe genutzt werden. Auch unter Linux und macOS sind solche Eingaben möglich, wobei diese unter macOS explizit aktiviert werden müssen.

Die Zeichentabelle unter Windows 10

Daneben existieren in den Betriebssystemen auch entsprechende Applikationen, wie die Zeichentabelle unter Windows, mit der Unicode-Zeichen über ein entsprechendes Interface herausgesucht und genutzt werden können.

Bei Programmiersprachen wie Java oder den auf .NET basierenden Sprachen wird intern ebenfalls mit UTF-16 gearbeitet. Unter Rust kann der primitive Typ einen sogenannten Unicode scalar value speichern. Dieser entspricht einem Code Point; allerdings sind die sogenannten Low- und High-Surrogate, welche für UTF-16 benötigt werden, hier nicht mit eingeschlossen.

Interessant ist auch die Betrachtung von Fonts in Bezug auf Unicode. Die erste Frage, die sich hier vielleicht stellt, ist ob ein Font existiert, welcher alle Unicode-Zeichen unterstützt.

Aus technischen Gründen ist dies schon nicht möglich. So kann eine TrueType-Schriftart maximal 65.535 Glyphen enthalten. Auch im OpenType-Format ist eine solche Beschränkung enthalten.

Einer der Fonts aus der Noto-Familie

Allerdings gibt es Font-Familien wie Noto, welche von Google beauftragt wurde und mittlerweile über 77.000 Code Points des Unicodes-Standards abdeckt. Daneben gibt es eine Reihe weiterer Fonts wie GNU Unifont oder WenQuanYi, welche ebenfalls eine Vielzahl an Zeichen aus dem Unicode-Zeichensatz unterstützen.

Der Name der Schriftartfamilie Noto steht für No Tofu und spielt auf das Kästchen an, welches angezeigt wird, wenn ein Font ein entsprechendes Unicode-Zeichen nicht enthält. Früher wurde hierbei häufig der Replacement Character, welcher sich in der Basic Multilingual Plane befindet, genutzt. Dieser zeigt eigentlich an, dass ein Zeichen nicht als valides Unicode-Zeichen erkannt werden konnte, also ein Kodierungsfehler vorliegt. Heutzutage wird für den Fall, dass ein valides Unicode-Zeichen vom Font nicht dargestellt werden kann, die .notdef Glyphe des entsprechenden Fonts angezeigt.

Auch bei der softwareseitigen Unterstützung von Unicode gibt es hier und da Probleme. So wurden bis in die 2000er-Jahre hauptsächlich die Code Points aus der Basic Multilingual Plane unterstützt. Andere Planes waren nicht wirklich zugänglich.

Auch die Zusammensetzung von Zeichen aus mehreren Code Points wird von vielen Anwendungen nicht richtig beherrscht. Gebessert hat sich dies unter anderem durch den Standard GB 18030 der chinesischen Regierung, welcher ebenfalls die entsprechenden Zeichen aus der Codeplane unterstützt und damit ein Unicode Transformation Format darstellt.

Dieser Standard definiert, welche Zeichen zwingend in entsprechenden Betriebssystemen und Anwendungen unterstützt werden müssen und brachte damit die Unicode-Unterstützung jenseits der Basic Multilingual Plane voran.

Auch in anderen Anwendungen, wie E-Mail kann Unicode dank des MIME-Standards seit vielen Jahren genutzt werden. Für die Codierungen von Domainnamen, mit Nicht-ASCII-Zeichen, waren ebenfalls Unicode Transformation Formate in der Entwicklung (UTF-5, UTF-6), allerdings hat sich hier Punnycode durchgesetzt.

Trotz der Durchdringung der Unicode-Standards, kommt es immer wieder zu kleineren und größeren Problemen mit ihm. So akzeptierte Outlook 2016 keine Passwörter mit Unicode-Zeichen und ein Schriftzeichen der Sprache Telugu führte zu Problemen unter iOS und macOS.

Kritik

Der Unicode-Standard ist nicht perfekt und so gab und gibt es immer wieder Kritik an diesem. Einer dieser Punkte ist die Han-Vereinheitlichung. Bei dieser ging und geht es darum, die Zeichen aus den unterschiedlichen ostasiatischen Sprachen auf ihre Grundformen zurückzuführen und diese entsprechend im Unicode-Standard abzubilden. Dies führte zu einiger Kritik, obwohl hierbei möglichst alle betroffenen Sprachgruppen eingebunden wurden, da teilweise Zeichen vereinheitlicht wurden, welche nicht unbedingt dieselbe Bedeutung hatten.

Auch das besagte Mapping bestehender Zeichensätze in den Unicode-Standard, wie im Abschnitt über die Altlasten beschrieben, war ein solcher Kritikpunkt.

Aus der Sicht der IT-Sicherheit wird manchmal die Nutzung von Homoglyphen kritisiert, da dort bestimmte Zeichen durch andere, ähnliche aussehende Zeichen ausgetauscht werden, um z. B. eine Domain einer Bank zu imitieren und den Kunden so um seine Zugangsdaten zu bringen. Ein bekanntes Beispiel hier ist der Austausch des Buchstabens O durch eine 0, welches schon beim ASCII-Standard funktionierte. Mit dem Unicode-Standard sind viele weitere Möglichkeiten für solche Homoglyphen dazugekommen.

Auch die Sortierung und Groß- und Kleinschreibung kann sich im Codespace von Unicode und entsprechender lokaler Regelungen manchmal als schwierig erweisen, da sich z. B. die Sortierreihenfolge nicht unbedingt aus der Anordnung der Zeichen innerhalb des Codespace ergibt.

Die Zukunft

Unicode ist bisher nicht überall angekommen. So wird der Standard ISO/IEC 8859-15 respektive Latin-9 als 8-Bit-Code weiterhin verwendet. Genutzt wird diese Codierung unter anderem bei amtlichen Werken wie der elektronischen Gesundheitskarte.

Im Internet sind mittlerweile über 97,6 % aller Webseiten als UTF-8 kodiert, 1,1 % als ISO-8859-1 und noch mal knapp ein Prozent entfallen auf die Codierungen Windows-1251 und Windows-1252.

Alte Zeichensätze und Codierungen werden über kurz oder lang ein Nischendasein führen und zum Großteil durch Unicode ersetzt werden, zumindest was moderne System angeht.

Im Rahmen der Script Encoding Initiative von Deborah Anderson, welche sie seit 2002 an der University of California in Berkeley betreibt, werden neue Schriftsysteme für den Standard vorgeschlagen, sodass auch in Zukunft weitere Zeichen in den Standard aufgenommen werden.

So zog 2016, mit Adlam, ein ungewöhnliches Schriftsystem in den Standard ein. Ungewöhnlich deshalb, weil dieses System erst seit 1989 existiert. Zwei Brüder entwickelten dieses System, um ihre Sprache, Fulani, phonetisch in einem Schriftsystem abbilden zu können. Etliche Jahre später wurde das System dank der Unterstützung der Script Encoding Initiative schließlich in den Unicode-Standard übernommen und wird mittlerweile unter Windows, Chrome OS sowie Android unterstützt.

Dieses Beispiel zeigt, wie Unicode eine Grundlage für die Nutzung und Überführung von Schriftsystemen in die digitale Welt ist. Noch einige Jahrzehnte zuvor war ein Großteil des Internets und der IT auf einige lateinische Buchstaben reduziert. Dank Unicode ist es möglich in seiner jeweiligen Muttersprache und in seinem angestammten Schriftsystem digital zu kommunizieren, sich zu informieren und teilzuhaben.

Das Unicode Consortium wird seine Arbeit fortsetzen und sich dabei auch in einem teilweise politischen Spannungsfeld bewegen. Wie Randall Munroe, Autor des xkcd-Comics, dazu einmal sagte:

I am endlessly delighted by the hopeless task that the Unicode Consortium has created for themselves. […] They started out just trying to unify a couple different character sets. And before they quite realized what was happening, they were grappling with decisions at the heart of how we use language, no matter how hard they tried to create policies to avoid these problems. It’s just a fun example of how weird language is and how hard human communication is and how you really can’t really get around those problems.

So bietet uns Unicode lateinische Schrift, Spielkarten, Operatoren, Emojis, Schriftzeichen aus vielen menschlichen Kulturen und mehr. Und da sich Schrift und Sprache im Laufe der Zeit verändern, wird der Unicode-Standard wohl nie fertiggestellt, sondern ein lebendiger und sich weiterentwickelnder Standard sein.

Mit seinen 144.697 Zeichen und der Abbildung von über 150 Schriftsystemen liefert er einen Beitrag zur Erhaltung der Schriftkultur und der Daten über die Jahrzehnte. In Zeiten von Globalisierung und weltweit miteinander interagierenden Systemen ist ein gemeinsamer Zeichensatz sicherlich nicht die schlechteste Idee gewesen.

Dieser Artikel erschien ursprünglich auf Golem.de und ist hier in einer alternativen Variante zu finden.