Zeichenklassen und ähnliche Konstrukte

(Auszug aus "Reguläre Ausdrücke" von Jeffrey E. F. Friedl)

Bei modernen Regex-Dialekten gibt es eine ganze Reihe von Möglichkeiten, an einer bestimmten Stelle in der Regex eine Auswahl von Zeichen zuzulassen, aber Zeichenklassen gibt es bei allen Dialekten.

Normale Klassen: [a-z] und [^a-z]

Die Grundbegriffe von Zeichenklassen wurden schon behandelt, aber ich betone noch einmal, dass Metazeichen inner- und außerhalb von Zeichenklassen ganz verschieden voneinander sind. Beispielsweise ist ˹*˼ innerhalb einer Zeichenklasse nie ein Metazeichen, aber ˹-˼ meistens. Metazeichen wie etwa ˹\b˼ können inner- und außerhalb von Zeichenklassen ganz verschiedene Bedeutungen haben (siehe Tabelle Einige Programme und die darin unterstützten Abkürzungen für Metazeichen).

Bei den meisten Systemen spielt es für die Effizienz der Regex keine Rolle, in welcher Reihenfolge die Zeichen innerhalb einer Klasse angegeben werden, ob ein Bereich angegeben wird oder ob alle Zeichen des Bereichs ausgeschrieben werden (d.h., [0-9] verhält sich genau wie [9081726354]). In manchen Implementationen werden aber Zeichenklassen mit Bereichen anders, und zwar besser, optimiert (im Java-Regex-Package von Sun), es kann da einen Vorteil bedeuten, Bereiche anzugeben.

Eine Zeichenklasse ist immer eine positive Zusicherung (assertion). Das bedeutet, dass sie immer auf ein Zeichen passen muss. Auch eine negierte Zeichenklasse muss auf ein Zeichen passen: einfach auf eines, das nicht in der Liste steht. Man stellt sich eine negierte Zeichenklasse am besten vor als »Klasse, die auf alle nicht aufgeführten Zeichen passt«. (Beachten Sie auch die Warnung im nächsten Abschnitt zum Punkt und zu negierten Zeichenklassen.) Früher konnte man sicher sein, dass ˹[^LMNOP]˼ das Gleiche war wie ˹[\x00-KQ-\xFF]˼. In einer reinen 8-Bit-Umgebung ist das nach wie vor der Fall, aber bei Unicode-fähigen Systemen mit Zeichenwerten über 255 (\xFF) enthält eine negierte Klasse wie ˹[^LMNOP]˼ plötzlich Tausende von möglichen Zeichen – nämlich alle außer L, M, N, O und P.

Bei Bereichen müssen Sie wissen, welcher Zeichensatz verwendet wird und wie er aufgebaut ist. Zum Beispiel ist ˹[a-Z]˼ ziemlich sicher ein Fehler, und ganz sicher deckt die Klasse nicht »alle alphabetischen Zeichen« ab. Dafür verwendet man oft ˹[a-zA-Z]˼, zumindest in der ASCII-Codierung. (Beachten Sie dazu die Unicode-Eigenschaft \p{L}, siehe den Abschnitt weiter unten Unicode-Eigenschaften, Schriftsysteme (Scripts) und Blockbereiche: \p{Prop}, \P{Prop}.) Wenn Sie mit Binärdaten arbeiten, kann dagegen ein Bereich wie \x80-\xFF absolut sinnvoll sein.

Fast jedes Zeichen: Punkt

Bei manchen Werkzeugen ist der Punkt eine Abkürzung für die Zeichenklasse, die alle möglichen Zeichen erkennt; bei manchen anderen vertritt er jedoch alle möglichen Zeichen außer dem Newline. Der Unterschied ist bei den Programmen wichtig, die es erlauben, reguläre Ausdrücke auf Variablen (oder Buffer, bei einem Editor) anzuwenden, die mehrere logische Zeilen enthalten. Zum Punkt gibt es noch Folgendes zu sagen:

  • Bei manchen Unicode-fähigen Systemen wie dem Java-Regex-Package von Sun passt der Punkt auf keines der Unicode-Zeilenendezeichen (siehe den Abschnitt Das Zeilenende in Unicode).
  • Es gibt Regex-Modi, die die Bedeutung des Punktes verändern.
  • Nach dem POSIX-Standard dürfte der Punkt nicht auf das NUL-Byte (das Zeichen mit dem Wert Null) passen, aber in allen bekannten Skriptsprachen tut er das.

Punkt oder negierte Zeichenklasse?

Wenn mit Programmen gearbeitet wird, die reguläre Ausdrücke auf mehrzeiligen Text anwenden können, muss berücksichtigt werden, dass der Punkt das Newline-Zeichen oft nicht enthält; eine negierte Zeichenklasse wie ˹[^"]˼ dagegen wohl. Das kann zu überraschenden Resultaten führen, wenn bei der Programmentwicklung etwa ein ˹".*"˼ in ein ˹"[^"]*"˼ geändert wird. Dieses Verhalten kann oft mit dem Modus »Punkt passt auf alles« verändert werden (siehe den Abschnitt Der Modus »Punkt passt auf alles«).

Genau ein Byte

Perl und PCRE (und damit auch PHP) unterstützen \C, das auf ein einzelnes Byte passt; auch auf eines, das Teil einer Sequenz ist, mit der ein Zeichen codiert wird (alle anderen Konstrukte beziehen sich auf Zeichen). Das kann gefährlich sein – bei falschem Gebrauch kann dies zu internen Fehlern führen; die Sequenz sollte nur dann verwendet werden, wenn man wirklich weiß, was man tut. Ich habe bisher keine Verwendung dafür gefunden und erwähne die Sequenz im Folgenden nicht weiter.

Unicode-Sequenz für kombinierende Zeichen: \X

In Perl und in PHP gibt es die Metasequenz ˹\X˼, die eigentlich nichts anderes als eine Abkürzung für ˹\P{M}\p{M}*˼ ist, also so etwas wie ein »erweiterter Punkt«. ˹\X˼ passt auf ein Grundzeichen (base character, nicht ein Zeichen aus ˹\p{M}˼), auf das eine beliebige Anzahl (inklusive null) von kombinierenden Zeichen (combining characters, ˹\p{M}˼) folgt.

Wie weiter vorn beschrieben wurde (siehe Zeichen und Zeichenkombinationen), kann man in Unicode Zeichen aus Grundzeichen und kombinierenden Zeichen aufbauen, die zusammen wie ein Zeichen aussehen, beispielsweise akzentuierte Zeichen wie ›ě‹ (›e‹, U+0065 kombiniert mit dem Breve ›˘‹, U+0306, das die Kürze des Vokals markiert). Man kann mehr als ein kombinierendes Zeichen anhängen; wenn Sie ein schweizerdeutsches Wörterbuch schreiben, werden Sie kaum ohne ›ö`‹ (ein ›o‹, U+006F, gefolgt von den Umlautpünktchen ›¨‹, U+0308, und einem Gravis ›`‹, U+0300) auskommen.

Wenn Sie mit einem regulären Ausdruck »francais« und auch »français« finden wollen, dann können Sie nicht einfach ˹fran.ais˼ oder ˹fran[cç]ais˼ benutzen, weil Sie nicht wissen können, ob das ›ç‹ mit dem einen Codierungspunkt U+00C7 codiert ist oder als ›c‹, gefolgt von einer Cedille (U+0063 und U+0327). Wenn Sie ganz exakt sein wollen, könnten Sie vielleicht ˹fran(c¸?|ç)ais˼ schreiben, aber in diesem Fall ist ˹fran\Xais˼ ein sehr guter Ersatz für ˹fran.ais˼.

Außer der Eigenschaft, dass ˹\X˼ auch die auf ein Grundzeichen folgenden kombinierenden Zeichen erkennt, bestehen noch weitere Unterschiede zum Punkt. Zum einen passt ˹\X˼ immer auf das Newline oder andere Unicode-Zeilenendezeichen (siehe den Abschnitt Das Zeilenende in Unicode); beim Punkt hängt das in den meisten Fällen von der Einstellung des Modus »Punkt passt auf alles« ab (siehe den Abschnitt Der Modus »Punkt passt auf alles«). Außer beim Newline passt der Punkt immer auf ein Zeichen, das ˹\X˼ passt aber nicht auf ein bloßes kombinierendes Zeichen ohne vorangehendes Grundzeichen.

Abkürzungen für Zeichenklassen: \w, \d, \s, \W, \D, \S

Die folgenden Abkürzungsmetazeichen werden sehr oft unterstützt:

\d

Ziffer. Das Gleiche wie ˹[0-9]˼ (d steht für ›Digit‹). Enthält in manchen Unicode-fähigen Sprachen alle Unicode-Ziffern.

\D

Nicht-Ziffer. Das Gleiche wie ˹[^0-9]˼.

\w

Wort-Bestandteil. Oft das Gleiche wie ˹[a-zA-Z0-9_]˼, in manchen Programmen aber ohne den Unterstrich. Bei manchen Programmen gehören alle Zeichen dazu, die im gewählten Locale zu den alphanumerischen Zeichen gezählt werden. Bei Unicode-Unterstützung enthält ˹\w˼ normalerweise alle alphanumerischen Zeichen, die Ausnahmen sind java.util.regex und PCRE (und damit PHP), bei denen ˹\w˼ exakt dasselbe ist wie ˹[a-zA-Z0-9_]˼.

\W

Nicht-Wortzeichen. Die Zeichenklasse, die alle Zeichen außer die von ˹\w˼ enthält.

\s

Whitespace-Zeichen. Bei Systemen, die nur ASCII kennen, ist dies meist das Gleiche wie ˹[\f\n\r\t\v]˼. Auf Unicode-fähigen Systemen gehört meistens noch das Steuerzeichen »next line« (U+0085) dazu, manchmal auch die »Whitespace«-Eigenschaft in Unicode, ˹\p{Z}˼ (siehe den nächsten Abschnitt).

\S

Nicht-Whitespace-Zeichen. Im Allgemeinen das Gleiche wie ˹[^\s]˼.

 

Wie im Abschnitt zum Konzept eines Locale beschrieben, kann das POSIX-Locale Einfluss auf manche dieser Abkürzungen nehmen (insbesondere auf ˹\w˼). Bei Unicode-fähigen Programmen passt ˹\w˼ häufig auf eine größere Anzahl von Zeichen, z.B. auf ˹\p{L}˼ (siehe den nächsten Abschnitt) und den Unterstrich.

Unicode-Eigenschaften, Schriftsysteme (Scripts) und Blockbereiche: \p{Prop}, \P{Prop}

Oberflächlich betrachtet, ist Unicode einfach eine Zuordnung von Werten und Zeichen (siehe Unicode), aber der Unicode-Standard geht weiter. Er legt zu jedem Zeichen »Qualitäten« fest, zum Beispiel »Das ist ein Kleinbuchstabe«, »Dieses Zeichen wird von rechts nach links geschrieben«, »Dieses Zeichen ist eine Marke, die zusammen mit einem Grundzeichen verwendet werden soll« usw.

Die Unterstützung für diese Qualitäten ist bei den verschiedenen Regex-Implementationen sehr unterschiedlich. Bei den meisten Unicode-fähigen Programmen sind Mustersuchen mit zumindest einigen Qualitäten durch ˹\p{qual}˼ (passt auf Zeichen mit dieser Qualität) und ˹\P{qual}˼ (passt auf Zeichen ohne diese) unterstützt. Ein Beispiel dafür ist ˹\p{L}˼. Dabei steht ›L‹ für die Qualität »Buchstabe« (»letter«, im Gegensatz zu Ziffer, Interpunktion, Akzent usw.). ˹\p{L}˼ ist ein Beispiel für eine allgemeine Eigenschaft (general property), die auch Kategorie (category) genannt wird. Wir werden bald auch andere Qualitäten kennenlernen, die man mit ˹\p{...}˼ und ˹\P{...}˼ abfragen kann, aber die allgemeinen Eigenschaften sind die häufigsten.

In der folgenden Tabelle sind die allgemeinen Unicode-Eigenschaften aufgeführt. Jedes Zeichen (oder genauer, jeder Codierungspunkt, denn es gibt auch solche ohne definiertes Zeichen) hat eine und nur eine Unicode-Eigenschaft. Die allgemeinen Eigenschaften haben einbuchstabige Namen (›L‹ für ›Letter‹ (Buchstabe), ›S‹ für Symbol usw.), aber manche Systeme, Perl beispielsweise, unterstützen auch leichter verständliche Synonyme (›Letter‹, ›Symbol‹ usw.).

Tabelle: Unicode-Eigenschaften – Grundbegriffe.

Klasse Synonym Beschreibung
\p{L} \p{Letter} Buchstaben
\p{M} \p{Mark} Ein »Markierungszeichen«; das sind Zeichen, die nicht allein auftreten, sondern nur mit einem Grundzeichen (base character) (Akzentzeichen, Kästchen ...)
\p{Z} \p{Separator} Trennzeichen »ohne Druckerschwärze« (verschiedene Arten von Leerzeichen)
\p{S} \p{Symbol} Symbole, »Dingbats«
\p{N} \p{Number} Jede Art von numerischen Zeichen
\p{P} \p{Punctuation} Interpunktion
\p{C} \p{Other} Auffangbecken für alles andere (aber kaum für normale Zeichen)

Bei manchen Systemen kann man bei den einbuchstabigen Namen auf die geschweiften Klammern verzichten (d.h., man schreibt ˹\pL˼ statt ˹\p{L}˼). Bei manchen muss (oder darf) man vor dem eigentlichen Buchstaben ein ›In‹ oder ›Is‹ setzen (also ˹\p{IsL}˼). Bei weiteren Qualitäten werden wir Fälle sehen, wo das Is/In-Präfix obligatorisch ist. (Anmerkung: Wie Sie (z.B. in der Tabelle Unicode-Eigenschaften, -Schriftsysteme und -Blockbereiche in verschiedenen Sprachen) sehen werden, ist diese Geschichte mit Is und In sehr unübersichtlich. In früheren Versionen von Unicode wurde das eine empfohlen, jetzt das andere. Ich habe in der Entwicklergruppe für Perl 5.8.0 mitgearbeitet, um die Dinge so einfach wie möglich zu machen. In Perl gilt nun die Regel: »›Is‹ und ›In‹ sind immer optional, außer bei einem Unicode-Blockbereich, da muss ›In‹ als Präfix verwendet werden.«)

Jede einbuchstabige Unicode-Eigenschaft (property) wird in mit einem zweiten Buchstaben bezeichnete Unter-Eigenschaften unterteilt (siehe folgende Tabelle). Die Eigenschaft »letter« wird beispielsweise in »lowercase letter«, »uppercase letter«, »titlecase letter«, »modifier letter« und »other letter« unterteilt. Jeder »letter«-Codepunkt gehört zu genau einer dieser Unter-Eigenschaften.

Bei manchen Implementationen gibt es davon wieder eine zusammengesetzte Untereigenschaft, ˹\p{L&}˼. Das ist eigentlich nur eine Abkürzung für ˹[\p{Lu}\p{Ll}\p{Lt}]˼.

In der nächsten Tabelle sind auch die ausgeschriebenen Synonyme (z.B. »Lowercase_Letter« statt »Ll«) aufgeführt, die manche Implementationen unterstützen. Der Unicode-Standard lässt auch eine Reihe von ähnlichen Schreibweisen zu (›LowercaseLetter‹, ›LOWERCASE_LETTER‹, ›LowercaseLetter‹, ›lowercase-letter‹ usw.). Ich empfehle dennoch, die Formen aus der folgenden Tabelle zu benutzen.

Tabelle: Grundlegende Unter-Eigenschaften in Unicode.

Eigenschaft, Synonym Beschreibung
\p{Ll} \p{Lowercase_Letter} Kleinbuchstaben
\p{Lu} \p{Uppercase_Letter} Großbuchstaben
\p{Lt} \p{Titlecase_Letter} Buchstabe in Titelschreibweise. Diese tritt nur am Wortanfang auf. Zum Beispiel ist Dž die Titelschreibweise des Großbuchstabens DŽ oder des Kleinbuchstabens dž.
\p{L&} Eine Abkürzung für alle Zeichen aus \p{Ll}, \p{Lu} und \p{Lt}
\p{Lm} \p{Modifier_Letter} Ein paar wenige buchstabenähnliche Spezialzeichen
\p{Lo} \p{Other_Letter} Buchstaben, die weder Kleinbuchstaben, Großbuchstaben, Akzente sind, z.B. solche aus den Sprachen Hebräisch, Arabisch, Tibetisch, Japanisch usw.
  
\p{Mn} \p{Non_Spacing_Mark} »Zeichen«, die andere Zeichen modifizieren, z.B. Akzente, diakritische Zeichen, Vokalzeichen, Betonungszeichen
\p{Mc} \p{Spacing_Combining_Mark} Modifizierende Zeichen, die aber selbst Platz einnehmen (meist Vokalzeichen in Sprachen wie Bengali, Gujarati, Tamil, Telugu, Kannada, Malayalam, Sinhala, Myanmar und Khmer)
\p{Me} \p{Enclosing_Mark} Zeichen, die andere Zeichen umschließen können, z.B. Kreis, Quadrat, Rhombus, »Tastensymbole«
  
\p{Zs} \p{Space_Separator} Verschiedene Arten von Leerzeichen, z.B. normales und geschütztes Leerzeichen, verschiedene Breiten von Leerzeichen
\p{Zl} \p{Line_Separator} Das Zeichen LINE SEPARATOR (U+2028)
\p{Zp} \p{Paragraph_Separator} Das Zeichen PARAGRAPH SEPARATOR (U+2029)
  
\p{Sm} \p{Math_Symbol} Mathematische Zeichen: +, ÷, schräger Bruchstrich, , ...
\p{Sc} \p{Currency_Symbol} Währungszeichen: $, ¢, £, ¥, €, ...
\p{Sk} \p{Modifier_Symbol} Vor allem die kombinierenden Zeichen (combining characters), diesmal als vollwertige Zeichen
\p{So} \p{Other_Symbol} Diverse »Dingbats«, Kästchen, Braille-Zeichen, chinesische Nichtbuchstaben-Zeichen, ...
  
\p{Nd} \p{Decimal_Digit_Number} Null bis neun in diversen Varianten (aber ohne Chinesisch, Japanisch, Koreanisch)
\p{Nl} \p{Letter_Number} Römische Ziffern
\p{No} \p{Other_Number} Zahlen als Superskripte, Zahlzeichen, die keine Ziffern sind (ohne Chinesisch, Japanisch, Koreanisch)
  
\p{Pd} \p{Dash_Punctuation} Bindestriche, Minuszeichen und sonstige Striche aller Art
\p{Ps} \p{Open_Punctuation} Öffnende Klammern wie (, , , ...
\p{Pe} \p{Close_Punctuation} Schließende Klammern wie ), , , ...
\p{Pi} \p{Initial_Punctuation} Anfangszeichen der Art ‹, «, „, <, ...
\p{Pf} \p{Final_Punctuation} Endezeichen der Art ›, », “, >, ...
\p{Pc} \p{Connector_Punctuation} Besondere linguistische Interpunktionszeichen wie der Unterstrich
\p{Po} \p{Other_Punctuation} Auffangbecken für alle restlichen Interpunktionszeichen: !, &, :, ⋮, ...
  
\p{Cc} \p{Control} Die Steuerzeichen aus ASCII und Latin-1 (TAB, LF, CR, CSI, ...)
\p{Cf} \p{Format} Unsichtbare Zeichen, die eine Formatierung bewirken (Zero width joiner, Activate Arabic form shaping ...)
\p{Co} \p{Private_Use} Codierungspunkte für private Zwecke (Firmenlogos usw.)
\p{Cn} \p{Unassigned} Unbenutzte Codierungspunkte

Schriftsysteme

Manche Systeme unterstützen die Mustersuche nach einem Script, einem Schriftsystem mit der Notation ˹\p{...}˼. Zum Beispiel passt ˹\p{Hebrew}˼ auf Zeichen, die explizit zum hebräischen Schriftsystem gehören, nicht aber auf allgemeine Zeichen, die auch zu einem anderen Schriftsystem gehören können, wie etwa Leerzeichen und Interpunktionszeichen.

Manche Unicode-Schriftsysteme beziehen sich auf eine bestimmte Sprache (Gujarati, Thai, Cherokee, ...), andere umfassen mehrere Sprachen (Latin, Cyrillic). Sprachen wie das Japanische enthalten Zeichen aus mehreren Schriftsystemen: Hiragana, Katakana, Han (»chinesische Zeichen«) und Latin. Mehr dazu in der Dokumentation zum jeweiligen Programm.

Ein Unicode-Script enthält nicht alle Zeichen eines Schriftsystems, sondern eher die Zeichen, die ausschließlich (oder überwiegend) in dem betreffenden Schriftsystem verwendet werden. Allgemeine Zeichen wie Leer- und Satzzeichen kommen in fast allen Schriftsystemen vor und sind deshalb im Pseudo-Script IsCommon untergebracht. Ein weiteres Pseudo-Script, Inherited, enthält bestimmte kombinierende Zeichen, die das Script von den Grundzeichen erben, auf die sie folgen.

Blockbereiche

Den Schriftsystemen verwandt, aber weniger vielseitig, sind die Unicode-Blockbereiche, das sind Bereiche von Codierungspunkten. Der Blockbereich Tibetan enthält die 256 Zeichen von U+0F00 bis U+0FFF. Zeichen in diesem Block können mit \p{InTibetan} (in Perl) und mit \p{IsTibetan} (in den .NET-Sprachen) erkannt werden. Mehr dazu später.

Es gibt eine ganze Reihe von Blockbereichen, mindestens einen für jedes Schriftsystem (Hebrew, Tamil, Basic_Latin, Hangul_Jamo, Cyrillic, Katakana, ...) und jede Zeichenart (Currency, Arrows, Box_Drawing, Dingbats, ...).

Tibetan ist ein besseres Beispiel für einen Blockbereich, weil alle darin enthaltenen Zeichen wirkliche tibetische Zeichen sind und weil es keine spezifisch tibetischen Zeichen außerhalb dieses Blockbereichs gibt. Dennoch ist die Qualität »Script« der von »Block« überlegen:

  • Blockbereiche können nicht belegte Codierungspunkte enthalten. Etwa 25 % der 256 Zeichen im Tibetan-Blockbereich sind gar nicht definiert.
  • Nicht alle Zeichen, von denen man annehmen sollte, dass sie zu einem bestimmten Blockbereich gehören, gehören tatsächlich dazu. Zum Beispiel enthält der Blockbereich Currency weder das allgemeine Währungssymbol ›¤‹ noch die doch nicht ganz unwichtigen Zeichen $, ¢, £, € und ¥. (Glücklicherweise kann man in diesem Fall stattdessen die entsprechende Unicode-Eigenschaft verwenden, \p{Sc}.)
  • Blockbereiche enthalten oft Zeichen, die nicht richtig dazugehören. Zum Beispiel findet man das Yen-Symbol ¥ im Blockbereich Latin_1_Supplement.
  • Was zu einem Schriftsystem gehören sollte, ist oft über mehrere Blockbereiche verteilt. Zum Beispiel sind die griechischen Buchstaben in den Blockbereichen Greek und Greek_Extended enthalten.

Dennoch werden Unicode-Blockbereiche häufiger unterstützt als die Schriftsysteme. Verwechslungen sind vorprogrammiert, denn in Unicode gibt es sowohl einen Blockbereich als auch ein Schriftsystem namens »Tibetan«.

Außerdem ist noch nicht einmal die Namensgebung vereinheitlicht. Wie aus der nächsten Tabelle hervorgeht, wird der »Tibetan«-Blockbereich in Perl und in Java ˹\p{InTibetan}˼ genannt, in den .NET-Sprachen aber ˹\p{IsTibetan}˼ (Letzteres ist dafür in Perl ein zulässiger Name des »Tibetan«-Schriftsystems).

Tabelle: Unicode-Eigenschaften, -Schriftsysteme und -Blockbereiche in verschiedenen Sprachen.

Eigenschaft Perl Java .NET PHP/PCRE
Grundeigenschaften wie \p{L}
Abkürzungen wie \pL
Ausgeschriebene Eigenschaften wie \p{IsL}
Vollständiger Name wie \p{Letter}
Zusammensetzung \p{L&}
Schriftsysteme mit \p{Greek}
Schriftsysteme mit \p{IsGreek}
Blockbereiche mit \p{Cyrillic} wenn kein Schriftsystem
Blockbereiche mit \p{InCyrillic}
Blockbereiche mit \p{IsCyrillic}
Negation mit \P{...}
Negation mit \p{^...}
\p{Any} (irgendein Zeichen) \p{all}
\p{Assigned} (definierte Zeichen) \P{Cn} \P{Cn} \P{Cn}
\p{Unassigned} (nicht definierte Zeichen) \p{Cn} \p{Cn} \P{Cn}
  

Haken (✓) in der linken Spalte: für neue Implementationen empfohlen   
Programmversionen siehe: Programmversionen in diesem Buch.

Andere Eigenschaften und Qualitäten

Nicht alles bisher Behandelte wird in jeder Unicode-fähigen Sprache unterstützt. Die obige Tabelle gibt einen Überblick.

In Unicode sind noch etliche weitere Qualitäten definiert, die alle ebenfalls über das ˹\p{...}˼-Konstrukt zugänglich sind, zum Beispiel die Schreibrichtung (von links nach rechts oder umgekehrt), Vokalzeichen usw. Bei manchen Implementationen kann man eigene Eigenschaften definieren. Alles weitere müssen Sie der Dokumentation zur verwendeten Sprache entnehmen.

Einfache Differenzen von Zeichenklassen: [[a-z]-[aeiou]]

In .NET gibt es eine einfache Art, wie man von einer Zeichenklasse die Elemente aus einer anderen Zeichenklasse »subtrahieren« kann. Zum Beispiel passt die Klasse ˹[[a-z]-[aeiou]]˼ auf alle Zeichen, auf die auch ˹[a-z]˼, nicht aber ˹[aeiou]˼ passt, also alle Kleinbuchstaben aus ASCII ohne die Vokale.

Oder ˹[\p{P}-[\p{Ps}\p{Pe}] ist eine Klasse, die alle Zeichen aus \p{P} enthält, außer denen aus ˹[\p{Ps}\p{Pe}]˼; anders gesagt, alle Interpunktionszeichen außer den öffnenden und den schließenden Klammern.

Mengenoperationen mit Zeichenklassen: [[a-z]&&[^aeiou]]

Im Java-Regex-Package von Sun kann man auf Zeichenklassen alle Basisoperationen für Mengen anwenden, also Vereinigung, Differenz und Schnittmenge. Zum Beispiel kann man eine Zeichenklasse für die englischen Konsonanten durch die Mengensubtraktion »[a-z] minus [aeiou]« konstruieren. Die Notation dafür unterscheidet sich von der aus dem vorherigen Abschnitt (und besonders die für die Differenz ist auf den ersten Blick merkwürdig: In Java schreibt man das Konsonanten-Beispiel von vorhin wie [[a-z]&&[^aeiou]]). Vor der Differenz betrachten wir aber die zwei grundlegenden Mengenoperationen OR und AND.

Mit OR kann man einer Klasse Zeichen hinzufügen, indem man statt einzelnen Zeichen oder Bereichen eine Unterklasse in eine bestehende Klasse einfügt. [abcxyz] kann man auch [[abc][xyz]], [abc[xyz]] oder [[abc]xyz] usw. schreiben. Mit OR werden Mengen zusammengesetzt, d.h. die Vereinigungsmenge gebildet. Das Prinzip ist ganz ähnlich wie beim »Bitweisen OR«-Operator, der in vielen Programmiersprachen als ›|‹ oder ›or‹ auftaucht. In einer Zeichenklasse ist die OR-Operation vor allem eine Annehmlichkeit, zusammen mit negierten Zeichenklassen allerdings in manchen Situationen sehr praktisch.

AND macht ungefähr das Gleiche wie das »Bitweise AND«: Es werden nur die Zeichen behalten, die in beiden Mengen vorkommen. Dazu werden die zwei Klassen mit der Metasequenz && verknüpft. [\p{InThai}&&\P{Cn}] passt beispielsweise auf alle definierten Codierungspunkte im Unicode-Blockbereich Thai. Das wird dadurch erreicht, dass man die Schnittmenge (d.h. die Elemente, die in beiden vorkommen) von \p{InThai} und \P{Cn} bildet. Sie erinnern sich, dass \P{...} (mit dem Großbuchstaben P) auf alle Zeichen passt, die die genannte Qualität nicht haben, also passt \P{Cn} auf alle nicht definierten Codierungspunkte. (Wenn Sun die Assigned-Qualität unterstützte, hätte ich hier \p{Assigned} statt \P{Cn} genommen.)

Verwechseln Sie bitte nicht OR und AND. Wie gut diese Bezeichnungen sind, hängt sehr vom Standpunkt ab. Wenn man [[klipp][klar]] so liest: »Passt auf alle Zeichen, die in [klipp] oder in [klar] enthalten sind«, dann stimmt der Name. Wenn man aber »Die Klasse enthält die Zeichen aus [klipp] und aus [klar]« liest, dann ist das zwar ebenso korrekt, aber der Name stimmt nicht.

Bei AND besteht diese Doppeldeutigkeit weniger, weil man einen Ausdruck wie [\p{InThai}&&\P{Cn}] in aller Regel so liest: »Passt auf jedes Zeichen, auf das \p{InThai} und auch \P{Cn} passt.«, Manchmal vielleicht auch: »Die Liste der erlaubten Zeichen ist der Durchschnitt von \p{InThai} und \P{Cn}

Diese unterschiedlichen Standpunkte machen die Kommunikation schwieriger; wofür ich OR und AND benutze, gebraucht jemand anderes die entsprechenden Begriffe aus der Mengenlehre, »Vereinigung« und »Durchschnitt«.

Subtraktion von Zeichenklassen

Vergegenwärtigen wir uns, dass \P{Cn} eigentlich auch als [^\p{Cn}] geschrieben werden kann, die ganze Klasse also als das viel komplizierter aussehende [[\p{InThai}&&[^\p{Cn}]].

In Worten ausgedrückt, ist »Die definierten Zeichen aus dem Thai-Blockbereich« dasselbe wie »Die Zeichen aus dem Thai-Blockbereich minus die undefinierten Zeichen.« Die doppelte Verneinung macht das etwas schwer verständlich, es sollte aber doch klar werden, dass [\p{InThai}&&[^\p{Cn}]] dasselbe bedeutet wie »\p{InThai} minus \p{Cn}

Damit sind wir beim Konsonantenbeispiel aus der Überschrift: ˹[[a-z]&&[^aeiou]]˼. Wir erkennen, wie wir Zeichenklassen voneinander subtrahieren können. Das Grundmuster ist ˹[dies&&[^das], es hat den Effekt »[dies] minus [das]«. Mir schwirrt bei doppelten Negationen bald der Kopf, die Struktur ˹[... &&[^...] kann ich mir leichter merken.

Mengenoperationen bei Klassen mit Lookaround imitieren

Wenn Mengenoperationen auf Zeichenklassen nicht unterstützt sind, dafür aber Lookaround (siehe den Abschnitt Lookahead; Lookbehind), dann kann man diese Operationen damit imitieren. Man kann den Ausdruck ˹[\p{InThai}&&[^\p{Cn}]]˼ durch den Lookahead-Ausdruck ˹(?!\p{Cn})\p{InThai}˼ ersetzen. (Anmerkung: In Perl könnte man für dieses Beispiel ganz einfach ˹\p{Thai}˼ schreiben, denn in Perl ist \p{Thai} ein Schriftsystem, und solche enthalten keine undefinierten Codierungspunkte. Die anderen Unterschiede zwischen Schriftsystemen und Blockbereichen sind sehr subtil, man muss dazu die Dokumentation zu Rate ziehen. In diesem Fall fehlen dem Schriftsystem tatsächlich ein paar Zeichen, die im Blockbereich enthalten sind. Details dazu finden Sie in den Zeichentabellen vom Unicode Consortium.) Das ist wohl nicht so effizient wie eine richtig implementierte Zeichenklasse, aber kaum weniger flexibel. Dieses Beispiel könnte mit Lookahead-Konstrukten auf vier verschiedene Arten umgeschrieben werden (bei .NET muss IsThai statt InThai verwendet werden, siehe unter Blockbereiche):

(?!\p{Cn})\p{InThai}
(?=\P{Cn})\p{InThai}
\p{InThai}(?<!\p{Cn})
\p{InThai}(?<=\P{Cn})

POSIX-Klammerausdruck »Zeichenklasse«: [[:alpha:]]

Was wir hier Zeichenklasse nennen, heißt bei POSIX Klammerausdruck. (Anmerkung: Engl. »bracket expression«. Im Allgemeinen verwende ich in diesem Buch die Begriffe »Zeichenklasse« und »POSIX-Klammerausdruck« synonym für das ganze Konstrukt. »POSIX-Zeichenklasse« bezieht sich dagegen auf die spezielle Art von Bereichen, wie sie hier besprochen werden.) POSIX benutzt den Begriff »Zeichenklasse« für ein bestimmtes Feature innerhalb eines POSIX-Klammerausdrucks.

Eine Zeichenklasse nach POSIX-Terminologie ist eine spezielle Metasequenz, die in POSIX-Klammerausdrücken benutzt wird. Ein Beispiel wäre [:lower:], das auf jeden Kleinbuchstaben im gewählten Locale passt (siehe Abschnitt zu Locale). In einem englischen Locale oder wenn kein Locale gesetzt ist, ist das mit a-z vergleichbar. Diese Sequenz ist nur innerhalb eines Klammerausdrucks zulässig, also ist ˹[[:lower:] die mit ˹[a-z]˼ vergleichbare Klasse. Das ist hässlich, aber es hat den Vorteil, dass die Klasse Zeichen wie ö, ñ usw. enthält, sofern diese im gewählten Locale als Kleinbuchstaben gelten.

Die vollständige Liste der von POSIX definierten Zeichenklassen hängt vom Locale ab, aber die in der folgenden Tabelle aufgeführten werden meist unterstützt.

Tabelle: POSIX-Zeichenklassen.

Klasse Beschreibung
[:alnum:] Alphabetische und numerische Zeichen
[:alpha:] Alphabetische Zeichen
[:blank:] Leerzeichen und Tabulator
[:cntrl:] Kontrollzeichen
[:digit:] Ziffern
[:graph:] »Schwarze« Zeichen (nicht: Leerzeichen, Tab, Kontrollzeichen usw.)
[:lower:] Kleinbuchstaben
[:print:] Wie [:graph:], zusätzlich das Leerzeichen
[:punct:] Interpunktionszeichen
[:space:] »Weiße« Zeichen ([:blank:], Newline, Carriage Return usw.)
[:upper:] Großbuchstaben
[:xdigit:] Hexadezimale Ziffern (0-9a-fA-F)

Manche Unicode-fähigen Programmiersprachen erweitern die POSIX-Klammerausdrücke und lassen darin Unicode-Eigenschaften zu (siehe den Abschnitt Unicode-Eigenschaften, Schriftsysteme (Scripts) und Blockbereiche: \p{Prop}, \P{Prop}). Die Unicode-Konstrukte sind vielseitiger und mächtiger und sollten, so vorhanden, bevorzugt benutzt werden.

POSIX-Klammerausdruck »Kollationssequenz«: [[.span-ll.]]

Ein Locale kann Kollationssequenzen definieren und damit angeben, wie Zeichen oder Zeichenkombinationen beim Sortieren und Ähnlichem behandelt werden sollen. Zum Beispiel wird im Spanischen das ll (etwa in tortilla) traditionell wie ein einziges Zeichen behandelt und im Alphabet zwischen l und m eingereiht, und im deutschen Alphabet kommt das ß zwischen s und t zu liegen, wird aber beim Sortieren wie die zwei Zeichen ss behandelt. Solche Regeln werden in Kollationssequenzen vereinbart, die beispielsweise span-ll oder eszet heißen könnten.

Eine Kollationssequenz, die mehreren Zeichen auf dem Papier entspricht wie das span-ll, wird von einem POSIX-System als ein einziges Zeichen behandelt. Eine Klasse wie ˹[^abc]˼ passt bei einer solchen Regex-Implementation also auf die ›ll‹-Kombination.

Eine Kollationssequenz wird innerhalb eines Klammerausdrucks mit der Notation [.....] geschrieben, also passt ˹torti[[.span-ll.]]a˼ auf tortilla. Eine Kollationssequenz kann auf Zeichen passen, die aus mehreren Zeichen aufgebaut sind, und umgekehrt ergibt es sich, dass ein einziger Klammerausdruck auf mehrere physische Zeichen passen kann.

POSIX-Klammerausdruck »Zeichenäquivalent«: [[=n=]]

In bestimmten Locales sind Zeichenäquivalente definiert. Diese dienen dazu, Zeichen für das alphabetische Sortieren als »gleich« zu erklären. Ein Locale kann beispielsweise eine Äquivalenz-Klasse namens ›n‹ definieren, die die Zeichen n und ñ enthält, oder eine namens ›a ‹, gebildet aus a, ä, á und à. Mit einer ähnlichen Notation wie [:...:], aber diesmal mit ›=‹ statt dem Doppelpunkt, können diese Äquivalenz-Klassen innerhalb eines POSIX-Klammerausdrucks benutzt werden: ˹[[=n=][=a=]]˼ passt auf alle angegebenen akzentuierten Zeichen (und natürlich auf a und n).

Wenn ein Zeichenäquivalent mit einem einbuchstabigen Namen benutzt wird, das im Locale nicht definiert ist, wird stattdessen auf eben dieses Zeichen aus der gewählten Kollationssequenz zurückgegriffen. In jedem Locale sind die »normalen« Buchstaben als Kollationssequenzen enthalten – [.a.], [.b.], [.c.] usw. –, daher wird aus ˹[[=n=][=a=]]˼ schlicht ˹[na]˼, wenn keine Zeichenäquivalente definiert sind.

Syntaxklassen in Emacs

In GNU Emacs werden die üblichen ˹\w˼, ˹\s˼ usw. nicht unterstützt. Mit ˹\s˼ werden dafür sogenannte »Syntaxklassen« gebildet:

\sZeichen   Passt auf Zeichen, die zur Emacs-Syntaxklasse Zeichen gehören.

\SZeichen   Passt auf Zeichen, die nicht zu dieser Syntaxklasse gehören.

˹\sw˼ erkennt einen Wortbestandteil und ˹\s-˼ ein Whitespace-Zeichen. Auf den meisten anderen Systemen benutzt man dafür ˹\w˼ und ˹\s˼.

Diese Syntaxklassen sind insofern speziell, als dass sie modifiziert werden können. Man kann so angeben, welche Zeichen als Wortbestandteile auftreten dürfen. So kann sich Emacs auf die Programmiersprache einstellen, je nachdem, was für eine Datei man editiert.

  

zum Seitenanfang

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Reguläre Ausdrücke bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:
   

Copyright der deutschen Ausgabe © 2008 by O’Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Reguläre Ausdrücke" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstr. 81, 50670 Köln