Regulare Ausdrucke Kochbuch 3897219573, 9783897219571 [PDF]


151 97 3MB

German Pages 536 Year 2010

Report DMCA / Copyright

DOWNLOAD PDF FILE

Regulare Ausdrucke Kochbuch
 3897219573, 9783897219571 [PDF]

  • 0 0 0
  • Gefällt Ihnen dieses papier und der download? Sie können Ihre eigene PDF-Datei in wenigen Minuten kostenlos online veröffentlichen! Anmelden
Datei wird geladen, bitte warten...
Zitiervorschau

l ria it to M s-Tu g ie

t ns Ei

Detaillierte Lösungen für acht Programmiersprachen

Reguläre Ausdrücke Kochbuch O’Reilly

Jan Goyvaerts & Steven Levithan Deutsche Übersetzung von Thomas Demmig

Reguläre Ausdrücke Kochbuch

Jan Goyvaerts & Steven Levithan Deutsche Übersetzung von Thomas Demmig

Beijing · Cambridge · Farnham · Köln · Sebastopol · Taipei · Tokyo

Die Informationen in diesem Buch wurden mit größter Sorgfalt erarbeitet. Dennoch können Fehler nicht vollständig ausgeschlossen werden. Verlag, Autoren und Übersetzer übernehmen keine juristische Verantwortung oder irgendeine Haftung für eventuell verbliebene Fehler und deren Folgen. Alle Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt und sind möglicherweise eingetragene Warenzeichen. Der Verlag richtet sich im Wesentlichen nach den Schreibweisen der Hersteller. 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.

Kommentare und Fragen können Sie gerne an uns richten: O’Reilly Verlag Balthasarstr. 81 50670 Köln Tel.: 0221/9731600 Fax: 0221/9731608 E-Mail: [email protected]

Copyright der deutschen Ausgabe: © 2010 by O’Reilly Verlag GmbH & Co. KG

Die Originalausgabe erschien 2009 unter dem Titel Regular Expressions Cookbook im Verlag O’Reilly Media, Inc.

Die Darstellung einer Spitzmaus im Zusammenhang mit dem Thema Reguläre Ausdrücke ist ein Warenzeichen von O’Reilly Media, Inc.

Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar.

Lektorat: Alexandra Follenius & Susanne Gerbert, Köln Korrektorat: Sibylle Feldmann, Düsseldorf Satz: Tim Mergemeier, Reemers Publishing Services GmbH, Krefeld, www.reemers.de Umschlaggestaltung: Michael Oreal, Köln Produktion: Karin Driesen & Andrea Miß, Köln Belichtung, Druck und buchbinderische Verarbeitung: Druckerei Kösel, Krugzell; www.koeselbuch.de ISBN 978-3-89721-957-1 Dieses Buch ist auf 100% chlorfrei gebleichtem Papier gedruckt.

Inhalt

Vorwort

.........................................................

XI

1 Einführung in reguläre Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Definition regulärer Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Suchen und Ersetzen mit regulären Ausdrücken . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Tools für das Arbeiten mit regulären Ausdrücken . . . . . . . . . . . . . . . . . . . . . . . . . . 8

2 Grundlagen regulärer Ausdrücke

..................................... Literalen Text finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nicht druckbare Zeichen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein oder mehrere Zeichen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein beliebiges Zeichen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Etwas am Anfang und/oder Ende einer Zeile finden. . . . . . . . . . . . . . . . . . Ganze Wörter finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode . . . . . Eine von mehreren Alternativen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . Gruppieren und Einfangen von Teilen des gefundenen Texts . . . . . . . . . . Vorher gefundenen Text erneut finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . Teile des gefundenen Texts einfangen und benennen . . . . . . . . . . . . . . . . Teile der Regex mehrfach wiederholen . . . . . . . . . . . . . . . . . . . . . . . . . . . . Minimale oder maximale Wiederholung auswählen . . . . . . . . . . . . . . . . . Unnötiges Backtracking vermeiden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Aus dem Ruder laufende Wiederholungen verhindern. . . . . . . . . . . . . . . . Etwas auf Übereinstimmung prüfen, ohne es dem Gesamtergebnis hinzuzufügen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.17 Abhängig von einer Bedingung eine von zwei Alternativen finden. . . . . . . 2.18 Kommentare für einen regulären Ausdruck . . . . . . . . . . . . . . . . . . . . . . . . 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10 2.11 2.12 2.13 2.14 2.15 2.16

27 28 30 33 37 39 44 47 59 61 64 66 69 72 75 78 81 87 90

| V

2.19 2.20 2.21 2.22

Literalen Text im Ersetzungstext nutzen. . . . . . . . . . . . . . . . . . . . . . . . . . . 92 Einfügen des Suchergebnisses in den Ersetzungstext . . . . . . . . . . . . . . . . . 95 Teile des gefundenen Texts in den Ersetzungstext einfügen . . . . . . . . . . . . 96 Suchergebniskontext in den Ersetzungstext einfügen . . . . . . . . . . . . . . . . 100

3 Mit regulären Ausdrücken programmieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 3.18 3.19 3.20 3.21

Literale reguläre Ausdrücke im Quellcode . . . . . . . . . . . . . . . . . . . . . . . . Importieren der Regex-Bibliothek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Erstellen eines Regex-Objekts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Optionen für reguläre Ausdrücke setzen. . . . . . . . . . . . . . . . . . . . . . . . . . Auf eine Übereinstimmung in einem Text prüfen. . . . . . . . . . . . . . . . . . . Auf eine vollständige Übereinstimmung einer Regex mit einem Text prüfen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Auslesen des übereinstimmenden Texts . . . . . . . . . . . . . . . . . . . . . . . . . . Position und Länge der Übereinstimmung ermitteln . . . . . . . . . . . . . . . . Teile des übereinstimmenden Texts auslesen . . . . . . . . . . . . . . . . . . . . . . Eine Liste aller Übereinstimmungen erhalten . . . . . . . . . . . . . . . . . . . . . . Durch alle Übereinstimmungen iterieren . . . . . . . . . . . . . . . . . . . . . . . . . Übereinstimmungen in prozeduralem Code überprüfen . . . . . . . . . . . . . Eine Übereinstimmung in einer anderen Übereinstimmung finden . . . . . Alle Übereinstimmungen ersetzen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übereinstimmungen durch Teile des gefundenen Texts ersetzen . . . . . . . Übereinstimmungen durch Text ersetzen, der im Code erzeugt wurde . . Alle Übereinstimmungen innerhalb der Übereinstimmungen einer anderen Regex ersetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Alle Übereinstimmungen zwischen den Übereinstimmungen einer anderen Regex ersetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einen String aufteilen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einen String aufteilen und die Regex-Übereinstimmungen behalten . . . . Zeile für Zeile suchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4 Validierung und Formatierung 4.1 4.2 4.3 4.4 4.5 4.6 4.7

VI | Inhalt

...................................... E-Mail-Adressen überprüfen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nordamerikanische Telefonnummern validieren . . . . . . . . . . . . . . . . . . . Internationale Telefonnummern überprüfen . . . . . . . . . . . . . . . . . . . . . . Klassische Datumsformate validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . Klassische Datumsformate exakt validieren . . . . . . . . . . . . . . . . . . . . . . . Klassische Zeitformate validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Datums- und Uhrzeitwerte im Format ISO 8601 validieren . . . . . . . . . . .

109 115 117 123 131 137 142 149 154 162 167 173 177 181 189 194 200 203 208 217 222

227 227 233 239 241 245 250 252

4.8 4.9 4.10 4.11 4.12 4.13 4.14 4.15 4.16 4.17 4.18

Eingabe auf alphanumerische Zeichen beschränken . . . . . . . . . . . . . . . . Die Länge des Texts begrenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Zeilenanzahl eines Texts beschränken . . . . . . . . . . . . . . . . . . . . . . . . Antworten auswerten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . US-Sozialversicherungsnummern validieren. . . . . . . . . . . . . . . . . . . . . . . ISBN validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ZIP-Codes validieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kanadische Postleitzahlen validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . Britische Postleitzahlen validieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Deutsche Postleitzahlen validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Namen von „Vorname Nachname“ nach „Nachname, Vorname“ umwandeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.19 Kreditkartennummern validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.20 Europäische Umsatzsteuer-Identifikationsnummern . . . . . . . . . . . . . . . .

5 Wörter, Zeilen und Sonderzeichen 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 5.10 5.11 5.12 5.13 5.14

................................... Ein bestimmtes Wort finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eines von mehreren Wörtern finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ähnliche Wörter finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Alle Wörter außer einem bestimmten finden . . . . . . . . . . . . . . . . . . . . . . Ein beliebiges Wort finden, auf das ein bestimmtes Wort nicht folgt . . . Ein beliebiges Wort finden, das nicht hinter einem bestimmten Wort steht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Wörter finden, die nahe beieinanderstehen . . . . . . . . . . . . . . . . . . . . . . . Wortwiederholungen finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Doppelte Zeilen entfernen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Vollständige Zeilen finden, die ein bestimmtes Wort enthalten . . . . . . . . Vollständige Zeilen finden, die ein bestimmtes Wort nicht enthalten . . . Führenden und abschließenden Whitespace entfernen . . . . . . . . . . . . . . Wiederholten Whitespace durch ein einzelnes Leerzeichen ersetzen . . . . Regex-Metazeichen maskieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6 Zahlen 6.1 6.2 6.3 6.4 6.5 6.6

......................................................... Integer-Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Hexadezimale Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Binärzahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Führende Nullen entfernen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zahlen innerhalb eines bestimmten Bereichs . . . . . . . . . . . . . . . . . . . . . . Hexadezimale Zahlen in einem bestimmten Bereich finden . . . . . . . . . . .

257 260 265 269 271 274 281 282 282 283 285 288 294

301 301 304 306 310 312 313 317 323 325 330 332 333 336 337

341 341 345 348 349 350 357

Inhalt | VII

6.7 Gleitkommazahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359 6.8 Zahlen mit Tausendertrennzeichen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363 6.9 Römische Zahlen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364

7 URLs, Pfade und Internetadressen 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 7.10 7.11 7.12 7.13 7.14 7.15 7.16 7.17 7.18 7.19 7.20 7.21 7.22 7.23 7.24 7.25

................................... URLs validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . URLs in einem längeren Text finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . URLs in Anführungszeichen in längerem Text finden . . . . . . . . . . . . . . . URLs mit Klammern in längerem Text finden . . . . . . . . . . . . . . . . . . . . . URLs in Links umwandeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . URNs validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Generische URLs validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Schema aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . Den Benutzer aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . Den Host aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . Den Port aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Den Pfad aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Query aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Fragment aus einer URL extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . Domainnamen validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . IPv4-Adressen finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . IPv6-Adressen finden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einen Pfad unter Windows validieren. . . . . . . . . . . . . . . . . . . . . . . . . . . . Pfade unter Windows in ihre Bestandteile aufteilen . . . . . . . . . . . . . . . . . Den Laufwerkbuchstaben aus einem Pfad unter Windows extrahieren . . Den Server und die Freigabe aus einem UNC-Pfad extrahieren . . . . . . . . Die Ordnernamen aus einem Pfad unter Windows extrahieren . . . . . . . . Den Dateinamen aus einem Pfad unter Windows extrahieren . . . . . . . . . Die Dateierweiterung aus einem Pfad unter Windows extrahieren . . . . . Ungültige Zeichen aus Dateinamen entfernen . . . . . . . . . . . . . . . . . . . . .

367 367 371 373 374 376 377 379 385 386 388 390 392 396 397 398 400 403 417 420 425 426 427 430 431 432

8 Markup und Datenaustausch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435 8.1 8.2 8.3 8.4 8.5

Tags im XML-Stil finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -Tags durch ersetzen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Alle Tags im XML-Stil außer und entfernen . . . . . . . . . . XML-Namen finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einfachen Text durch Ergänzen von

- und
- Tags nach HTML konvertieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.6 Ein bestimmtes Attribut in Tags im XML-Stil finden . . . . . . . . . . . . . . . .

VIII | Inhalt

442 459 463 466 473 476

8.7 Tags vom Typ

ein Attribut „cellspacing“ hinzufügen, die es noch nicht haben. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.8 Kommentare im XML-Stil entfernen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.9 Wörter in Kommentaren im XML-Stil finden . . . . . . . . . . . . . . . . . . . . . . 8.10 Ändern der Feldbegrenzer in CSV-Dateien . . . . . . . . . . . . . . . . . . . . . . . . 8.11 CSV-Felder aus einer bestimmten Spalte extrahieren . . . . . . . . . . . . . . . . 8.12 Sektionsüberschriften in INI-Dateien finden . . . . . . . . . . . . . . . . . . . . . . 8.13 Sektionsblöcke in INI-Dateien finden . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.14 Name/Wert-Paare in INI-Dateien finden . . . . . . . . . . . . . . . . . . . . . . . . .

Index

..........................................................

481 484 488 493 496 500 502 503

505

Inhalt | IX

Vorwort

Im letzten Jahrzehnt ist die Beliebtheit regulärer Ausdrücke deutlich angestiegen. Heutzutage gibt es in allen verbreiteten Programmiersprachen mächtige Bibliotheken zur Verarbeitung regulärer Ausdrücke. Zum Teil bietet die Sprache sogar selbst die entsprechenden Möglichkeiten. Viele Entwickler nutzen diese Features, um den Anwendern ihrer Applikationen das Suchen und Filtern der Daten mithilfe regulärer Ausdrücke zu ermöglichen. Reguläre Ausdrücke sind überall. Es gibt viele Bücher, die sich mit regulären Ausdrücken befassen. Die meisten machen ihren Job ganz gut – sie erklären die Syntax und enthalten ein paar Beispiele sowie eine Referenz. Aber es gibt keine Bücher, die Lösungen vorstellen. Lösungen, die auf regulären Ausdrücken basieren und für eine ganze Reihe von praktischen Problemen aus der realen Welt genutzt werden können. Bei solchen Problemen geht es vor allem um Fragen zu Texten auf einem Computer und um Internetanwendungen. Wir, Steve und Jan, haben uns dazu entschieden, diese Lücke mit diesem Buch zu füllen. Wir wollten vor allem zeigen, wie Sie reguläre Ausdrücke in Situationen verwenden können, in denen weniger Erfahrene im Umgang mit regulären Ausdrücken sagen würden, das sei nicht möglich, oder in denen Softwarepuristen der Meinung sind, ein regulärer Ausdruck sei nicht das richtige Tool für diese Aufgabe. Da reguläre Ausdrücke heute überall zu finden sind, sind sie oft auch als Tool verfügbar, das von Endanwendern genutzt werden kann. Auch Programmierer können durch die Verwendung einiger weniger regulärer Ausdrücke viel Zeit sparen, etwa wenn sie Informationen suchen und verändern müssen. Die Alternative ist oft, Stunden oder Tage mit dem Umsetzen in prozeduralen Code zu verbringen oder eine Bibliothek von dritter Seite zu nutzen.

Gefangen im Gewirr der verschiedenen Versionen Vergleichbar mit anderen beliebten Dingen der IT-Branche, gibt es auch reguläre Ausdrücke in vielen unterschiedlichen Ausprägungen mit unterschiedlicher Kompatibilität. Das hat dazu geführt, dass es diverse Varianten eines regulären Ausdrucks gibt, die sich nicht immer gleich verhalten oder die teilweise gar nicht funktionieren.

| XI

Viele Bücher erwähnen, dass es unterschiedliche Varianten gibt, und führen auch einige der Unterschiede auf. Aber immer wieder lassen sie bestimmte Varianten unerwähnt – vor allem wenn in diesen Varianten Features fehlen –, statt auf alternative Lösungen und Workarounds hinzuweisen. Das ist frustrierend, wenn Sie mit unterschiedlichen Varianten regulärer Ausdrücke in den verschiedenen Anwendungen oder Programmiersprachen arbeiten müssen. Saloppe Bemerkungen in der Literatur wie „jeder nutzt mittlerweile reguläre Ausdrücke im Perl-Stil“ bagatellisieren leider eine ganze Reihe von Inkompatibilitäten. Selbst Pakete im „Perl-Stil“ besitzen entscheidende Unterschiede, und Perl entwickelt sich ja auch noch weiter. Solche oberflächlichen Äußerungen können für Programmierer trostlose Folgen haben und zum Beispiel dazu führen, dass sie eine halbe Stunde oder mehr damit verbringen, nutzlos im Debugger herumzustochern, statt die Details ihrer Implementierung für reguläre Ausdrücke zu kontrollieren. Selbst wenn sie herausfinden, dass ein Feature, auf dem sie aufbauen, nicht vorhanden ist, wissen sie nicht immer, wie sie stattdessen vorgehen können. Dieses Buch ist das erste, das die am meisten verbreiteten und umfangreichsten Varianten regulärer Ausdrücke nebeneinander aufführt – und zwar durchgängig im ganzen Buch.

Für wen dieses Buch gedacht ist Sie sollten dieses Buch lesen, wenn Sie regelmäßig am Computer mit Text zu tun haben – egal ob Sie einen Stapel Dokumente durchsuchen, Text in einem Texteditor bearbeiten oder Software entwickeln, die Text durchsuchen oder verändern soll. Reguläre Ausdrücke sind für diese Aufgaben exzellente Hilfsmittel. Das Reguläre Ausdrücke Kochbuch erklärt Ihnen alles, was Sie über reguläre Ausdrücke wissen müssen. Sie brauchen kein Vorwissen, da wir selbst einfachste Aspekte regulärer Ausdrücke erklären werden. Wenn Sie schon Erfahrung mit regulären Ausdrücken haben, werden Sie eine Menge Details kennenlernen, die in anderen Büchern oder Onlineartikeln häufig einfach übergangen werden. Sind Sie jemals über eine Regex gestolpert, die in einer Anwendung funktioniert hat, in einer anderen aber nicht, werden Sie die in diesem Buch detailliert und gleichwertig behandelten Beschreibungen zu sieben der verbreitetsten Varianten regulärer Ausdrücke sehr hilfreich finden. Wir haben das ganze Buch als Kochbuch aufgebaut, sodass Sie direkt zu den Themen springen können, die Sie interessieren. Wenn Sie dieses Buch von vorne bis hinten durchlesen, werden Sie am Ende Meister regulärer Ausdrücke sein. Mit diesem Buch erfahren Sie alles, was Sie über reguläre Ausdrücke wissen müssen, und noch ein bisschen mehr – unabhängig davon, ob Sie Programmierer sind oder nicht. Wenn Sie reguläre Ausdrücke in einem Texteditor nutzen wollen, in einem Suchtool oder in irgendeiner Anwendung, die ein Eingabefeld „Regex“ enthält, können Sie dieses Buch auch ganz ohne Programmiererfahrung lesen. Die meisten Rezepte bieten Lösungen an, die allein auf einem oder mehreren regulären Ausdrücken basieren. XII | Vorwort

Die Programmierer unter Ihnen erhalten in Kapitel 3 alle notwendigen Informationen zum Implementieren regulärer Ausdrücke in ihrem Quellcode. Dieses Kapitel geht davon aus, dass Sie mit den grundlegenden Features der Programmiersprache Ihrer Wahl vertraut sind, aber Sie müssen keine Erfahrung mit dem Einsatz regulärer Ausdrücke in Ihrem Quellcode mitbringen.

Behandelte Technologien .NET, Java, JavaScript, PCRE, Perl, Python und Ruby kommen nicht ohne Grund auf dem Rückseitentext vor. Vielmehr stehen diese Begriffe für die sieben Varianten regulärer Ausdrücke, die in diesem Buch behandelt werden, wobei alle sieben gleichermaßen umfassend beschrieben werden. Insbesondere haben wir versucht, alle Uneinheitlichkeiten zu beschreiben, die wir in diesen verschiedenen Varianten finden konnten. Das Kapitel zur Programmierung (Kapitel 3) enthält Code-Listings in C#, Java, JavaScript, PHP, Perl, Python, Ruby und VB.NET. Auch hier gibt es zu jedem Rezept Lösungen und Erläuterungen für alle acht Sprachen. Damit gibt es in diesem Kapitel zwar einige Wiederholungen, aber Sie können die Abhandlungen über Sprachen, an denen Sie nicht interessiert sind, gern überspringen, ohne etwas in der Sprache zu verpassen, die Sie selbst anwenden.

Aufbau des Buchs In den ersten drei Kapiteln dieses Buchs geht es um nützliche Tools und grundlegende Informationen, die eine Basis für die Verwendung regulärer Ausdrücke bilden. Jedes der folgenden Kapitel stellt dann eine Reihe von regulären Ausdrücken vor, die bestimmte Bereiche der Textbearbeitung behandeln. Kapitel 1, Einführung in reguläre Ausdrücke, erläutert die Rolle regulärer Ausdrücke und präsentiert eine Reihe von Tools, die das Erlernen, Aufbauen und Debuggen erleichtern. Kapitel 2, Grundlagen regulärer Ausdrücke, beschreibt alle Elemente und Features regulärer Ausdrücke zusammen mit wichtigen Hinweisen zu einer effektiven Nutzung. Kapitel 3, Mit regulären Ausdrücken programmieren, stellt Coding-Techniken vor und enthält Codebeispiele für die Verwendung regulärer Ausdrücke in jeder der in diesem Buch behandelten Programmiersprachen. Kapitel 4, Validierung und Formatierung, enthält Rezepte für den Umgang mit typischen Benutzereingaben, wie zum Beispiel Datumswerten, Telefonnummern und Postleitzahlen in den verschiedenen Staaten. Kapitel 5, Wörter, Zeilen und Sonderzeichen, behandelt häufig auftretende Textbearbeitungsaufgaben, wie zum Beispiel das Testen von Zeilen auf die An- oder Abwesenheit bestimmter Wörter.

Vorwort | XIII

Kapitel 6, Zahlen, zeigt, wie man Integer-Werte, Gleitkommazahlen und viele andere Formate in diesem Bereich aufspürt. Kapitel 7, URLs, Pfade und Internetadressen, zeigt Ihnen, wie Sie mit den Strings umgehen, die im Internet und in Windows-Systemen für das Auffinden von Inhalten genutzt werden. Kapitel 8, Markup und Datenaustausch, dreht sich um das Bearbeiten von HTML, XML, Comma-Separated Values (CSV) und Konfigurationsdateien im INI-Stil.

Konventionen in diesem Buch Die folgenden typografischen Konventionen werden in diesem Buch genutzt: Kursiv Steht für neue Begriffe, URLs, E-Mail-Adressen, Dateinamen und Dateierweiterungen. Feste Breite

Wird genutzt für Programme, Programmelemente wie Variablen oder Funktionsnamen, Werte, die das Ergebnis einer Ersetzung mithilfe eines regulären Ausdrucks sind, und für Elemente oder Eingabetexte, die einem regulären Ausdruck übergeben werden. Dabei kann es sich um den Inhalt eines Textfelds in einer Anwendung handeln, um eine Datei auf der Festplatte oder um den Inhalt einer String-Variablen. Feste Breite, kursiv

Zeigt Text, der vom Anwender oder durch den Kontext bestimmt werden sollte. ‹RegulärerzAusdruck›

Steht für einen regulären Ausdruck, entweder allein oder so, wie Sie ihn in das Suchfeld einer Anwendung eingeben würden. Leerzeichen in regulären Ausdrücken werden durch graue Kreise wiedergegeben, außer im Free-Spacing-Modus. «Textzzuzersetzen»

Steht für den Text, der bei einer Suchen-und-Ersetzen-Operation durch den regulären Ausdruck gefunden wird und dann ersetzt werden soll. Leerzeichen im zu ersetzenden Text werden mithilfe grauer Kreise dargestellt. Gefundener Text

Steht für den Teil des Texts, der zu einem regulären Ausdruck passt. ...

Graue Punkte in einem regulären Ausdruck weisen darauf hin, dass Sie diesen Bereich erst mit Leben füllen müssen, bevor Sie den regulären Ausdruck nutzen können. Der Begleittext erklärt, was Sie dort eintragen können. (CR), (LF) und (CRLF) CR, LF und CRLF in Rahmen stehen für die echten Zeichen zum Zeilenumbruch in Strings und nicht für die Escape-Zeichen \r, \n und \r\n. Solche Strings können entstehen, wenn man in einem mehrzeiligen Eingabefeld die Eingabetaste drückt oder im Quellcode mehrzeilige String-Konstanten genutzt werden, wie zum Beispiel die Verbatim-Strings in C# oder Strings mit dreifachen Anführungszeichen in Python.

XIV | Vorwort

Der „Wagenrücklauf“-Pfeil, den Sie vielleicht auf Ihrer Tastatur auf der Eingabetaste sehen, wird genutzt, wenn wir eine Zeile auftrennen müssen, damit sie auf die Druckseite passt. Geben Sie den Text in Ihrem Quellcode ein, sollten Sie hier nicht die Eingabetaste drücken, sondern alles auf einer Zeile belassen. Dieses Icon steht für einen Tipp, einen Vorschlag oder eine allgemeine Anmerkung.

Dieses Icon steht für einen Warnhinweis.

Die Codebeispiele verwenden Dieses Buch ist dazu da, Ihnen bei Ihrer Arbeit zu helfen. Sie können den Code dieses Buchs in Ihren Programmen und Dokumentationen verwenden. Sie brauchen uns nicht um Erlaubnis zu fragen, solange Sie nicht einen beachtlichen Teil des Codes wiedergeben. Beispielsweise benötigen Sie keine Erlaubnis, um ein Programm zu schreiben, das einige Codeteile aus diesem Buch verwendet. Für den Verkauf oder die Verbreitung einer CDROM mit Beispielen aus O’Reilly-Büchern brauchen Sie auf jeden Fall unsere Erlaubnis. Die Beantwortung einer Frage durch das Zitieren dieses Buchs und seiner Codebeispiele benötigt wiederum keine Erlaubnis. Wenn Sie einen erheblichen Teil der Codebeispiele dieses Buchs in die Dokumentation Ihres Produkts einfügen, brauchen Sie eine Erlaubnis.

Wir freuen uns über einen Herkunftsnachweis, bestehen aber nicht darauf. Eine Referenz enthält i.d.R. Titel, Autor, Verlag und ISBN, zum Beispiel: „Reguläre Ausdrücke Kochbuch von Jan Goyvaerts & Steven Levithan, Copyright 2010, O’Reilly Verlag, ISBN 9783-89721-957-1.“ Wenn Sie denken, Ihre Verwendung unserer Codebeispiele könnte den angemessenen Gebrauch oder die hier erteilte Erlaubnis überschreiten, nehmen Sie einfach mit uns über [email protected] Kontakt auf.

Danksagung Wir danken Andy Oram, unserem Lektor bei O’Reilly Media, Inc., für seine Begleitung bei diesem Projekt vom Anfang bis zum Ende. Ebenso danken wir Jeffrey Friedl, Zak Greant, Nikolaj Lindberg und Ian Morse für ihre sorgfältigen fachlichen Korrekturen, durch die dieses Buch umfassender und genauer wurde.

Vorwort | XV

KAPITEL 1

Einführung in reguläre Ausdrücke

Wenn Sie dieses Kochbuch aufgeschlagen haben, sind Sie wahrscheinlich schon ganz erpicht darauf, ein paar der hier beschriebenen seltsamen Strings mit Klammern und Fragezeichen in Ihren Code einzubauen. Falls Sie schon so weit sind: nur zu! Die verwendbaren regulären Ausdrücke sind in den Kapiteln 4 bis 8 aufgeführt und beschrieben. Aber wenn Sie zunächst die ersten Kapitel dieses Buchs lesen, spart Ihnen das langfristig möglicherweise eine Menge Zeit. So finden Sie in diesem Kapitel zum Beispiel eine Reihe von Hilfsmitteln – einige wurden von Jan, einem der beiden Autoren, erstellt –, mit denen Sie einen regulären Ausdruck testen und debuggen können, bevor Sie ihn in Ihrem Code vergraben, wo Fehler viel schwieriger zu finden sind. Außerdem zeigen Ihnen diese ersten Kapitel, wie Sie die verschiedenen Features und Optionen regulärer Ausdrücke nutzen können, um Ihr Leben leichter zu machen. Sie werden verstehen, wie reguläre Ausdrücke funktionieren, um deren Performance zu verbessern, und Sie lernen die subtilen Unterschiede kennen, die in den verschiedenen Programmiersprachen existieren – selbst in unterschiedlichen Versionen Ihrer bevorzugten Programmiersprache. Wir haben uns also mit diesen Grundlageninformationen viel Mühe gegeben und sind recht zuversichtlich, dass Sie sie lesen werden, bevor Sie mit der Anwendung regulärer Ausdrücke beginnen – oder spätestens dann, wenn Sie nicht mehr weiterkommen und Ihr Wissen aufstocken wollen.

Definition regulärer Ausdrücke Im Rahmen dieses Buchs ist ein regulärer Ausdruck eine bestimmte Art von Textmuster, das Sie in vielen modernen Anwendungen und Programmiersprachen nutzen können. Mit ihm können Sie prüfen, ob eine Eingabe zu einem Textmuster passt, Sie können Texte eines bestimmten Musters in einer größeren Datei finden, durch anderen Text oder durch eine veränderte Version des bisherigen Texts ersetzen, einen Textabschnitt in eine Reihe von Unterabschnitten aufteilen – oder auch sich selbst ins Knie schießen. Dieses Buch hilft Ihnen dabei, genau zu verstehen, was Sie tun, um Katastrophen zu vermeiden.

| 1

Geschichte des Begriffs „regulärer Ausdruck“ Der Begriff regulärer Ausdruck kommt aus der Mathematik und der theoretischen Informatik. Dort steht er für eine Eigenschaft mathematischer Ausdrücke namens Regularität. Solch ein Ausdruck kann als Software mithilfe eines deterministischen endlichen Automaten (DEA) implementiert werden. Ein DEA ist ein endlicher Automat, der kein Backtracking nutzt. Die Textmuster, die von den ersten grep-Tools genutzt wurden, waren reguläre Ausdrücke im mathematischen Sinn. Auch wenn der Name geblieben ist, sind aktuelle reguläre Ausdrücke im Perl-Stil keine regulären Ausdrücke im mathematischen Sinn. Sie sind mit einem nicht deterministischen endlichen Automaten (NEA) implementiert. Später werden Sie noch mehr über Backtracking erfahren. Alles, was ein normaler Entwickler aus diesem Textkasten mitnehmen muss, ist, dass ein paar Informatiker in ihren Elfenbeintürmen sehr verärgert darüber sind, dass ihr wohldefinierter Begriff durch eine Technologie überlagert wurde, die in der realen Welt viel nützlicher ist.

Wenn Sie reguläre Ausdrücke sinnvoll einsetzen, vereinfachen sie viele Programmierund Textbearbeitungsaufgaben oder ermöglichen gar erst deren Umsetzung. Ohne sie bräuchten Sie Dutzende, wenn nicht Hunderte von Zeilen prozeduralen Codes, um zum Beispiel alle E-Mail-Adressen aus einem Dokument zu ziehen – Code, der nicht besonders spannend zu schreiben ist und der sich auch nur schwer warten lässt. Mit dem passenden regulären Ausdruck, wie er in Rezept 4.1, zu finden ist, braucht man nur ein paar Zeilen Code, wenn nicht sogar nur eine einzige. Aber wenn Sie versuchen, mit einem einzelnen regulären Ausdruck zu viel auf einmal zu machen, oder wenn Sie Regexes auch dort nutzen, wo sie eigentlich nicht sinnvoll sind, werden Sie folgendes Statement nachvollziehen können:1 Wenn sich manche Menschen einem Problem gegenübersehen, denken sie: „Ah, ich werde reguläre Ausdrücke nutzen.“ Jetzt haben sie zwei Probleme.

Dieses zweite Problem taucht jedoch nur auf, wenn diese Menschen die Anleitung nicht gelesen haben, die Sie gerade in den Händen halten. Lesen Sie weiter. Reguläre Ausdrücke sind ein mächtiges Tool. Wenn es bei Ihrer Arbeit darum geht, Text auf einem Computer zu bearbeiten oder zu extrahieren, wird Ihnen ein solides Grundwissen über reguläre Ausdrücke viele Überstunden ersparen.

Viele Varianten regulärer Ausdrücke Okay, der Titel des vorigen Abschnitts war gelogen. Wir haben gar nicht definiert, was reguläre Ausdrücke sind. Das können wir auch nicht. Es gibt keinen offiziellen Standard, der genau definiert, welche Textmuster reguläre Ausdrücke sind und welche nicht. Wie 1 Jeffrey Friedl hat die Geschichte dieses Zitats in seinem Blog verfolgt: http://regex.info/blog/2006-09-15/247.

2 | Kapitel 1: Einführung in reguläre Ausdrücke

Sie sich vorstellen können, hat jeder Designer einer Programmiersprache und jeder Entwickler einer textverarbeitenden Anwendung eine andere Vorstellung davon, was genau ein regulärer Ausdruck tun sollte. Daher sehen wir uns einem ganzen Reigen von Varianten regulärer Ausdrücke gegenüber. Glücklicherweise sind die meisten Designer und Entwickler faul. Warum sollte man etwas total Neues aufbauen, wenn man kopieren kann, was schon jemand anderer gemacht hat? Im Ergebnis lassen sich alle modernen Varianten regulärer Ausdrücke, einschließlich derer, die in diesem Buch behandelt werden, auf die Programmiersprache Perl zurückverfolgen. Wir nennen diese Varianten reguläre Ausdrücke im Perl-Stil. Ihre Syntax ist sehr ähnlich und meist auch kompatibel, aber eben nicht immer. Autoren sind ebenfalls faul. Meistens verwenden wir den Ausdruck Regex oder Regexp, um einen einzelnen regulären Ausdruck zu bezeichnen, und Regexes für den Plural. Regex-Varianten entsprechen nicht eins zu eins den Programmiersprachen. Skriptsprachen haben meist ihre eigene, eingebaute Regex-Variante. Andere Programmiersprachen nutzen Bibliotheken, um eine Unterstützung regulärer Ausdrücke anzubieten. Manche Bibliotheken gibt es für mehrere Sprachen, während man bei anderen Sprachen auch aus unterschiedlichen Bibliotheken wählen kann. Dieses einführende Kapitel kümmert sich nur um die Varianten regulärer Ausdrücke und ignoriert vollständig irgendwelche Programmierüberlegungen. Kapitel 3 beginnt dann mit den Codebeispielen, sodass Sie schon mal in Kapitel 3, Mit regulären Ausdrücken programmieren, spicken könnten, um herauszufinden, mit welchen Varianten Sie arbeiten werden. Aber ignorieren Sie erst einmal den ganzen Programmierkram. Die im nächsten Abschnitt vorgestellten Tools ermöglichen einen viel einfacheren Weg, die Regex-Syntax durch „Learning by Doing“ kennenzulernen.

Regex-Varianten in diesem Buch In diesem Buch haben wir die Regex-Varianten ausgewählt, die heutzutage am verbreitetsten sind. Es handelt sich bei allen um Regex-Varianten im Perl-Stil. Manche der Varianten besitzen mehr Features als andere. Aber wenn zwei Varianten das gleiche Feature besitzen, haben sie meist auch die gleiche Syntax dafür. Wir werden auf die wenigen, aber nervigen Unregelmäßigkeiten hinweisen, wenn wir ihnen begegnen. All diese Regex-Varianten sind Teile von Programmiersprachen und Bibliotheken, die aktiv entwickelt werden. Aus der Liste der Varianten können Sie ersehen, welche Versionen in diesem Buch behandelt werden. Im weiteren Verlauf des Buchs führen wir die Varianten ohne Versionen auf, wenn die vorgestellte Regex überall gleich funktioniert. Das ist nahezu immer der Fall. Abgesehen von Fehlerkorrekturen, die Grenzfälle betreffen, ändern sich Regex-Varianten im Allgemeinen nicht, es sei denn, es werden neue Features ergänzt, die einer Syntax Bedeutung verleihen, die vorher als Fehler angesehen wurde:

Definition regulärer Ausdrücke | 3

Perl Perls eingebaute Unterstützung für reguläre Ausdrücke ist der Hauptgrund dafür, dass Regexes heute so beliebt sind. Dieses Buch behandelt Perl 5.6, 5.8 und 5.10. Viele Anwendungen und Regex-Bibliotheken, die behaupten, reguläre Ausdrücke im Perl- oder einem zu Perl kompatiblen Stil zu nutzen, tun das meist gar nicht. In Wirklichkeit nutzen sie eine Regex-Syntax, die der von Perl ähnlich ist, aber nicht die gleichen Features unterstützt. Sehr wahrscheinlich verwenden sie eine der Regex-Varianten aus dem Rest der Liste. Diese Varianten nutzen alle den Perl-Stil. PCRE PCRE ist die C-Bibliothek „Perl-Compatible Regular Expressions”, die von Philip Hazel entwickelt wurde. Sie können diese Open Source-Bibliothek unter http://www.pcre.org herunterladen. Dieses Buch behandelt die Versionen 4 bis 7. Obwohl die PCRE behauptet, zu Perl kompatibel zu sein, und dies vermutlich mehr als alle anderen Varianten in diesem Buch auch ist, setzt sie eigentlich nur den PerlStil um. Manche Features, wie zum Beispiel die Unicode-Unterstützung, sind etwas anders, und Sie können keinen Perl-Code mit Ihrer Regex mischen, wie es bei Perl selbst möglich ist. Aufgrund der Open Source-Lizenz und der ordentlichen Programmierung hat die PCRE Eingang in viele Programmiersprachen und Anwendungen gefunden. Sie ist in PHP eingebaut und in vielen Delphi-Komponenten verpackt. Wenn eine Anwendung behauptet, reguläre Ausdrücke zu nutzen, die „zu Perl kompatibel“ sind, ohne aufzuführen, welche Regex-Variante genau genutzt wird, ist es sehr wahrscheinlich PCRE. .NET Das .NET Framework von Microsoft stellt über das Paket System.Text.RegularExpressions eine vollständige Regex-Variante im Perl-Stil bereit. Dieses Buch behandelt die .NET-Versionen 1.0 bis 3.5. Im Prinzip gibt es aber nur zwei Versionen von System.Text.RegularExpressions: 1.0 und 2.0. In .NET 1.1, 3.0 und 3.5 gab es keine Änderungen an den Regex-Klassen. Jede .NET-Programmiersprache, unter anderem C#, VB.NET, Delphi for .NET und sogar COBOL.NET, hat einen vollständigen Zugriff auf die .NET-Variante der Regexes. Wenn eine Anwendung, die mit .NET entwickelt wurde, eine RegexUnterstützung anbietet, können Sie ziemlich sicher sein, dass sie die .NET-Variante nutzt, selbst wenn sie behauptet, „reguläre Ausdrücke von Perl“ zu nutzen. Eine wichtige Ausnahme bildet das Visual Studio (VS) selbst. Die in VS integrierte Entwicklungsumgebung (IDE) nutzt immer noch die gleiche alte Regex-Variante, die sie von Anfang an unterstützt hat. Und die ist überhaupt nicht im Perl-Stil nutzbar. Java Java 4 ist das erste Java-Release, das durch das Paket java.util.regex eine eingebaute Unterstützung regulärer Ausdrücke anbietet. Schnell wurden dadurch die verschiedenen Regex-Bibliotheken von dritter Seite verdrängt. Abgesehen davon, dass es sich um ein Standardpaket handelt, bietet es auch einen vollständige Regex-Variante im Perl-Stil an und hat eine ausgezeichnete Performance, selbst im Vergleich zu

4 | Kapitel 1: Einführung in reguläre Ausdrücke

Anwendungen, die in C geschrieben wurden. Dieses Buch behandelt das Paket java.util.regex in Java 4, 5 und 6. Wenn Sie Software verwenden, die in den letzten paar Jahren mit Java entwickelt wurden, wird jede Unterstützung regulärer Ausdrücke vermutlich auf die Java-Variante zurückgreifen. JavaScript In diesem Buch nutzen wir den Begriff JavaScript, um damit die Variante regulärer Ausdrücke zu beschreiben, die in Version 3 des ECMA-262-Standards definiert ist. Dieser Standard ist die Basis der Programmiersprache ECMAScript. Diese ist besser bekannt durch ihre Implementierungen JavaScript und JScript in den verschiedenen Webbrowsern. Internet Explorer 5.5 bis 8.0, Firefox, Opera und Safari implementieren alle Version 3 von ECMA-262. Trotzdem gibt es in allen Browsern unterschiedliche Grenzfälle, in denen sie vom Standard abweichen. Wir weisen auf solche Probleme hin, wenn sie eine Rolle spielen. Ist es möglich, auf einer Website mit einem regulären Ausdruck suchen oder filtern zu können, ohne auf eine Antwort vom Webserver warten zu müssen, wird die Regex-Variante von JavaScript genutzt, die die einzige browserübergreifende RegexVariante auf Clientseite ist. Selbst VBScript von Microsoft und ActionScript 3 von Adobe nutzen sie. Python Python unterstützt reguläre Ausdrücke durch sein Modul re. Dieses Buch behandelt Python 2.4 und 2.5. Die Unterstützung regulärer Ausdrücke in Python hat sich seit vielen Jahren nicht geändert. Ruby Die Unterstützung regulärer Ausdrücke in Ruby ist wie bei Perl Teil der Sprache selbst. Dieses Buch behandelt Ruby 1.8 und 1.9. Eine Standardkompilierung von Ruby 1.8 verwendet die Variante regulärer Ausdrücke, die direkt im Quellcode von Ruby implementiert ist. Eine Standardkompilierung von Ruby 1.9 nutzt die Oniguruma-Bibliothek für reguläre Ausdrücke. Ruby 1.8 kann so kompiliert werden, dass es Oniguruma verwendet, und Ruby 1.9 so, dass es die ältere Ruby-Regex-Variante nutzt. In diesem Buch bezeichnen wir die native Ruby-Variante als Ruby 1.8 und die Oniguruma-Variante als Ruby 1.9. Um herauszufinden, welche Ruby-Regex-Variante Ihre Site nutzt, versuchen Sie, den regulären Ausdruck ‹a++› zu verwenden. Ruby 1.8 wird Ihnen mitteilen, dass der reguläre Ausdruck ungültig ist, da es keine possessiven Quantoren unterstützt, während Ruby 1.9 damit einen String finden wird, der aus einem oder mehreren Zeichen besteht. Die Oniguruma-Bibliothek ist so entworfen, dass sie abwärtskompatibel zu Ruby 1.8 ist und neue Features so ergänzt, dass keine bestehenden Regexes ungültig werden. Die Implementatoren haben sogar Features darin gelassen, die man wohl besser entfernt hätte, wie zum Beispiel die Verwendung von (?m), mit der der Punkt auch Zeilenumbrüche findet, wofür andere Varianten (?s) nutzen.

Definition regulärer Ausdrücke | 5

Suchen und Ersetzen mit regulären Ausdrücken Suchen und Ersetzen ist eine Aufgabe, für die reguläre Ausdrücke häufig eingesetzt werden. Solch eine Funktion übernimmt einen Ausgangstext, einen regulären Ausdruck und einen Ersetzungsstring als Eingabe. Die Ausgabe ist der Ausgangstext, in dem alle Übereinstimmungen mit dem regulären Ausdruck durch den Ersetzungstext ausgetauscht wurden. Obwohl der Ersetzungstext kein regulärer Ausdruck ist, können Sie bestimmte Syntaxelemente nutzen, um einen dynamischen Ersetzungstext aufzubauen. Bei allen Varianten können Sie den Text einfügen, der durch den regulären Ausdruck gefunden wurde, oder einen Teil davon. In den Rezepten 2.20 und 2.21 wird das beschrieben. Manche Varianten unterstützen auch noch das Einfügen von passendem Kontext im Ersetzungstext, wie in Rezept 2.22 zu lesen ist. In Kapitel 3, Rezept 3.16 erfahren Sie, wie man für jede Übereinstimmung einen anderen Ersetzungstext erzeugen kann.

Viele Varianten des Ersetzungstexts Unterschiedliche Ideen von unterschiedlichen Softwareentwicklern haben dazu geführt, dass es viele verschiedene Varianten regulärer Ausdrücke gibt. Jede davon hat eine andere Syntax und unterstützt andere Features. Bei den Ersetzungstexten ist das nicht anders. Tatsächlich gibt es sogar noch mehr Varianten als bei den regulären Ausdrücken selbst. Es ist schwer, eine Engine für reguläre Ausdrücke aufzubauen. Die meisten Programmierer bevorzugen es, eine bestehende zu nutzen, aber es ist recht einfach, eine Funktion zum Suchen und Ersetzen auf einer bestehenden Regex-Engine aufzubauen. So gibt es viele Varianten für Ersetzungstexte in Regex-Bibliotheken, die nicht von sich aus bereits Funktionen zum Ersetzen anbieten. Glücklicherweise haben alle Varianten regulärer Ausdrücke in diesem Buch entsprechende Varianten für den Ersetzungstext, mit Ausnahme der PCRE. Diese Lücke in PCRE macht den Programmierern, die damit arbeiten, das Leben schwer. Die Open Source-PCRE-Bibliothek enthält keinerlei Funktionen für Ersetzungen. Das heißt, alle Anwendungen und Programmiersprachen, die auf PCRE aufbauen, müssen ihre eigenen Funktionen bereitstellen. Die meisten Programmierer versuchen, eine bestehende Syntax zu kopieren, aber sie machen es nie exakt gleich. Dieses Buch behandelt die folgenden Ersetzungstextvarianten. In „Viele Varianten regulärer Ausdrücke“ auf Seite 2, finden Sie weitere Informationen über die Regex-Varianten, die zu den entsprechenden Ersetzungstextvarianten gehören: Perl Perl besitzt eine eingebaute Unterstützung für Ersetzungen mit regulären Ausdrücken. Dazu nutzt es den Operator s/regex/replace/. Die Perl-Variante für Ersetzungen gehört zu der Perl-Variante für reguläre Ausdrücke. Dieses Buch behandelt Perl 5.6 bis Perl 5.10. In der letzten Version ist eine Unterstützung für benannte Rückwärtsreferenzen im Ersetzungstext hinzugekommen, so wie auch bei den regulären Ausdrücken nun benannte einfangende Gruppen enthalten sind. 6 | Kapitel 1: Einführung in reguläre Ausdrücke

PHP In diesem Buch bezieht sich die PHP-Variante für den Ersetzungstext auf die Funktion preg_replace. Diese Funktion nutzt die PCRE-Variante für die regulären Ausdrücke und die PHP-Variante für den Ersetzungstext. Andere Programmiersprachen, die PCRE nutzen, verwenden nicht die gleiche Ersetzungstextvariante wie PHP. Abhängig davon, woher die Designer Ihrer Programmiersprache ihre Inspiration beziehen, kann die Ersetzungstextsyntax der von PHP gleichen oder eine beliebige andere Variante aus diesem Buch nutzen. PHP bietet zudem noch die Funktion ereg_replace. Diese Funktion nutzt eine andere Variante für reguläre Ausdrücke (POSIX ERE) und auch eine andere Variante für die Ersetzungstexte. PHPs ereg-Funktionen werden in diesem Buch nicht behandelt. .NET Das Paket System.Text.RegularExpressions stellt eine ganze Reihe von Funktionen zum Suchen und Ersetzen bereit. Die .NET-Variante für den Ersetzungstext gehört zur .NET-Variante für die regulären Ausdrücke. Alle Versionen von .NET verwenden die gleiche Ersetzungstextvariante. Die neuen Features in .NET 2.0 für die regulären Ausdrücke haben keinen Einfluss auf die Syntax für den Ersetzungstext. Java Das Paket java.util.regex enthält Funktionen zum Suchen und Ersetzen. Dieses Buch behandelt Java 4, 5 und 6. Alle Versionen greifen auf die gleiche Ersetzungstextsyntax zurück. JavaScript In diesem Buch nutzen wir den Begriff JavaScript, um sowohl auf die Variante für den Ersetzungstext zu verweisen als auch auf die für die regulären Ausdrücke. Beides ist in Edition 3 des ECMA-262-Standards definiert. Python Pythons Modul re stellt eine Funktion sub bereit, mit der gesucht und ersetzt werden kann. Die Python-Variante für den Ersetzungstext gehört zur Python-Variante für reguläre Ausdrücke. Dieses Buch behandelt Python 2.4 und 2.5. Die RegexUnterstützung ist bei Python in den letzten Jahren sehr stabil gewesen. Ruby Die Unterstützung regulärer Ausdrücke in Ruby ist Bestandteil der Sprache selbst und auch die Funktion zum Suchen und Ersetzen. Dieses Buch behandelt Ruby 1.8 und 1.9. Eine Standardkompilierung von Ruby 1.8 nutzt die Variante für reguläre Ausdrücke, die direkt im Quellcode von Ruby definiert ist, während eine Standardkompilierung von Ruby 1.9 die Oniguruma-Bibliothek für reguläre Ausdrücke nutzt. Ruby 1.8 kann so kompiliert werden, dass Oniguruma verwendet wird, während Ruby 1.9 so kompiliert werden kann, dass die alte Regex-Variante von Ruby genutzt wird. In diesem Buch bezeichnen wir die native Ruby-Variante als Ruby 1.8 und die Oniguruma-Variante als Ruby 1.9.

Suchen und Ersetzen mit regulären Ausdrücken | 7

Die Syntax für Ersetzungstexte ist in Ruby 1.8 und 1.9 die gleiche, nur dass Ruby 1.9 auch noch benannte Rückwärtsreferenzen im Ersetzungstext unterstützt. Benannte einfangende Gruppen sind ein neues Feature bei den regulären Ausdrücken von Ruby 1.9.

Tools für das Arbeiten mit regulären Ausdrücken Sofern Sie nicht schon längere Zeit mit regulären Ausdrücken gearbeitet haben, empfehlen wir Ihnen, Ihre ersten Experimente in einem Tool durchzuführen und nicht in Quellcode. Die Beispiel-Regexes in diesem Kapitel und in Kapitel 2, sind einfache reguläre Ausdrücke, die keine zusätzliche Maskierung in einer Programmiersprache (oder sogar in einer Unix-Shell) brauchen. Sie können diese regulären Ausdrücke direkt in das Suchfeld einer Anwendung eingeben. Kapitel 3 erklärt, wie Sie reguläre Ausdrücke in Ihren Quellcode einbauen können. Will man einen regulären Ausdruck als String mit Anführungszeichen versehen, wird er noch schwerer lesbar, weil sich dann die Maskierungsregeln für Strings mit denen für reguläre Ausdrücke vermischen. Wir heben uns das für Rezept 3.1, auf. Haben Sie einmal die Grundlagen regulärer Ausdrücke verstanden, werden Sie auch durch den BackslashDschungel finden. Die in diesem Abschnitt beschriebenen Tools ermöglichen es Ihnen auch, reguläre Ausdrücke zu debuggen, die Syntax zu prüfen und andere Informationen zu bekommen, die in den meisten Programmierumgebungen nicht erhältlich sind. Wenn Sie also reguläre Ausdrücke in Ihren Anwendungen entwickeln, ist es für Sie vielleicht sinnvoll, einen komplizierten regulären Ausdruck in einem dieser Tools aufzubauen, bevor Sie ihn in Ihrem Programm einsetzen.

RegexBuddy RegexBuddy (Abbildung 1-1) ist das Tool mit den (zum Zeitpunkt der Entstehung dieses Buchs) meisten verfügbaren Features zum Erstellen, Testen und Implementieren regulärer Ausdrücke. Es bietet die einzigartige Möglichkeit, alle Varianten regulärer Ausdrücke zu emulieren, die in diesem Buch behandelt werden, und sogar zwischen den verschiedenen Varianten zu konvertieren. RegexBuddy wurde von Jan Goyvaerts entworfen und entwickelt, einem der Autoren dieses Buchs. Dadurch wurde Jan zu einem Experten für reguläre Ausdrücke und mithilfe von RegexBuddy war Koautor Steven in der Lage, sich so gut mit regulären Ausdrücken vertraut zu machen, dass er sich an diesem Buch beteiligen konnte. Wenn der Screenshot (Abbildung 1-1) ein bisschen unruhig aussieht, liegt das daran, dass wir einen Großteil der Panels auf den Bildschirm gebracht haben, um zu zeigen, was RegexBuddy alles drauf hat. Die Standardansicht ordnet alle Panels hübsch in Registerkarten an. Sie können sie aber auch abreißen, um sie auf einen zweiten Monitor zu ziehen.

8 | Kapitel 1: Einführung in reguläre Ausdrücke

Abbildung 1-1: RegexBuddy

Um einen der regulären Ausdrücke aus diesem Buch auszuprobieren, tippen Sie ihn einfach in das Eingabefeld im oberen Bereich des RegexBuddy-Fensters ein. RegexBuddy stellt den Ausdruck dann automatisch mit Syntax-Highlighting dar und macht deutlich auf Fehler und fehlende Klammern aufmerksam. Das Create-Panel baut automatisch eine detaillierte Analyse in englischer Sprache auf, während Sie die Regex eintippen. Durch einen Doppelklick auf eine Beschreibung im Baum mit der Struktur des regulären Ausdrucks können Sie diesen Teil des regulären Ausdrucks bearbeiten. Neue Teile lassen sich in Ihren regulären Ausdruck per Hand oder über den Button Insert Token einfügen. Bei Letzterem können Sie dann aus einem Menü auswählen, was Sie haben wollen. Wenn Sie sich zum Beispiel nicht an die komplizierte Syntax für einen positiven Lookahead erinnern, können Sie RegexBuddy bitten, die passenden Zeichen für Sie einzufügen. Geben Sie einen Beispieltext im Test-Panel ein – indem Sie ihn entweder eintippen oder hineinkopieren. Wenn der Highlight-Button aktiv ist, markiert RegexBuddy automatisch den Text, der zur Regex passt. Einige dieser Buttons werden Sie wahrscheinlich am häufigsten nutzen: List All Gibt eine Liste mit allen Übereinstimmungen aus.

Tools für das Arbeiten mit regulären Ausdrücken | 9

Replace Der Replace-Button am oberen Fensterrand zeigt ein neues Fenster an, in dem Sie den Ersetzungstext eingeben können. Der Replace-Button im Test-Panel zeigt Ihnen dann den Ausgangstext an, nachdem die Ersetzungen vorgenommen wurden. Split (der Button im Test-Panel, nicht der am oberen Fensterrand) Behandelt den regulären Ausdruck als Separator und teilt den Ausgangstext in Tokens auf, die zeigen, wo in Ihrem Text Übereinstimmungen gefunden wurden. Klicken Sie auf einen dieser Buttons und wählen Sie Update Automatically, damit RegexBuddy die Ergebnisse dynamisch aktualisiert, wenn Sie Ihre Regex oder den Ausgangstext anpassen. Um genau zu sehen, wie Ihre Regex funktioniert (oder warum nicht), klicken Sie im TestPanel auf eine hervorgehobene Übereinstimmung oder auf eine Stelle, an der die Regex nicht funktioniert hat, und danach auf den Button Debug. RegexBuddy wechselt dadurch zum Debug-Panel und zeigt den gesamten Prozess zum Finden von Übereinstimmungen Schritt für Schritt. Klicken Sie in die Ausgabe des Debuggers, um herauszufinden, welches Regex-Token zu dem Text passte, den Sie angeklickt haben. Im Use-Panel wählen Sie Ihre bevorzugte Programmiersprache aus. Dann wählen Sie eine Funktion, um den Quellcode für das Implementieren Ihrer Regex zu erzeugen. Die Quellcode-Templates von Regex-Buddy lassen sich mit dem eingebauten Template-Editor problemlos bearbeiten. Sie können neue Funktionen und sogar neue Sprachen ergänzen oder bestehende anpassen. Möchten Sie Ihre Regex mit einer größeren Datenmenge testen, wechseln Sie zum GREPPanel, um eine beliebige Anzahl von Dateien und Ordnern zu durchsuchen (und eventuell Ersetzungen vorzunehmen). Wenn Sie in Code, den Sie warten, eine Regex finden, kopieren Sie sie in die Zwischenablage – einschließlich der umschließenden Anführungszeichen oder Schrägstriche. In RegexBuddy klicken Sie auf den Paste-Button und wählen den String-Stil Ihrer Programmiersprache aus. Ihre Regex wird dann in RegexBuddy als pure Regex erscheinen – ohne die zusätzlichen Anführungszeichen oder Maskierungen, die für String-Literale notwendig sind. Mit dem Copy-Button erzeugen Sie einen String in der gewünschten Syntax, sodass Sie ihn in Ihren Quellcode einfügen können. Sobald Sie mehr Erfahrung haben, können Sie im Library-Panel eine praktische Bibliothek mit regulären Ausdrücke aufbauen. Ergänzen Sie die dort abgelegten Regexes auf jeden Fall um eine detaillierte Beschreibung und einen Test-String. Reguläre Ausdrücke können sehr kryptisch sein, selbst für Experten. Haben Sie dennoch ein Problem damit, eine passende Regex aufzubauen, klicken Sie auf das Forum-Panel und dann auf den Login-Button. Falls Sie RegexBuddy gekauft haben, erscheint das Anmeldefenster. Klicken Sie auf OK, sind Sie direkt mit dem Benutzerforum von RegexBuddy verbunden. Steven und Jan sind dort häufig zu finden.

10 | Kapitel 1: Einführung in reguläre Ausdrücke

RegexBuddy läuft unter Windows 98, ME, 2000, XP und Vista. Falls Sie eher Linux oder Apple bevorzugen, lässt sich RegexBuddy auch gut mit VMware, Parallels, CrossOver Office und (mit ein paar Einschränkungen) auch mit WINE betreiben. Sie können eine kostenlose Testversion von RegexBuddy unter http://www.regexbuddy.com/RegexBuddyCookbook.exe herunterladen. Abgesehen vom Benutzerforum ist die Testversion sieben Tage lang uneingeschränkt nutzbar.

RegexPal RegexPal (Abbildung 1-2) ist ein Online-Testtool für reguläre Ausdrücke. Es wurde von Steven Levithan erstellt, einem der Autoren dieses Buchs. Sie brauchen dazu lediglich einen modernen Webbrowser. RegexPal ist vollständig in JavaScript geschrieben. Daher unterstützt es nur die JavaScript-Variante für reguläre Ausdrücke, so wie sie in dem von Ihnen verwendeten Webbrowser implementiert ist.

Abbildung 1-2: RegexPal

Um einen der regulären Ausdrücke auszuprobieren, die in diesem Buch vorgestellt werden, rufen Sie http://www.regexpal.com auf. Geben Sie die Regex in das Feld ein, in dem Enter regex here. steht. RegexPal zeigt Ihren regulären Ausdruck automatisch mit SyntaxHighlighting an, wodurch Sie auch Syntaxfehler in der Regex erkennen können. RegexPal ist sich der Probleme unterschiedlicher Implementierungen in den verschiedenen

Tools für das Arbeiten mit regulären Ausdrücken | 11

Browsern bewusst, die Ihnen das Leben ganz schön schwer machen können. Wenn daher eine bestimmte Syntax in manchen Browsern nicht korrekt arbeitet, wird RegexPal diese als Fehler hervorheben. Jetzt geben Sie einen Beispieltext in das Feld mit dem Inhalt Enter test data here. ein. RegexPal hebt automatisch die Übereinstimmungen zu Ihrer Regex hervor. Es gibt keine Buttons, die man anklicken muss – das macht RegexPal zu einem der angenehmsten Onlinetools für das Testen regulärer Ausdrücke.

Weitere Onlinetools für Regexes Es ist einfach, ein simples Onlinetool für das Testen regulärer Ausdrücke aufzubauen. Wenn Sie ein paar grundlegende Web-Entwicklungskenntnisse besitzen, reichen die Informationen aus Kapitel 3 aus, um selbst etwas zu bauen. Hunderte von Entwicklern haben das schon getan, ein paar haben Features ergänzt, die erwähnenswert sind.

regex.larsolavtorvik.com Lars Olav Torvik hat ein tolles kleines Testtool für reguläre Ausdrücke unter http://regex.larsolavtorvik.com bereitgestellt (siehe Abbildung 1-3). Zunächst wählen Sie die Variante aus, mit der Sie arbeiten wollen, indem Sie im oberen Bereich der Seite auf deren Namen klicken. Lars bietet PHP PCRE, PHP POSIX und JavaScript an. PHP PCRE, die PCRE-Variante, die wir in diesem Buch behandeln, wird von der PHP-Funktion preg genutzt. POSIX ist eine alte und recht eingeschränkte RegexVariante, die von der PHP-Funktion ereg verwendet wird, aber in diesem Buch keine weitere Erwähnung findet. Wenn Sie JavaScript auswählen, arbeiten Sie mit der JavaScriptImplementierung Ihres Browsers. Geben Sie Ihren regulären Ausdruck in das Pattern-Feld und Ihren Ausgangstext in das Subject-Feld ein. Einen Moment später wird im Matches-Feld Ihr Ausgangstext mit den hervorgehobenen Übereinstimmungen angezeigt. Das Code-Feld enthält eine einzelne Codezeile, die Ihre Regex auf Ihren Ausgangstext anwendet. Kopieren Sie diese Zeile in Ihren Codeeditor, brauchen Sie die Regexp nicht selbst in ein String-Literal umzuwandeln. Strings oder Arrays, die vom Code zurückgegeben werden, finden Sie im ResultFeld. Da Lars Ajax-Technologie verwendet hat, um seine Site aufzubauen, sind die Ergebnisse für alle Varianten immer nach kurzer Zeit verfügbar. Um dieses Tool nutzen zu können, müssen Sie online sein, da auf dem Server PHP verarbeitet wird. Die zweite Spalte zeigt eine Liste mit Regex-Befehlen und -Optionen an. Diese hängen davon ab, welche Variante Sie gewählt haben. Zu den Befehlen gehören üblicherweise solche zum Finden von Übereinstimmungen (match), zum Ersetzen von Text (replace) und zum Aufteilen von Texten (split). Die Optionen enthalten die üblichen Verdächtigen, wie zum Beispiel das Ignorieren von Groß- und Kleinschreibung, aber auch implementierungsspezifische Optionen. Diese Befehle und Optionen werden in Kapitel 3, beschrieben.

12 | Kapitel 1: Einführung in reguläre Ausdrücke

Abbildung 1-3: regex.larsolavtorvik.com

Nregex http://www.nregex.com (Abbildung 1-4) ist ein unkompliziertes Online-Testtool für Regexes, das von David Seruyange mit .NET-Technologie gebaut wurde. Die Site erwähnt zwar nicht, welche Variante sie nutzt, aber als dieses Buch hier geschrieben wurde, war es .NET 1.x. Das Layout der Seite ist ein bisschen verwirrend. Sie geben Ihren regulären Ausdruck in das Feld unter dem Text Regular Expression ein und setzen die Regex-Optionen mithilfe der Kontrollkästchen darunter. Geben Sie Ihren Ausgangstext in das große Feld am unteren Ende der Seite ein, wobei Sie den Standardtext If I just had $5.00 then "she" wouldn't be so @#$! mad. ersetzen. Wenn Ihr Text von einer Webseite kommt, geben Sie die URL in das Feld Load Target From URL ein und klicken auf den Button Load, der sich darunter befindet. Möchten Sie eine Datei auf Ihrer Festplatte als Ausgangsbasis nehmen, klicken Sie auf den Button Browse, wählen die gewünschte Datei aus und klicken dann auf den Button Load unter dem entsprechenden Eingabefeld.

Tools für das Arbeiten mit regulären Ausdrücken | 13

Abbildung 1-4: Nregex

Ihr Ausgangstext wird im Feld Matches & Replacements in der Mitte der Webseite nochmals erscheinen, wobei die Übereinstimmungen zur Regex hervorgehoben sind. Wenn Sie etwas in das Feld Replacement String eingeben, wird stattdessen das Ergebnis des Ersetzungsvorgangs angezeigt. Wenn Ihr regulärer Ausdruck ungültig ist, erscheint: ... Das Auswerten der Regex wird mit .NET-Code auf dem Server durchgeführt, daher müssen Sie für diese Site online sein. Arbeiten die automatischen Aktualisierungen langsam – vielleicht weil Ihr Ausgangstext sehr groß ist –, markieren Sie das Kontrollkästchen Manually Evaluate Regex über dem Eingabefeld für Ihren regulären Ausdruck, um einen Evaluate-Button zu erhalten. Diesen können Sie dann anklicken, um das Feld Matches & Replacements zu aktualisieren.

14 | Kapitel 1: Einführung in reguläre Ausdrücke

Rubular Michael Lovitt hat ein minimalistisches Regex-Testtool unter http://www.rubular.com (Abbildung 1-5) bereitgestellt, wobei die Regex-Variante von Ruby 1.8 genutzt wird.

Abbildung 1-5: Rubular

Geben Sie Ihren regulären Ausdruck im Feld Your regular expression zwischen den beiden Schrägstrichen ein. Sie können Groß- und Kleinschreibung ignorieren, wenn Sie in das kleine Feld nach dem zweiten Schrägstrich ein i tippen. Genauso können Sie die Option Punkt findet auch Zeilenumbruch durch die Eingabe eines m im gleichen Feld aktivieren. im schaltet beide Optionen ein. Auch wenn diese Konventionen ein wenig unpraktisch zu sein scheinen, wenn Sie mit Ruby noch nicht so vertraut sind, entsprechen Sie dennoch der Syntax /regex/im, die in Ruby genutzt wird, um einen regulären Ausdruck anzugeben. Tragen Sie Ihren Ausgangstext in das Feld Your test string ein und warten Sie einen Moment. Es wird ein neues Feld Match result auf der rechten Seite erscheinen, in dem Ihr Ausgangstext mit allen Übereinstimmungen hervorgehoben zu finden ist.

myregexp.com Sergey Evdokimov hat eine ganze Reihe von Regex-Testtools für Java-Entwickler geschrieben. Die Homepage unter http://www.myregexp.com (Abbildung 1-6) bietet auch eine Onlineversion an. Dabei handelt es sich um ein Java-Applet, das in Ihrem Browser

Tools für das Arbeiten mit regulären Ausdrücken | 15

läuft. Dafür muss die Java 4-Runtime (oder eine neuere) auf Ihrem Computer installiert sein. Das Applet nutzt das Paket java.util.regex (das seit Java 4 existiert), um Ihren regulären Ausdruck auszuwerten. In diesem Buch bezieht sich die Java-Variante auf dieses Paket.

Abbildung 1-6: myregexp.com

Geben Sie Ihren regulären Ausdruck in das Feld Regular Expression ein. Mit dem Menü Flags können Sie die gewünschten Regex-Optionen setzen. Drei der Optionen sind auch als Kontrollkästchen direkt auf der Oberfläche zu finden. Wenn Sie eine Regex testen wollen, die schon als String in Java-Code vorhanden ist, kopieren Sie den gesamten String in die Zwischenablage. Im myregexp.com-Tester wählen Sie im Edit-Menü den Punkt Paste Regex from Java String aus. Im gleichen Menü können Sie auch Copy Regex for Java Source aufrufen, wenn Sie mit der Bearbeitung des regulären Ausdrucks fertig sind. In diesem Menü gibt es außerdem ähnliche Einträge für JavaScript und XML.

16 | Kapitel 1: Einführung in reguläre Ausdrücke

Unterhalb des regulären Ausdrucks gibt es vier Registerkarten, auf denen vier verschiedene Tests ausgeführt werden können: Find Alle Übereinstimmungen zum regulären Ausdruck werden im Ausgangstext hervorgehoben. Dies sind die von der Java-Methode Matcher.find() gefundenen Elemente. Match Prüft, ob der reguläre Ausdruck mit dem Ausgangstext vollständig übereinstimmt. Wenn das der Fall ist, wird der gesamte Text hervorgehoben. Die Methoden String.matches() und Matcher.matches() gehen so vor. Split Das zweite Feld auf der rechten Seite zeigt das Array mit Strings an, das von String.split() oder Pattern.split() zurückgegeben wird. Replace Geben Sie einen Ersetzungstext ein, zeigt das Feld auf der rechten Seite den von String.replaceAll() oder Matcher.replaceAll() zurückgegebenen Text an. Sie finden die anderen Regex-Tools von Sergey über die Links oben auf der Seite http://www.myregexp.com. Eines ist ein Plug-in für Eclipse, das andere eines für IntelliJ IDEA.

reAnimator Oliver Steeles reAnimator unter http://osteele.com/tools/reanimator (Abbildung 1-7) bringt keine tote Regex ins Leben zurück. Aber es ist ein nettes kleines Tool, das eine grafische Darstellung des endlichen Automaten ausgibt, den eine Regex-Engine nutzt, um einen regulären Ausdruck umzusetzen. Die Syntax des reAnimator ist sehr eingeschränkt. Es lassen sich alle in diesem Buch behandelten Varianten nutzen. Jede Regex, die Sie im reAnimator darstellen können, wird in allen Varianten funktionieren, die in diesem Buch beschrieben sind, aber das Gegenteil ist definitiv nicht der Fall. Das liegt daran, dass die regulären Ausdrücke von reAnimator im mathematischen Sinn regulär sind. Der Kasten „Geschichte des Begriffs ,regulärer Ausdruck’“ auf Seite 2 erklärt das kurz. Gehen Sie zum Pattern-Feld im oberen Bereich der Seite und klicken Sie auf den Edit-Button. Geben Sie nun Ihren regulären Ausdruck ein und klicken Sie auf Set. Tippen Sie langsam den Ausgangstext in das Input-Feld ein. Beim Eintippen jedes einzelnen Zeichens bewegen sich bunte Bälle durch den Automaten, um zu zeigen, wo sich der Endpunkt aufgrund Ihrer bisherigen Eingabe befindet. Blaue Bälle stehen für eine vom Automaten akzeptierte Eingabe, die aber noch mehr benötigt, um eine vollständige Übereinstimmung zu erreichen. Grüne Bälle zeigen, dass der endliche Automat das ganze Muster abbilden kann. Wenn keine Bälle zu sehen sind, kann der Automat mit der Eingabe nichts anfangen.

Tools für das Arbeiten mit regulären Ausdrücken | 17

Abbildung 1-7: reAnimator

reAnimator zeigt Ihnen nur dann eine Übereinstimmung, wenn der reguläre Ausdruck zum ganzen Eingabetext passt – so also ob Sie ihn zwischen die Anker ‹^› und ‹$› gesetzt hätten. Das ist eine weitere Eigenschaft von Ausdrücken, die im mathematischen Sinn regulär sind.

Weitere Desktop-Tools für das Arbeiten mit regulären Ausdrücken Expresso Expresso (nicht zu verwechseln mit dem koffeinhaltigen Espresso) ist eine .NET-Anwendung, mit der reguläre Ausdrücke erstellt und getestet werden können. Das Programm lässt sich unter http://www.ultrapico.com/Expresso.htm herunterladen. Um es zu nutzen, muss das .NET Framework 2.0 auf Ihrem Computer installiert sein. Beim Download handelt es sich um eine Testversion, die 60 Tage kostenlos nutzbar ist. Danach müssen Sie das Programm registrieren, da ansonsten ein Großteil der Funktionalität nicht mehr nutzbar ist. Das Registrieren ist kostenfrei, aber Sie müssen den Jungs von Ultrapico Ihre E-Mail-Adresse mitteilen. Der Registrierungsschlüssel wird per E-Mail verschickt.

18 | Kapitel 1: Einführung in reguläre Ausdrücke

Expresso präsentiert sich, wie in Abbildung 1-8 gezeigt. Der Bereich Regular Expression, in den Sie Ihren regulären Ausdruck eingeben, ist immer sichtbar. Es gibt kein SyntaxHighlighting. Im Bereich Regex Analyzer wird automatisch eine kurze Analyse Ihres regulären Ausdrucks in englischer Sprache aufgebaut. Auch er ist stets sichtbar.

Abbildung 1-8: Expresso

Im Designmodus können Sie am unteren Ende des Fensters die Regex-Optionen setzen, zum Beispiel Ignore Case. Den meisten Platz auf dem Bildschirm nimmt eine Reihe von Registerkarten ein, auf denen Sie das Regex-Token auswählen können, das Sie einfügen wollen. Wenn Sie zwei Monitore nutzen oder auch einen großen, klicken Sie auf den Undock-Button, um die Registerkarten zu „lösen“. Dann können Sie Ihren regulären Ausdruck auch im anderen Modus (Testmodus) erstellen. Im Testmodus geben Sie Ihren Ausgangstext in der linken unteren Ecke ein. Dann klicken Sie auf den Button Run Match, um eine Liste aller Übereinstimmungen im Bereich Search Results zu erhalten. Es gibt keine Hervorhebungen des Ausgangstexts. Klicken Sie auf eine Übereinstimmung in den Ergebnissen, um die entsprechende Stelle im Ausgangstext anzuwählen. Tools für das Arbeiten mit regulären Ausdrücken | 19

Die Expression Library enthält eine Liste mit Beispiel-Regexes und eine mit den zuletzt verwendeten. Ihre Regex wird dieser Liste immer dann hinzugefügt, wenn Sie Run Match anklicken. Sie können die Bibliothek über das Menü Library im Hauptmenü anpassen.

The Regulator The Regulator kann von http://sourceforge.net/projects/regulator heruntergeladen werden. Dabei handelt es sich um eine weitere .NET-Anwendung für das Erstellen und Testen regulärer Ausdrücke. Die neueste Version erfordert .NET 2.0 oder neuer. Ältere Versionen für .NET 1.x können immer noch heruntergeladen werden. The Regulator ist Open Source, und man muss weder etwas bezahlen, noch muss man sich registrieren. Im Regulator passiert alles in einem Fenster (Abbildung 1-9). Auf der Registerkarte New Document geben Sie Ihren regulären Ausdruck ein. Es gibt ein automatisches SyntaxHighlighting, aber Syntaxfehler in Ihrer Regex werden nicht hervorgehoben. Per Rechtsklick wählen Sie das Regex-Token aus, das Sie aus einem Menü einfügen wollen. Sie können die Optionen für reguläre Ausdrücke über die Buttons in der Toolbar setzen. Die Icons sind ein wenig kryptisch. Warten Sie auf den Tooltipp, um zu verstehen, welche Einstellung Sie mit welchem Button vornehmen.

Abbildung 1-9: The Regulator

Rechts unterhalb des Bereichs für Ihre Regex klicken Sie auf den Input-Button, um den Bereich anzuzeigen, in dem Ihr Ausgangstext eingegeben werden kann. Klicken Sie auf den Button Replace with, um den Ersetzungstext einzugeben, wenn Sie suchen und ersetzen wollen. Links unterhalb der Regex können Sie die Ergebnisse Ihrer Regex-Operation sehen. Diese werden nicht automatisch aktualisiert, Sie müssen stattdessen auf einen der Buttons Match, Replace oder Split in der Toolbar klicken. Es gibt für die Eingabe kein Highlighting. Klicken Sie auf eine Übereinstimmung in den Ergebnissen, um sich den entsprechenden Teil im Ausgangstext anzeigen zu lassen.

20 | Kapitel 1: Einführung in reguläre Ausdrücke

Das Panel Regex Analyzer zeigt eine einfache Analyse Ihres regulären Ausdrucks in englischer Sprache. Diese wird allerdings nicht automatisch erstellt und ist auch nicht interaktiv. Um die Analyse zu aktualisieren, wählen Sie im Menü View den Punkt Regex Analyzer, auch wenn sie schon sichtbar ist. Klicken Sie dagegen auf die Analyse, wird nur der Textcursor bewegt.

grep Der Name grep leitet sich vom Befehl g/re/p ab, der im Unix-Texteditor ed eine Suche mithilfe eines regulären Ausdrucks durchführt. Dieser Befehl wurde so häufig genutzt, dass mittlerweile alle Unix-Systeme ein eigenes grep-Tool haben, um Dateien mit regulären Ausdrücken zu durchsuchen. Wenn Sie Unix, Linux oder OS X nutzen, geben Sie in einem Terminal-Fenster man grep ein, um alles darüber zu lernen. Die folgenden drei Tools sind Windows-Anwendungen, die das tun, was auch grep tut, und sogar mehr.

PowerGREP PowerGREP wurde von Jan Goyvaerts entwickelt, einem der Autoren dieses Buchs, und ist vermutlich das umfassendste grep-Tool, das für Microsoft Windows verfügbar ist (Abbildung 1-10). PowerGREP nutzt eine eigene Regex-Variante, die das Beste aller in diesem Buch besprochenen Varianten kombiniert. Diese Variante wird in RegexBuddy als „JGsoft“ bezeichnet. Um schnell eine Suche mit regulären Ausdrücken durchzuführen, klicken Sie im ActionMenü einfach auf Clear und geben Ihren regulären Ausdruck in das Suchfeld des ActionPanels ein. Klicken Sie dann auf einen Ordner im Panel File Selector und wählen Sie im Menü File Selector entweder Include File or Folder oder Include Folder and Subfolders. Dann wählen Sie im Action-Menü Execute aus, um Ihre Suche zu starten. Um Suchergebnisse auch zu ersetzen, wählen Sie in der linken oberen Ecke des ActionPanels in der Auswahlliste Action Type den Eintrag search-and-replace aus. Es erscheint dann unterhalb des Suchfelds ein Replace-Feld. Geben Sie hier Ihren Ersetzungstext ein. Alle anderen Schritte laufen genau so ab wie beim reinen Suchen. PowerGREP besitzt die einmalige Fähigkeit, bis zu drei Listen mit regulären Ausdrücken gleichzeitig nutzen zu können, wobei jede Liste eine beliebige Anzahl von Regexes enthalten kann. Während die beiden vorigen Absätze beschrieben haben, wie Sie einfache Suchen durchführen können, die auch jedes andere grep-Tool ermöglicht, muss man schon ein bisschen in der umfangreichen Dokumentation zu PowerGREP lesen, um alle Möglichkeiten ausschöpfen zu können.

Tools für das Arbeiten mit regulären Ausdrücken | 21

Abbildung 1-10: PowerGREP

PowerGREP läuft unter Windows 98, ME, 2000, XP und Vista. Sie können eine kostenlose Testversion über http://www.powergrep.com/PowerGREPCookbook.exe herunterladen. Abgesehen von der Möglichkeit, Ergebnisse und Bibliotheken speichern zu können, ist die Testversion für 15 Tage vollständig nutzbar. In dieser Zeit lassen sich zwar keine Ergebnisse aus dem Results-Panel abspeichern, aber Suchen-und-Ersetzen-Vorgänge ändern trotzdem Ihre Dateien, so wie es die vollständige Version auch tut.

Windows Grep Windows Grep (http://www.wingrep.com) ist eines der ältesten grep-Tools für Windows. Seine Oberfläche ist zwar schon etwas angestaubt (Abbildung 1-11), aber es erledigt das, was es soll, ohne Probleme. Dabei wird eine eingeschränkte Regex-Variante namens POSIX ERE unterstützt. Bei den unterstützten Features wird die gleiche Syntax genutzt wie bei den Varianten in diesem Buch. Windows Grep ist Shareware, Sie können es also kostenlos herunterladen, aber es wird erwartet, dass Sie es bezahlen, wenn Sie längerfristig damit arbeiten wollen. Um mit einer Suche zu beginnen, wählen Sie im Search-Menü den Eintrag Search. Das sich daraufhin öffnende Fenster sieht je nach gewähltem Modus unterschiedlich aus – es gibt im Options-Menü einen Beginner Mode und einen Expert Mode. Anfänger erhalten

22 | Kapitel 1: Einführung in reguläre Ausdrücke

Abbildung 1-11: Windows Grep

einen Wizard, der sie durch die einzelnen Schritte führt, während Experten einen Dialog mit Registerkarten vorfinden. Wenn Sie eine Suche eingerichtet haben, führt Windows Grep sie sofort aus und zeigt Ihnen eine Liste von Dateien an, in denen Treffer gefunden wurden. Klicken Sie auf eine der Dateien, um die Übereinstimmungen zu sehen. Per Doppelklick öffnen Sie die Datei. Mit All Matches im View-Menü zeigt das untere Panel alles an. Um ein Suchen und Ersetzen durchzuführen, wählen Sie im Search-Menü den Eintrag Replace.

RegexRenamer RegexRenamer (Abbildung 1-12) ist eigentlich kein grep-Tool. Statt den Inhalt von Dateien zu durchsuchen, sucht und ersetzt es in Dateinamen. Sie können das Programm auf http://regexrenamer.sourceforge.net herunterladen. RegexRenamer benötigt das .NET Framework von Microsoft in der Version 2.0.

Tools für das Arbeiten mit regulären Ausdrücken | 23

Abbildung 1-12: RegexRenamer

Geben Sie Ihren regulären Ausdruck in das Feld Match ein, den Ersetzungstext ins Feld Replace. Klicken Sie auf /i, um Groß- und Kleinschreibung zu ignorieren, oder auf /g, um alle Übereinstimmungen in einem Dateinamen zu ersetzen statt nur die erste. Mit /x wird zur Freiformsyntax gewechselt, was hier nicht sehr sinnvoll ist, da Sie nur eine Zeile haben, um Ihren regulären Ausdruck einzugeben. Nutzen Sie den Baum auf der linken Seite, um den Ordner auszuwählen, der die umzubenennenden Dateien enthält. Sie können in der rechten oberen Ecke eine Dateimaske oder einen Regex-Filter definieren. Damit wird die Liste der Dateien, auf die Ihre Regex zum Suchen und Ersetzen angewandt werden soll, eingeschränkt. Es ist viel praktischer, eine Regex zum Filtern und eine zum Ersetzen zu nutzen, statt beides mit einer einzigen Regex abhandeln zu wollen.

Beliebte Texteditoren Die meisten modernen Texteditoren bieten zumindest eine grundlegende Unterstützung für reguläre Ausdrücke an. Beim Suchen oder beim Ersetzen finden Sie im Allgemeinen eine Checkbox, mit der reguläre Ausdrücke genutzt werden können. Manche Editoren, wie zum Beispiel EditPad Pro, nutzen zudem reguläre Ausdrücke für verschiedenste Features bei der Textbearbeitung, wie zum Beispiel beim Syntax-Highlighting oder zum Erstellen von Klassen- und Funktionslisten. Die Dokumentation jedes Editors beschreibt alle diese Features. Einige der beliebtesten Texteditoren mit einer Unterstützung regulärer Ausdrücke sind:

24 | Kapitel 1: Einführung in reguläre Ausdrücke

• Boxer Text Editor (PCRE) • Dreamweaver (JavaScript) • EditPad Pro (eigene Variante, die das Beste der Varianten aus diesem Buch kombiniert, in RegexBuddy als „JGsoft“ bezeichnet) • Multi-Edit (PCRE, wenn Sie die Option Perl auswählen) • NoteTab (PCRE) • UltraEdit (PCRE) • TextMate (Ruby 1.9 [Oniguruma])

Tools für das Arbeiten mit regulären Ausdrücken | 25

KAPITEL 2

Grundlagen regulärer Ausdrücke

Die in diesem Kapitel vorgestellten Probleme sind keine echten Probleme, die Ihr Chef oder Ihr Kunde von Ihnen gelöst haben will. Stattdessen sind es technische Probleme, denen Sie sich vielleicht gegenübersehen, wenn Sie reguläre Ausdrücke erstellen oder bearbeiten, mit denen die eigentlichen Probleme gelöst werden sollen. Das erste Rezept erklärt zum Beispiel, wie man literalen Text mit einem regulären Ausdruck finden kann. Das ist normalerweise nicht das eigentliche Ziel, da Sie keinen regulären Ausdruck brauchen, wenn Sie nur nach literalem Text suchen wollen. Aber wenn Sie eine Regex erstellen, werden Sie auch literalen Text finden wollen, daher müssen Sie wissen, welche Zeichen zu maskieren sind. Rezept 2.1 beschreibt Ihnen, wie das geht. Die Rezepte beginnen mit sehr einfachen Techniken für reguläre Ausdrücke. Wenn bereits Sie Regexes verwendet haben, reicht es wahrscheinlich, sie nur zu überfliegen, oder Sie überspringen sie sogar. Die Rezepte weiter unten in diesem Kapitel werden Ihnen dann aber sicherlich doch etwas Neues vermitteln, sofern Sie nicht schon Reguläre Ausdrücke von Jeffrey E. F. Friedl (O’Reilly) von vorne bis hinten durchgelesen haben. Wir haben die Rezepte in diesem Kapitel so zusammengestellt, dass jedes einen bestimmten Aspekt der Syntax regulärer Ausdrücke erklärt. Lesen Sie sie von Anfang bis Ende, um sich ein solides Wissen über regulären Ausdrücke anzueignen. Oder Sie schauen sich direkt die reguläre Ausdrücke aus der realen Welt an, die Sie in den Kapiteln 4 bis 8 finden, um dann dort den Verweisen auf dieses Kapitel zu folgen, wenn Sie sich einer Ihnen unbekannten Syntax gegenübersehen. Dieses Tutorium-Kapitel kümmert sich nur um reguläre Ausdrücke und ignoriert vollständig sämtliche Überlegungen zur Programmierung. Das nächste Kapitel ist das mit den ganzen Codebeispielen. Sie können schon mal einen Blick auf „Programmiersprachen und Regex-Varianten“ in Kapitel 3 werfen, um herauszufinden, welche Variante regulärer Ausdrücke Ihre Programmiersprache nutzt. Die Varianten, die in diesem Kapitel behandelt werden, wurden in „Regex-Varianten in diesem Buch“ auf Seite 3, vorgestellt.

| 27

2.1

Literalen Text finden

Problem Erstellen eines regulären Ausdrucks, der genau auf den so hübsch ausgedachten folgenden Satz passt: Die Sonderzeichen in der ASCII-Tabelle sind: !"#$%&'()*+,-./:;?@ [\]^_`{|}~.

Lösung DiezSonderzeichenzinzderzASCII-Tabellezsind:z !"#\$%&'\(\)\*\+,-\./:;‹=>\?@\[\\]\^_`\{\|}~

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Jeder reguläre Ausdruck, der keines der zwölf Zeichen $()*+.?[\^{| enthält, findet einfach sich selbst. Um herauszufinden, ob sich in dem Text, den Sie gerade bearbeiten, Ein Loch ist im Eimer findet, suchen Sie einfach nach ‹EinzLochzistzimzEimer›. Es ist dabei egal, ob das Kontrollkästchen Regulärer Ausdruck in Ihrem Texteditor markiert ist oder nicht. Die zwölf Sonderzeichen, durch die die regulären Ausdrücke so spannend werden, heißen Metazeichen. Wenn Sie mit Ihrer Regex literal nach ihnen suchen wollen, müssen Sie sie maskieren, indem Sie einen Backslash vor sie setzen. Somit passt die Regex: \$\(\)\*\+\.\?\[\\\^\{\|

zum Text: $()*+.?[\^{|

Bemerkenswerterweise fehlen in dieser Liste die schließende eckige Klammer ], der Bindestrich - und die schließende geschweifte Klammer }. Die Klammer ]wird nur dann zu einem Metazeichen, wenn es vorher ein nicht maskiertes [ gab, und } nur nach einem nicht maskierten {. Es gibt keinen Grund, } jemals zu maskieren. Regeln für Metazeichen für die Blöcke zwischen [ und ] werden in Rezept 2.3 erläutert. Das Maskieren eines beliebigen anderen nicht alphanumerischen Zeichens ändert nichts daran, wie Ihr regulärer Ausdruck arbeitet – zumindest nicht, solange Sie mit einer der in diesem Buch behandelten Varianten arbeiten. Das Maskieren eines alphanumerischen Zeichens verpasst ihm entweder eine spezielle Bedeutung, oder es führt zu einem Syntaxfehler. User, die mit regulären Ausdrücken noch nicht so vertraut sind, maskieren häufig alle Sonderzeichen, die ihnen über den Weg laufen. Lassen Sie nicht jeden wissen, dass Sie ein Anfänger sind. Maskieren Sie weise. Ein Dschungel unnötiger Backslashs sorgt dafür,

28 | Kapitel 2: Grundlagen regulärer Ausdrücke

dass reguläre Ausdrücke schwerer zu lesen sind, insbesondere wenn alle diese Backslashs auch noch verdoppelt werden müssen, um die Regex als literalen String im Quellcode unterbringen zu können.

Variationen Blockmaskierung DiezSonderzeichenzinzderzASCII-Tabellezsind:z \Q!"#$%&'()*+,-./:;?@[\]^_`{|}~\E

Regex-Optionen: Keine Regex-Varianten: Java 6, PCRE, Perl Perl, PCRE und Java unterstützen die Regex-Tokens ‹\Q› und ‹\E›. ‹\Q› unterdrückt die Bedeutung aller Metazeichen, einschließlich des Backslashs, bis ein ‹\E› kommt. Wenn Sie ‹\E› weglassen, werden alle Zeichen nach dem ‹\Q› bis zum Ende des Regex als Literale behandelt. Einziger Vorteil von ‹\Q...\E› ist, dass es leichter lesbar ist als ‹\.\.\.›. Obwohl Java 4 und 5 dieses Feature unterstützen, sollten Sie es nicht verwenden. Fehler in der Implementierung führen dazu, dass reguläre Ausdrücke mit ‹\Q...\E› zu etwas anderem passen, als Sie erwarten und als PCRE, Perl oder Java 6 finden. Diese Fehler wurden in Java 6 behoben, womit es sich genau so verhält wie PCRE und Perl.

Übereinstimmungen unabhängig von Groß- und Kleinschreibung ascii

Regex-Optionen: Ignorieren von Groß- und Kleinschreibung Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby (?i)ascii

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Standardmäßig reagieren reguläre Ausdrücke auf Groß- und Kleinschreibung. ‹regex› passt zu regex, aber nicht zu Regex, REGEX oder ReGeX. Um ‹regex› zu all diesen Texten passen zu lassen, muss die Regex Groß- und Kleinschreibung ignorieren. Bei den meisten Anwendungen lässt sich das über das Markieren eines Kontrollkästchens erledigen. Alle Programmiersprachen, die im nächsten Kapitel behandelt werden, haben eine Option oder Eigenschaft, die Sie setzen können, damit Ihre Regex nicht auf Großund Kleinschreibung reagiert. Rezept 3.4 im nächsten Kapitel erläutert, wie Sie die zu jeder Lösung aufgeführten Regex-Optionen in Ihrem Quellcode umsetzen können.

2.1 Literalen Text finden | 29

Wenn Sie das Ignorieren von Groß- und Kleinschreibung nicht außerhalb der Regex einschalten können, lässt sich das auch über den Modus-Modifikator ‹(?i)› erreichen, wie bei ‹(?i)regex›. Das funktioniert mit den Varianten .NET, Java, PCRE, Perl, Python und Ruby. .NET, Java, PCRE, Perl und Ruby unterstützen lokale Modus-Modifikatoren, die nur einen Teil des regulären Ausdrucks beeinflussen. ‹empfindlich(?i)unempfindlich(?-i) empfindlich› passt zu empfindlichUNEMPFINDLICHempfindlich, aber nicht zu EMPFINDLICHunempfindlichEMPFINDLICH. ‹(?i)› schaltet das Ignorieren von Groß- und Kleinschreibung für den Rest der Regex ein, während ‹(?-i)› dies wieder deaktiviert. Beide lokalen Modifikatoren funktionieren als Umschalter. Rezept 2.10 zeigt, wie man lokale Modus-Modifikatoren mit Gruppen statt mit Umschaltern nutzt.

Siehe auch Rezepte 2.3 und 5.14.

2.2

Nicht druckbare Zeichen finden

Problem Finden eines Strings mit den folgenden ASCII-Steuerzeichen: Bell, Escape, Form Feed, Line Feed, Carriage Return, horizontaler Tab, vertikaler Tab. Diese Zeichen haben die hexadezimalen ASCII-Codes 07, 1B, 0C, 0A, 0D, 09, 0B.

Lösung \a\e\f\n\r\t\v

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Python, Ruby \x07\x1B\f\n\r\t\v

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Python, Ruby \a\e\f\n\r\t\0x0B

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Diskussion Sieben der meistgenutzten ASCII-Steuerzeichen haben eigene Maskierungssequenzen. Sie bestehen alle aus einem Backslash, gefolgt von einem Buchstaben. Das ist die gleiche Syntax, die auch in vielen Programmiersprachen für String-Literale genutzt wird. Tabelle 2-1 zeigt die gebräuchlichsten nicht druckbaren Zeichen und ihre Repräsentation.

30 | Kapitel 2: Grundlagen regulärer Ausdrücke

Tabelle 2-1: Nichtdruckbare Zeichen Repräsentation

Bedeutung

Hexadezimale Repräsentation

‹\a›

Bell

0x07

‹\e›

Escape

0x1B

‹\f›

Form Feed/Seitenvorschub

0x0C

‹\n›

Line Feed (Newline)/Zeilenvorschub

0x0A

‹\r›

Carriage Return/Wagenrücklauf

0x0D

‹\t›

horizontaler Tab

0x09

‹\v›

vertikaler Tab

0x0B

Der Standard ECMA-262 unterstützt ‹\a› und ‹\e› nicht. Daher nutzen wir für die JavaScript-Beispiele im Buch eine andere Syntax, auch wenn viele Browser ‹\a› und ‹\e› trotzdem unterstützen. Perl unterstützt ‹\v› nicht, daher müssen wir hier eine andere Syntax für den vertikalen Tab verwenden. Diese Steuerzeichen, wie auch ihre alternative Syntax, die im folgenden Abschnitt gezeigt wird, können in Ihrem regulären Ausdruck innerhalb und außerhalb von Zeichenklassen gleichermaßen genutzt werden.

Variationen zur Repräsentation nicht druckbarer Zeichen Die 26 Steuerzeichen \cG\x1B\cL\cJ\cM\cI\cK

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Ruby 1.9 Mit ‹\cA› bis ‹\cZ› können Sie eines der 26 Steuerzeichen finden, die in der ASCIITabelle die Positionen 1 bis 26 einnehmen. Das c muss dabei kleingeschrieben sein. In den meisten Varianten ist es egal, ob der dem c folgende Buchstabe klein- oder großgeschrieben ist. Wir empfehlen aber, immer einen Großbuchstaben zu nutzen, da Java dies benötigt. Die Syntax kann praktisch sein, wenn Sie es gewohnt sind, Steuerzeichen auf Konsolensystemen einzugeben, indem Sie die Strg-Taste zusammen mit einem Buchstaben drücken. Auf einem Terminal schickt Strg-H einen Rückschritt. In einer Regex findet ‹\cH› dementsprechend einen Rückschritt. Python und die klassische Ruby-Engine in Ruby 1.8 unterstützen diese Syntax nicht, die Oniguruma-Engine in Ruby 1.9 dagegen schon. Das Escape-Steuerzeichen an Position 27 in der ASCII-Tabelle lässt sich so mit dem englischen Alphabet nicht mehr abbilden, daher müssen wir in diesem Fall in unserem regulären Ausdruck ‹\x1B› nutzen.

2.2 Nicht druckbare Zeichen finden |

31

Der 7-Bit-Zeichensatz \x07\x1B\x0C\x0A\x0D\x09\x0B

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Ein kleines \x gefolgt von zwei hexadezimalen Ziffern (als Großbuchstaben) findet ein einzelnes Zeichen im ASCII-Set. Abbildung 2-1, zeigt, welche hexadezimalen Kombinationen von ‹\x00› bis ‹\x7F› zu welchem Zeichen im ganzen ASCII-Zeichensatz passen. Die Tabelle ist so aufgebaut, dass die erste hexadezimale Ziffer links von oben nach unten läuft, während die zweite Ziffer oben von links nach rechts läuft.

0 1 2 3 4 5 6 7

0 1 2 3 4 5 6 7 8 9 NUL SOH STX ETX EOT ENQ ACK BEL BS HT DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SP ! " # $ % & ' ( ) 0 1 2 3 4 5 6 7 8 9 @ A B C D E F G H I P Q R S T U V W X Y ` a b c d e f g h i p q r s t u v w x y

A B C LF VT FF SUB ESC FS * + , : ; < J K L Z [ \ j k l z { |

D CR GS = M ] m }

E SO RS . > N ^ n ~

F SI US / ? O _ o DEL

Abbildung 2-1ASCII-Tabelle

Es hängt von Ihrer Regex-Engine und der Codepage, in der Ihr Ausgangstext kodiert ist, ab, welche Zeichen von ‹\x80› bis ‹\xFF› passen. Wir empfehlen, ‹\x80› bis ‹\xFF› nicht zu verwenden. Stattdessen greifen Sie besser auf die Unicode-Codepoint-Token zurück, die in Rezept 2.7, beschrieben werden. Wenn Sie Ruby 1.8 nutzen oder PCRE ohne UTF-8-Unterstützung kompiliert haben, können Sie die Unicode-Codepoints nicht nutzen. Ruby 1.8 und PCRE ohne UTF-8 sind Regex-Engines für 8-Bit-Zeichen. Sie kennen keine Textkodierungen oder Zeichen aus mehr als einem Byte. ‹\xAA› findet in diesen Engines einfach das Byte 0xAA, egal welches Zeichen 0xAA darstellt oder ob 0xAA sogar Teil eines Multibyte-Zeichens ist.

Siehe auch Rezept 2.7.

32 | Kapitel 2: Grundlagen regulärer Ausdrücke

2.3

Ein oder mehrere Zeichen finden

Problem Erstellen eines regulären Ausdrucks, der alle üblichen Schreibfehler von Kalender findet, sodass Sie dieses Wort in einem Dokument finden können, ohne den Rechtschreibkünsten des Autors vertrauen zu müssen. Es soll für jeden Vokal ein a oder e möglich sein. Zudem soll ein anderer regulärer Ausdruck erstellt werden, um ein einzelnes hexadezimales Zeichen zu finden. Schließlich soll noch eine dritte Regex erstellt werden, um ein einzelnes Zeichen zu finden, das kein hexadezimales Zeichen ist.

Lösung Kalender mit Schreibfehlern K[ae]l[ae]nd[ae]r

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Hexadezimales Zeichen [a-fA-F0-9]

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Nicht hexadezimales Zeichen [^a-fA-F0-9]

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Die Notation mit eckigen Klammern wird als Zeichenklasse bezeichnet. Eine Zeichenklasse passt zu einem einzelnen Zeichen aus einer Liste möglicher Zeichen. Die drei Klassen in der ersten Regex passen entweder zu einem a oder einem e, und zwar unabhängig voneinander. Wenn Sie Kalender mit dieser Regex testen, passt die erste Zeichenklasse zu a, die zweite zu e und die dritte ebenfalls zu e. Außerhalb von Zeichenklassen sind zwölf Sonderzeichen Metazeichen. Innerhalb einer Zeichenklasse haben nur vier Zeichen eine besondere Bedeutung: \, ^, - und ]. Wenn Sie Java oder .NET nutzen, ist die öffnende eckige Klammer [ ebenfalls ein Metazeichen innerhalb einer Zeichenklasse. Alle anderen Zeichen sind Literale und ergänzen die Zei-

2.3 Ein oder mehrere Zeichen finden | 33

chenklasse. Der reguläre Ausdruck ‹[$()*+.?{|]› passt zu jedem der neun Zeichen innerhalb der eckigen Klammern. Der Backslash maskiert immer das ihm folgende Zeichen, so wie er es auch außerhalb von Zeichenklassen tut. Das maskierte Zeichen kann ein einzelnes Zeichen sein oder der Anfang bzw. das Ende eines Bereichs. Die anderen vier Metazeichen spielen ihre spezielle Bedeutung nur dann aus, wenn sie an einer bestimmten Position stehen. Es ist möglich, sie als literale Zeichen in eine Zeichenklasse mit aufzunehmen, ohne sie zu maskieren. Dazu muss man sie dort einfügen, wo sich ihre besondere Bedeutung nicht auswirkt. ‹[][^-]› macht das deutlich, wenn Sie nicht gerade eine JavaScript-Implementierung nutzen, die sich strikt an den Standard hält. Aber wir empfehlen Ihnen, diese Metazeichen immer zu maskieren, sodass die vorige Regex so aussehen sollte: ‹[\]\[\^\-]›. Durch das Maskieren der Metazeichen wird Ihr regulärer Ausdruck besser verständlich. Alphanumerische Zeichen können nicht mit einem Backslash maskiert werden. Macht man es trotzdem, führt das entweder zu einem Fehler, oder man erzeugt ein Regex-Token (etwas mit einer speziellen Bedeutung in einem regulären Ausdruck). Wenn wir bestimmte andere Regex-Tokens besprechen, wie zum Beispiel in Rezept 2.2, erwähnen wir, ob sie innerhalb von Zeichenklassen genutzt werden können. Alle diese Tokens bestehen aus einem Backslash und einem Zeichen, manchmal gefolgt von einer Reihe weiterer Zeichen. Somit findet ‹[\r\n]› ein Carriage Return (\r) oder einen Line Break (\n). Der Zirkumflex (^) negiert die Zeichenklasse, wenn Sie ihn direkt nach der öffnenden eckigen Klammer platzieren. Dadurch wird durch die Zeichenklasse jedes Zeichen gefunden, das sich nicht in der Liste befindet. Eine negierte Zeichenklasse passt zu Zeilenumbruchzeichen, sofern Sie sie nicht zur negierten Zeichenklasse hinzufügen. Der Bindestrich (-) erstellt einen Bereich, wenn er zwischen zwei Zeichen platziert wird. Der Bereich besteht dann aus der Zeichenklasse mit dem Zeichen vor dem Bindestrich, dem Zeichen nach dem Bindestrich und allen Zeichen, die in numerischer Reihenfolge dazwischenliegen. Um zu wissen, welche Zeichen das sind, müssen Sie sich die ASCII- oder Unicode-Zeichentabelle anschauen. ‹[A-z]› enthält alle Zeichen in der ASCII-Tabelle zwischen dem großen A und dem kleinen z. Der Bereich enthält auch einige Sonderzeichen, daher sollte man eher die Zeichenklasse ‹[A-Z\[\\\]\^_`a-z]› nutzen, die die Zeichen expliziter angibt. Wir empfehlen Ihnen, Bereiche nur zwischen zwei Ziffern oder zwischen zwei Buchstaben, die beide entweder Groß- oder Kleinbuchstaben sind, zu erstellen Umgekehrte Bereiche, wie zum Beispiel ‹[z-a]›, sind nicht erlaubt.

Variationen Abkürzungen [a-fA-F\d]

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

34 | Kapitel 2: Grundlagen regulärer Ausdrücke

Sechs Regex-Tokens, die aus einem Backslash und einem Buchstaben bestehen, stehen jeweils für Abkürzungen von Zeichenklassen. Sie können diese sowohl innerhalb als auch außerhalb einer Zeichenklasse nutzen. ‹\d› und ‹[\d]› entsprechen beide einer einzelnen Ziffer. Jedes Regex-Token mit einem Kleinbuchstaben hat auch ein entsprechendes Token mit einem Großbuchstaben, der für das Gegenteil steht. Daher entspricht ‹\D› jedem Zeichen, das keine Ziffer ist, ist also identisch mit ‹[^\d]›. ‹\w› findet ein einzelnes Wortzeichen. Ein Wortzeichen ist ein Zeichen, das als Teil eines

Worts vorkommen kann. Dazu gehören Buchstaben, Ziffern und der Unterstrich. Diese Festlegung mutet ein bisschen seltsam an, aber sie wurde getroffen, da diese Zeichen üblicherweise auch für Bezeichner in Programmiersprachen erlaubt sind. ‹\W› passt dementsprechend zu jedem Zeichen, das nicht Teil eines Worts ist. In Java, JavaScript, PCRE und Ruby entspricht ‹\w› immer ‹[a-zA-Z0-9_]›. In .NET und Perl sind auch Zeichen und Ziffern aus allen anderen Schriftsystemen enthalten (Kyrillisch, Thailändisch und so weiter). In Python sind die anderen Schriftzeichen und -ziffern nur enthalten, wenn Sie beim Erzeugen der Regex die Option UNICODE oder U mitgeben. ‹\d› folgt in allen Varianten den gleichen Regeln. In .NET und Perl sind Ziffern aus anderen Schriftsystemen immer enthalten, während Python sie nur dann berücksichtigt, wenn Sie die Option UNICODE oder U mitgeben. ‹\s› findet jedes Whitespace-Zeichen. Dazu gehören Leerzeichen, Tabs und Zeilenumbrüche. In .NET, Perl und JavaScript passt ‹\s› auch zu jedem Zeichen, das durch den Unicode-Standard als Whitespace definiert ist. Beachten Sie, dass JavaScript für ‹\s› Unicode nutzt, für ‹\d› und ‹\w› aber ASCII. ‹\S› passt zu jedem Zeichen, das nicht von ‹\s› gefunden wird.

Das Ganze wird noch inkonsistenter, wenn wir ‹\b› hinzufügen. ‹\b› ist keine Abkürzung für eine Zeichenklasse, sondern eine Wortgrenze. Sie gehen vielleicht davon aus, dass ‹\b› Unicode unterstützt, wenn dies auch ‹\w› tut, und nur ASCII, wenn auch ‹\w› nur auf ASCII zurückgreift, aber das ist nicht immer der Fall. Der Abschnitt „Wortzeichen“ auf Seite 46 in Rezept 2.6 geht da näher drauf ein.

Groß- und Kleinschreibung ignorieren (?i)[A-F0-9]

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby (?i)[^A-F0-9]

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Das Ignorieren von Groß- und Kleinschreibung beeinflusst auch Zeichenklassen, egal ob durch eine externe Option gesetzt (siehe Rezept 3.4) oder durch einen Modus-Modifikator innerhalb der Regex (siehe Rezept 2.1). Die oben gezeigten beiden Regexes verhalten sich genau so wie die in der ursprünglichen Lösung.

2.3 Ein oder mehrere Zeichen finden | 35

JavaScript hält sich an die gleichen Regeln, unterstützt aber nicht ‹(?i)›. Um einen regulären Ausdruck in JavaScript Groß- und Kleinschreibung ignorieren zu lassen, setzen Sie beim Erstellen die Option /i.

Variantenspezifische Features Zeichenklassendifferenz in .NET [a-zA-Z0-9-[g-zG-Z]]

Dieser reguläre Ausdruck passt zu einem einzelnen hexadezimalen Zeichen, allerdings auf recht umständlichen Wegen. Die grundlegende Zeichenklasse passt zu jedem alphanumerischen Zeichen, und eine eingebettete Klasse zieht dann davon die Buchstaben g bis z ab. Diese eingebettete Klasse muss am Ende der Basisklasse erscheinen und durch ein Minuszeichen (einen Bindestrich) von ihr „abgezogen“ werden: ‹[Klasse-[Subtrahend]]›. Die Differenz von Zeichenklassen ist insbesondere im Zusammenhang mit UnicodeEigenschaften, -Blöcken und -Schriftsystemen nützlich. So findet zum Beispiel ‹\p{IsThai}› jedes Zeichen im Thai-Block. ‹\P{N}› findet jedes Zeichen, das keine Nummerneigenschaft besitzt. Kombiniert man beides mithilfe der Differenz, findet ‹[\p{IsThai}-[\P{N}]]› jede der zehn thailändischen Ziffern.

Zeichenklassenvereinigung, -differenz und -schnittmenge in Java [a-f[A-F][0-9]] [a-f[A-F[0-9]]]

Java erlaubt es, eine Zeichenklasse in einer anderen einzubetten. Wenn die eingebettete Klasse direkt enthalten ist, entspricht die Ergebnisklasse der Vereinigungsmenge beider Klassen. Sie können so viele Klassen einbetten, wie Sie wollen. Beide obigen Regexes haben den gleichen Effekt wie die ursprüngliche Regex ohne die zusätzlichen eckigen Klammern. [\w&&[a-fA-F0-9\s]]

Diese Regex könnte einen Preis in einem Regex-Verschleierungswettbewerb gewinnen. Die grundlegende Zeichenklasse passt zu jedem Wortzeichen. Die eingebettete Klasse findet jedes hexadezimale Zeichen und jeden Whitespace. Die Ergebnisklasse ist die Schnittmenge von beiden, wodurch nur hexadezimale Zeichen gefunden werden und sonst nichts. Da die Basisklasse kein Whitespace findet und die eingebettete Klasse nicht die Zeichen ‹[g-zG-Z_]›, werden alle aus der Ergebnis-Zeichenklasse herausgenommen und es verbleiben nur die hexadezimalen Zeichen. [a-zA-Z0-9&&[^g-zG-Z]]

Dieser reguläre Ausdruck findet ein einzelnes hexadezimales Zeichen, aber ebenfalls recht umständlich. Die grundlegende Zeichenklasse findet jedes alphanumerische Zeichen und eine eingebettete Klasse zieht dann davon die Buchstaben g bis z ab. Bei dieser

36 | Kapitel 2: Grundlagen regulärer Ausdrücke

eingebetteten Klasse muss es sich um eine negierte Zeichenklasse handeln, der zwei Kaufmanns-Und-Zeichen vorangestellt sind: ‹[Klasse&&[^Subtrahend]]›. Vereinigungen und Differenzen von Zeichenklassen sind besonders nützlich, wenn man mit Unicode-Eigenschaften, -Blöcken und -Schriftsystemen arbeitet. So findet zum Beispiel ‹\p{IsThai}› jedes Zeichen im Thai-Block. ‹\P{N}› findet jedes Zeichen, das keine Nummerneigenschaft besitzt. Zusammen findet ‹[\p{IsThai}-[\P{N}]]› jede der zehn thailändischen Ziffern. Wenn Sie sich über die feinen Unterschiede des Regex-Tokens ‹\p› wundern, werden Sie die Antworten darauf in Rezept 2.7 finden.

Siehe auch Rezepte 2.1, 2.2 und 2.7.

2.4

Ein beliebiges Zeichen finden

Problem Finden eines Zeichens in Anführungszeichen. Bereitstellen einer Lösung, die ein beliebiges Zeichen zwischen den Anführungszeichen findet, mit Ausnahme eines Zeilenumbruchs. Bereitstellen einer anderen Regex, die wirklich jedes Zeichen zulässt, auch Zeilenumbrüche.

Lösung Jedes Zeichen mit Ausnahme von Zeilenumbrüchen '.'

Regex-Optionen: Keine (die Option Punkt passt zu Zeilenumbruch darf nicht gesetzt sein) Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Jedes Zeichen einschließlich Zeilenumbrüchen '.'

Regex-Optionen: Punkt passt zu Zeilenumbruch Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby '[\s\S]'

Regex-Optionen: Keine Regex-Varianten .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

2.4 Ein beliebiges Zeichen finden | 37

Diskussion Jedes Zeichen außer Zeilenumbrüchen Der Punkt ist eines der ältesten und einfachsten Elemente regulärer Ausdrücke. Seine Bedeutung war schon immer die, ein beliebiges einzelnes Zeichen zu finden. Allerdings ist nicht ganz klar, was beliebiges Zeichen genau bedeutet. Die allerersten Tools, die reguläre Ausdrücke nutzten, verarbeiteten Dateien Zeile für Zeile, daher enthielt der Ausgangstext auch nie Zeilenumbrüche. Die in diesem Buch besprochenen Programmiersprachen verarbeiten den Ausgangstext als Ganzes, egal ob er Zeilenumbrüche enthält oder nicht. Wenn Sie wirklich eine zeilenbasierte Verarbeitung brauchen, müssen Sie etwas Code schreiben, der Ihren Text in ein Array mit Zeilen aufteilt und die Regex auf jede Zeile in diesem Array anwendet. Rezept 3.21 zeigt Ihnen, wie das geht. Larry Wall, der Entwickler von Perl, wollte, dass Perl sich so verhält wie die klassischen zeilenbasierten Tools, bei denen der Punkt niemals einen Zeilenumbruch (\n) fand. Alle anderen in diesem Buch behandelten Varianten haben sich daran orientiert. ‹.› findet daher jedes Zeichen mit Ausnahme eines Zeilenumbruchs.

Jedes Zeichen einschließlich Zeilenumbrüchen Wenn Ihr regulärer Ausdruck auch über mehr als eine Zeile hinaus arbeiten soll, müssen Sie die Option Punkt passt zu Zeilenumbruch aktivieren. Diese Option läuft unter verschiedenen Namen. Perl und viele andere nennt sie verwirrenderweise „Single Line“Modus, während Java sie als „Dot All“-Modus bezeichnet. In Rezept 3.4 im nächsten Kapitel finden Sie alle Details. Aber egal wie der Name dieser Option in Ihrer bevorzugten Programmiersprache lautet – bezeichnen Sie sie am besten als „Punkt passt zu Zeilenumbruch“-Modus. Denn genau das tut die Option. Bei JavaScript ist eine andere Lösung erforderlich, denn es besitzt diese Option nicht. Wie in Rezept 2.3 erläutert, findet ‹\s› jedes Whitespace-Zeichen, während ‹\S› jedes Zeichen findet, das nicht von ‹\s› gefunden wird. Kombiniert man das zu ‹[\s\S]›, erhält man eine Zeichenklasse, die alle Zeichen enthält, einschließlich der Zeilenumbrüche. ‹[\d\D]› und ‹[\w\W]› haben den gleichen Effekt.

Punkt-Missbrauch Der Punkt ist das am meisten missbrauchte Zeichen in regulären Ausdrücken. ‹\d\d.\d\d.\d\d› ist zum Beispiel kein guter regulärer Ausdruck, um ein Datum zu finden. Er passt zwar zu 16-05-08, aber auch zu 99/99/99. Schlimmer noch ist, dass er auch zu 12345678 passt. Ein guter regulärer Ausdruck, der nur gültige Datumswerte findet, wird in einem späteren Kapitel behandelt. Aber es ist sehr leicht, den Punkt durch eine passendere Zeichenklasse zu ersetzen. ‹\d\d[/.\-]\d\d[/.\-]\d\d› erlaubt einen Schrägstrich, einen Punkt oder einen Bindestrich als Datumstrenner. Diese Regex findet zwar immer noch 99/99/99, aber nicht mehr 12345678.

38 | Kapitel 2: Grundlagen regulärer Ausdrücke

Verwenden Sie den Punkt nur, wenn Sie wirklich jedes Zeichen zulassen wollen. In allen anderen Situationen sollten Sie eher eine Zeichenklasse oder eine negierte Zeichenklasse verwenden.

Variationen (?s)'.'

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Python (?m)'.'

Regex-Optionen: Keine Regex-Varianten: Ruby Wenn Sie den „Punkt passt zu Zeilenumbruch“-Modus nicht außerhalb des regulären Ausdrucks anschalten können, haben Sie die Möglichkeit, einen Modus-Modifikator an den Anfang des regulären Ausdrucks zu setzen. Wir erläutern das Konzept der ModusModifikatoren und deren Fehlen in JavaScript in Abschnitt „Übereinstimmungen unabhängig von Groß- und Kleinschreibung“ auf Seite 29 unter Rezept 2.1. ‹(?s)› ist der Modus-Modifikator für den „Punkt passt zu Zeilenumbruch“-Modus in .NET, Java, PCRE, Perl und Python. Das s steht für „Singe Line“-Modus, den etwas ver-

wirrenden Namen in Perl. Die Terminologie ist so irritierend, dass der Entwickler von Rubys Regex-Engine sie falsch kopiert hat. Ruby nutzt ‹(?m)›, um den „Punkt passt zu Zeilenumbruch“-Modus einzuschalten. Abgesehen vom Buchstaben ist die Funktionalität vollkommen identisch. Die neue Engine in Ruby 1.9 nutzt weiterhin ‹(?m)› für „Punkt passt zu Zeilenumbruch“. Die Bedeutung von ‹(?m)› in Perl wird in Rezept 2.5 erläutert.

Siehe auch Rezepte 2.3, 3.4 und 3.21.

2.5

Etwas am Anfang und/oder Ende einer Zeile finden

Problem Erstellen von vier regulären Ausdrücken. Finden des Worts Alpha, aber nur dann, wenn es am Anfang des Ausgangstexts steht. Finden des Worts Omega, aber nur, wenn es ganz am Ende des Ausgangstexts steht. Finden des Worts Anfang, aber nur, wenn es am Anfang einer Zeile steht. Finden des Worts Ende, aber nur, wenn es am Ende einer Zeile steht.

2.5 Etwas am Anfang und/oder Ende einer Zeile finden | 39

Lösung Anfang des Texts ^Alpha

Regex-Optionen: Keine (^ und $ passen auf Zeilenumbrüche darf nicht gesetzt sein) Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python \AAlpha

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Ende des Texts Omega$

Regex-Optionen: Keine (^ und $ passen auf Zeilenumbrüche darf nicht gesetzt sein) Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python Omega\Z

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Zeilenanfang ^Anfang

Regex-Optionen: ^ und $ passen auf Zeilenumbrüche Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Zeilenende Ende$

Regex-Optionen: ^ und $ passen auf Zeilenumbrüche Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Anker und Zeilen Die Regex-Tokens ‹^›, ‹$›, ‹\A›, ‹\Z› und ‹\z› werden als Anker bezeichnet. Sie passen zu keinem Zeichen. Stattdessen passen sie zu bestimmten Positionen, wodurch der reguläre Ausdruck an diesen Positionen verankert wird. Eine Zeile ist der Teil des Ausgangstexts, der zwischen dem Anfang des Texts und einem Zeilenumbruch, zwischen zwei Zeilenumbrüchen oder zwischen einem Zeilenumbruch und dem Ende des Texts liegt. Wenn es im Text keine Zeilenumbrüche gibt, wird der

40 | Kapitel 2: Grundlagen regulärer Ausdrücke

ganze Text als eine Zeile angesehen. Daher besteht der folgende Text aus vier Zeilen, jeweils einer für eins, zwei, einem leeren String und vier: eins zwei vier

Der Text könnte in einem Programm als eins(LF) zwei (LF|LF) vier repräsentiert werden.

Anfang des Texts Der Anker ‹\A› passt immer zum Anfang des Ausgangstexts, noch vor dem ersten Zeichen. Das ist der einzige Ort, an dem er passt. Setzen Sie ‹\A› an den Anfang Ihres regulären Ausdrucks, um zu prüfen, ob der Ausgangstext mit dem Text beginnt, den Sie finden wollen. Das „A“ muss ein Großbuchstabe sein. JavaScript unterstützt kein ‹\A›. Der Anker ‹^› entspricht ‹\A›, solange Sie nicht die Option ^ und $ passen auf Zeilenumbrüche aktiviert haben. Diese Option ist standardmäßig für alle Regex-Varianten außer Ruby abgeschaltet. Ruby bietet auch keine Möglichkeit an, diese Option zu deaktivieren. Sofern Sie nicht JavaScript nutzen, empfehlen wir Ihnen, immer ‹\A› statt ‹^› zu nutzen. Die Bedeutung von ‹\A› ändert sich niemals, dadurch vermeidet man Irritationen oder Fehler, wenn man die Regex-Optionen setzt.

Ende des Texts Die Anker ‹\Z› und ‹\z› passen immer zum Ende des Ausgangstexts, und zwar nach dem letzten Zeichen. Setzen Sie ‹\Z› oder ‹\z› an das Ende Ihres regulären Ausdrucks, um zu prüfen, ob der Ausgangstext mit dem Text endet, den Sie finden wollen. .NET, Java, PCRE, Perl und Ruby unterstützen sowohl ‹\Z› als auch ‹\z›. Python unterstützt nur ‹\Z›. JavaScript unterstützt weder ‹\Z› noch ‹\z›. Der Unterschied zwischen ‹\Z› und ‹\z› wird dann relevant, wenn das letzte Zeichen Ihres Ausgangstexts ein Zeilenumbruch ist. In diesem Fall passt ‹\Z› zum einen am Ende des Ausgangstexts, aber auch direkt vor diesem Zeilenumbruch. Der Vorteil ist, dass Sie nach ‹Omega\Z› suchen können, ohne sich darum kümmern zu müssen, einen abschließenden Zeilenumbruch zu entfernen. Wenn Sie eine Datei Zeile für Zeile einlesen, nehmen einige Tools den Zeilenumbruch am Ende der Zeile mit auf, während andere ihn weglassen. ‹\Z› übergeht diesen Unterschied, während ‹\z› nur ganz am Ende des Ausgangstexts passt, sodass es nicht passt, wenn noch ein abschließender Zeilenumbruch folgt. Der Anker ‹$› entspricht ‹\Z›, sofern Sie die Option ^ und $ passen auf Zeilenumbrüche nicht eingeschaltet haben. Diese Option ist standardmäßig für alle Regex-Varianten außer Ruby ausgeschaltet. Ruby bietet keine Möglichkeit an, diese Option zu deaktivieren. Wie bei ‹\Z› passt ‹$› ganz am Ende des Ausgangstexts, aber auch direkt vor dem letzten, abschließenden Zeilenumbruch, wenn es einen gibt. 2.5 Etwas am Anfang und/oder Ende einer Zeile finden | 41

Um diese subtile und ein wenig konfuse Situation zu entwirren, wollen wir uns ein Beispiel in Perl anschauen. Davon ausgehend, dass $/ (der aktuelle Datensatztrenner) auf den Standardwert \n gesetzt ist, liest die folgende Perl-Anweisung eine einzelne Zeile vom Terminal ein (Standardeingabe): $line = ;

Perl belässt den Zeilenumbruch im Inhalt der Variablen $line. Daher wird ein Ausdruck wie ‹EndezderzEingabe.\z› nicht passen. Aber sowohl ‹EndezderzEingabe.\Z› als auch ‹EndezderzEingabe.$› werden gefunden, da sie den abschließenden Zeilenumbruch ignorieren. Um die Verarbeitung zu vereinfachen, entfernen Perl-Programmierer häufig die Zeilenumbrüche mit: chomp $line;

Danach werden alle drei Anker passen. (Technisch gesehen, entfernt chomp den aktuellen Datensatzseperator vom Ende eines Strings.) Sofern Sie nicht JavaScript nutzen, empfehlen wir, immer ‹\Z› statt ‹$› zu verwenden. Die Bedeutung von ‹\Z› ändert sich niemals, dadurch vermeidet man Irritationen oder Fehler, wenn man die Regex-Optionen setzt.

Anfang einer Zeile Standardmäßig passt ‹^› nur am Anfang des Ausgangstexts, so wie ‹\A›. Lediglich in Ruby passt ‹^› immer am Anfang einer Zeile. Bei allen anderen Varianten müssen Sie die Option einschalten, mit der der Zirkumflex und das Dollarzeichen auch bei Zeilenumbrüchen passen. Diese Option wird üblicherweise als „Multiline“-Modus bezeichnet. Verwechseln Sie das nicht mit dem „Singleline“-Modus, der besser als „Punkt passt zu Zeilenumbruch“-Modus bezeichnet werden sollte. Der „Multiline“-Modus betrifft nur den Zirkumflex und das Dollarzeichen, während der „Singleline“-Modus ausschließlich den Punkt beeinflusst, wie in Rezept 2.4 beschrieben wurde. Es ist ohne Probleme möglich, sowohl den „Singleline“- als auch den „Multiline“-Modus gleichzeitig einzuschalten. Standardmäßig sind beide Optionen ausgeschaltet. Mit den korrekten Optionen passt ‹^› am Anfang jeder Zeile des Ausgangstexts. Genauer gesagt, passt er vor dem ersten Zeichen in der Datei, so wie er es immer tut, und nach jedem Zeilenumbruchzeichen im Ausgangstext. Der Zirkumflex ist in ‹\n^› redundant, weil ‹^› immer nach einem ‹\n› passt.

Ende einer Zeile Standardmäßig passt ‹$› nur am Ende des Ausgangstexts oder vor dem letzten Zeilenumbruch, so wie ‹\Z›. Lediglich in Ruby passt ‹$› immer am Ende jeder Zeile. Bei allen anderen Varianten müssen Sie die „Multiline“-Option aktivieren, damit Zirkumflex und Dollarzeichen auch bei Zeilenumbrüchen passen.

42 | Kapitel 2: Grundlagen regulärer Ausdrücke

Mit den korrekt gesetzten Optionen passt ‹$› am Ende jeder Zeile im Ausgangstext. (Natürlich passt es auch nach dem letzten Zeichen im Text, weil das immer das Ende einer Zeile ist.) Das Dollarzeichen ist in ‹$\n› überflüssig, weil ‹$› immer vor ‹\n› passt.

Finden von Text ohne Inhalt Ein regulärer Ausdruck kann problemlos aus nichts mehr als einem oder mehreren Ankern bestehen. Solch ein regulärer Ausdruck hat dann an jeder Stelle, an der der Anker passt, ein Suchergebnis der Länge null. Wenn Sie mehrere Anker kombinieren, müssen alle Anker an der gleichen Stelle passen, damit die Regex etwas findet. Sie können solch einen regulären Ausdruck beim Suchen und Ersetzen nutzen. Ersetzen Sie ‹\A› oder ‹\Z›, um dem gesamten Ausgangstext etwas voranzustellen oder hinten anzufügen. Ersetzen Sie ‹^› oder ‹$› im „^ und $ passen bei Zeilenumbruch“-Modus, um jeder Zeile im Ausgangstext etwas voranzustellen oder am Ende anzufügen. Kombinieren Sie zwei Anker, um auf leere Zeilen oder eine fehlende Eingabe zu testen. ‹\A\Z› passt beim leeren String, aber auch bei einem String, der nur einen einzelnen Zeilenumbruch enthält. ‹\A\z› passt nur beim leeren String, ‹^$› im „^ und $ passen bei Zeilenumbruch“-Modus passt bei jeder leeren Zeile im Ausgangstext.

Variationen (?m)^Anfang

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Python (?m)Ende$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Python Wenn sich der „^ und $ passen bei Zeilenumbruch“-Modus nicht außerhalb des regulären Ausdrucks aktivieren lässt, können Sie einen Modus-Modifikator an den Anfang des regulären Ausdrucks setzen. Das Konzept von Modus-Modifikatoren und die fehlende Unterstützung in JavaScript werden in Abschnitt „Übereinstimmungen unabhängig von Groß- und Kleinschreibung“ auf Seite 29 in Rezept 2.1 erläutert. ‹(?m)› ist der Modus-Modifikator für den „^ und $ passen bei Zeilenumbruch“-Modus in .NET, Java, PCRE, Perl und Python. Das m steht für „Multiline“-Modus. Das ist die

verwirrende Perl-Bezeichnung für „^ und $ passen bei Zeilenumbruch“. Wie schon erläutert hat diese Terminologie so verwirrt, dass der Entwickler von Rubys Regex-Engine es falsch kopiert hat. Ruby nutzt ‹(?m)›, um den „Punkt passt zu Zeilenumbruch“-Modus einzuschalten. Rubys ‹(?m)› hat nichts mit dem Zirkumflex- und Dollar-Anker zu tun. In Ruby passen ‹^› und ‹$› immer am Anfang und Ende jeder Zeile.

2.5 Etwas am Anfang und/oder Ende einer Zeile finden | 43

Abgesehen von der unglücklichen Verwechslung der Buchstaben ist die Entscheidung von Ruby, ‹^› und ‹$› exklusiv für Zeilen zu nutzen, eine gute Entscheidung. Sofern Sie nicht JavaScript nutzen, empfehlen wir Ihnen, das auch in Ihre eigenen regulären Ausdrücke zu übernehmen. Jan hat diese Idee beim Design von EditPad Pro und PowerGREP umgesetzt. Sie werden kein Kontrollkästchen finden, das den Text „^ und $ passen bei Zeilenumbruch“ enthält, aber es gibt natürlich eins für „Punkt passt zu Zeilenumbruch“. Sofern Sie Ihren regulären Ausdruck nicht mit ‹(?-m)› beginnen, müssen Sie ‹\A› und ‹\Z› nutzen, um Ihre Regex am Anfang oder Ende Ihrer Datei zu verankern.

Siehe auch Rezepte 3.4 und 3.21.

2.6

Ganze Wörter finden

Problem Erstellen einer Regex, die rot in Mein Auto ist rot findet, aber nicht in rotieren oder Graubrot. Erstellen einer anderen Regex, die rot in Karotte findet, aber in keinem der vorigen drei Strings.

Lösung Wortgrenzen \brot\b

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Nicht-Wortgrenzen \Brot\B

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Wortgrenzen Das Regex-Token ‹\b› wird als Wortgrenze bezeichnet. Es passt an den Anfang oder das Ende eines Worts. Allein liefert es ein Ergebnis ohne Länge zurück. ‹\b› ist ein Anker, so wie die Tokens, die im vorigen Abschnitt vorgestellt wurden.

44 | Kapitel 2: Grundlagen regulärer Ausdrücke

Genau genommen passt ‹\b› an diesen drei Stellen: • Vor dem ersten Zeichen des Texts, wenn das erste Zeichen ein Wortzeichen ist. • Nach dem letzten Zeichen des Texts, wenn das letzte Zeichen ein Wortzeichen ist. • Zwischen zwei Zeichen im Text, wenn eines ein Wortzeichen und das andere kein Wortzeichen ist. Keine der in diesem Buch behandelten Varianten hat eigene Tokens, die nur vor oder nur nach einem Wort passen. Sofern Sie keine Regexes erstellen wollen, die nur aus Wortgrenzen bestehen, sind diese auch nicht notwendig. Die Tokens vor oder nach dem ‹\b› in Ihrem regulären Ausdruck legen fest, wo ‹\b› passen kann. Das ‹\b› in ‹\bx› und ‹!\b› passt nur am Wortanfang. Das ‹\b› in ‹x\b› und ‹\b!› kann nur am Ende eines Worts passen. ‹x\bx› und ‹!\b!› können niemals irgendwo passen. Um eine Suche nach ganzen Wörtern mit regulären Ausdrücken durchzuführen, stecken Sie das Wort einfach zwischen zwei Wortgrenzen, so wie wir es mit ‹\brot\b› gemacht haben. Das erste ‹\b› benötigt das ‹r›, damit es am Anfang des Texts oder nach einem Nicht-Wortzeichen passt. Das zweite ‹\b› benötigt das ‹t›, damit es entweder am Ende des Strings oder vor einem Nicht-Wortzeichen passt. Zeilenumbrüche sind Nicht-Wortzeichen. ‹\b› passt also nach einem Zeilenumbruch, wenn diesem direkt ein Wortzeichen folgt. Auch passt es vor einem Zeilenumbruch, wenn direkt davor ein Wortzeichen steht. So wird also ein Wort, das eine ganze Zeile einnimmt, auch von einer „Wortsuche“ gefunden. ‹\b› wird nicht vom „Multiline“-Modus oder ‹(?m)› beeinflusst. Das ist einer der Gründe dafür, dass dieses Buch den „Multiline“-Modus als „^ und $ passen bei Zeilenumbruch“-Modus bezeichnet.

Nicht-Wortgrenzen ‹\B› passt an jeder Position im Ausgangstext, an der ‹\b› nicht passt, und ‹\B› passt an

jeder Position, die nicht der Anfang oder das Ende eines Worts ist. Genauer gesagt, passt ‹\B› an diesen fünf Stellen: • • • • •

Vor dem ersten Zeichen des Texts, wenn das erste Zeichen kein Wortzeichen ist. Nach dem letzten Zeichen des Texts, wenn das letzte Zeichen kein Wortzeichen ist. Zwischen zwei Wortzeichen. Zwischen zwei Nicht-Wortzeichen. Beim leeren String.

‹\Brot\B› passt zu rot in Karotte, aber nicht zu Mein Auto ist rot, rotieren oder Graubrot.

Um das Gegenteil einer reinen Wortsuche durchzuführen (also Mein Auto ist rot auszuschließen, aber Karotte, rotieren und Graubrot zu finden), müssen Sie eine Alternation nutzen, um ‹\Brot› und ‹rot\B› zu ‹\Brot|rot\B› zu kombinieren. ‹\Brot› findet rot in Karotte und Graubrot. ‹rot\B› findet rot in rotieren (und Karotte, wenn sich ‹\Brot› nicht schon darum gekümmert hat). Rezept 2.8 beschreibt die Alternation. 2.6 Ganze Wörter finden | 45

Wortzeichen Jetzt haben wir die ganze Zeit über Wortgrenzen geredet, aber nicht darüber, was ein Wortzeichen ist. Ein Wortzeichen ist ein Zeichen, das als Teil eines Worts vorkommen kann. Der Abschnitt „Abkürzungen“ auf Seite 34 in Rezept 2.3 hat erklärt, welche Zeichen in ‹\w› enthalten sind, womit ein einzelnes Wortzeichen gefunden wird. Leider gilt für ‹\b› nicht das Gleiche. Auch wenn alle Varianten in diesem Buch ‹\b› und ‹\B› unterstützen, unterscheiden sie sich darin, welche Zeichen für sie Wortzeichen sind. .NET, JavaScript, PCRE, Perl, Python und Ruby lassen ‹\b› zwischen zwei Zeichen passend sein, bei denen eines durch ‹\w› gefunden wird und das andere durch ‹\W›. ‹\B› passt immer zwischen zwei Zeichen, die entweder durch ‹\w› oder ‹\W› gefunden werden. JavaScript, PCRE und Ruby betrachten nur ASCII-Zeichen als Wortzeichen. ‹\w› entspricht ‹[a-zA-Z0-9_]›. Bei diesen Varianten können Sie eine Wortsuche in Sprachen vornehmen, die nur die Buchstaben A bis Z, aber keine diakritischen Zeichen nutzen, wie zum Beispiel Englisch. Es lassen sich dort aber keine Wortsuchen in anderen Sprachen vornehmen, wie zum Beispiel im Spanischen oder im Russischen. .NET und Perl behandeln Buchstaben und Ziffern aus allen Schriftsystemen als Wortzeichen. Bei diesen Varianten können Sie eine Wortsuche in jeder Sprache durchführen, auch in solchen, die keine lateinischen Buchstaben nutzen. Python lässt Ihnen die Wahl. Nicht-ASCII-Zeichen werden nur dann berücksichtigt, wenn Sie beim Erstellen der Regex die Option UNICODE oder U mitgeben. Diese Option beeinflusst ‹\b› und ‹\w› gleichermaßen. Java verhält sich inkonsistent. ‹\w› passt nur auf ASCII-Zeichen, ‹\b› berücksichtigt dagegen Unicode und funktioniert mit jeder Sprache. In Java findet ‹\b\w\b› einen einzelnen englischen Buchstaben, eine Ziffer oder den Unterstrich, der in keiner Sprache der Welt als Teil eines Worts vorkommt. ‹\b кошка \b› findet das russische Wort für „rot”, da ‹\b› Unicode unterstützt. Aber ‹\w+› wird kein russisches Wort finden, weil ‹\w› nur für ASCII-Zeichen ausgelegt ist.

Siehe auch Rezept 2.3.

46 | Kapitel 2: Grundlagen regulärer Ausdrücke

2.7

Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode

Problem Verwenden eines regulären Ausdrucks, um das Trademark-Zeichen (™) zu finden. Dazu soll sein Unicode-Codepoint angegeben und nicht ein vorhandenes Trademark-Zeichen kopiert werden. Wenn Sie kein Problem mit dem Kopieren haben, ist das TrademarkZeichen einfach nur ein weiteres literales Zeichen, selbst wenn Sie es nicht direkt über Ihre Tastatur eingeben können. Literale Zeichen werden in Rezept 2.1 behandelt. Erstellen eines regulären Ausdrucks, der jedes Zeichen mit der Unicode-Eigenschaft „Währungssymbol“ findet. Unicode-Eigenschaften werden auch als Unicode-Kategorien bezeichnet. Erstellen eines regulären Ausdrucks, der jedes Zeichen im Unicode-Block „Greek Extended“ findet. Erstellen eines regulären Ausdrucks, der jedes Zeichen findet, das laut Unicode-Standard Teil des griechischen Schriftsystems ist. Erstellen eines regulären Ausdrucks, der ein Graphem findet, also das, was üblicherweise als Zeichen angesehen wird: ein Basiszeichen mit all seinen Ergänzungen.

Lösung Unicode-Codepoint \u2122

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, Python Diese Regex funktioniert in Python nur, wenn sie als Unicode-String eingegeben wird: u"\u2122". \x{2122}

Regex-Optionen: Keine Regex-Varianten: PCRE, Perl, Ruby 1.9 PCRE muss mit UTF-8-Unterstützung kompiliert werden. In PHP muss die UTF-8Unterstützung durch den Muster-Modifikator /u aktiviert werden. Ruby 1.8 unterstützt keine Unicode-Regexes.

2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode | 47

Unicode-Eigenschaft oder -Kategorie \p{Sc}

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Ruby 1.9 PCRE muss mit UTF-8-Unterstützung kompiliert werden. In PHP muss die UTF-8Unterstützung durch den Muster-Modifikator /u aktiviert werden. JavaScript und Python unterstützen keine Unicode-Eigenschaften. Ruby 1.8 unterstützt gar keine UnicodeRegexes.

Unicode-Block \p{IsGreekExtended}

Regex-Optionen: Keine Regex-Varianten: .NET, Perl \p{InGreekExtended}

Regex-Optionen: Keine Regex-Varianten: Java, Perl JavaScript, PCRE, Python und Ruby unterstützen keine Unicode-Blöcke.

Unicode-Schriftsystem \p{Greek}

Regex-Optionen: Keine Regex-Varianten: PCRE, Perl, Ruby 1.9 Für eine Unterstützung von Unicode-Schriftsystemen (Skripten) wird die PCRE 6.5 benötigt, die mit UTF-8-Unterstützung kompiliert werden muss. In PHP wird die UTF-8Unterstützung durch den Muster-Modifikator /u aktiviert. .NET, JavaScript und Python unterstützen keine Unicode-Schriftsysteme. Ruby 1.8 unterstützt gar keine UnicodeRegexes.

Unicode-Graphem \X

Regex-Optionen: Keine Regex-Varianten: PCRE, Perl PCRE und Perl haben ein eigenes Token für das Finden von Graphemen, sie unterstützen aber auch den Workaround mit Unicode-Eigenschaften. \P{M}\p{M}*

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Ruby 1.9

48 | Kapitel 2: Grundlagen regulärer Ausdrücke

PCRE muss mit UTF-8-Unterstützung kompiliert werden. In PHP muss die UTF-8Unterstützung durch den Muster-Modifikator /u aktiviert werden. JavaScript und Python unterstützen keine Unicode-Eigenschaften. Ruby 1.8 unterstützt gar keine UnicodeRegexes.

Diskussion Unicode-Codepoint Ein Codepoint ist ein Eintrag in der Unicode-Zeichendatenbank, jedoch nicht unbedingt das Gleiche wie ein Zeichen – abhängig davon, was Sie mit „Zeichen“ meinen. Was auf dem Bildschirm erscheint, wird in Unicode als Graphem bezeichnet. Der Unicode-Codepoint U+2122 steht für das „Trademark“-Zeichen. Sie können ihn, abhängig von der genutzten Variante, mit ‹\u2122› oder ‹\x{2122}› finden. Bei der ‹\u›-Syntax muss man genau vier hexadezimale Ziffern angeben. Sie können sie also nur für die Unicode-Codepoints von U+0000 bis U+FFFF nutzen. Bei der ‹\x›-Syntax lassen sich beliebig viele hexadezimale Ziffern angeben, wodurch alle Codepoints von U+000000 bis U+10FFFF unterstützt werden. U+00E0 finden Sie zum Beispiel mit ‹\x{E0}› oder ‹\x{00E0}›. Codepoints ab U+100000 werden nur sehr selten genutzt und auch durch Fonts und Betriebssysteme eher wenig unterstützt. Codepoints können innerhalb und außerhalb von Zeichenklassen genutzt werden.

Unicode-Eigenschaften oder -Kategorien Jeder Unicode-Codepoint hat genau eine Unicode-Eigenschaft oder passt zu einer einzelnen Unicode-Kategorie. Diese Begriffe haben dabei die gleiche Bedeutung. Es gibt 30 Unicode-Kategorien, die in sieben übergeordneten Kategorien zusammengefasst sind: ‹\p{L}›

Jegliche Buchstaben beliebiger Sprachen. ‹\p{Ll}›

Kleinbuchstaben, für die es auch Großbuchstaben gibt. ‹\p{Lu}›

Großbuchstaben, für die es auch Kleinbuchstaben gibt. ‹\p{Lt}›

Ein Buchstabe, der am Anfang eines Worts steht, wenn nur der erste Buchstabe des Worts großgeschrieben ist. ‹\p{Lm}›

Ein Sonderzeichen, das als Buchstabe verwendet wird. ‹\p{Lo}›

Ein Buchstabe oder Ideogramm, das keine Varianten in Klein- oder Großschreibung besitzt.

2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode | 49

‹\p{M}›

Ein Zeichen, das dafür gedacht ist, mit anderen Zeichen kombiniert zu werden (Akzente, Umlaute, umschließende Kästchen und so weiter). ‹\p{Mn}›

Ein Zeichen, das dafür gedacht ist, mit einem anderen Zeichen kombiniert zu werden, und keinen zusätzlichen Raum einnimmt (zum Beispiel Akzente und Umlaute). ‹\p{Mc}›

Ein Zeichen, das dafür gedacht ist, mit einem anderen Zeichen kombiniert zu werden, und zusätzlichen Raum einnimmt (zum Beispiel Akzente in vielen östlichen Sprachen). ‹\p{Me}›

Ein Zeichen, das andere Zeichen umschließt (Kreise, Quadrate, Tastenkappen und so weiter). ‹\p{Z}›

Jeglicher Whitespace oder unsichtbare Trenner. ‹\p{Zs}›

Ein Whitespace-Zeichen, das unsichtbar ist, aber Raum einnimmt. ‹\p{Zl}›

Das Zeilentrennzeichen U+2028. ‹\p{Zp}›

Das Absatztrennzeichen U+2029. ‹\p{S}›

Mathematische Symbole, Währungssymbole, Dingbats, Zeichen für Kästen und so weiter. ‹\p{Sm}›

Mathematische Symbole. ‹\p{Sc}›

Währungssymbole. ‹\p{Sk}›

Ein Modifikator als eigenes Zeichen. ‹\p{So}›

Verschiedene Symbole, die keine mathematischen Symbole, Währungssymbole oder Modifikatorzeichen sind. ‹\p{N}›

Numerische Zeichen in allen Schriftsystemen. ‹\p{Nd}›

Eine Ziffer von 0 bis 9 in jeglichem Schriftsystem mit Ausnahme ideografischer Schriften. ‹\p{Nl}›

Eine Zahl, die wie ein Buchstabe aussieht, zum Beispiel eine römische Ziffer. ‹\p{No}›

Eine hoch- oder tiefgestellte Ziffer oder eine Zahl, die keine Ziffer von 0 bis 9 ist (außer Zahlen aus ideografischen Schriften). 50 | Kapitel 2: Grundlagen regulärer Ausdrücke

‹\p{P}›

Jegliches Satzzeichen. ‹\p{Pd}›

Striche (Bindestriche, Gedankenstriche und so weiter). ‹\p{Ps}›

Öffnende Klammern. ‹\p{Pe}›

Schließende Klammern. ‹\p{Pi}›

Öffnende Anführungszeichen. ‹\p{Pf}›

Schließende Anführungszeichen. ‹\p{Pc}›

Ein Satzzeichen, das Wörter verbindet, wie zum Beispiel ein Unterstrich. ‹\p{Po}›

Satzzeichen, die kein Strich, keine Klammer, kein Anführungszeichen und kein Verbindungszeichen sind. ‹\p{C}›

Unsichtbare Steuerzeichen und ungenutzte Codepoints. ‹\p{Cc}›

Ein Steuerzeichen im Bereich ASCII 0x00…0x1F oder Latin-1 0x80…0x9F. ‹\p{Cf}›

Eine unsichtbare Formatanweisung. ‹\p{Co}›

Codepoints, die für eigene Anwendungen reserviert sind. ‹\p{Cs}›

Eine Hälfte eines Stellvertreterpaars in UTF-16-Kodierung. ‹\p{Cn}›

Codepoints, denen kein Zeichen zugewiesen wurde. ‹\p{Ll}› passt zu einem einzelnen Codepoint, der die Eigenschaft Ll oder „Kleinbuchstabe“ besitzt. ‹\p{L}› ist eine einfachere Schreibweise der Zeichenklasse ‹[\p{Ll}\p{Lu}\ p{Lt}\p{Lm}\p{Lo}]›. Diese findet einen einzelnen Codepoint in einer der „Buchstaben“-

Kategorien. ‹\P› ist die negierte Version von ‹\p›. ‹\P{Ll}› passt zu einem einzelnen Codepoint, der nicht die Eigenschaft Ll besitzt. ‹\P{L}› passt zu einem einzelnen Codepoint, der keine der „Buchstaben“-Eigenschaften besitzt. Das ist nicht das Gleiche wie ‹[\P{Ll}\P{Lu}\P{Lt}\P{Lm}\P{Lo}]›, da mit Letzterem alle Codepoints gefunden werden. ‹\P{Ll}› passt zu den Codepoints mit der Eigenschaft Lu (und jeder anderen Eigenschaft außer Ll), während ‹\P{Lu}› die Codepoints mit Ll enthält. Kombiniert man beide in einer Codepoint-Klasse, werden immer alle möglichen Codepoints gefunden.

2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode | 51

Unicode-Block Die Zeichendatenbank von Unicode teilt alle Codepoints in Blöcke auf. Jeder Block besteht aus einem zusammenhängenden Bereich von Codepoints. Die Codepoints U+0000 bis U+FFFF sind in 105 Blöcke unterteilt: U+0000…U+007F

‹\p{InBasic_Latin}›

U+0080…U+00FF

‹\p{InLatin-1_Supplement}›

U+0100…U+017F

‹\p{InLatin_Extended-A}›

U+0180…U+024F

‹\p{InLatin_Extended-B}›

U+0250…U+02AF

‹\p{InIPA_Extensions}›

U+02B0…U+02FF

‹\p{InSpacing_Modifier_Letters}›

U+0300…U+036F

‹\p{InCombining_Diacritical_Marks}›

U+0370…U+03FF

‹\p{InGreek_and_Coptic}›

U+0400…U+04FF

‹\p{InCyrillic}›

U+0500…U+052F

‹\p{InCyrillic_Supplementary}›

U+0530…U+058F

‹\p{InArmenian}›

U+0590…U+05FF

‹\p{InHebrew}›

U+0600…U+06FF

‹\p{InArabic}›

U+0700…U+074F

‹\p{InSyriac}›

U+0780…U+07BF

‹\p{InThaana}›

U+0900…U+097F

‹\p{InDevanagari}›

U+0980…U+09FF

‹\p{InBengali}›

U+0A00…U+0A7F

‹\p{InGurmukhi}›

U+0A80…U+0AFF

‹\p{InGujarati}›

U+0B00…U+0B7F

‹\p{InOriya}›

U+0B80…U+0BFF

‹\p{InTamil}›

U+0C00…U+0C7F

‹\p{InTelugu}›

U+0C80…U+0CFF

‹\p{InKannada}›

U+0D00…U+0D7F

‹\p{InMalayalam}›

U+0D80…U+0DFF

‹\p{InSinhala}›

U+0E00…U+0E7F

‹\p{InThai}›

U+0E80…U+0EFF

‹\p{InLao}›

U+0F00…U+0FFF

‹\p{InTibetan}›

U+1000…U+109F

‹\p{InMyanmar}›

U+10A0…U+10FF

‹\p{InGeorgian}›

U+1100…U+11FF

‹\p{InHangul_Jamo}›

U+1200…U+137F

‹\p{InEthiopic}›

52 | Kapitel 2: Grundlagen regulärer Ausdrücke

U+13A0…U+13FF

‹\p{InCherokee}›

U+1400…U+167F

‹\p{InUnified_Canadian_Aboriginal_Syllabics}›

U+1680…U+169F

‹\p{InOgham}›

U+16A0…U+16FF

‹\p{InRunic}›

U+1700…U+171F

‹\p{InTagalog}›

U+1720…U+173F

‹\p{InHanunoo}›

U+1740…U+175F

‹\p{InBuhid}›

U+1760…U+177F

‹\p{InTagbanwa}›

U+1780…U+17FF

‹\p{InKhmer}›

U+1800…U+18AF

‹\p{InMongolian}›

U+1900…U+194F

‹\p{InLimbu}›

U+1950…U+197F

‹\p{InTai_Le}›

U+19E0…U+19FF

‹\p{InKhmer_Symbols}›

U+1D00…U+1D7F

‹\p{InPhonetic_Extensions}›

U+1E00…U+1EFF

‹\p{InLatin_Extended_Additional}›

U+1F00…U+1FFF

‹\p{InGreek_Extended}›

U+2000…U+206F

‹\p{InGeneral_Punctuation}›

U+2070…U+209F

‹\p{InSuperscripts_and_Subscripts}›

U+20A0…U+20CF

‹\p{InCurrency_Symbols}›

U+20D0…U+20FF

‹\p{InCombining_Diacritical_Marks_for_Symbols}›

U+2100…U+214F

‹\p{InLetterlike_Symbols}›

U+2150…U+218F

‹\p{InNumber_Forms}›

U+2190…U+21FF

‹\p{InArrows}›

U+2200…U+22FF

‹\p{InMathematical_Operators}›

U+2300…U+23FF

‹\p{InMiscellaneous_Technical}›

U+2400…U+243F

‹\p{InControl_Pictures}›

U+2440…U+245F

‹\p{InOptical_Character_Recognition}›

U+2460…U+24FF

‹\p{InEnclosed_Alphanumerics}›

U+2500…U+257F

‹\p{InBox_Drawing}›

U+2580…U+259F

‹\p{InBlock_Elements}›

U+25A0…U+25FF

‹\p{InGeometric_Shapes}›

U+2600…U+26FF

‹\p{InMiscellaneous_Symbols}›

U+2700…U+27BF

‹\p{InDingbats}›

U+27C0…U+27EF

‹\p{InMiscellaneous_Mathematical_Symbols-A}›

U+27F0…U+27FF

‹\p{InSupplemental_Arrows-A}›

U+2800…U+28FF

‹\p{InBraille_Patterns}›

U+2900…U+297F

‹\p{InSupplemental_Arrows-B}›

2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode | 53

U+2980…U+29FF

‹\p{InMiscellaneous_Mathematical_Symbols-B}›

U+2A00…U+2AFF

‹\p{InSupplemental_Mathematical_Operators}›

U+2B00…U+2BFF

‹\p{InMiscellaneous_Symbols_and_Arrows}›

U+2E80…U+2EFF

‹\p{InCJK_Radicals_Supplement}›

U+2F00…U+2FDF

‹\p{InKangxi_Radicals}›

U+2FF0…U+2FFF

‹\p{InIdeographic_Description_Characters}›

U+3000…U+303F

‹\p{InCJK_Symbols_and_Punctuation}›

U+3040…U+309F

‹\p{InHiragana}›

U+30A0…U+30FF

‹\p{InKatakana}›

U+3100…U+312F

‹\p{InBopomofo}›

U+3130…U+318F

‹\p{InHangul_Compatibility_Jamo}›

U+3190…U+319F

‹\p{InKanbun}›

U+31A0…U+31BF

‹\p{InBopomofo_Extended}›

U+31F0…U+31FF

‹\p{InKatakana_Phonetic_Extensions}›

U+3200…U+32FF

‹\p{InEnclosed_CJK_Letters_and_Months}›

U+3300…U+33FF

‹\p{InCJK_Compatibility}›

U+3400…U+4DBF

‹\p{InCJK_Unified_Ideographs_Extension_A}›

U+4DC0…U+4DFF

‹\p{InYijing_Hexagram_Symbols}›

U+4E00…U+9FFF

‹\p{InCJK_Unified_Ideographs}›

U+A000…U+A48F

‹\p{InYi_Syllables}›

U+A490…U+A4CF

‹\p{InYi_Radicals}›

U+AC00…U+D7AF

‹\p{InHangul_Syllables}›

U+D800…U+DB7F

‹\p{InHigh_Surrogates}›

U+DB80…U+DBFF

‹\p{InHigh_Private_Use_Surrogates}›

U+DC00…U+DFFF

‹\p{InLow_Surrogates}›

U+E000…U+F8FF

‹\p{InPrivate_Use_Area}›

U+F900…U+FAFF

‹\p{InCJK_Compatibility_Ideographs}›

U+FB00…U+FB4F

‹\p{InAlphabetic_Presentation_Forms}›

U+FB50…U+FDFF

‹\p{InArabic_Presentation_Forms-A}›

U+FE00…U+FE0F

‹\p{InVariation_Selectors}›

U+FE20…U+FE2F

‹\p{InCombining_Half_Marks}›

U+FE30…U+FE4F

‹\p{InCJK_Compatibility_Forms}›

U+FE50…U+FE6F

‹\p{InSmall_Form_Variants}›

U+FE70…U+FEFF

‹\p{InArabic_Presentation_Forms-B}›

U+FF00…U+FFEF

‹\p{InHalfwidth_and_Fullwidth_Forms}›

U+FFF0…U+FFFF

‹\p{InSpecials}›

54 | Kapitel 2: Grundlagen regulärer Ausdrücke

Ein Unicode-Block ist ein zusammenhängender, mit anderen Blöcken nicht überlappender Bereich von Codepoints. Auch wenn viele Blöcke die Namen von Unicode-Schriftsystemen und Unicode-Kategorien tragen, entsprechen sie ihnen nicht zu 100% Prozent. Der Name eines Blocks beschreibt nur seine wichtigsten Anwendungen. Der Währungsblock enthält nicht die Symbole für Dollar und Yen. Diese finden sich aus historischen Gründen in den Blöcken Basic_Latin und Latin-1_Supplement. Um ein beliebiges Währungssymbol zu finden, müssen Sie \p{Sc} statt \p{InCurrency} nutzen. Die meisten Blöcke enthalten auch nicht zugewiesene Codepoints, die durch die Eigenschaft ‹\p{Cn}› gekennzeichnet sind. Keine andere Unicode-Eigenschaft und keines der Unicode-Schriftsysteme enthält nicht zugewiesene Codepoints. Die Syntax mit ‹\p{InBlockName}› funktioniert bei .NET und Perl. Java verwendet eine Syntax mit ‹\p{IsBlockName}›. Perl unterstützt ebenfalls die Variante mit Is, aber wir empfehlen, bei In zu bleiben, um nicht mit Unicode-Schriftsystemen durcheinanderzukommen. Bei Schriftsystemen unterstützt Perl nämlich ‹\p{Script}› und ‹\p{IsScript}›, aber nicht ‹\p{InScript}›.

Unicode-Schriftsysteme Jeder Unicode-Codepoint ist Teil genau eines Unicode-Schriftsystems (abgesehen von den nicht zugewiesenen Codepoints, die zu keinem Schriftsystem gehören). Die zugewiesenen Codepoints bis U+FFFF gehören zu einem der folgenden Schriftsysteme (Skripten): ‹\p{Common}› ‹\p{Arabic}› ‹\p{Armenian}› ‹\p{Bengali}› ‹\p{Bopomofo}› ‹\p{Braille}› ‹\p{Buhid}› ‹\p{CanadianAboriginal}› ‹\p{Cherokee}› ‹\p{Cyrillic}› ‹\p{Devanagari}› ‹\p{Ethiopic}› ‹\p{Georgian}› ‹\p{Greek}› ‹\p{Gujarati}› ‹\p{Gurmukhi}› ‹\p{Han}› ‹\p{Hangul}› ‹\p{Hanunoo}›

‹\p{Katakana}› ‹\p{Khmer}› ‹\p{Lao}› ‹\p{Latin}› ‹\p{Limbu}› ‹\p{Malayalam}› ‹\p{Mongolian}› ‹\p{Myanmar}› ‹\p{Ogham}› ‹\p{Oriya}› ‹\p{Runic}› ‹\p{Sinhala}› ‹\p{Syriac}› ‹\p{Tagalog}› ‹\p{Tagbanwa}› ‹\p{Taile}› ‹\p{Tamil}› ‹\p{Telugu}› ‹\p{Thaana}›

2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode | 55

‹\p{Hebrew}› ‹\p{Hiragana}› ‹\p{Inherited}› ‹\p{Kannada}›

‹\p{Thai}› ‹\p{Tibetan}› ‹\p{Yi}›

Ein Schriftsystem ist eine Gruppe von Codepoints, die von einem bestimmten menschlichen Schreibsystem genutzt wird. Manche Schriftsysteme, wie zum Beispiel Thai, entsprechen einer einzelnen Sprache. Andere, wie Latin, umfassen mehrere Sprachen. Einige Sprachen bestehen aber auch aus mehreren Schriftsystemen. So gibt es zum Beispiel kein japanisches Unicode-Schriftsystem, stattdessen stellt Unicode die Schriftsysteme Hiragana, Katakana, Han und Latin bereit, aus denen japanische Dokumente normalerweise erstellt werden. Wir haben das Schriftsystem Common an erster Stelle aufgeführt, auch wenn es damit aus der alphabetischen Reihenfolge herausfällt. Dieses Schriftsystem beinhaltet alle Arten von Zeichen, die für viele Schriftsysteme genutzt werden, wie zum Beispiel Satzzeichen, Whitespace und verschiedene Symbole.

Unicode-Graphem Der Unterschied zwischen Codepoints und Zeichen wird dann relevant, wenn es sich um Kombinationszeichen handelt. Der Unicode-Codepoint U+0061 steht für „Latin small letter a“ (also das kleine „a“), während U+00E0 für „Latin small letter a with grave accent“ steht (also das kleine „à“). Beide stellen etwas dar, was die meisten Leute als Buchstaben beschreiben würden. U+0300 ist das Kombinationszeichen „combining grave accent“. Es kann nur nach einem Buchstaben sinnvoll verwendet werden. Ein String, der aus den Unicode-Codepoints U+0061 und U+0300 besteht, wird als à dargestellt, genauso wie U+00E0. Das Kombinationszeichen U+0300 wird dabei über dem Zeichen U+0061 angezeigt. Der Grund für diese beiden unterschiedlichen Vorgehensweisen beim Anzeigen eines Buchstaben mit Akzent liegt darin, dass viele alte Zeichensätze „a mit Gravis“ als einzelnes Zeichen kodieren. Die Designer von Unicode dachten, es wäre nützlich, sowohl alte Zeichensätze eins zu eins abbilden als auch den Unicode-Weg gehen zu können, bei dem Akzente und Grundbuchstaben getrennt sind. Mit Letzterem lassen sich nämlich beliebige Kombinationen erzeugen, die nicht von den alten Zeichensätzen unterstützt wurden. Für Sie als Regex-Anwender ist es wichtig zu wissen, dass alle in diesem Buch behandelten Regex-Varianten mit Codepoints und nicht mit grafischen Zeichen arbeiten. Wenn wir sagen, dass der reguläre Ausdruck ‹.› zu einem einzelnen Zeichen passt, passt er in Wirklichkeit nur zu einem einzelnen Codepoint. Besteht Ihre Text aus den beiden Codepoints U+0061 und U+0300, die zum Beispiel in einer Programmiersprache wie Java als String-Literal "\u0061\u0300" dargestellt werden können, wird der Punkt nur den Codepoint U+0061, also das a finden, ohne dabei den Akzent U+0300 zu berücksichtigen. Die Regex ‹..› wird beides finden.

56 | Kapitel 2: Grundlagen regulärer Ausdrücke

Perl und PCRE bieten ein spezielles Regex-Token ‹\X› an, das zu jedem einzelnen Unicode-Graphem passt. Im Endeffekt ist es die Unicode-Version des altehrwürdigen Punkts. Es passt zu jedem Unicode-Codepoint, der kein Kombinationszeichen ist, und schließt alle direkt darauffolgenden Kombinationszeichen ein, wenn es welche gibt. Mit ‹\P{M}\p{M}*› kann man das Gleiche mit der Unicode-Eigenschaftssyntax erreichen. ‹\X› findet zwei Übereinstimmungen im Text àà, egal wie dieser kodiert wurde. Wenn er als "\u00E0\u0061\u0300" kodiert ist, wird die erste Übereinstimmung "\u00E0" und die zweite "\u0061\u0300" sein.

Variationen Negierte Form Das großgeschriebene ‹\P› ist die negierte Form des kleinen ‹\p›. So passt zum Beispiel ‹\P{Sc}› zu jedem Zeichen, das nicht die Unicode-Eigenschaft „Währungssymbol“ besitzt. ‹\P› wird von allen Varianten für alle Eigenschaften, Blöcke und Schriftsprachen unterstützt, die auch ‹\p› anbieten.

Zeichenklassen Alle Varianten lassen Tokens der Form ‹\u›, ‹\x›, ‹\p› und ‹\P› auch innerhalb von Zeichenklassen zu – sofern sie sie überhaupt unterstützen. Das vom Codepoint repräsentierte Zeichen oder die Zeichen in der Kategorie, dem Block oder dem Schriftsystem werden der Zeichenklasse hinzugefügt. So können Sie zum Beispiel ein Zeichen finden, das entweder ein öffnendes Anführungszeichen, ein schließendes Anführungszeichen oder das Trademark-Symbol (U+2122) ist, indem Sie folgende Zeichenklasse nutzen: [\p{Pi}\p{Pf}\x{2122}]

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Ruby 1.9

Alle Zeichen auflisten Wenn die von Ihnen genutzte Regex-Variante keine Kategorien, Blöcke oder Schriftsysteme von Unicode unterstützt, können Sie die Zeichen, die sich in der Kategorie, im Block oder im Schriftsystem befinden, in einer Zeichenklasse aufführen. Bei Blöcken ist das sehr einfach – jeder Block besteht einfach aus einem Bereich zwischen zwei Codepoints. Der Block „Greek Extended“ besteht aus den Zeichen U+1F00 bis U+1FFF: [\u1F00-\u1FFF]

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, Python [\x{1F00}-\x{1FFF}]

Regex-Optionen: Keine Regex-Varianten: PCRE, Perl, Ruby 1.9

2.7 Codepoints, Eigenschaften, Blöcke und Schriftsysteme bei Unicode | 57

Bei den meisten Kategorien und in vielen Schriftsystemen muss man für die Zeichenklasse eine lange Liste einzelner Codepoints und kurzer Bereiche angeben. Die Zeichen jeder Kategorie und vieler Schriftsysteme sind über die ganze Unicode-Tabelle verstreut. Dies ist das griechische Schriftsystem: [\u0370-\u0373\u0375\u0376-\u0377\u037A\u037B-\u037D\u0384\u0386 \u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03E1\u03F0-\u03F5\u03F6 \u03F7-\u03FF\u1D26-\u1D2A\u1D5D-\u1D61\u1D66-\u1D6A\u1DBF\u1F00-\u1F15 \u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D \u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBD\u1FBE\u1FBF-\u1FC1 \u1FC2-\u1FC4\u1FC6-\u1FCC\u1FCD-\u1FCF\u1FD0-\u1FD3\u1FD6-\u1FDB \u1FDD-\u1FDF\u1FE0-\u1FEC\u1FED-\u1FEF\u1FF2-\u1FF4\u1FF6-\u1FFC \u1FFD-\u1FFE\u2126]

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, Python Wir haben diesen regulären Ausdruck aufgebaut, indem wir die Liste für das griechische Schriftsystem aus http://www.unicode.org/Public/UNIDATA/Scripts.txt kopierten und dann drei reguläre Ausdrücke nutzten, um sie per Suchen und Ersetzen anzupassen: 1. Suchen nach dem regulären Ausdruck ‹;.*› und Ersetzen seiner Übereinstimmun-

gen durch leeren Text. Das entfernt die Kommentare. Wenn unabsichtlich jeglicher Text gelöscht wird, machen Sie den Vorgang rückgängig und schalten die Option Punkt passt zu Zeilenumbruch ab. 2. Suchen nach ‹^› mit aktiver Option ^ und $ passen zu Zeilenumbruch und Ersetzen durch «\u». Das versieht die Codepoints mit \u. Mit einem Ersetzen von ‹\.\.› durch «-\u» werden die Bereiche angepasst. 3. Schließlich wird ‹\s+› durch leeren Text ersetzt, um die Zeilenumbrüche zu entfernen. Ergänzt man jetzt noch die eckigen Klammern, ist die Regex fertig. Eventuell müssen Sie zusätzlich ganz am Anfang der Zeichenklasse ein \u ergänzen und/oder es am Ende entfernen. Das hängt davon ab, ob Sie führende oder abschließende Leerzeilen aus Scripts.txt mitkopiert haben. Das mag recht aufwendig erscheinen, aber Jan brauchte dafür weniger als eine Minute. Das Erstellen der Beschreibung erforderte mehr Zeit. Für die \x{}-Syntax ist es genauso einfach: 1. Suchen nach dem regulären Ausdruck ‹;.*› und Ersetzen seiner Übereinstimmun-

gen durch leeren Text. Das entfernt die Kommentare. Wenn unabsichtlich jeglicher Text gelöscht wird, machen Sie den Vorgang rückgängig und schalten die Option Punkt passt zu Zeilenumbruch ab. 2. Suchen nach ‹^› mit aktiver Option ^ und $ passen zu Zeilenumbruch und Ersetzen durch «\x{». Das versieht die Codepoints mit \x{. Mit einem Ersetzen von ‹\.\.› durch «}-\x{» werden die Bereiche angepasst. 3. Schließlich wird ‹\s+› durch «}» ersetzt, um die schließende geschweifte Klammer zu ergänzen und die Zeilenumbrüche zu entfernen. Fügt man jetzt noch die eckigen Klammern hinzu, ist die Regex fertig. Eventuell müssen Sie ganz am Anfang der Zei-

58 | Kapitel 2: Grundlagen regulärer Ausdrücke

chenklasse ein \x{ ergänzen und/oder es am Ende entfernen. Das hängt davon ab, ob Sie führende oder abschließende Leerzeilen aus Scripts.txt mitkopiert haben. Das Ergebnis ist: [\x{0370}-\x{0373}\x{0375}\x{0376}-\x{0377}\x{037A}\x{037B}-\x{037D} \x{0384}\x{0386}\x{0388}-\x{038A}\x{038C}\x{038E}-\x{03A1} \x{03A3}-\x{03E1}\x{03F0}-\x{03F5}\x{03F6}\x{03F7}-\x{03FF} \x{1D26}-\x{1D2A}\x{1D5D}-\x{1D61}\x{1D66}-\x{1D6A}\x{1DBF} \x{1F00}-\x{1F15}\x{1F18}-\x{1F1D}\x{1F20}-\x{1F45}\x{1F48}-\x{1F4D} \x{1F50}-\x{1F57}\x{1F59}\x{1F5B}\x{1F5D}\x{1F5F}-\x{1F7D} \x{1F80}-\x{1FB4}\x{1FB6}-\x{1FBC}\x{1FBD}\x{1FBE}\x{1FBF}-\x{1FC1} \x{1FC2}-\x{1FC4}\x{1FC6}-\x{1FCC}\x{1FCD}-\x{1FCF}\x{1FD0}-\x{1FD3} \x{1FD6}-\x{1FDB}\x{1FDD}-\x{1FDF}\x{1FE0}-\x{1FEC}\x{1FED}-\x{1FEF} \x{1FF2}-\x{1FF4}\x{1FF6}-\x{1FFC}\x{1FFD}-\x{1FFE}\x{2126} \x{10140}-\x{10174}\x{10175}-\x{10178}\x{10179}-\x{10189} \x{1018A}\x{1D200}-\x{1D241}\x{1D242}-\x{1D244}\x{1D245}]

Regex-Optionen: Keine Regex-Varianten: PCRE, Perl, Ruby 1.9

Siehe auch http://www.unicode.org ist die offizielle Website des Unicode Consortium, von der Sie alle offiziellen Unicode-Dokumente, Zeichentabellen und so weiter herunterladen können. Unicode ist ein umfangreiches Thema, zu dem ganze Bücher geschrieben wurden. Eines davon ist Unicode Explained von Jukka K. Korpela (O’Reilly). Wir können nicht alles, was Sie über die Codepoints, Eigenschaften, Blöcke und Schriftsysteme wissen sollten, in einem Abschnitt beschreiben. Wir haben noch nicht einmal erklärt, warum Sie sich damit beschäftigen sollten – Sie sollten es einfach. Die bequeme Einfachheit der erweiterten ASCII-Tabelle ist in der heutigen globalisierten Welt ein einsames Plätzchen.

2.8

Eine von mehreren Alternativen finden

Problem Erstellen eines regulären Ausdrucks, der beim wiederholten Anwenden auf den Text Maria, Julia und Susanne gingen zu Marias Haus nacheinander Maria, Julia, Susanne und dann wieder Maria findet. Weitere Suchen sollten kein Ergebnis mehr liefern.

Lösung Maria|Julia|Susanne

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

2.8 Eine von mehreren Alternativen finden | 59

Diskussion Der vertikale Balken, auch Pipe-Symbol genannt, teilt den regulären Ausdruck in mehrere Alternativen auf. ‹Maria|Julia|Susanne› passt zu Maria oder Julia oder Susanne. Bei jeder Suche passt immer nur ein Name, aber es kann immer ein anderer sein. Alle Varianten regulärer Ausdrücke, die in diesem Buch behandelt werden, nutzen eine Regex-gesteuerte Engine. Die Engine ist einfach die Software, die dafür sorgt, dass der reguläre Ausdruck genutzt werden kann. Regex-gesteuert1 heißt, dass alle möglichen Permutationen des regulären Ausdrucks an jeder Stelle des Ausgangstexts ausprobiert werden, bevor die Regex mit dem nächsten Zeichen fortfährt. Wenn Sie ‹Maria|Julia|Susanne› auf Maria, Julia und Susanne gingen zu Marias Haus anwenden, wird die Übereinstimmung Maria direkt am Anfang des Strings gefunden. Wenden Sie die gleiche Regex auf den Rest des Strings an – indem Sie zum Beispiel in Ihrem Texteditor auf Weitersuchen klicken –, versucht die Regex-Engine, ‹Maria› auf das erste Komma im String anzuwenden. Das geht schief. Dann versucht sie, ‹Julia› an der gleichen Stelle zu finden, was ebenfalls misslingt, genauso wie der Versuch, ‹Susanne› am Komma zu finden. Erst dann springt die Regex-Engine zum nächsten Zeichen im String weiter. Aber auch beim ersten Leerzeichen wird keine der drei Alternativen gefunden. Geht die Engine nun zum J weiter, wird die erste Alternative ‹Maria› nicht gefunden. Dann wird versucht, die zweite Alternative ‹Julia› beim J zu finden. Das passt zu Julia. Die Regex-Engine hat endlich Erfolg. Beachten Sie, dass Julia gefunden wurde, obwohl es im Ausgangstext ein weiteres Vorkommen von Maria gibt und ‹Maria› in der Regex vor ‹Julia› steht. Zumindest in diesem Fall ist die Reihenfolge der Alternativen im regulären Ausdruck egal. Der reguläre Ausdruck findet die am weitesten links stehende Übereinstimmung. Der Text wird von links nach rechts überprüft, und es wird bei jedem Schritt versucht, alle Alternativen im regulären Ausdruck anzuwenden. Sobald eine der Alternativen passt, wird an der ersten Position abgebrochen. Wenn wir im restlichen Text weitersuchen, wird Susanne gefunden. Die vierte Suche findet dann erneut Maria. Bitten Sie die Engine jedoch um eine fünfte Suche, wird diese fehlschlagen, da keine der drei Alternativen zum verbleibenden String s Haus passt. Die Reihenfolge der Alternativen im regulären Ausdruck ist nur dann von Belang, wenn zwei von ihnen an der gleichen Position im Text passen können. Die Regex ‹Julia|Juliane› besitzt zwei Alternativen, die an der gleichen Position im Text Ihr Name ist Juliane passen. Es gibt im regulären Ausdruck keine Wortgrenzen. Die Tatsache, dass ‹Julia› das Wort Juliane in Ihr Name ist Juliane nur teilweise abdeckt, spielt keine Rolle. 1 Es gibt auch textgesteuerte Engines. Der Hauptunterschied liegt darin, dass eine textgesteuerte Engine jedes Zeichen im Text nur einmal aufsucht, während eine Regex-gesteuerte Engine jedes Zeichen eventuell mehrfach anfasst. Textgesteuerte Engines sind viel schneller, lassen sich aber nur mit regulären Ausdrücken im echten mathematischen Sinn nutzen (beschrieben am Anfang von Kapitel 1). Die tollen regulären Ausdrücke im PerlStil, die dieses Buch so interessant machen, lassen sich nur durch eine Regex-gesteuerte Engine umsetzen.

60 | Kapitel 2: Grundlagen regulärer Ausdrücke

‹Julia|Juliane› passt zu Julia in Ihr Name ist Juliane, weil eine Regex-gesteuerte

Engine ungeduldig ist. Während sie den Text von links nach rechts durchforstet, um die am weitesten links stehende Übereinstimmung zu finden, sucht sie auch in den Alternativen der Regex von links nach rechts. Sobald sie eine Alternative findet, die passt, bricht sie ab. Wenn ‹Julia|Juliane› das J in Ihr Name ist Juliane erreicht, passt die erste Alternative ‹Julia›. Die zweite Alternative wird gar nicht ausprobiert. Weisen wir die Engine an, nach einer zweiten Übereinstimmung zu suchen, ist vom Ausgangstext nur noch das ne übrig. Darauf passt keine der beiden Alternativen. Es gibt zwei Möglichkeiten, Julia davon abzuhalten, Juliane die Show zu stehlen. Eine ist, die längere Alternative an den Anfang zu stellen: ‹Juliane|Julia›. Besser und zuverlässiger ist es allerdings, genauer zu sagen, was wir wollen: Wir suchen nach Namen, und Namen sind vollständige Wörter. Reguläre Ausdrücke kümmern sich nicht um Wörter, aber sie können auf Wortgrenzen Rücksicht nehmen. Daher werden sowohl ‹\bJulia\b|\bJuliane\b› als auch ‹\bJuliane\b|\bJulia\b› den Namen Juliane in Ihr Name ist Juliane finden. Aufgrund der Wortgrenzen passt nur eine Alternative. Die Reihenfolge ist dabei unwichtig. Rezept 2.12 erklärt, warum ‹\bJuliane?\b› die beste Lösung ist.

Siehe auch Rezept 2.9.

2.9

Gruppieren und Einfangen von Teilen des gefundenen Texts

Problem Verbessern des regulären Ausdrucks zum Finden von Maria, Julia oder Susanne, indem die Übereinstimmung ein ganzes Wort sein soll. Durch Gruppieren soll ein Paar Wortgrenzen für die ganze Regex reichen, anstatt dass für jede Alternative Paare genutzt werden müssen. Erstellen eines regulären Ausdrucks, der jedes Datum im Format yyyy-mm-dd findet und dabei das Jahr, den Monat und den Tag getrennt einfängt. Ziel ist, mit diesen drei Werten im Code, der die Regex-Auswertung startet, arbeiten zu können. Es soll davon ausgegangen werden, dass alle Datumswerte in Ausgangstext gültig sind. Der reguläre Ausdruck muss sich nicht mit Werten wie 9999-99-99 herumschlagen, da sie nicht vorkommen.

2.9 Gruppieren und Einfangen von Teilen des gefundenen Texts | 61

Lösung \b(Maria|Julia|Susanne)\b

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby \b(\d\d\d\d)-(\d\d)-(\d\d)\b

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Der Alternationsoperator, der im vorigen Abschnitt beschrieben wurde, hat (nahezu) den höchsten Rang aller Regex-Operatoren. Wenn Sie probieren, ‹\bMaria|Julia|Susanne\b› zu nutzen, sind die drei Alternativen ‹\bMaria›, ‹Julia› und ‹Susanne\b›. Diese Regex findet Julia in Ihr Name ist Julia. Möchten Sie, dass in Ihrer Regex etwas von der Alternation ausgeschlossen wird, müssen Sie die Alternativen gruppieren. Das geschieht durch (normale, runde) Klammern. Sie haben, wie in den meisten Programmiersprachen, den allerhöchsten Rang. ‹\b(Maria|Julia|Susanne)\b› hat drei Alternativen – ‹Maria›, ‹Julia› und ‹Susanne› – zwischen zwei Wortgrenzen. Diese Regex passt nicht zu Ihr Name ist Juliane. Wenn die Regex-Engine im Ausgangstext das J in Juliane erreicht, passt die erste Wortgrenze. Dann kümmert sich die Engine um die Gruppe. Die erste Alternative in der Gruppe, ‹Maria›, schlägt fehl. Die zweite Alternative ‹Julia› ist erfolgreich. Die Engine verlässt die Gruppe. Nun bleibt noch das ‹\b›. Die Wortgrenze passt aber nicht zwischen das a und das ne am Ende des Texts. Somit gibt es bei J keine Übereinstimmung. Ein Klammernpaar ist nicht nur eine Gruppe, sondern sogar eine einfangende Gruppe. Bei der Regex für Maria, Julia und Susanne ist das Einfangen nicht so nützlich, da es nur um das Finden selbst geht. Das Einfangen lässt sich dann gebrauchen, wenn es nur um Teile der Regex geht, wie zum Beispiel bei ‹\b(\d\d\d\d)-(\d\d)-(\d\d)\b›. Dieser reguläre Ausdruck passt zu einem Datum im Format yyyy-mm-dd. Die Regex ‹\b\d\d\d\d-\d\d-\d\d\b› macht das Gleiche. Da dieser reguläre Ausdruck keine Alternation oder Wiederholung verwendet, wird die Gruppierungsfunktion der Klammern nicht benötigt. Aber das Einfangen ist sehr praktisch. Die Regex ‹\b(\d\d\d\d)-(\d\d)-(\d\d)\b› hat drei Gruppen zum Einfangen. Gruppen werden durchnummeriert, indem die öffnenden Klammern von links nach rechts gezählt werden. Somit ist ‹(\d\d\d\d)› die Gruppe 1, ‹(\d\d)› Gruppe 2, und das zweite ‹(\d\d)› ist Gruppe 3. Während des Findens speichert die Regex-Engine den Inhalt der so gefundenen Gruppen, sobald die schließende Klammer erreicht wird. Findet unsere Regex den Wert 200805-24, wird 2008 für die erste Gruppe gespeichert, 05 für die zweite Gruppe und 24 für die dritte Gruppe.

62 | Kapitel 2: Grundlagen regulärer Ausdrücke

Es gibt drei Möglichkeiten, den „gefangenen“ Text zu verwenden. Rezept 2.10 in diesem Kapitel erklärt, wie Sie den gefangenen Text nochmals innerhalb der gleichen Regex zum Suchen nutzen können. Rezept 2.21 zeigt, wie Sie den gefangenen Text in den Ersetzungstext einfügen, wenn Sie suchen und ersetzen. Rezept 3.9 im nächsten Kapitel erläutert, wie Ihre Anwendung die Teile der Regex selbst nutzen kann.

Variationen Nicht-einfangende Gruppen In der Regex ‹\b(Maria|Julia|Susanne)\b› benötigten wir die Klammern nur für das Gruppieren. Anstatt eine einfangende Gruppe zu verwenden, können wir auch eine nicht-einfangende Gruppe nutzen: \b(?:Maria|Julia|Susanne)\b

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Die drei Zeichen ‹(?:› öffnen eine nicht-einfangende Gruppe. Die Klammer ‹)› schließt sie. Die nicht-einfangende Gruppe bietet die gleiche Funktionalität zum Gruppieren, fängt aber nichts ein. Wenn die öffnenden Klammern einfangender Gruppen gezählt werden, um ihre Position zu bestimmen, werden die Klammern nicht-einfangender Gruppen nicht mitgezählt. Das ist der Hauptvorteil nicht-einfangender Gruppen: Sie können sie einer bestehenden Regex hinzufügen, ohne die Referenzen auf die durchnummerierten einfangenden Gruppen durcheinanderzubringen. Ein weiterer Vorteil nicht-einfangender Gruppen ist eine höhere Geschwindigkeit. Wenn Sie für eine bestimmte Gruppe keine Rückwärtsreferenzen nutzen (Rezept 2.10), sie nicht in den Ersetzungstext einfügen (Rezept 2.21) und sie auch nicht in Ihrem Quellcode nutzen wollen (Rezept 3.9), erzeugt eine einfangende Gruppe nur unnötigen Overhead, den Sie durch eine nicht-einfangende Gruppe vermeiden können. In der Praxis werden Sie die Performanceunterschiede kaum spüren, sofern Sie die Regex nicht in einer großen Schleife und/oder mit vielen Daten verwenden.

Gruppen mit Modus-Modifikatoren In der Variation „Übereinstimmungen unabhängig von Groß- und Kleinschreibung“ von Rezept 2.1 haben wir erklärt, dass .NET, Java, PCRE, Perl und Ruby lokale ModusModifikatoren unterstüzen, indem sie Modusschalter nutzen: ‹empfindlich(?i) unempfindlich(?-i)empfindlich›. Auch wenn diese Syntax ebenfalls Klammern enthält, geht es bei Schaltern wie ‹(?i)› auch nicht um Gruppierungen.

2.9 Gruppieren und Einfangen von Teilen des gefundenen Texts | 63

Statt Schalter zu nutzen, können Sie Modus-Modifikatoren in einer nicht-einfangenden Gruppe verwenden: \b(?i:Maria|Julia|Susanne)\b

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Ruby empfindlich(?i:unempfindlich)empfindlich

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Ruby Durch das Hinzufügen von Modus-Modifikatoren zu einer nicht-einfangenden Gruppe wird der Modus für den Teil des regulären Ausdrucks innerhalb der Gruppe gesetzt. Die vorigen Einstellungen werden mit der schließenden Klammer wieder zurückgesetzt. Da das Berücksichtigen von Groß- und Kleinschreibung das Standardverhalten ist, wird so nur dieser Teil innerhalb der Gruppe unempfindlich für die Schreibweise: (?i:...)

Sie können mehrere Modifikatoren kombinieren: ‹(?ism:Gruppe)›. Mit einem Minuszeichen schalten Sie die Modifikatoren ab: ‹(?-ism:Gruppe)› schaltet alle drei Optionen ab. ‹(?i-sm)› schaltet die Unempfindlichkeit für Groß- und Kleinschreibung ein (i) und sowohl Punkt passt zu Zeilenumbruch (s) als auch Zirkumflex und Dollar passen zu Zeilenumbruch (m) ab. Diese Optionen werden in den Rezepten 2.4 und 2.5 erläutert.

Siehe auch Rezepte 2.10, 2.11, 2.21 und 3.9.

2.10 Vorher gefundenen Text erneut finden Problem Erstellen eines regulären Ausdrucks, der „magische“ Datumswerte im Format yyyy-mmdd findet. Ein Datum ist magisch, wenn Jahr (ohne Jahrhundert), Monat und Tag den gleichen Wert haben. So ist zum Beispiel 2008-08-08 ein magisches Datum. Sie können davon ausgehen, dass alle Datumswerte im Ausgangstext gültig sind. Der reguläre Ausdruck muss sich nicht mit Werten wie 9999-99-99 herumschlagen. Es müssen nur die magischen Datumswerte gefunden werden.

Lösung \b\d\d(\d\d)-\1-\1\b

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

64 | Kapitel 2: Grundlagen regulärer Ausdrücke

Diskussion Um schon vorher gefundenen Text weiter hinten in einer Regex nochmals zu finden, müssen wir zunächst den ersten Text einfangen. Das machen wir mit einer einfangenden Gruppe, wie es in Rezept 2.9 gezeigt wurde. Danach können wir den gleichen Text irgendwo in der Regex mithilfe einer Rückwärtsreferenz erneut suchen. Sie können die ersten neun einfangenden Gruppen referenzieren, indem Sie einen Backslash, gefolgt von einer einzelnen Ziffer zwischen eins und neun nutzen. Für die Gruppen 10 bis 99 nutzen Sie ‹\10› bis ‹\99›. Verwenden Sie nicht ‹\01›. Das ist entweder eine oktale Maskierung oder ein Fehler. Wir verwenden in diesem Buch keine oktalen Maskierungen, da die hexadezimalen Maskierungen wie ‹\xFF› viel einfacher zu verstehen sind.

Wenn der reguläre Ausdruck ‹\b\d\d(\d\d)-\1-\1\b› den Wert 2008-08-08 vorfindet, passen die ersten ‹\d\d› zu 20. Die Regex-Engine betritt dann die einfangende Gruppe und merkt sich die Position, die sie im Ausgangstext erreicht hat. Die ‹\d\d› innerhalb der Gruppe passen zu 08, und die Engine erreicht das Ende der Gruppe. Damit wird die erste Teilübereinstimmung 08 als einfangende Gruppe 1 abgespeichert. Das nächste Token ist der Bindestrich, der als Literal passt. Dann kommt die Rückwärtsreferenz. Die Regex-Engine holt sich den Inhalt der ersten einfangenden Gruppe: 08. Sie versucht nun, diesen Text als Literal zu finden. Wenn sich der reguläre Ausdruck nicht um Groß- und Kleinschreibung kümmert, wird der eingefangene Text dementsprechend gefunden. Somit war die Rückwärtsreferenz erfolgreich. Der nächste Bindestrich und die weitere Rückwärtsreferenz passen ebenfalls. Schließlich passt die Wortgrenze zum Ende des Ausgangstexts, und das Gesamtergebnis ist gefunden: 2008-08-08. Die einfangende Gruppe hat immer noch den Wert 08 gespeichert. Wenn eine eingefangene Gruppe wiederholt wird – sei es durch einen Quantor (Rezept 2.12) oder durch eine Rückwärtsreferenz (Rezept 2.13) –, wird der gespeicherte Wert jedes Mal überschrieben, wenn die einfangende Gruppe etwas Passendes findet. Eine Rückwärtsreferenz auf die Gruppe passt nur zu dem Text, der als Letztes von der Gruppe eingefangen wurde. Wenn die gleiche Regex auf 2008-05-24 2007-07-07 angewandt wird, passt zunächst ‹\b\d\d(\d\d)› auf 2008, womit 08 für die erste (und einzige) Gruppe abgespeichert wird. Als Nächstes passt der Bindestrich. Die Rückwärtsreferenz versucht, ‹08› zu finden, entdeckt aber nur 05. Da es keine weiteren Alternativen im regulären Ausdruck gibt, beendet die Engine den Versuch. Damit werden die Ergebnisse aller einfangenden Gruppen gelöscht. Wenn die Engine anschließend einen neuen Versuch startet und mit der ersten 0 im Text beginnt, enthält ‹\1› gar keinen Text.

2.10 Vorher gefundenen Text erneut finden | 65

Bei der Verarbeitung von 2008-05-24 2007-07-07 passt die Gruppe das nächste Mal, wenn ‹\b\d\d(\d\d)› den Wert 2007 findet. Nun wird 07 abgelegt. Dann passt der Bindestrich. Jetzt versucht die Rückwärtsreferenz, ‹07› zu finden. Das ist erfolgreich, genauso wie der nächste Bindestrich, die Rückwärtsreferenz und die Wortgrenze. Es wurde also 2007-0707 gefunden. Da die Regex-Engine von links nach rechts vorgeht, müssen Sie die einfangenden Klammern vor die Rückwärtsreferenz setzen. Die regulären Ausdrücke ‹\b\d\d\1-(\d\d)-\1› und ‹\b\d\d\1-\1-(\d\d)\b› werden niemals etwas finden. Da die Rückwärtsreferenz vor der einfangenden Gruppe ausgewertet wird, besitzt er noch keinen Wert. Wenn Sie nicht gerade mit JavaScript arbeiten, schlägt eine Rückwärtsreferenz immer fehl, wenn er auf eine Gruppe verweist, die noch nicht ausgewertet wurde. Eine Gruppe, die noch nicht ausgewertet wurde, ist nicht das Gleiche wie eine Gruppe, die eine Übereinstimmung der Länge null eingefangen hat. Eine Rückwärtsreferenz auf eine Gruppe mit der Länge null ist immer erfolgreich. Wenn ‹(^)\1› am Anfang des Texts erfolgreich gefunden wurde, enthält die erste einfangende Gruppe die Übereinstimmung des Zirkumflex mit Null-Länge. Damit ist auch ‹\1› erfolgreich. In der Praxis kann das passieren, wenn der Inhalt einer einfangenden Gruppe vollständig optional ist. JavaScript ist die einzige uns bekannte Variante, die mit der jahrzehntelangen Tradition der Rückwärtsreferenzen bricht. In JavaScript, zumindest in Implementierungen, die dem JavaScript-Standard folgen, ist eine Rückwärtsreferenz auf eine Gruppe, die noch nicht ausgewertet wurde, immer erfolgreich – so wie eine Rückwärtsreferenz auf eine Gruppe mit NullLänge. Daher passt ‹\b\d\d\1-\1-(\d\d)\b› in JavaScript zu 12--34.

Siehe auch Rezepte 2.9, 2.11, 2.21 und 3.9.

2.11 Teile des gefundenen Texts einfangen und benennen Problem Erstellen eines regulären Ausdrucks, der jedes Datum im Format yyyy-mm-dd findet. Bereitstellen separater einfangender Gruppen für das Jahr, den Monat und den Tag. Ziel ist es, mit diesen Werten im Code bequem arbeiten zu können. Dazu sollen dem eingefangenen Text die beschreibenden Namen „Jahr“, „Monat“ und „Tag“ zugewiesen werden. Erstellen eines weiteren regulären Ausdrucks, der „magische“ Datumswerte im Format yyyy-mm-dd findet. Ein Datum ist magisch, wenn Jahr (ohne Jahrhundert), Monat und Tag den gleichen Wert haben. So ist zum Beispiel 2008-08-08 ein magisches Datum. Einfangen der magischen Zahl (in diesem Beispiel 08) und Benennen mit „magisch“.

66 | Kapitel 2: Grundlagen regulärer Ausdrücke

Es kann davon ausgegangen werden, dass alle Datumswerte im Ausgangstext gültig sind. Die regulären Ausdrücke müssen sich nicht mit Werten wie 9999-99-99 herumschlagen.

Lösung Benannte Captures \b(?\d\d\d\d)-(?\d\d)-(?\d\d)\b

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 \b(?'Jahr'\d\d\d\d)-(?'Monat'\d\d)-(?'Tag'\d\d)\b

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 \b(?P\d\d\d\d)-(?P\d\d)-(?P\d\d)\b

Regex-Optionen: Keine Regex-Varianten: PCRE 4 und neuer, Perl 5.10, Python

Benannte Rückwärtsreferenzen \b\d\d(?\d\d)-\k-\k\b

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 \b\d\d(?'magisch'\d\d)-\k'magisch'-\k'magisch'\b

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 \b\d\d(?P\d\d)-(?P=magisch)-(?P=magisch)\b

Regex-Optionen: Keine Regex-Varianten: PCRE 4 und neuer, Perl 5.10, Python

Diskussion Benannte Captures Die Rezepte 2.9 und 2.10 beschreiben einfangende Gruppen und Rückwärtsreferenzen. Genauer gesagt, haben diese Rezepte nummerierte einfangende Gruppen und nummerierte Rückwärtsreferenzen genutzt. Jede Gruppe erhält automatisch eine Zahl, die Sie für die Rückwärtsreferenz nutzen können. Moderne Regex-Varianten unterstützen neben den nummerierten auch benannte einfangende Gruppen. Der einzige Unterschied zwischen benannten und nummerierten Gruppen ist, dass Sie den erstgenannten einen beschreibenden Namen zuweisen können, statt

2.11 Teile des gefundenen Texts einfangen und benennen | 67

sich mit automatisch vorgegebenen Zahlen herumschlagen zu müssen. Benannte Gruppen lassen Ihren regulären Ausdruck lesbarer und wartbarer werden. Fügt man eine einfangende Gruppe in eine bestehende Regex ein, können sich die Zahlen ändern, die den einfangenden Gruppen zugewiesen sind. Selbstvergebene Namen bleiben dagegen bestehen. Python nutzte die erste Regex-Variante, die benannte Captures unterstützte. Dort wurde die Syntax ‹(?PRegex)› genutzt. Der Name musste aus Wortzeichen bestehen, die durch ‹\w› gefunden werden können. ‹(?P› ist die öffnende Klammer der Gruppe, während ‹)› die schließende Klammer ist. Die Designer der .NET-Klasse Regex entwickelten ihre eigene Syntax für benannte Captures, die zwei Versionen ermöglicht. ‹(?Regex)› simuliert die Python-Syntax, nur ohne das P. Der Name muss aus Wortzeichen bestehen, die durch ‹\w› gefunden werden können. ‹(?› ist die öffnende Klammer der Gruppe, während ‹)› die schließende Klammer ist. Die spitzen Klammern in der Syntax sind nervig, wenn Sie in XML kodieren oder dieses Buch per DocBook XML schreiben wollen. Das ist der Grund für die Alternative in .NET: ‹(?'Name'Regex)›. Die spitzen Klammern wurden durch einfache Anführungszeichen ersetzt. Sie können sich aussuchen, welche Syntax Sie nutzen wollen. Die Funktionalität ist identisch. Vielleicht aufgrund der größeren Beliebtheit von .NET gegenüber Python scheint die .NET-Syntax diejenige zu sein, die Entwickler von anderen Regex-Bibliotheken lieber kopieren. Perl 5.10 nutzt sie genauso wie die Oniguruma-Engine in Ruby 1.9. PCRE hat die Python-Syntax vor langer Zeit kopiert, als Perl noch keine benannten Captures unterstützte. PCRE 7, die Version, die in Perl 5.10 neue Features ergänzt hat, unterstützt sowohl die .NET-Syntax als auch die Python-Syntax. Vielleicht als Anerkennung des Erfolgs von PCRE unterstützt Perl 5.10 in einer Art umgekehrter Kompatibilität auch die Python-Syntax. In PCRE und Perl 5.10 ist die Funktionalität der .NET-Syntax und der Python-Syntax für benannte Captures identisch. Wählen Sie die Syntax aus, die für Sie am nützlichsten ist. Wenn Sie in PHP entwickeln und wollen, dass Ihr Code auch mit älteren Versionen von PHP arbeitet, die dementsprechend ältere Versionen von PCRE nutzen, verwenden Sie am besten die Python-Syntax. Wenn Sie keine Kompatibilität zu älteren Versionen brauchen und auch mit .NET oder Ruby arbeiten, erleichtert die .NET-Syntax das Kopieren zwischen all diesen Sprachen. Sind Sie unsicher, nutzen Sie die Python-Syntax für PHP/PCRE. Leute, die Ihren Code mit einer älteren Version von PCRE kompilieren, werden nicht so glücklich sein, wenn die Regexes in Ihrem Code plötzlich nicht mehr funktionieren. Wenn Sie eine Regex nach .NET oder Ruby kopieren, ist das Löschen von ein paar P kein so großer Aufwand. Die Dokumentationen zu PCRE 7 und Perl 5.10 erwähnen die Python-Syntax kaum, aber sie ist auf jeden Fall nicht veraltet. Bei PCRE und PHP empfehlen wir sie sogar.

68 | Kapitel 2: Grundlagen regulärer Ausdrücke

Benannte Rückwärtsreferenzen Mit den benannten Captures kommen auch benannte Rückwärtsreferenzen. So, wie benannte einfangende Gruppe funktional identisch mit nummerierten einfangenden Gruppen sind, sind auch benannte Rückwärtsreferenzen funktional identisch mit nummerierten Rückwärtsreferenzen. Sie lassen sich nur einfacher lesen und warten. Python nutzt die Syntax ‹(?P=Name)›, um eine Rückwärtsreferenz für die Gruppe Name zu erzeugen. Auch wenn diese Syntax Klammern nutzt, ist die Rückwärtsreferenz keine Gruppe. Sie können zwischen den Namen und die schließende Klammer nichts anderes schreiben. Eine Rückwärtsreferenz ‹(?P=Name)› ist genauso wie ‹\1› ein singuläres RegexToken. .NET nutzt die Syntax ‹\k› und ‹\k'Name'›. Beide Versionen sind funktional identisch und können beliebig kombiniert werden. Eine benannte Gruppe, die in der Syntax mit spitzen Klammern erstellt wurde, kann mit der Anführungszeichensyntax für die Rückwärtsreferenz genutzt werden und umgekehrt. Wir empfehlen Ihnen dringend, benannte und nummerierte Gruppen nicht zusammen in einer Regex zu nutzen. Die verschiedenen Varianten haben unterschiedliche Regeln für das Durchnummerieren unbenannter Gruppen, die zwischen benannten Gruppen auftauchen. Perl 5.10 und Ruby 1.9 haben die .NET-Syntax übernommen, folgen aber nicht den Regeln von .NET für das Nummerieren benannter Gruppen oder das Mischen nummerierter Gruppen mit benannten Gruppen. Anstatt die Unterschiede zu erläutern, empfehle ich einfach, benannte und nummerierte Gruppen nicht zusammen zu nutzen. Vermeiden Sie Chaos und geben Sie entweder allen unbenannten Gruppen einen Namen oder sorgen Sie dafür, dass sie nicht einfangend sind.

Siehe auch Rezepte 2.9, 2.10, 2.21 und 3.9.

2.12 Teile der Regex mehrfach wiederholen Problem Erstellen von regulären Ausdrücken, die zu folgenden Arten von Zahlen passen: • einem Googol (eine Dezimalzahl mit 100 Stellen), • einer Hexadezimalzahl mit 32 Bit, • einer Hexadezimalzahl mit 32 Bit und einem optional Suffix h, • einer Gleitkommazahl mit einem optionalen ganzzahligen Anteil, einem verpflichtenden Nachkommateil und einem optionalen Exponenten. Jeder Teil kann eine beliebige Zahl von Stellen haben.

2.12 Teile der Regex mehrfach wiederholen | 69

Lösung Googol \b\d{100}\b

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Hexadezimale Zahl \b[a-f0-9]{1,8}\b

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Hexadezimale Zahl mit optionalem Suffix \b[a-f0-9]{1,8}h?\b

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Gleitkommazahl \d*\.\d+(e\d+)?

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Feste Wiederholung Der Quantor ‹{n}› wiederholt das vorige Regex-Token n Mal, wobei n eine positive Zahl ist. Das ‹\d{100}› in ‹\b\d{100}\b› findet also einen Text mit 100 Ziffern. Sie könnten das Gleiche auch erreichen, indem Sie 100 Mal ‹\d› eingeben. ‹{1}› wiederholt das vorige Token ein Mal, so als gäbe es keinen Quantor. ‹ab{1}c› ist die gleiche Regex wie ‹abc›. ‹{0}› wiederholt das vorige Token null Mal, womit es im Endeffekt aus dem regulären Ausdruck entfernt wird. ‹ab{0}c› ist die gleiche Regex wie ‹ac›.

Variable Wiederholung Bei der variablen Wiederholung nutzen wir den Quantor ‹{n,m}›, wobei n eine positive Zahl und m größer als n ist. ‹\b[a-f0-9]{1,8}\b› passt zu einer hexadezimalen Zahl mit einer bis acht Stellen. Bei einer variablen Wiederholung wird die Reihenfolge der Alternativen wichtig. Rezept 2.13 geht an dieser Stelle mehr ins Detail.

70 | Kapitel 2: Grundlagen regulärer Ausdrücke

Wenn n und m gleich sind, haben wir wieder die feste Wiederholung. ‹\b\d{100,100}\b› ist die gleiche Regex wie ‹\b\d{100}\b›.

Unendliche Wiederholung Der Quantor ‹{n,}›, bei dem n eine positive Zahl ist, ermöglicht unendlich viele Wiederholungen. Im Prinzip ist eine unendliche Wiederholung eine variable Wiederholung ohne Obergrenze. ‹\d{1,}› passt zu einer oder mehr Ziffern und ist das Gleiche wie ‹\d+›. Ein Plus nach

einem Regex-Token, bei dem es sich nicht um einen Quantor handelt, bedeutet „eins oder mehr“. Rezept 2.13 erklärt die Bedeutung eines Pluszeichens nach einem Quantor. ‹\d{0,}› passt zu null oder mehr Ziffern und ist das Gleiche wie ‹\d*›. Der Stern bedeutet immer „null oder mehr“. Neben der unendlichen Wiederholung sorgen ‹{0,}› und

der Stern auch dafür, dass das vorige Token optional wird.

Etwas optional machen Wenn wir eine variable Wiederholung nutzen, bei der n auf null gesetzt wurde, machen wir damit das Token, das vor dem Quantor steht, optional. ‹h{0,1}› passt zu einem ‹h› – oder gar keinem. Wenn es kein h gibt, führt ‹h{0,1}› zu einer Übereinstimmung mit Null-Länge. Nutzen Sie ‹h{0,1}› als regulären Ausdruck allein, findet dieser eine Übereinstimmung mit Null-Länge vor jedem Zeichen im Text, das kein h ist. Jedes h führt dagegen zu einer Übereinstimmung mit einem Zeichen (dem h). ‹h?› ist das Gleiche wie ‹h{0,1}›. Ein Fragezeichen nach einem gültigen und vollständigen Regex-Token, das kein Quantor ist, bedeutet „null oder ein Mal“. Das nächste Rezept beschreibt die Bedeutung eines Fragezeichens nach einem Quantor. Ein Fragezeichen oder ein anderer Quantor direkt nach einer öffnenden Klammer führt zu einem Syntaxfehler. Perl und die darauf aufbauenden Varianten nutzen diese Form, um der Regex-Syntax „Perl-Erweiterungen“ hinzuzufügen. Vorige Rezepte haben nicht-einfangende Gruppen und benannte einfangende Gruppen vorgestellt, die alle ein Fragezeichen nach einer öffnenden Klammer als Teil ihrer Syntax verwenden. Diese Fragezeichen sind keine Quantoren, sondern einfach Teil der Syntax für nicht-einfangende und benannte einfangende Gruppen. Die folgenden Rezepte werden noch mehr Arten von Gruppen vorstellen, die eine Syntax mit ‹(?› nutzen.

Wiederholende Gruppen Wenn Sie einen Quantor nach der schließenden Klammer einer Gruppe setzen, wird die ganze Gruppe wiederholt. ‹(?:abc){3}› ist das Gleiche wie ‹abcabcabc›.

2.12 Teile der Regex mehrfach wiederholen | 71

Quantoren können verschachtelt werden. ‹(e\d+)?› passt zu einem e, gefolgt von einer oder mehreren Ziffern, oder zu einer leeren Übereinstimmung. Bei unserer Regex für die Gleitkommazahl ist dies der optionale Exponent. Einfangende Gruppen können wiederholt werden. Wie in Rezept 2.9 beschrieben, wird die Übereinstimmung der Gruppe jedes Mal gespeichert, wenn die Engine die Gruppe verlässt. Dabei wird der alte Wert immer überschrieben. ‹(\d\d){1,3}› passt zu einem String mit zwei, vier oder sechs Ziffern. Die Engine verlässt diese Gruppe drei Mal. Wenn die Regex 123456 findet, wird die einfangende Gruppe den Wert 56 enthalten, da 56 bei der letzten Iteration der Gruppe gespeichert wurde. Die anderen beiden Übereinstimmungen durch die Gruppe, 12 und 34, können nicht ausgelesen werden. ‹(\d\d){3}› findet den gleichen Text wie ‹\d\d\d\d(\d\d)›. Wenn Sie alle zwei, vier oder sechs Ziffern einfangen wollen und nicht nur die letzten beiden, müssen Sie die einfangende Gruppe um den Quantor herum einrichten und nicht die einfangende Gruppe wiederholen: ‹((?:\d\d){1,3})›. Hier nutzen wir eine nicht-einfangende Gruppe, um die Gruppierungsfunktion von der einfangenden Gruppe zu übernehmen. Wir hätten auch zwei einfangende Gruppen verwenden können: ‹((\d\d){1,3})›. Wenn diese letzte Regex 123456 findet, enthält ‹\1› den Wert 123456 und ‹\2› den Wert 56.

Die Regex-Engine von .NET ist die einzige, die es Ihnen erlaubt, alle Iterationen einer wiederholten einfangenden Gruppe auszulesen. Wenn Sie direkt die Eigenschaft Value der Gruppe abfragen, die einen String zurückliefert, werden Sie 56 erhalten, so wie bei allen anderen Regex-Engines auch. Rückwärtsverweise im regulären Ausdruck und im Ersetzungstext nutzen ebenfalls die 56, aber wenn Sie die CaptureCollection der Gruppe verwenden, erhalten Sie einen Stack mit 56, 34 und 12.

Siehe auch Rezepte 2.9, 2.13, 2.14.

2.13 Minimale oder maximale Wiederholung auswählen Problem Ein Paar XHTML-Tags der Form

und

und den Text dazwischen finden. Der Text zwischen den Tags kann andere XHTML-Tags enthalten.

Lösung

.*?



Regex-Optionen: Punkt passt zu Zeilenumbruch Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

72 | Kapitel 2: Grundlagen regulärer Ausdrücke

Diskussion Alle in Rezept 2.12 behandelten Quantoren sind gierig (greedy), das heißt, sie versuchen, so häufig wie möglich wiederholt zu werden und erst dann mit der Regex weiterzumachen, wenn es keine weitere Übereinstimmung mehr gibt. Damit kann es schwer werden, Tags in XHTML (von XML abgeleitet; damit braucht jedes öffnende Tag ein schließendes) paarweise zu behandeln. Schauen Sie sich den folgenden einfachen Ausschnitt eines XHTML-Texts an:

Die Aufgabe ist es, den Anfang eines Absatzes zu finden.

Dann müssen Sie das Ende des Absatzes finden.



Es gibt hier zwei öffnende

-Tags und zwei schließende

-Tags. Sie wollen das erste

mit dem ersten

finden, da beide zusammen einen einzelnen Absatz auszeichnen. Beachten Sie, dass dieser Absatz ein verschachteltes -Tag enthält, daher kann die Regex nicht einfach abbrechen, wenn sie einem \d+)› entspricht, passt nicht auf abc123. ‹\w++› findet abc123 vollständig. Dann versucht die Regex-Engine, ‹\d++› am Ende des Texts zu finden. Da es aber keine weiteren Buchstaben gibt, schlägt ‹\d++› fehl. Ohne sich Backtracking-Positionen zu merken, ist diese

Suche dann nicht erfolgreich.

2.14 Unnötiges Backtracking vermeiden | 77

‹(?>\w+\d+)› hat zwei gierige Quantoren innerhalb der gleichen atomaren Gruppe. In der

Gruppe wird das Backtracking normal durchgeführt. Die Backtracking-Positionen werden erst verworfen, wenn die Engine die gesamte Gruppe verlässt. Lautet der Text abc123, passt ‹\w+› zu abc123. Der gierige Quantor merkt sich Backtracking-Positionen. Wenn ‹\d+› nichts findet, rückt ‹\w+› ein Zeichen heraus. ‹\d+› passt dann zu 3. Jetzt verlässt die Engine die atomare Gruppe und verwirft alle Backtracking-Positionen, die sie sich für ‹\w+› und ‹\d+› gemerkt hat. Da das Ende der Regex erreicht wurde, macht das hier auch keinen Unterschied. Es gibt aber ein Suchergebnis. Falls das Ende noch nicht erreicht ist, so wie in ‹(?>\w+\d+)\d+›, hätten wir die gleiche Situation wie bei ‹\w++\d++›. Für das zweite ‹\d+› wäre am Ende des Texts nichts mehr übrig. Da die Backtracking-Positionen weggeworfen wurden, kann die Regex-Engine nur aufgeben. Possessive Quantoren und atomare Gruppen helfen nicht nur beim Optimieren regulärer Ausdrücke, sie können durchaus auch zu anderen Übereinstimmungen führen, da die Zeichen herausfallen, die durch das Backtracking erreicht worden wären. Dieses Rezept zeigt Ihnen, wie Sie possessive Quantoren und atomare Gruppen nutzen, um kleinere Optimierungen vorzunehmen. Die wirken sich eventuell nicht einmal messbar auf die Geschwindigkeit aus. Das nächste Rezept wird aber zeigen, wie man durch atomare Gruppen auch für drastische Unterschiede sorgen kann.

Siehe auch Rezepte 2.12 und 2.15.

2.15 Aus dem Ruder laufende Wiederholungen verhindern Problem Mit einem einzelnen regulären Ausdruck eine komplette HTML-Datei abdecken und auf korrekt verschachtelte html-, head-, title- und body-Tags prüfen. Der reguläre Ausdruck muss bei HTML-Dateien mit nicht korrekt genutzten Tags effizient fehlschlagen.

Lösung (?>.*?)(?>.*?)(?>.*?) (?>.*?)(?>.*?]*>)(?>.*?).*?

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Punkt passt zu Zeilenumbruch Regex-Varianten: .NET, Java, PCRE, Perl, Ruby JavaScript und Python unterstützen keine atomaren Gruppen. Es gibt daher bei diesen beiden Varianten keine Möglichkeit, ein unnötiges Backtracking zu vermeiden. Wenn Sie

78 | Kapitel 2: Grundlagen regulärer Ausdrücke

in diesen Sprachen programmieren, können Sie das Problem umgehen, indem Sie für jedes dieser Tags eine literale Textsuche durchführen und dabei nach dem jeweils nächsten Tag vom Endpunkt der vorherigen Suche aus suchen.

Diskussion Die korrekte Lösung für dieses Problem lässt sich einfacher verstehen, wenn wir mit folgender naiven Lösung beginnen: .*?.*?.*? .*?.*?]*>.*?.*?

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Punkt passt zu Zeilenumbruch Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Wenn Sie diese Regex mit einer korrekten HTML-Datei testen, funktioniert sie wunderbar. ‹.*?› springt über alles andere hinweg, weil wir Punkt passt zu Zeilenumbruch aktiviert haben. Der genügsame Stern stellt sicher, dass die Regex immer nur ein Zeichen weitergeht und jedes Mal prüft, ob das nächste Tag passt. Die Rezepte 2.4 und 2.13 erläutern das genauer. Aber wenn der Ausgangstext nicht alle diese HTML-Tags besitzt, kommen Sie mit dieser Regex in Schwierigkeiten. Am schlimmsten ist es, wenn fehlt. Stellen Sie sich vor, die Regex-Engine hat alle vorigen Tags gefunden und ist nun damit beschäftigt, das letzte ‹.*?› zu erweitern. Da ‹› nie passen kann, wird ‹.*?› immer weiter bis zum Ende der Datei expandiert. Ist das Ende der Datei erreicht, schlägt die Regex fehl. Aber das ist noch nicht das Ende der Geschichte. Alle anderen sechs ‹.*?› haben sich eine Backtracking-Position gemerkt, die es ihnen erlaubt, weiter zu wachsen. Wenn das letzte ‹.*?› erfolglos war, wird das vorherige erweitert, das dann Schritt für Schritt findet. Der gleiche Text war schon vorher durch das literale ‹› in der Regex gefunden worden. Dieses ‹.*?› wird aber auch bis zum Ende der Datei erweitert, ebenso wie alle vorherigen genügsamen Punkte. Erst wenn das erste ‹.*?› das Ende der Datei erreicht, wird die Regex-Engine erkennen, dass die Suche erfolglos war. Dieser reguläre Ausdruck hat im schlimmsten Fall eine Komplexität von O(n7), also die Länge des Ausgangstexts in siebter Potenz. Es gibt sieben genügsame Punkte, die potenziell bis zum Ende der Datei erweitert werden. Wenn die Datei doppelt so groß ist, kann die Regex 128 Mal mehr Schritte brauchen, um zu erkennen, dass sie nicht passt. Wir nennen das katastrophales Backtracking. Es wird so viel Backtracking durchgeführt, dass die Regex entweder ewig läuft oder Ihre Anwendung zum Absturz bringt. Manche Regex-Implementierungen sind schlau und brechen solche aus dem Ruder laufenden Versuche ab, aber selbst dann wird die Regex für die Performance Ihrer Anwendung vernichtend sein.

2.15 Aus dem Ruder laufende Wiederholungen verhindern | 79

Das katastrophale Backtracking ist eine Form eines Phänomens, das als kombinatorische Explosion bekannt ist. Viele orthogonale Bedingungen treffen zusammen, und alle Kombinationen müssen ausprobiert werden. Sie könnten auch sagen, dass die Regex ein kartesisches Produkt der verschiedenen Wiederholungsoperatoren ist.

Die Lösung ist, atomare Gruppen zu nutzen, um das überflüssige Backtracking zu unterbinden. Es gibt für das sechste ‹.*?› keine Notwendigkeit, sich weiter auszudehnen, wenn ‹› gefunden wurde. Wird ‹› nicht gefunden, wird auch ein Erweitern des sechsten genügsamen Punkts kein schließendes html-Tag aus dem Hut zaubern können. Damit sich ein Quantor-Regex-Token nicht weiter ausdehnt, wenn das darauffolgende Element passt, stecken Sie beides – den Quantor-Teil der Regex und das folgende Element – zusammen in eine atomare Gruppe: ‹(?>.*?)›. Jetzt verwirft die RegexEngine alle Positionen für ‹.*?›, wenn ‹› gefunden wird. Wird später ‹› nicht gefunden, hat die Regex-Engine schon den Teil ‹.*?› vergessen, und es gibt keine zusätzlichen Erweiterungen. Wenn wir das Gleiche für alle anderen ‹.*?› in der Regex machen, wird sich keine von ihnen erweitern. Obwohl es immer noch sieben genügsame Punkte in der Regex gibt, werden sie sich nie gegenseitig überlappen. Damit verringert sich die Komplexität des regulären Ausdrucks auf O(n), ist also linear im Verhältnis zur Länge des Ausgangstexts. Ein regulärer Ausdruck kann nie effizienter sein.

Variationen Möchten Sie wirklich einmal ein katastrophales Backtracking beobachten, wenden Sie die Regex ‹(x+x+)+y› auf den Text xxxxxxxxxx an. Wenn schnell einen Misserfolg gemeldet wird, fügen Sie dem Text ein x hinzu. Wiederholen Sie das so lange, bis die Regex sehr langsam wird oder Ihre Anwendung abstürzt. Es werden nicht viele zusätzliche x benötigt werden, sofern Sie nicht Perl nutzen. Von den in diesem Buch behandelten Regex-Varianten ist nur Perl in der Lage, zu erkennen, dass der reguläre Ausdruck zu komplex ist, um anschließend die Suche ohne einen Crash abzubrechen. Die Komplexität dieser Regex ist O(2n). Wenn ‹y› nicht gefunden wird, wird die RegexEngine alle möglichen Wiederholungspermutationen für jedes ‹x+› und die sie enthaltende Gruppe durchprobieren. Eine solche Permutation kann so sein, dass ‹x+› auf xxx passt, das zweite ‹x+› auf x und die Gruppe dann drei weitere Male wiederholt wird, wobei jedes ‹x+› zu x passt. Bei zehn x gibt es 1.024 solcher Permutationen. Wenn wir die Anzahl auf 32 erhöhen, kommen wir auf über 4 Milliarden Permutationen. Damit wird jede Regex-Engine straucheln, sofern sie keine Sicherheitssysteme besitzt, mit deren Hilfe sie die Suche abbrechen und melden kann, dass sie zu kompliziert ist.

80 | Kapitel 2: Grundlagen regulärer Ausdrücke

In diesem Fall lässt sich der nicht sehr sinnhafte reguläre Ausdruck leicht umschreiben zu ‹xx+y›, womit genau die gleichen Übereinstimmungen mit linearem Zeitaufwand gefunden werden. In der Praxis ist die Lösung aber nicht so augenscheinlich, wenn die Regexes komplizierter sind. Im Prinzip müssen Sie aufpassen, ob zwei oder mehr Teile des regulären Ausdrucks auf den gleichen Text passen können. In diesen Fällen brauchen Sie eventuell atomare Gruppen, um sicherzustellen, dass die Regex-Engine nicht alle Möglichkeiten ausprobiert, den Text zwischen den beiden Teilen der Regex aufzuteilen. Testen Sie Ihre Regex immer mit (langen) Testtexten, in denen sich Abschnitte befinden, die zwar teilweise zur Regex passen, aber eben nicht ganz.

Siehe auch Rezepte 2.13 und 2.14.

2.16 Etwas auf Übereinstimmung prüfen, ohne es dem Gesamtergebnis hinzuzufügen Problem Finden eines beliebigen Worts, das zwischen einem Paar HTML-Bold-Tags steht, ohne die Tags in das Regex-Suchergebnis mit aufzunehmen. Wenn der Text zum Beispiel Meine Katze ist flauschig lautet, soll als Ergebnis nur Katze herauskommen.

Lösung (?-Tags), können Sie nun einfach den von der zweiten einfangenden Gruppe ermittelten Text verwenden, statt auf das vollständige Suchergebnis zuzugreifen.

86 | Kapitel 2: Grundlagen regulärer Ausdrücke

Möchten Sie eigentlich nur das Wort zwischen den Tags per Suchen und Ersetzen austauschen, verwenden Sie einfach eine Rückwärtsreferenz auf die erste einfangende Gruppe, um das öffnende Tag in den Ersetzungstext einzufügen. In dem hier genutzten Beispiel brauchen Sie die erste einfangende Gruppe eigentlich nicht, da das öffnende Tag immer das gleiche ist. Aber wenn es variabel ist, fügt die einfangende Gruppe genau das ein, das gefunden wurde. Rezept 2.21 erklärt das genauer. Wenn Sie schließlich wirklich ein Lookbehind simulieren wollen, können Sie das über zwei reguläre Ausdrücke machen. Zunächst suchen Sie nach Ihrer Regex ohne Lookbehind. Wenn sie passt, kopieren Sie den Teil des Ausgangstexts vor der Übereinstimmung in eine neue String-Variable. Führen Sie nun den Test, den Sie eigentlich innerhalb des Lookbehind machen wollten, mit einer zweiten Regex durch, wobei Sie einen EndeAnker anfügen (‹\z› oder ‹$›). Der Anker stellt sicher, dass die Übereinstimmung der zweiten Regex am Ende des Strings liegt. Da Sie den String dort abgeschnitten haben, wo die erste Regex passte, ist auf diese Weise dafür gesorgt, dass die zweite Übereinstimmung direkt links von der ersten liegt. In JavaScript könnten Sie das mit folgendem Code erreichen: var mainregexp = /\w+(?=)/; var lookbehind = /$/; if (match = mainregexp.exec("Meine Katze ist flauschig")) { // Wort vor einem schließenden Tag gefunden var potentialmatch = match[0]; var leftContext = match.input.substring(0, match.index); if (lookbehind.exec(leftContext)) { // Lookbehind passte: // potentialmatch steht zwischen -Tags } else { // Lookbehind passte nicht: potentialmatch ist nicht das Gewünschte } } else { // Kein Wort vor einem schließenden Tag gefunden }

Siehe auch Rezepte 5.5 und 5.6.

2.17 Abhängig von einer Bedingung eine von zwei Alternativen finden Problem Erstellen eines regulären Ausdrucks, der eine durch Kommata getrennte Liste mit den Worten eins, zwei und drei findet. Jedes Wort kann beliebig häufig vorkommen, muss aber mindestens ein Mal vorhanden sein.

2.17 Abhängig von einer Bedingung eine von zwei Alternativen finden | 87

Lösung \b(?:(?:(eins)|(zwei)|(drei))(?:,|\b)){3,}(?(1)|(?!))(?(2)|(?!))(?(3)|(?!))

Regex-Optionen: Keine Regex-Varianten: .NET, JavaScript, PCRE, Perl, Python Java und Ruby unterstützen keine bedingten Ausdrücke. Wenn Sie in Java oder Ruby programmieren (oder in einer anderen Sprache), können Sie den regulären Ausdruck ohne die Bedingungen nutzen und mit zusätzlichem Code prüfen, ob jede der drei einfangenden Gruppen etwas enthält. \b(?:(?:(eins)|(zwei)|(drei))(?:,|\b)){3,}

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion .NET, JavaScript, PCRE, Perl und Python unterstützen bedingte Ausdrücke mithilfe nummerierter einfangender Gruppen. ‹(?(1)then|else)› ist ein bedingter Ausdruck, der prüft, ob die erste einfangende Gruppe schon etwas gefunden hat. Wenn dem so ist, versucht die Regex-Engine, das ‹then› zu finden. Enthält die einfangende Gruppe bisher noch keine Übereinstimmung, wird versucht, den ‹else›-Teil zu finden. Die Klammern, das Fragezeichen und der vertikale Balken gehören alle zur Syntax für bedingte Ausdrücke. Sie haben an dieser Stelle nicht ihre normale Bedeutung. Sie können für die ‹then›- und ‹else›-Teile beliebige Regex-Ausdrücke nutzen. Die einzige Einschränkung besteht darin, dass Sie bei der Verwendung von Alternationen eine Gruppe nutzen müssen, um sie zusammenzuhalten. Im bedingten Ausdruck selbst ist nur ein einziger vertikaler Balken direkt erlaubt. Wenn Sie möchten, können Sie einen der beiden Teile ‹then› oder ‹else› auch weglassen. Die leere Regex findet immer eine Übereinstimmung der Länge null. Die Lösung für dieses Rezept nutzt drei bedingte Ausdrücke, die alle einen leeren ‹then›-Teil haben. Wenn die einfangende Gruppe etwas enthält, ist der bedingte Ausdruck direkt erfolgreich. Im ‹else›-Teil steht ein leeres negatives Lookahead ‹(?!)›. Da die leere Regex immer passt, schlägt ein negatives Lookahead mit einer leeren Regex immer fehl. Damit schlägt auch der bedingte Ausdruck ‹(?(1)|(?!))› immer fehl, wenn die erste einfangende Gruppe nichts Passendes enthält. Indem jede der drei erforderlichen Alternativen in ihrer eigenen einfangenden Gruppe untergebracht ist, können wir am Ende der Regex drei bedingte Ausdrücke nutzen, um zu prüfen, ob alle einfangenden Gruppen etwas enthalten. .NET unterstützt auch benannte bedingte Ausdrücke. ‹(?(Name)then|else)› prüft, ob die benannte einfangende Gruppe Name schon etwas enthält.

88 | Kapitel 2: Grundlagen regulärer Ausdrücke

Um besser verstehen zu können, wie bedingte Ausdrücke funktionieren, wollen wir uns den regulären Ausdruck ‹(a)?b(?(1)c|d)› anschauen. Dieser ist im Prinzip nichts anderes als eine komplizierte Form von ‹abc|bd›. Beginnt der Ausgangstext mit einem a, wird dieses in der ersten einfangenden Gruppe gesichert. Wenn nicht, enthält diese Gruppe auch nichts. Es ist wichtig, dass sich das Fragezeichen außerhalb der einfangenden Gruppe befindet, da damit die gesamte Gruppe optional wird. Wenn es kein a gibt, wird die Gruppe null Mal wiederholt und erhält auch keine Chance, etwas anderes einzufangen. Sie kann keinen String der Länge null abspeichern. Wenn Sie ‹(a?)› nutzen, nimmt die Gruppe immer an den Übereinstimmungsversuchen teil. Es gibt dann nach der Gruppe keinen Quantor, daher wird sie genau ein Mal ausgeführt. Die Gruppe wird entweder ein a oder gar nichts einfangen. Unabhängig davon, ob ein ‹a› gefunden wurde oder nicht, ist das nächste Token ein ‹b›. Dann kommt der bedingte Ausdruck. Wenn die einfangende Gruppe etwas einfangen konnte – selbst einen String der Länge null (was hier nicht geht) –, wird mit ‹c› weitergemacht. Wenn nicht, nutzt die Regex-Engine stattdessen ‹d›. Einfach gesagt, passt ‹(a)?b(?(1)c|d)› entweder zu ab, gefolgt von c, oder zu b, gefolgt von d. Bei .NET, PCRE und Perl, aber nicht bei Python, können bedingte Ausdrücke auch Lookarounds nutzen. ‹(?(?=if)then|else)› prüft zunächst ‹(?=if)› als normales Lookahead. Rezept 2.16 beschreibt dazu die Details. Wenn das Lookaround erfolgreich war, wird mit dem ‹then›-Teil weitergemacht, ansonsten mit dem ‹else›-Teil. Da ein Lookaround die Länge null hat, werden die ‹then›- und ‹else›-Regexes an der gleichen Position im Ausgangstext gesucht – egal ob ‹if› erfolgreich war oder fehlschlug. Sie können im bedingten Ausdruck Lookbehinds statt Lookaheads nutzen. Negative Lookarounds sind ebenfalls möglich, auch wenn wir das eher nicht empfehlen, da man dann nur mit den verschiedenen Zweigen der bedingten Ausführung durcheinandergerät. Ein bedingter Ausdruck, der Lookarounds nutzt, kann auch ohne den bedingten Ausdruck mittels ‹(?=if)then|(?!if)else› geschrieben werden. Wenn das positive Lookahead erfolgreich ist, wird ‹then› getestet. Wenn es keinen Erfolg hatte, springt die Alternation ein. Das negative Lookahead führt den gleichen Test durch. Wenn ‹if› erfolglos geprüft wird – was sichergestellt ist, weil ‹(?=if)› fehlschlug –, ist es erfolgreich. Daher wird der ‹else›-Zweig getestet. Setzt man das Lookahead in einen bedingten Ausdruck, spart man allerdings Zeit, weil ‹if› damit nur ein Mal ausgetestet wird.

Siehe auch Rezepte 2.9 und 2.16.

2.17 Abhängig von einer Bedingung eine von zwei Alternativen finden | 89

2.18 Kommentare für einen regulären Ausdruck Problem ‹\d{4}-\d{2}-\d{2}› passt zu einem Datum im Format yyyy-mm-dd, allerdings ohne eine Überprüfung der Zahlen auf Sinnhaftigkeit. Solch ein einfacher regulärer Ausdruck ist dann passend, wenn Sie wissen, dass Ihre Daten keine ungültigen Werte enthalten. Durch Hinzufügen von Kommentaren zum regulären Ausdruck soll deutlich gemacht werden, was jeder Teil des Ausdrucks tut.

Lösung \d{4} \d{2} \d{2}

# # # # #

Jahr Trennzeichen Monat Trennzeichen Tag

Regex-Optionen: Freiform Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Diskussion Freiform-Modus Reguläre Ausdrücke können schnell unübersichtlich und schwer verständlich werden. Analog zum Kommentieren von Quellcode sollten Sie auch alle regulären Ausdrücke kommentieren, die nicht ganz trivial sind. Alle in diesem Buch behandelten Regex-Varianten, mit Ausnahme von JavaScript, bieten eine alternative Syntax für reguläre Ausdrücke an, die es sehr leicht macht, Ihre regulären Ausdrücke sauber zu kommentieren. Sie können diese Syntax aktivieren, indem Sie die Freiform-Option einschalten. In den verschiedenen Programmiersprachen hat sie allerdings unterschiedliche Namen. In .NET setzen Sie die Option RegexOptions.IgnorePatternWhitespace. In Java übergeben Sie die Option Pattern.COMMENTS. Python erwartet re.VERBOSE. PHP, Perl und Ruby nutzen die Option /x. Schaltet man den Freiform-Modus ein, hat das zwei Effekte. Das Hash-Symbol (#) wird (außerhalb von Zeichenklassen) zu einem Metazeichen. Mit dem Hash wird ein Kommentar eingeleitet, der bis zum Ende der Zeile oder bis zum Ende der Regex läuft (je nachdem, was zuerst eintritt). Das Hash und alles danach wird von der Regex-Engine schlicht ignoriert. Um ein literales Hash-Zeichen zu finden, stecken Sie es entweder in eine Zeichenklasse ‹[#]› oder maskieren es per ‹\#›.

90 | Kapitel 2: Grundlagen regulärer Ausdrücke

Der andere Effekt ist, dass Leerraum – also Leerzeichen, Tabs und Zeilenumbrüche – ebenfalls außerhalb von Zeichenklassen ignoriert werden. Um ein literales Leerzeichen zu finden, müssen Sie es entweder innerhalb einer Zeichenklasse unterbringen ‹[z]› oder es maskieren ‹\z›. Wenn Sie sich Sorgen um die Lesbarkeit machen, können Sie stattdessen auch die hexadezimale Markierung ‹\x20› oder die Unicode-Maskierung ‹\u0020› bzw. ‹\x{0020}› nutzen. Um ein Tab-Zeichen zu finden, nutzen Sie ‹\t›, für Zeilenumbrüche ‹\r\n› (Windows) oder ‹\n› (Unix/Linux/OS X). Innerhalb von Zeichenklassen ändert sich durch den Freiform-Modus nichts. Eine Zeichenklasse ist ein einzelnes Token. Jegliche Whitespace-Zeichen oder Hashes innerhalb von Zeichenklassen sind literale Zeichen und Bestandteile der Zeichenklasse. Sie können diese Klassen nicht aufbrechen, um Kommentare darin unterzubringen.

Java hat Freiform-Zeichenklassen Reguläre Ausdrücke würden ihrem Ruf nicht gerecht, wenn nicht wenigstens eine Variante inkompatibel zu den anderen wäre. In diesem Fall ist Java der Ausreißer. In Java werden Zeichenklassen nicht als einzelnes Token verarbeitet. Wenn Sie den Freiform-Modus aktivieren, ignoriert Java Leerraum in Zeichenklassen, und Hashes beginnen auch dort einen Kommentar. Das bedeutet, dass Sie ‹[z]› und ‹[#]› nicht nutzen können, um diese literalen Zeichen zu finden. Greifen Sie stattdessen auf ‹\u0020› und ‹\#› zurück.

Variationen (?#Jahr)\d{4}(?#Trennzeichen)-(?#Monat)\d{2}-(?#Tag)\d{2}

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE, Perl, Python, Ruby Wenn Sie aus irgendeinem Grund keine Freiformsyntax nutzen können oder wollen, haben Sie immer noch die Möglichkeit, per ‹(?#Kommentar)› Kommentare einzufügen. Alle Zeichen zwischen ‹(?#› und ‹)› werden ignoriert. Leider ist JavaScript die einzige Variante in diesem Buch, die weder den Freiform-Modus noch die hier beschriebene Kommentarsyntax kennt. Java unterstützt Letztere ebenfalls nicht. (?x)\d{4} \d{2} \d{2}

# # # # #

Jahr Trennzeichen Monat Trennzeichen Tag

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Wenn Sie den Freiform-Modus außerhalb des regulären Ausdrucks nicht einschalten können, haben Sie die Möglichkeit, den Modus-Modifikator ‹(?x)› an den Anfang des

2.18 Kommentare für einen regulären Ausdruck | 91

regulären Ausdrucks zu setzen. Stellen Sie sicher, dass es vor dem ‹(?x)› keinen Leerraum gibt. Der Freiform-Modus beginnt erst mit dem Modus-Modifikator, jeglicher Leerraum vorher wird für die Regex berücksichtigt. Modus-Modifikatoren werden detailliert in „Übereinstimmungen unabhängig von Großund Kleinschreibung“ auf Seite 29, einem Abschnitt von Rezept 2.1, erläutert.

2.19 Literalen Text im Ersetzungstext nutzen Problem Suchen und Ersetzen des Suchergebnisses eines regulären Ausdrucks durch die acht literalen Zeichen $%\*$1\1.

Lösung $%\*$$1\1

Ersetzungstextvarianten: .NET, JavaScript \$%\\*\$1\\1

Ersetzungstextvariante: Java $%\*\$1\\1

Ersetzungstextvariante: PHP \$%\*\$1\\1

Ersetzungstextvariante: Perl $%\*$1\\1

Ersetzungstextvarianten: Python, Ruby

Diskussion Welche Zeichen wo im Ersetzungstext zu maskieren sind Dieses Rezept zeigt Ihnen die verschiedenen Maskierungsregeln, die von den unterschiedlichen Ersetzungstextvarianten genutzt werden. Die einzigen beiden Zeichen, die Sie im Ersetzungstext je maskieren müssen, sind das Dollarzeichen und der Backslash. Die Maskierungszeichen sind ebenfalls das Dollarzeichen und der Backslash. Das in diesem Beispiel genutzte Prozentzeichen und der Stern sind immer literale Zeichen, auch wenn ein davor gesetzter Backslash als Maskierungszeichen statt als literaler Backslash behandelt werden kann. «$1» und/oder «\1» sind Rückwärtsreferenzen auf eine einfangende Gruppe. Rezept 2.21 erklärt, welche Variante welche Syntax für Rückwärtsreferenzen nutzt.

92 | Kapitel 2: Grundlagen regulärer Ausdrücke

Anhand der Tatsache, dass dieses Problem fünf verschiedene Lösungen für sieben Ersetzungstextvarianten hat, sieht man, dass es wirklich keinen Standard für die Ersetzungstextsyntax gibt.

.NET und JavaScript .NET und JavaScript behandeln einen Backslash immer als literales Zeichen. Maskieren Sie ihn niemals mit einem anderen Backslash, da Sie ansonsten im Ersetzungstext zwei Backslashs vorfinden werden. Ein allein stehendes Dollarzeichen ist ein literales Zeichen. Dollarzeichen müssen nur dann maskiert werden, wenn ihnen eine Ziffer, ein Kaufmanns-Und, ein Gravis (Backtick), ein einfaches Anführungszeichen, ein Unterstrich, ein Pluszeichen oder ein weiteres Dollarzeichen folgt. Um ein Dollarzeichen zu maskieren, nutzen Sie ein zweites Dollarzeichen. Sie können alle Dollarzeichen verdoppeln, wenn Sie das Gefühl haben, dass Ihr Ersetzungstext dadurch lesbarer wird. Diese Lösung ist daher gleichwertig: $$%\*$$1\1

Ersetzungstextvarianten: .NET, JavaScript .NET fordert zudem, dass Dollarzeichen, auf die eine öffnende geschweifte Klammer folgt, maskiert werden. «${Gruppe}» ist in .NET ein benannter Rückwärtsverweis. JavaScript unterstützt diese nicht.

Java In Java wird der Backslash genutzt, um Backslashs und Dollarzeichen im Ersetzungstext zu maskieren. Alle literalen Backslashs und Dollarzeichen müssen maskiert werden. Wenn Sie das nicht tun, wirft Java eine Exception.

PHP PHP braucht für Backslashs, auf die eine Ziffer folgt, und für Dollarzeichen, auf die eine Ziffer oder eine öffnende geschweifte Klammer folgt, eine Maskierung durch einen Backslash. Ein Backslash maskiert auch andere Backslashs. So müssen Sie also «\\\\» schreiben, um das Suchergebnis durch zwei literale Backslashs zu ersetzen. Alle andere Backslashs werden als literale Backslashs behandelt.

Perl Perl unterscheidet sich ein wenig von den anderen Ersetzungstextvarianten. Es hat eigentlich gar keine Ersetzungstextvariante. Wo andere Programmiersprachen eine besondere Logik haben, mit der die Routinen zum Suchen und Ersetzen Elemente substituieren, wie zum Beispiel «$1», gibt es in Perl einfach die normale Variablenersetzung. Im Ersetzungstext müssen Sie alle literalen Dollarzeichen mit einem Backslash maskieren, so wie Sie es in jedem normalen String in doppelten Anführungszeichen machen würden.

2.19 Literalen Text im Ersetzungstext nutzen | 93

Eine Ausnahme ist bei Perl, dass es die Syntax «\1» für Rückwärtsreferenzen unterstützt. Daher müssen Sie einen Backslash, auf den eine Ziffer folgt, maskieren, wenn der Backslash ein Literal sein soll. Ein Backslash, dem ein Dollarzeichen folgt, muss auch maskiert werden, um zu verhindern, dass der Backslash das Dollarzeichen maskiert. Ein Backslash maskiert zudem einen anderen Backslash. So müssen Sie also «\\\\» schreiben, um das Suchergebnis durch zwei literale Backslashs zu ersetzen. Alle anderen Backslashs werden als literale Backslashs behandelt.

Python und Ruby Das Dollarzeichen hat bei Python und Ruby keine besondere Bedeutung im Ersetzungstext. Backslashs müssen mit einem anderen Backslash maskiert werden, wenn ihnen ein Zeichen folgt, das dem Backslash eine besondere Bedeutung zuweist. Bei Python erzeugen «\1» bis «\9» und «\g\g

Ersetzungstextvariante: Python

Diskussion Durch das Einfügen des vollständigen Suchergebnisses der Regex in den Ersetzungstext ist es einfach, vor und/oder hinter dem gefundenen Text zusätzlichen Text einzufügen. Es lassen sich sogar mehrere Kopien des gefundenen Texts erzeugen. Sofern Sie nicht Python nutzen, müssen Sie auch keine einfangenden Gruppen in Ihrem regulären Ausdruck nutzen, um das gesamte Suchergebnis zu verwenden. In Perl ist «$&» sogar eine Variable. Perl speichert darin nach jeder erfolgreichen Anwendung einer Regex das Suchergebnis.

2.20 Einfügen des Suchergebnisses in den Ersetzungstext | 95

.NET und JavaScript haben die Perl-Syntax «$&» übernommen, um das Suchergebnis im Ersetzungstext einzufügen. Ruby nutzt statt der Dollarzeichen Backslashs, daher wird dort für das Suchergebnis «\&» verwendet. Java, PHP und Python haben kein spezielles Token, mit dem das gesamte Suchergebnis eingefügt werden kann, aber man kann einfangende Gruppen nutzen, um deren Inhalte in den Ersetzungstext einzufügen. Das Gesamtsuchergebnis ist dabei eine implizite einfangende Gruppe mit der Nummer 0. Bei Python müssen wir die Syntax für benannte Captures nutzen, um auf die Gruppe null zu verweisen. Dort wird «\0» nicht unterstützt. .NET und Ruby unterstützen ebenfalls eine einfangende Gruppe mit der Nummer 0. Das Ergebnis ist dort unabhängig von der Syntax.

Siehe auch „Suchen und Ersetzen mit regulären Ausdrücken“ in Kapitel 1 und Rezept 3.15.

2.21 Teile des gefundenen Texts in den Ersetzungstext einfügen Problem Finden einer zusammenhängende Folge von zehn Ziffern, wie zum Beispiel 1234567890. Umwandeln der Zahl in eine hübsch formatierte (amerikanische) Telefonnummer, zum Beispiel (123) 456-7890.

Lösung Regulärer Ausdruck \b(\d{3})(\d{3})(\d{4})\b

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Ersetzungstext ($1)z$2-$3

Ersetzungstextvarianten: .NET, Java, JavaScript, PHP, Perl (${1})z${2}-${3}

Ersetzungstextvarianten: .NET, PHP, Perl (\1)z\2-\3

Ersetzungstextvarianten: PHP, Python, Ruby

96 | Kapitel 2: Grundlagen regulärer Ausdrücke

Diskussion Verwendung einfangender Gruppen in Ersetzungstexten Rezept 2.10 erklärt, wie Sie einfangende Gruppen in Ihrem regulären Ausdruck nutzen können, um den gleichen Text mehr als ein Mal zu finden. Der von jeder einfangenden Gruppe gefundene Text ist aber auch nach jeder erfolgreichen Übereinstimmung verfügbar. Sie können ihn – in beliebiger Reihenfolge und auch mehr als ein Mal – in den Ersetzungstext einfügen. Manche Varianten, wie zum Beispiel Python und Ruby, nutzen die gleiche Syntax für Rückwärtsreferenzen («\1») sowohl im regulären Ausdruck als auch im Ersetzungstext. Andere Varianten greifen auf die Perl-Syntax «$1» zurück, bei der ein Dollarzeichen statt eines Backslashs verwendet wird. PHP unterstützt beide Formen. In Perl ist «$1», auch mit höheren Ziffern, eine echte Variable. Diese Variablen werden nach jeder erfolgreichen Suche per Regex gesetzt. Sie können sie bis zur nächsten RegexSuche beliebig im Code nutzen. .NET, Java, JavaScript und PHP lassen «$1» nur im Ersetzungstext selbst zu. Diese Programmiersprachen bieten andere Wege an, um im Code auf den Inhalt von einfangenden Gruppen zuzugreifen. Kapitel 3 beschreibt das detailliert.

$10 und größer Alle in diesem Buch behandelten Regex-Varianten unterstützen in ihren regulären Ausdrücken bis zu 99 einfangende Gruppen. Im Ersetzungstext kann es dabei schwierig sein, bei «$10» oder «\10» und noch höheren Gruppennummern zu entscheiden, was gemeint ist. Man kann dies entweder als die zehnte einfangende Gruppe betrachten oder als erste einfangende Gruppe, auf die eine literale Null folgt. .NET, PHP und Perl ermöglichen es Ihnen, um die Zahl eine geschweifte Klammer zu legen, um Ihr Anliegen deutlicher zu machen. «${10}» ist immer die zehnte einfangende Gruppe, und «${1}0» ist immer die erste einfangende Gruppe, gefolgt von einer literalen Null. Java und JavaScript versuchen, bei «$10» schlau zu sein. Wenn in Ihrem regulären Ausdruck eine einfangende Gruppe mit der angegebenen zweistelligen Zahl vorhanden ist, werden beide Ziffern für die Gruppe genutzt. Gibt es weniger einfangende Gruppen, wird nur die erste Ziffer für die Gruppe und die zweite als literale Ziffer genutzt. Damit ist «$23» die 23. einfangende Gruppe, wenn es sie denn gibt. Wenn nicht, handelt es sich um die zweite einfangende Gruppe, gefolgt von einer literalen «3». .NET, PHP, Perl, Python und Ruby behandeln «$10» und «\10» immer als zehnte einfangende Gruppe, egal ob sie existiert oder nicht. Wenn sie nicht vorhanden ist, kommt das Verhalten bei nicht-existierenden Gruppen ins Spiel.

2.21 Teile des gefundenen Texts in den Ersetzungstext einfügen | 97

Referenzen auf nicht-existierende Gruppen Der reguläre Ausdruck in der Lösung für dieses Rezept enthält drei einfangende Gruppen. Wenn Sie im Ersetzungstext «$4» oder «\4» einfügen, ergänzen Sie damit eine Referenz auf eine einfangende Gruppe, die gar nicht vorhanden ist. Damit wird eine von drei verschiedenen Verhaltensweisen ausgelöst. Java und Python werden laut aufschreien und eine Exception werfen oder eine Fehlermeldung liefern. Bei diesen Varianten sollten Sie also auf keinen Fall ungültige Rückwärtsverweise nutzen. (Eigentlich sollten Sie bei keiner Variante ungültige Rückwärtsverweise verwenden.) Wenn Sie «$4» oder «\4» als Literal einfügen wollen, müssen Sie das Dollarzeichen oder den Backslash maskieren. Rezept 2.19 beschreibt das im Detail. PHP, Perl und Ruby ersetzen alle Rückwärtsreferenzen im Ersetzungstext, auch solche, die auf nicht vorhandene Gruppen zeigen. Solche Gruppen enthalten natürlich keinen Text, daher werden die Referenzen auf diese Gruppen einfach durch nichts ersetzt. .NET und JavaScript belassen schließlich Rückwärtsverweise auf Gruppen, die nicht vorhanden sind, als literalen Text im Ersetzungstext. Alle Varianten ersetzen Gruppen, die zwar im regulären Ausdruck vorhanden sind, aber nichts gefunden haben. Dabei wird leerer Text eingefügt.

Lösung mit benannten Captures Regulärer Ausdruck \b(?\d{3})(?\d{3})(?\d{4})\b

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 \b(?'area'\d{3})(?'exchange'\d{3})(?'number'\d{4})\b

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 \b(?P\d{3})(?P\d{3})(?P\d{4})\b

Regex-Optionen: Keine Regex-Varianten: PCRE 4 und neuer, Perl 5.10, Python

Ersetzungstext (${area})z${exchange}-${number}

Ersetzungstextvariante: .NET (\g)z\g-\g

Ersetzungstextvariante: Python

98 | Kapitel 2: Grundlagen regulärer Ausdrücke

(\k)z\k-\k

Ersetzungstextvariante: Ruby 1.9 (\k'area')z\k'exchange'-\k'number'

Ersetzungstextvariante: Ruby 1.9 ($1)z$2-$3

Ersetzungstextvarianten: .NET, PHP, Perl 5.10 (${1})z${2}-${3}

Ersetzungstextvarianten: .NET, PHP, Perl 5.10 (\1)z\2-\3

Ersetzungstextvarianten: PHP, Python, Ruby 1.9

Varianten, die benannte Captures unterstützen .NET, Python und Ruby 1.9 ermöglichen es Ihnen, benannte Rückwärtsreferenzen im Ersetzungstext zu verwenden, wenn Sie benannte einfangende Gruppen in Ihrem regulären Ausdruck eingebaut haben. Bei .NET und Python funktioniert die Syntax für benannte Rückwärtsreferenzen genau so wie die von benannten und nummerierten einfangenden Gruppen. Geben Sie einfach den Namen oder die Nummer der Gruppe zwischen den geschweiften oder spitzen Klammern an. Ruby nutzt für Rückwärtsreferenzen die gleiche Syntax im Ersetzungstext wie im regulären Ausdruck. Bei benannten einfangenden Gruppen ist die Syntax in Ruby 1.9 «\k» oder «\k'group'». Die Wahl zwischen spitzen Klammern und einfachen Anführungszeichen ist schlicht Geschmackssache. Perl 5.10 und PHP (mit PCRE) unterstützen benannte einfangende Gruppen in regulären Ausdrücken, aber nicht im Ersetzungstext. Dort können Sie nummerierte Rückwärtsreferenzen verwenden, um auf die benannten einfangenden Gruppen aus dem regulären Ausdruck zuzugreifen. Perl 5.10 und PCRE weisen auch benannten Gruppen Nummern zu – von links nach rechts. .NET, Python und Ruby 1.9 lassen ebenfalls nummerierte Referenzen auf benannte Gruppen zu. Allerdings verwendet .NET ein anderes Nummerierungsschema für benannte Gruppen, wie in Rezept 2.11 erläutert. Ein Mischen von Namen und Nummern ist in .NET, Python oder Ruby nicht empfehlenswert. Entweder geben Sie all Ihren Gruppen Namen, oder Sie lassen es ganz. Nutzen Sie aber vor allem immer benannte Rückwärtsreferenzen für benannte Gruppen.

Siehe auch „Suchen und Ersetzen mit regulären Ausdrücken“ in Kapitel 1 und die Rezepte 2.9, 2.10, 2.11 und 3.15.

2.21 Teile des gefundenen Texts in den Ersetzungstext einfügen | 99

2.22 Suchergebniskontext in den Ersetzungstext einfügen Problem Erstellen eines Ersetzungstexts, der das Suchergebnis durch den Text vor der Übereinstimmung ersetzt, gefolgt vom kompletten Ausgangstext und dem Text nach der Übereinstimmung. Wenn zum Beispiel das Wort Treffer in VorherTrefferNachher gefunden wird, soll die Übereinstimmung durch VorherVorherTrefferNachherNachher ersetzt werden, was zum Ergebnis VorherVorherVorherTrefferNachherNachherNachher führt.

Lösung $`$_$'

Ersetzungstextvarianten: .NET, Perl \`\`\&\'\'

Ersetzungstextvariante: Ruby $`$`$&$'$'

Ersetzungstextvariante: JavaScript

Diskussion Der Begriff Kontext bezieht sich auf den Ausgangstext, auf den der reguläre Ausdruck angewandt wurde. Es gibt drei Elemente des Kontexts: den Ausgangstext vor dem Übereinstimmungsbereich, den Ausgangstext nach dem Übereinstimmungsbereich und den gesamten Ausgangstext. Der Text vor dem Übereinstimmungsbereich wird manchmal als linker Kontext und der danach dementsprechend als rechter Kontext bezeichnet. Der gesamte Ausgangstext setzt sich aus dem linken Kontext, dem Übereinstimmungsbereich und dem rechten Kontext zusammen. .NET und Perl unterstützen «$`», «$'» und «$_», um alle drei Kontextelemente im Ersetzungstext einzufügen. In Perl handelt es sich sogar um Variablen, die nach einer erfolgreichen Regex-Suche gefüllt werden und bis zum nächsten Suchvorgang im Code zur Verfügung stehen. Dollar plus Gravis (Backtick) ist der linke Kontext. Auf einer deutschen Tastatur erreichen Sie den Gravis, indem Sie die Umschalttaste zusammen mit der Taste rechts neben dem scharfen s (ß) und danach eventuell noch einmal die Leertaste drücken. Dollar plus einfaches Anführungszeichen ist der rechte Kontext. Das einfache Anführungszeichen erreicht man auf einer deutschen Tastatur üblicherweise über die Umschalttaste zusammen mit der Taste rechts neben dem Ä. Dollar plus Unterstrich enthält den gesamten Ausgangstext. Wie .NET und Perl nutzt JavaScript «$`» und «$'» für den linken und rechten Kontext. Allerdings gibt es in JavaScript kein Token für das Einfügen des gesamten Ausgangstexts. Sie können ihn zusammensetzen, indem Sie das Suchergebnis «$&» mit dem linken und rechten Kontext verknüpfen.

100 | Kapitel 2: Grundlagen regulärer Ausdrücke

In Ruby kann man auf den linken und rechten Kontext über «\`» und «\'» zugreifen. «\&» fügt das vollständige Suchergebnis ein. Wie bei JavaScript gibt es kein Token für den gesamten Ausgangstext.

Siehe auch „Suchen und Ersetzen mit regulären Ausdrücken“ in Kapitel 1 und Rezept 3.15.

2.22 Suchergebniskontext in den Ersetzungstext einfügen | 101

KAPITEL 3

Mit regulären Ausdrücken programmieren

Programmiersprachen und Regex-Varianten Dieses Kapitel beschreibt, wie man reguläre Ausdrücke mit der Programmiersprache Ihrer Wahl implementiert. Die Rezepte in diesem Kapitel gehen davon aus, dass Sie schon einen regulären Ausdruck haben, der funktioniert. Das vorhergehende Kapitel kann Ihnen da eventuell weiterhelfen. Jetzt haben Sie die Aufgabe, einen regulären Ausdruck in Quellcode umzuwandeln und auch dafür zu sorgen, dass er tatsächlich etwas tut. Wir geben in diesem Kapitel unser Bestes, um genau zu erklären, wie und warum jeder Codeabschnitt so arbeitet, wie er arbeitet. Aufgrund der vielen Details in diesem Kapitel kann es etwas ermüdend sein, es vom Anfang bis zum Ende durchzulesen. Wenn Sie das Reguläre Ausdrücke Kochbuch das erste Mal lesen, empfehlen wir Ihnen, dieses Kapitel zunächst nur zu überfliegen, um zu verstehen, was möglich ist und was nicht. Wollen Sie dann später einen der regulären Ausdrücke aus den folgenden Kapiteln implementieren, kommen Sie hierhin zurück, um zu erfahren, wie man genau die Regexes in Ihre Programmiersprache integriert. Die Kapitel 4 bis 8 nutzen reguläre Ausdrücke, um reale Probleme zu lösen. Diese Kapitel konzentrieren sich auf die regulären Ausdrücke selbst, und viele Rezepte enthalten überhaupt keinen Quellcode. Damit die dort gefundenen regulären Ausdrücke auch wirklich funktionieren, fügen Sie sie einfach in einen der Codeabschnitte aus diesem Kapitel ein. Da sich die anderen Kapitel auf die regulären Ausdrücke konzentrieren, werden die Lösungen dort für bestimmte Regex-Varianten vorgestellt und nicht für bestimmte Programmiersprachen. Regex-Varianten entsprechen nicht eins zu eins Programmiersprachen. Skriptsprachen haben meist schon ihre eigene Regex-Variante eingebaut, während andere Programmiersprachen auf Bibliotheken zurückgreifen. Einige der Bibliotheken gibt es für mehrere Sprachen, während manche Sprache auch mehrere Bibliotheken im Angebot hat. „Viele Varianten regulärer Ausdrücke“ auf Seite 2, beschreibt alle in diesem Buch behandelten Regex-Varianten, „Viele Varianten des Ersetzungstexts“ auf Seite 6, zählt die Vari| 103

anten für die Ersetzungstexte auf, die beim Suchen und Ersetzen mithilfe regulärer Ausdrücke genutzt werden. Alle in diesem Kapitel besprochenen Programmiersprachen nutzen eine dieser Varianten.

In diesem Kapitel behandelte Sprachen Dieses Kapitel behandelt acht Programmiersprachen. Jedes Rezept enthält eigene Lösungen für alle acht Sprachen, und viele Rezepte enthalten auch getrennte Diskussionsabschnitte für alle acht Sprachen. Wenn sich eine Technik mit mehr als einer Sprache einsetzen lässt, wiederholen wir sie für jede der Sprachen. Das haben wir gemacht, damit Sie ohne Gefahr die Diskussionen zu Programmiersprachen überspringen können, an denen Sie nicht interessiert sind. C# C# nutzt das .NET Framework von Microsoft. Die Klassen in System.Text.RegularExpressions nutzen die .NET-Variante für reguläre Ausdrücke und Ersetzungstexte. Dieses Buch behandelt C# in den Versionen 1.0 bis 3.5 oder Visual Studio 2002 bis 2008. VB.NET Dieses Buch nutzt VB.NET und Visual Basic.NET, um auf Visual Basic 2002 und neuer zu verweisen und diese Versionen von Visual Basic 6 und älter zu unterscheiden. Visual Basic nutzt mittlerweile das .NET Framework von Microsoft. Die Klassen in System.Text.RegularExpressions nutzen die .NET-Variante für reguläre Ausdrücke und Ersetzungstexte. Dieses Buch behandelt Visual Basic 2002 bis 2008. Java Java 4 ist das erste Java-Release, das reguläre Ausdrücke auch ohne externe Anbieter unterstützt. Dazu dient das Paket java.util.regex. Es verwendet die Java-Variante für reguläre Ausdrücke und Ersetzungstexte. Dieses Buch behandelt Java 4, 5 und 6. JavaScript Dies ist die Regex-Variante, die in der Programmiersprache genutzt wird, die allgemein als JavaScript bekannt ist. Alle modernen Webbrowser haben sie implementiert: Internet Explorer (ab Version 5.5), Firefox, Opera, Safari und Chrome. Viele andere Anwendungen nutzen ebenfalls JavaScript als Skriptsprache. Streng genommen verwenden wir den Begriff JavaScript in diesem Buch, um uns auf die Programmiersprache zu beziehen, die in Version 3 des ECMA-262-Standards definiert ist. Dieser Standard definiert die Programmiersprache ECMAScript, die eher durch ihre Implementierungen JavaScript und JScript in den verschiedenen Webbrowsern bekannt ist. ECMA-262v3 definiert auch die Variante für reguläre Ausdrücke und Ersetzungstexte, die von JavaScript genutzt wird. In diesem Buch bezeichnen wir diese Varianten als „JavaScript“.

104 | Kapitel 3: Mit regulären Ausdrücken programmieren

PHP PHP bietet drei verschiedene Arten von Regex-Funktionen an. Wir empfehlen dringend, die preg-Funktionen zu nutzen. Daher geht es in diesem Buch nur um die preg-Funktionen, die es in PHP seit Version 4.2.0 gibt. Dieses Buch behandelt PHP 4 und 5. Die preg-Funktionen sind PHP-Wrapper um die PCRE-Bibliothek. Die PCRE-Regex-Variante wird in diesem Buch als „PCRE“ bezeichnet. Da PCRE keine Funktionalität zum Suchen und Ersetzen enthält, haben die PHP-Entwickler ihre eigene Ersetzungstextsyntax für preg_replace entwickelt. Diese Ersetzungstextvariante wird im Weiteren als „PHP“ bezeichnet. Die mb_ereg-Funktionen sind Teil von PHPs „Multibyte“-Funktionen, die dafür gedacht sind, gut mit Sprachen zusammenzuarbeiten, die traditionell mit MehrbyteZeichensätzen kodiert sind, wie zum Beispiel Japanisch und Chinesisch. In PHP 5 nutzen die mb_ereg-Funktionen die Regex-Bibliothek Oniguruma, die ursprünglich für Ruby entwickelt wurde. Die Oniguruma-Regex-Variante wird hier als „Ruby 1.9“ bezeichnet. Sie sollten die mb_ereg-Funktionen nur dann nutzen, wenn Sie auf jeden Fall mit Mehrbyte-Codepages arbeiten müssen und schon mit den mb_-Funktionen in PHP vertraut sind. Die Gruppe der ereg-Funktionen enthält die ältesten Regex-Funktionen in PHP und gilt offiziell seit PHP 5.3.0 als veraltet. Diese Funktionen benötigen keine externen Bibliotheken, und sie implementieren die POSIX ERE-Variante. Diese Variante stellt nur einen eingeschränkten Satz an Features zur Verfügung und wird auch nicht in diesem Buch behandelt. POSIX ERE ist eine echte Untermenge der Varianten Ruby 1.9 und PCRE. Sie können jede Regex aus einem ereg-Funktionsaufruf nehmen und mit mb_ereg oder preg aufrufen. Bei preg müssen Sie noch Begrenzer im Perl-Stil hinzufügen (Rezept 3.1). Perl Die in Perl eingebaute Unterstützung für reguläre Ausdrücke ist der Hauptgrund für deren aktuelle Beliebtheit. Die von den Perl-Operatoren m// und s/// genutzten Varianten für Regexes und Ersetzungstexte werden in diesem Buch als „Perl“ bezeichnet. Behandelt werden Perl 5.6, 5.8 und 5.10. Python Python unterstützt reguläre Ausdrücke über sein Modul re. Die Varianten für reguläre Ausdrücke und Ersetzungstexte, die dieses Modul nutzt, werden in diesem Buch als „Python“ bezeichnet. Behandelt werden Python 2.4 and 2.5. Ruby Ruby enthält bereits in der Sprache eine Unterstützung für reguläre Ausdrücke. In diesem Buch werden Ruby 1.8 und Ruby 1.9 behandelt. Diese zwei Versionen von Ruby nutzen unterschiedliche Regex-Engines. Ruby 1.9 verwendet die OnigurumaEngine, die mehr Features enthält als die klassische Engine aus Ruby 1.8. „RegexVarianten in diesem Buch“ auf Seite 3 geht darauf detaillierter ein. In diesem Kapitel geht es uns nicht so sehr um die Unterschiede zwischen Ruby 1.8 und 1.9. Die regulären Ausdrücke in diesem Kapitel sind sehr einfach und verwen-

Programmiersprachen und Regex-Varianten | 105

den keine der neuen Features aus Ruby 1.9. Da die Unterstützung für reguläre Ausdrücke in die Sprache selbst hineinkompiliert ist, ist der Ruby-Code, den Sie zum Implementieren Ihrer regulären Ausdrücke nutzen, unabhängig von der verwendeten Engine – egal ob Sie Ruby mit der klassischen Regex-Engine oder mit der Oniguruma-Engine kompiliert haben. Sie können Ruby 1.8 neu kompilieren und dabei die Oniguruma-Engine nutzen, wenn Sie deren Features benötigen.

Mehr Programmiersprachen Die Programmiersprachen in der folgenden Liste werden in diesem Buch nicht explizit behandelt, aber sie nutzen eine der hier vorgestellten Regex-Varianten. Sollten Sie mit einer der folgenden Sprachen arbeiten, können Sie dieses Kapitel überspringen, aber alle weiteren werden trotzdem nützlich für Sie sein: ActionScript ActionScript ist Adobes Implementierung des ECMA-262-Standards. Seit Version 3.0 enthält ActionScript eine vollständige Unterstützung der in ECMA-262v3 definierten regulären Ausdrücke. Diese Regex-Variante wird in diesem Buch als „JavaScript“ bezeichnet. Die Sprache ActionScript ist JavaScript sehr ähnlich. Sie sollten die in diesem Kapitel vorgestellten JavaScript-Beispiele an ActionScript anpassen können. C C kann eine große Zahl an Bibliotheken für reguläre Ausdrücke nutzen. Die Open Source-Bibliothek PCRE ist vermutlich die beste Wahl aus den hier vorgestellten Varianten. Sie können den vollständigen C-Quellcode unter http://www.pcre.org herunterladen. Der Code ist so geschrieben, dass er sich mit einer Vielzahl von Compilern auf vielen Plattformen kompilieren lässt. C++ C++ kann ebenfalls diverse Bibliotheken für reguläre Ausdrücke nutzen. Die Open Source-Bibliothek PCRE ist vermutlich die beste Wahl aus den hier vorgestellten Varianten. Sie können entweder direkt die C-API oder die C++-Wrapper-Klassen nutzen, die in der PCRE bereits enthalten sind (siehe http://www.pcre.org). Unter Windows können Sie das COM-Objekt VBScript 5.5 RegExp COM importieren, wie es später für Visual Basic 6 erläutert wird. Das kann nützlich sein, um in einem C++-Backend und einem JavaScript-Frontend die gleichen Regexes nutzen zu können. Delphi for Win32 Während der Entstehung dieses Texts enthält die Win32-Version von Delphi keine eingebaute Unterstützung für reguläre Ausdrücke. Es gibt viele VCL-Komponenten, mit denen reguläre Ausdrücke genutzt werden können. Ich empfehle, eine zu verwenden, die auf PCRE basiert. Delphi kann C-Objektdateien in Ihre Anwendungen einbinden, und viele VCL-Wrapper für PCRE nutzen solche Objektdateien. Damit können Sie Ihre Anwendung als EXE-Datei ausliefern.

106 | Kapitel 3: Mit regulären Ausdrücken programmieren

Sie können die von mir (Jan Goyvaerts) entwickelte TPerlRegEx-Komponente unter http://www.regexp.info/delphi.html herunterladen. Dabei handelt es sich um eine VCL-Komponente, die sich selbst in der Komponentenpalette installiert, sodass sie sich leicht auf eine Form ziehen lässt. Ein weiterer beliebter PCRE-Wrapper für Delphi ist die Klasse TJclRegEx, die Teil der JCL-Bibliothek ist (siehe http://www.delphijedi.org). TJclRegEx leitet sich von TObject ab, daher lässt sie sich nicht auf eine Form ziehen. Beide Bibliotheken sind Open Source und befinden sich unter der Mozilla Public License. Delphi Prism In Delphi Prism können Sie die vom .NET Framework angebotene Unterstützung für reguläre Ausdrücke nutzen. Fügen Sie einfach der uses-Klausel einer Delphi Prism Unit, in der Sie reguläre Ausdrücke nutzen wollen, System.Text.RegularExpressions hinzu. Wenn Sie das erledigt haben, können Sie die gleichen Techniken nutzen, die in den Codeschnipseln für C# und VB.NET in diesem Kapitel demonstriert werden. Groovy Sie können in Groovy reguläre Ausdrücke genau wie in Java mit dem Paket java.util.regex nutzen. Tatsächlich müssten alle in diesem Kapitel vorgestellten Lösungen für Java auch mit Groovy funktionieren. Die Groovy-eigene Syntax für reguläre Ausdrücke stellt eigentlich nur Notationsabkürzungen dar. Eine literale Regex, die durch Schrägstriche begrenzt ist, ist eine Instanz von java.lang.String, und der Operator =~ instantiiert java.util.regex.Matcher. Sie können die GroovySyntax mit der normalen Java-Syntax beliebig mischen – die Klassen und Objekte sind überall gleich. PowerShell PowerShell ist Microsofts eigene Shell-Skriptsprache. Sie basiert auf dem .NET Framework. Die in die PowerShell eingebauten Operatoren -match und -replace nutzen die in diesem Buch vorgestellten .NET-Varianten für reguläre Ausdrücke und Ersetzungstexte. R Das Projekt R unterstützt reguläre Ausdrücke über die Funktionen grep, sub und regexpr aus dem Paket base. Alle diese Funktionen erwarten ein Argument namens perl, das auf FALSE gesetzt wird, wenn Sie es weglassen. Setzen Sie es auf TRUE, um die in diesem Buch beschriebene PCRE-Variante zu verwenden. Die für PCRE 7 gezeigten Ausdrücke arbeiten mit R 2.5.0 und neuer. Frühere Versionen von R nutzen reguläre Ausdrücke, die in diesem Buch mit „PCRE 4 und neuer“ gekennzeichnet sind. Die von R unterstützten Varianten „basic“ und „extended“ sind älter und eingeschränkter. In diesem Buch werden sie nicht behandelt.

Programmiersprachen und Regex-Varianten | 107

REALbasic REALbasic hat eine eingebaute Klasse RegEx. Intern nutzt diese Klasse die UTF-8Version der PCRE-Bibliothek. Das bedeutet, dass Sie die Unicode-Unterstützung der PCRE nutzen können, aber die REALbasic-Klasse TextConverter verwenden müssen, um Nicht-ASCII-Texte nach UTF-8 zu konvertieren, bevor Sie sie an die Klasse RegEx übergeben. Alle in diesem Buch für PCRE 6 gezeigten regulären Ausdrücke funktionieren in REALbasic. Allerdings sind in REALbasic die Optionen Groß-/Kleinschreibung ignorieren und Zirkumflex und Dollar passen zu Zeilenumbrüchen (“Multiline”) standardmäßig eingeschaltet. Wenn Sie einen regulären Ausdruck aus diesem Buch nutzen wollen, der nicht explizit darauf hinweist, diese Optionen einzuschalten, müssen Sie sie in REALbasic deaktivieren. Scala Scala stellt eine eingebaute Unterstützung durch das Paket scala.util.matching bereit. Dabei wird die Regex-Engine aus dem Java-Paket java.util.regex genutzt. Die von Java und Scala genutzten Varianten für reguläre Ausdrücke und Ersetzungstexte werden in diesem Buch als „Java“ bezeichnet. Visual Basic 6 Visual Basic 6 ist die letzte Version von Visual Basic, die nicht das .NET Framework benötigt. Das bedeutet aber auch, dass Visual Basic 6 die ausgezeichnete Unterstützung von regulären Ausdrücken durch das .NET Framework nicht nutzen kann. Die Codebeispiele in diesem Kapitel für VB.NET funktionieren nicht mit VB 6. Mit Visual Basic 6 kann man aber sehr leicht Funktionalität aus ActiveX- und COMBibliotheken integrieren. Eine solche Bibliothek ist die VBScript-Skripting-Bibliothek von Microsoft, die ab Version 5.5 eine halbwegs anständige Unterstützung für reguläre Ausdrücke anbietet. Die Skripting-Bibliothek implementiert die gleiche Regex-Variante, die auch in JavaScript genutzt wird und die in ECMA-262v3 standardisiert ist. Diese Bibliothek ist Teil des Internet Explorer 5.5 und neuer. Sie steht auf allen Rechnern zur Verfügung, die unter Windows XP oder Vista laufen, aber auch unter älteren Windows-Versionen, wenn der Benutzer den IE auf Version 5.5 oder neuer aktualisiert hat. Das bedeutet, nahezu jeder Windows-PC, der für das Surfen im Internet verwendet wird, bietet diese Bibliothek an. Um diese Bibliothek in Ihrer Visual Basic-Anwendung zu nutzen, wählen Sie im VBIDE-Menü Project/References. Blättern Sie durch die Liste, bis Sie den Eintrag Microsoft VBScript Regular Expressions 5.5 finden, der direkt unter Microsoft VBScript Regular Expressions 1.0 liegt. Stellen Sie sicher, dass Sie Version 5.5 markiert haben und nicht Version 1.0. Letztere ist nur aus Gründen der Abwärtskompatibilität verfügbar, und ihre Fähigkeiten sind nicht sehr umfangreich. Nach dem Hinzufügen der Referenz können Sie sehen, welche Klassen und KlassenMember durch die Bibliothek bereitgestellt werden. Wählen Sie im Menü View/ Object Browser aus. Im Object Browser wählen Sie aus der Auswahlliste in der linken oberen Ecke die Bibliothek VBScript_RegExp_55 aus.

108 | Kapitel 3: Mit regulären Ausdrücken programmieren

3.1

Literale reguläre Ausdrücke im Quellcode

Problem Sie haben den regulären Ausdruck ‹[$"'\n\d/\\]› als Lösung für ein Problem erhalten. Dieser reguläre Ausdruck besteht nur aus einer Zeichenklasse, mit der ein Dollarzeichen, ein doppeltes Anführungszeichen, ein einfaches Anführungszeichen, ein Line-Feed, eine Ziffer von 0 bis 9, ein Schrägstrich oder ein Backslash gefunden werden kann. Sie wollen diesen regulären Ausdruck hartkodiert in Ihrem Quellcode als String-Konstante oder als Regex-Operator einbauen.

Lösung C# Als normaler String: "[$\"'\n\\d/\\\\]"

Als Verbatim-String: @"[$""'\n\d/\\]"

VB.NET "[$""'\n\d/\\]"

Java "[$\"'\n\\d/\\\\]"

JavaScript /[$"'\n\d\/\\]/

PHP '%[$"\'\n\d/\\\\]%'

Perl Mustererkennungsoperator: /[\$"'\n\d\/\\]/ m![\$"'\n\d/\\]!

Substitutionsoperator: s![\$"'\n\d/\\]!!

3.1 Literale reguläre Ausdrücke im Quellcode |

109

Python String mit dreifachem Anführungszeichen: r"""[$"'\n\d/\\]"""

Normaler String: "[$\"'\n\\d/\\\\]"

Ruby Literale Regex, begrenzt durch Schrägstriche: /[$"'\n\d\/\\]/

Literale Regex, begrenzt durch ein Zeichen Ihrer Wahl: %r![$"'\n\d/\\]!

Diskussion Wenn in diesem Buch ein regulärer Ausdruck allein vorgestellt wird (und nicht als Teil eines längeren Quellcodeabschnitts), wird er immer ganz schmucklos präsentiert. Dieses Rezept ist die einzige Ausnahme dazu. Wollen Sie ein Programm zum Testen regulärer Ausdrücke nutzen, wie zum Beispiel RegexBuddy oder RegexPal, würden Sie die Regex genau so eingeben. Erwartet Ihre Anwendung einen regulären Ausdruck als Benutzereingabe, würde der Anwender sie ebenfalls so eingeben. Möchten Sie jedoch den regulären Ausdruck hartkodiert in Ihrem Quellcode unterbringen, müssen Sie ein bisschen mehr tun. Wenn Sie einfach einen regulären Ausdruck aus einem Testprogramm in Ihren Quellcode kopieren – oder umgekehrt –, wird das häufig dazu führen, dass Sie sich am Kopf kratzen und sich fragen, warum der Ausdruck in Ihrem Tool funktioniert, aber nicht in Ihrem Quellcode, oder warum das Testprogramm nichts mit einer Regex anfangen kann, die Sie aus dem Quellcode von jemand anderem kopiert haben. Bei allen in diesem Buch besprochenen Programmiersprachen müssen literale reguläre Ausdrücke auf eine bestimmte Art und Weise begrenzt sein, wobei manche Sprachen Strings nutzen und andere dagegen eine spezielle Regex-Konstante verwenden. Wenn Ihre Regex eines der Begrenzungszeichen Ihrer Sprache enthält oder andere Zeichen mit besonderen Bedeutungen, müssen Sie sie maskieren. Der Backslash ist das am häufigsten genutzte Maskierungszeichen. Das ist der Grund dafür, dass die meisten Lösungen für dieses Problem deutlich mehr Backslashs enthalten als die im ursprünglichen regulären Ausdruck vorhandenen vier.

C# In C# können Sie literale reguläre Ausdrücke an den Konstruktor Regex() und eine ganze Reihe von Member-Funktionen der Klasse Regex übergeben. Der Parameter, der den regulären Ausdruck aufnimmt, ist immer als String deklariert.

110 | Kapitel 3: Mit regulären Ausdrücken programmieren

C# unterstützt zwei Arten von String-Literalen. Die gebräuchlichste ist der String in doppelten Anführungszeichen, der auch aus Sprachen wie C++ und Java bekannt ist. Bei solchen Strings müssen doppelte Anführungszeichen und Backslashs durch einen Backslash maskiert werden. Maskierte Zeichen für nicht druckbare Zeichen, wie zum Beispiel ‹\n›, sind in Strings ebenfalls möglich. Es gibt aber einen Unterschied zwischen "\n" und "\\n", wenn RegexOptions.IgnorePatternWhitespace genutzt wird (siehe Rezept 3.4), um den Freiform-Modus einzuschalten (erklärt in Rezept 2.18). "\n" ist ein String mit einem literalen Zeilenumbruch, was als Whitespace ignoriert wird, "\\n" ist dagegen ein String mit dem Regex-Token ‹\n›, das zu einem Newline-Zeichen passt. Verbatim-Strings beginnen mit einem At-Zeichen und einem doppelten Anführungszeichen, und sie enden mit einem doppelten Anführungszeichen. Um ein doppeltes Anführungszeichen in einem Verbatim-String zu nutzen, müssen Sie es verdoppeln. Backslashs werden zum Maskieren nicht benötigt, wodurch sich die Lesbarkeit eines regulären Ausdrucks deutlich verbessert. @"\n" ist immer das Regex-Token ‹\n›, mit dem ein Zeilenumbruch gefunden wird, selbst im Freiform-Modus. Verbatim-Strings unterstützen kein ‹\n› auf String-Ebene, sie können jedoch mehrere Zeilen umfassen. Damit sind sie ideale Kandidaten für reguläre Ausdrücke im Freiform-Modus. Die Wahl ist klar: Für reguläre Ausdrücke sollte man im C#-Quellcode Verbatim-Strings nutzen.

VB.NET In VB.NET können Sie literale reguläre Ausdrücke an den Konstruktor Regex() und verschiedene Member-Funktionen der Klasse Regex übergeben. Die dafür genutzten Parameter sind immer als String deklariert. Visual Basic nutzt Strings zwischen doppelten Anführungszeichen. Innerhalb solcher Strings müssen doppelte Anführungszeichen verdoppelt werden. Andere Zeichen sind nicht zu maskieren.

Java In Java können Sie literale reguläre Ausdrücke an die Klassenfabrik Pattern.compile() und an eine Reihe von Funktionen der Klasse String übergeben. Die entsprechenden Parameter für die regulären Ausdrücke sind immer als Strings deklariert. Java nutzt Strings zwischen doppelten Anführungszeichen. In solchen Strings müssen doppelte Anführungszeichen und Backslashs durch einen Backslash maskiert werden. Maskierte Zeichen für nicht druckbare Zeichen, wie zum Beispiel ‹\n›, und UnicodeMaskierungen wie ‹\uFFFF› werden in Strings ebenfalls unterstützt. Es gibt einen Unterschied zwischen "\n" und "\\n", wenn man Pattern.COMMENTS nutzt (siehe Rezept 3.4), um den Freiform-Modus einzuschalten (erklärt in Rezept 2.18). "\n" ist ein String mit einem literalen Zeilenumbruch, was als Whitespace ignoriert wird, "\\n" ist dagegen ein String mit dem Regex-Token ‹\n›, das zu einem Newline-Zeichen passt.

3.1 Literale reguläre Ausdrücke im Quellcode |

111

JavaScript In JavaScript werden reguläre Ausdrücke am besten erstellt, indem man die spezielle Syntax für das Deklarieren literaler regulärer Ausdrücke nutzt. Fassen Sie Ihren regulären Ausdruck einfach in Schrägstriche ein. Wenn innerhalb des regulären Ausdrucks selbst ein Schrägstrich vorkommt, maskieren Sie ihn mit einem Backslash. Obwohl es möglich ist, ein RegExp-Objekt aus einem String zu erstellen, ist es nicht so sinnvoll, die String-Notation für literale reguläre Ausdrücke in Ihrem Code zu nutzen. Sie müssten Anführungszeichen und Backslashs maskieren, was im Allgemeinen zu einem ganzen Wald von Backslashs führt.

PHP Literale reguläre Ausdrücke für die preg-Funktionen von PHP nutzen eine interessante Syntax. Anders als in JavaScript oder Perl gibt es in PHP keinen eingebauten Typ für reguläre Ausdrücke. Sie müssen immer als Strings eingegeben werden. Das gilt auch für die ereg- und mb_ereg-Funktionen. Aber bei dem Versuch, sich so weit wie möglich an Perl zu orientieren, haben die Entwickler der PHP-Wrapper-Funktionen für die PCRE noch eine weitere Anforderung hinzugefügt. Innerhalb des Strings muss der reguläre Ausdruck wie eine literale Regex in Perl notiert werden. Würden Sie also in Perl /Regex/ schreiben, bräuchten die preg-Funktionen in PHP für den String den Wert '/Regex/'. Wie in Perl können Sie beliebige Begrenzungszeichen nutzen. Wenn das Regex-Begrenzungszeichen innerhalb der Regex auftaucht, muss es durch einen Backslash maskiert werden. Um das zu verhindern, sollten Sie ein Begrenzungszeichen wählen, das nicht in der Regex vorkommt. Für dieses Rezept nutze ich das Prozentzeichen, weil der Schrägstrich Teil der Regex ist. Ist er jedoch nicht Teil der Regex, sollten Sie ihn als Begrenzungszeichen nutzen, da er in Perl das am häufigsten dafür genutzte Zeichen ist. In JavaScript und Ruby ist er sogar verpflichtend. PHP unterstützt Strings sowohl mit einfachen als auch mit doppelten Anführungszeichen. Bei beiden müssen die Anführungszeichen (einfache und doppelte) sowie der Backslash innerhalb einer Regex mit einem Backslash maskiert werden. In Strings mit doppelten Anführungszeichen muss auch das Dollarzeichen maskiert werden. Für reguläre Ausdrücke sollten Sie Strings in einfachen Anführungszeichen nutzen, sofern Sie nicht tatsächlich Variablen innerhalb Ihrer Regex auflösen wollen.

Perl In Perl werden literale reguläre Ausdrücke zusammen mit dem Operator zur Musterübereinstimmung und zum Ersetzen genutzt. Der Operator zur Musterübereinstimmung besteht aus zwei Schrägstrichen, zwischen denen sich die Regex befindet. Schrägstriche innerhalb des regulären Ausdrucks müssen durch einen Backslash maskiert werden. Andere Zeichen benötigen keine Maskierung, außer vielleicht $ und @, wie am Ende dieses Abschnitts noch erläutert wird.

112 | Kapitel 3: Mit regulären Ausdrücken programmieren

Eine alternative Notation für den Musterübereinstimmungsoperator steckt den regulären Ausdruck zwischen zwei beliebige Satzzeichen, vor denen der Buchstabe m steht. Wenn Sie beliebige Zeichen für den öffnenden und schließenden Begrenzer nutzen (runde, geschweifte oder eckige Klammern), müssen diese zueinander passen, zum Beispiel bei m{Regex}. Verwenden Sie andere Satzzeichen, können Sie das Zeichen doppelt nutzen. Die Lösung für dieses Rezept verwendet das Ausrufezeichen. Damit müssen wir den literalen Schrägstrich im regulären Ausdruck nicht maskieren. Wenn das öffnende und das schließende Zeichen unterschiedlich sind, muss nur das schließende Zeichen mit einem Backslash maskiert werden, wenn es als Literal im regulären Ausdruck vorkommt. Der Ersetzungsoperator sieht dem Musterübereinstimmungsoperator sehr ähnlich. Er beginnt mit einem s statt mit einem m, und am Ende findet sich noch der Ersetzungstext. Wenn Sie Klammern als Begrenzer nutzen, brauchen Sie zwei Paare: s[Regex][Ersetzung]. Alle anderen Satzzeichen nutzen Sie drei Mal: s/Regex/Ersetzung/. Perl parst die beiden Operatoren als Strings in doppelten Anführungszeichen. Wenn Sie m/Ich heiße $name/ schreiben und $name den Wert "Jan" enthält, führt das im Endeffekt zum regulären Ausdruck ‹IchzheißezJan›. $" ist in Perl ebenfalls eine Variable, daher müssen wir das literale Dollarzeichen in unserer Zeichenklasse hier im Rezept maskieren. Maskieren Sie niemals ein Dollarzeichen, das Sie als Anker nutzen wollen (siehe Rezept 2.5). Ein maskiertes Dollarzeichen ist immer ein Literal. Perl ist schlau genug, zwischen Dollarzeichen als Anker und Dollarzeichen für das Auswerten von Variablen zu unterscheiden. Denn Anker können sich sinnvollerweise nur am Ende einer Gruppe, einer ganzen Regex oder vor einem Newline befinden. Sie dürfen das Dollarzeichen in ‹m/^Regex$/› nicht maskieren, wenn Sie herausfinden wollen, ob „Regex“ dem ganzen Ausgangstext entspricht. Das At-Zeichen (der Klammeraffe) hat in regulären Ausdrücken keine besondere Bedeutung, es wird aber beim Auswerten von Variablen in Perl verwendet. Daher müssen Sie es in literalen regulären Ausdrücken im Perl-Code maskieren, so wie Sie es auch in Strings mit doppelten Anführungszeichen tun.

Python Die Funktionen im re-Modul von Python erwarten die ihnen übergebenen regulären Ausdrücke als Strings. Sie können selbst entscheiden, welchen der von Python angebotenen Wege Sie nutzen wollen, um die Strings im Quelltext zu nutzen. Je nachdem, welche Zeichen Sie in Ihrem regulären Ausdruck verwenden, werden durch die unterschiedlichen Begrenzungszeichen weniger Backslashs zum Maskieren notwendig sein. Im Allgemeinen sind Raw-Strings die beste Wahl. In solchen Strings müssen in Python keine Zeichen maskiert werden. Wenn Sie einen Raw-String verwenden, brauchen Sie die Backslashs in Ihrem regulären Ausdruck nicht zu verdoppeln. r"\d+" lässt sich einfacher lesen als "\\d+", insbesondere wenn Ihre Regex länger wird.

3.1 Literale reguläre Ausdrücke im Quellcode |

113

Raw-Strings sind allerdings nicht so ideal, wenn Ihr regulärer Ausdruck sowohl einzelne als auch doppelte Anführungszeichen enthält. Dann können Sie keinen Raw-String nutzen, der von einzelnen oder doppelten Anführungszeichen umschlossen ist, da sich diese Zeichen innerhalb des Ausdrucks nicht maskieren lassen. In diesem Fall können Sie drei Anführungszeichen als Begrenzer nutzen, wie wir es auch in der Python-Lösung für dieses Rezept getan haben. Der normale String ist zum Vergleich mit angegeben. Wenn Sie das in Rezept 2.7 beschriebene Unicode-Feature in Ihrem regulären Ausdruck nutzen wollen, müssen Sie Unicode-Strings verwenden. Ein String lässt sich in einen Unicode-String verwandeln, indem Sie ihm ein u voranstellen. Raw-Strings unterstützen keine nicht druckbaren Zeichen wie zum Beispiel \n. Dort werden solche Maskierungssequenzen als literaler Text betrachtet. Das ist für das Modul re allerdings kein Problem. Diese Maskierungen werden dort als Teil der Regex-Syntax und der Ersetzungstextsyntax unterstützt. Ein literales \n in einem Raw-String wird in Ihren regulären Ausdrücken und Ersetzungstexten trotzdem als Newline interpretiert werden. Es gibt einen Unterschied zwischen dem String "\n" einerseits und dem String "\\n" und dem Raw-String r"\n" andererseits, wenn man mit re.VERBOSE (siehe Rezept 3.4) den Freiform-Modus aktiviert (erläutert in Rezept 2.18). "\n" ist ein String mit einem literalen Zeilenumbruch, der als Whitespace ignoriert wird. "\\n" und r"\n" sind Strings mit dem Regex-Token ‹\n›, das zu einem Newline passt. Im Freiform-Modus sind Raw-Strings mit drei Anführungszeichen als Begrenzer (zum Beispiel r"""\n""") die beste Wahl, da sie über mehrere Zeilen reichen können. Zudem wird ‹\n› nicht auf String-Ebene interpretiert, sodass es von der Regex-Engine genutzt werden kann, um einen Zeilenumbruch zu finden.

Ruby In Ruby werden reguläre Ausdrücke am besten mit der speziellen Syntax für das Deklarieren literaler regulärer Ausdrücke erstellt. Setzen Sie Ihren regulären Ausdruck einfach zwischen zwei Schrägstriche. Wenn die Regex selbst einen Schrägstrich enthält, maskieren Sie ihn mit einem Backslash. Wenn Sie keine Schrägstriche in Ihrer Regex maskieren wollen, können Sie Ihrem regulären Ausdruck ein %r voranstellen und dann ein beliebiges Satzzeichen als Begrenzer nutzen. Es ist zwar möglich, ein Regexp aus einem String zu erstellen, aber das ist bei literalen Regexes nicht sehr sinnvoll. Sie müssten dann Anführungszeichen und Backslashs maskieren, was im Allgemeinen zu einem ganzen Wald von Backslashs führt. Ruby ähnelt hier sehr stark JavaScript, nur dass die Klasse in Ruby Regexp heißt, während sie in JavaScript den Namen RegExp (in CamelCase) trägt.

114 | Kapitel 3: Mit regulären Ausdrücken programmieren

Siehe auch Rezept 2.3 erklärt, wie Zeichenklassen funktionieren und warum im regulären Ausdruck zwei Backslashs erforderlich sind, um in der Zeichenklasse nur einen zu nutzen. Rezept 3.4 erklärt, wie man die Optionen für reguläre Ausdrücke setzt. In manchen Programmiersprachen ist das Teil des literalen regulären Ausdrucks.

3.2

Importieren der Regex-Bibliothek

Problem Um reguläre Ausdrücke in Ihrer Anwendung nutzen zu können, wollen Sie die RegexBibliothek oder den entsprechenden Namensraum in Ihren Quellcode importieren. Bei den weiteren Quellcodeschnipseln, die in diesem Buch verwendet werden, wird davon ausgegangen, dass das schon geschehen ist (sofern nötig).

Lösung C# using System.Text.RegularExpressions;

VB.NET Imports System.Text.RegularExpressions

Java import java.util.regex.*;

Python import re

Diskussion In manche Programmiersprachen wurde die Unterstützung regulärer Ausdrücke bereits eingebaut. Dort müssen Sie nichts tun, um Regexes nutzen zu können. Bei anderen Sprachen wird die Funktionalität über eine Bibliothek bereitgestellt, die mit einer entsprechenden Anweisung in Ihrem Quellcode importiert werden muss. Einige Sprachen bieten gar keine Unterstützung regulärer Ausdrücke an. Dort müssen Sie selbst eine entsprechende Funktionalität kompilieren und linken.

3.2 Importieren der Regex-Bibliothek | 115

C# Wenn Sie die using-Anweisung am Anfang Ihrer C#-Quellcodedatei eintragen, können Sie direkt auf die Klassen mit der Regex-Funktionalität verweisen, ohne jedes Mal ihren vollständig qualifizierten Namen angeben zu müssen. So können Sie zum Beispiel Regex() statt System.Text.RegularExpressions.Regex() schreiben.

VB.NET Tragen Sie die Imports-Anweisung am Anfang Ihrer VB.NET-Quellcodedatei ein, können Sie direkt auf die Klassen mit der Regex-Funktionalität verweisen, ohne jedes Mal ihren vollständig qualifizierten Namen angeben zu müssen. So können Sie zum Beispiel Regex() statt System.Text.RegularExpressions.Regex() schreiben.

Java Sie müssen das Paket java.util.regex in Ihre Anwendung importieren, um die bei Java mitgelieferte Bibliothek für reguläre Ausdrücke nutzen zu können.

JavaScript In JavaScript ist die Unterstützung regulärer Ausdrücke schon eingebaut und direkt verfügbar.

PHP Die preg-Funktionen sind bereits eingebaut und ab PHP 4.2.0 immer verfügbar.

Perl Die Unterstützung regulärer Ausdrücke ist in Perl eingebaut und immer verfügbar.

Python Sie müssen das Modul re in Ihr Skript importieren, um die Regex-Funktionen von Python nutzen zu können.

Ruby Die Unterstützung regulärer Ausdrücke ist in Ruby eingebaut und immer verfügbar.

116 | Kapitel 3: Mit regulären Ausdrücken programmieren

3.3

Erstellen eines Regex-Objekts

Problem Sie wollen ein Objekt für einen regulären Ausdruck instantiieren oder stattdessen einen regulären Ausdruck so kompilieren, dass er in Ihrer ganzen Anwendung effizient genutzt werden kann.

Lösung C# Wenn Sie wissen, dass die Regex korrekt ist: Regex regexObj = new Regex("Regex-Muster");

Wenn die Regex vom Endanwender angegeben wird (UserInput sei eine String-Variable): try { Regex regexObj = new Regex(UserInput); } catch (ArgumentException ex) { // Syntaxfehler im regulären Ausdruck }

VB.NET Wenn Sie wissen, dass die Regex korrekt ist: Dim RegexObj As New Regex("Regex-Muster")

Wenn die Regex vom Endanwender angegeben wird (UserInput sei eine String-Variable): Try Dim RegexObj As New Regex(UserInput) Catch ex As ArgumentException 'Syntaxfehler im regulären Ausdruck End Try

Java Wenn Sie wissen, dass die Regex korrekt ist: Pattern regex = Pattern.compile("Regex-Muster");

Wenn die Regex vom Endanwender angegeben wird (userInput sei eine String-Variable): try { Pattern regex = Pattern.compile(userInput); } catch (PatternSyntaxException ex) { // Syntaxfehler im regulären Ausdruck }

3.3 Erstellen eines Regex-Objekts | 117

Um die Regex auf einen String anzuwenden, erzeugen Sie einen Matcher: Matcher regexMatcher = regex.matcher(subjectString);

Um die Regex auf einen anderen String anzuwenden, können Sie, wie eben gezeigt, einen neuen Matcher erstellen oder einen bestehenden wiederverwenden: regexMatcher.reset(anotherSubjectString);

JavaScript Literaler regulärer Ausdruck in Ihrem Code: var myregexp = /Regex-Muster/;

Regulärer Ausdruck, der von einem Benutzer angegeben wurde und der als String in der Variablen userinput abgelegt ist: var myregexp = new RegExp(userinput);

Perl $myregex = qr/Regex-Muster/

Regulärer Ausdruck, der von einem Benutzer angegeben wurde und der als String in der Variablen $userinput abgelegt ist: $myregex = qr/$userinput/

Python reobj = re.compile("Regex-Muster")

Regulärer Ausdruck, der von einem Benutzer angegeben wurde und der als String in der Variablen userinput abgelegt ist: reobj = re.compile(userinput)

Ruby Literaler regulärer Ausdruck in Ihrem Code: myregexp = /Regex-Muster/;

Regulärer Ausdruck, der von einem Benutzer angegeben wurde und der als String in der Variablen userinput abgelegt ist: myregexp = Regexp.new(userinput);

Diskussion Bevor die Regex-Engine einen regulären Ausdruck auf einen String anwenden kann, muss der Ausdruck kompiliert werden. Das geschieht, während Ihre Anwendung ausgeführt wird. Der Regex-Konstruktor oder die Kompilierungsfunktion parst den String, der Ihren regulären Ausdruck enthält, und wandelt ihn in eine Baumstruktur oder einen endlichen

118 | Kapitel 3: Mit regulären Ausdrücken programmieren

Automaten um. Bei der eigentlichen Musterüberprüfung wird dieser Baum beziehungsweise dieser Automat durchlaufen, während der String überprüft wird. Programmiersprachen, die literale reguläre Ausdrücke unterstützen, kompilieren den regulären Ausdruck, wenn die Ausführungsabfolge den entsprechenden Regex-Operator erreicht.

.NET In C# und VB.NET hält die .NET-Klasse System.Text.RegularExpressions.Regex einen kompilierten regulären Ausdruck. Der einfachste Konstruktor erwartet nur einen Parameter: einen String mit Ihrem regulären Ausdruck. Wenn es einen Syntaxfehler im regulären Ausdruck gibt, wirft der Regex()-Konstruktor eine ArgumentException. Der Exception-Text gibt dann genauer Auskunft darüber, was für ein Fehler aufgetreten ist. Es ist wichtig, diese Exception abzufangen, wenn der reguläre Ausdruck vom Anwender angegeben wird. Zeigen Sie dann den Exception-Text an und bitten Sie den Anwender, den Ausdruck zu korrigieren. Ist Ihr regulärer Ausdruck als String-Literal hartkodiert, können Sie sich das Fangen der Exception sparen, falls Sie ein Tool nutzen, durch das sichergestellt ist, dass diese Codezeile keinen Fehler werfen wird. Der Zustand oder Modus kann sich nicht so ändern, dass die gleiche literale Regex einmal kompiliert werden kann und einmal nicht. Beachten Sie, dass die Exception bei einer fehlerhaften Regex beim Ausführen Ihres Programms auftreten wird, nicht beim Kompilieren. Sie sollten ein Regex-Objekt erstellen, wenn Sie den regulären Ausdruck in einer Schleife oder zumindest wiederholt in Ihrer Anwendung nutzen wollen. Durch das Erstellen des Regex-Objekts gibt es keinen zusätzlichen Overhead. Der statische Member der RegexKlasse, der die Regex als String-Parameter übernimmt, erzeugt so oder so intern ein Regex-Objekt, daher können Sie das auch in Ihrem Code machen und sich eine Referenz auf das Objekt merken. Wenn Sie vorhaben, die Regex nur ein paar wenige Male zu nutzen, können Sie stattdessen auch die statischen Member der Regex-Klasse nutzen, um sich eine Codezeile zu sparen. Die statischen Regex-Member werfen das intern erzeugte Regex-Objekt nicht sofort wieder weg, stattdessen verwalten sie einen Cache mit den 15 am häufigsten genutzten regulären Ausdrücken. Sie können die Größe des Caches durch das Setzen der Eigenschaft Regex.CacheSize anpassen. Im Cache wird nach der Regex gesucht, indem der Regex-String verglichen wird. Aber verlassen Sie sich nicht komplett auf den Cache. Wenn Sie viele Regex-Objekte immer wieder benötigen, bauen Sie lieber einen eigenen Cache auf, den Sie dann auch effizienter durchsuchen können.

Java In Java verwaltet die Pattern-Klasse einen kompilierten regulären Ausdruck. Sie können Objekte dieser Klasse mit der Klassenfabrik Pattern.compile() erstellen. Dafür ist nur ein Parameter notwendig: ein String mit Ihrem regulären Ausdruck.

3.3 Erstellen eines Regex-Objekts | 119

Wenn es im regulären Ausdruck einen Syntaxfehler gibt, wirft die Pattern.compile()Fabrik eine PatternSyntaxException. Der Exception-Text beschreibt genauer, was für ein Fehler aufgetreten ist. Es ist wichtig, diese Exception abzufangen, wenn der reguläre Ausdruck aus einer Benutzereingabe stammt. Zeigen Sie dann die Fehlermeldung an und bitten Sie den Anwender, den regulären Ausdruck zu korrigieren. Ist Ihr regulärer Ausdruck als String-Literal hartkodiert, können Sie sich das Fangen der Exception sparen, falls Sie ein Tool nutzen, durch das sichergestellt ist, dass diese Codezeile keinen Fehler werfen wird. Der Zustand oder Modus kann sich nicht so ändern, dass die gleiche literale Regex einmal kompiliert werden kann und einmal nicht. Beachten Sie, dass die Exception bei einer fehlerhaften Regex beim Ausführen Ihres Programms auftreten wird, nicht beim Kompilieren. Sofern Sie eine Regex nicht nur einmal nutzen wollen, sollten Sie ein Pattern-Objekt erstellen, statt die statischen Member der Klasse String zu verwenden. Das erfordert zwar ein paar zusätzliche Codezeilen, aber der Code wird effizienter ablaufen. Die statischen Aufrufe kompilieren Ihre Regex jedes Mal neu. Tatsächlich bietet Java statische Aufrufe nur für ein paar wenige, sehr einfache Regex-Aufgaben an. Ein Pattern-Objekt speichert nur einen kompilierten regulären Ausdruck und führt keinerlei echte Aufgaben aus. Die eigentliche Musterfindung wird durch die Klasse Matcher ausgeführt. Um einen Matcher zu erstellen, rufen Sie die Methode matcher() für Ihren kompilierten regulären Ausdruck auf. Übergeben Sie dabei den Ausgangstext als Argument an matcher(). Rufen matcher() so oft auf, wie Sie den gleichen regulären Ausdruck auf unterschiedliche Strings anwenden wollen. Sie können mit mehreren Matchern für die gleiche Regex arbeiten, solange Sie alles in einem einzelnen Thread halten. Die Klassen Pattern und Matcher sind nicht Thread-sicher. Wenn Sie die gleiche Regex in mehreren Threads nutzen wollen, rufen Sie in jedem Pattern.compile() auf. Haben Sie eine Regex auf einen String angewendet und möchten die gleiche Regex auf einen anderen String anwenden, können Sie das Matcher-Objekt wiederverwenden, indem Sie reset() aufrufen. Übergeben Sie den neuen Ausgangstext als Argument. Das ist effizienter als das Erstellen eines neuen Matcher-Objekts. reset() liefert den gleichen Matcher zurück, für den Sie es aufgerufen haben, sodass Sie in einer Zeile einen Matcher zurücksetzen und wieder anwenden können, zum Beispiel regexMatcher.reset(nextString).find().

JavaScript Die in Rezept 3.2 gezeigte Notation für literale reguläre Ausdrücke erstellt immer ein neues Regex-Objekt. Um dasselbe Objekt mehrfach zu verwenden, weisen Sie es einfach einer Variablen zu. Wenn Sie einen regulären Ausdruck in einer String-Variablen abgelegt haben (weil Sie zum Beispiel den Anwender gebeten haben, einen regulären Ausdruck einzugeben), verwenden Sie den Konstruktor RegExp(), um den regulären Ausdruck zu kompilieren. 120 | Kapitel 3: Mit regulären Ausdrücken programmieren

Beachten Sie, dass der reguläre Ausdruck innerhalb des Strings nicht durch Schrägstriche begrenzt wird. Diese Schrägstriche sind Teil der Notation für literale RegExp-Objekte in JavaScript und gehören nicht zum regulären Ausdruck selbst. Da das Zuweisen einer literalen Regex zu einer Variablen so einfach ist, lassen die meisten JavaScript-Lösungen in diesem Kapitel diese Codezeile weg und verwenden den literalen regulären Ausdruck direkt. In Ihrem eigenen Code sollten Sie die Regex einer Variablen zuweisen und diese dann verwenden, falls Sie sie mehrfach benötigen. Dadurch verbessert sich die Performance, und Ihr Code lässt sich leichter warten.

PHP PHP bietet keine Möglichkeit, einen kompilierten regulären Ausdruck in einer Variablen abzulegen. Immer wenn Sie etwas mit einer Regex tun wollen, müssen Sie sie als String an eine der preg-Funktionen übergeben. Die preg-Funktionen verwalten intern einen Cache von bis zu 4.096 kompilierten regulären Ausdrücken. Auch wenn die Hash-basierte Cache-Suche nicht so schnell wie eine Referenz auf eine Variable ist, ist der Performanceverlust dennoch weit geringer als bei einem regelmäßigen Neukompilieren des gleichen regulären Ausdrucks. Wenn der Cache voll ist, wird die Regex zuerst entfernt, deren Kompilierungszeitpunkt am weitesten zurückliegt.

Perl Sie können den „Quote-Regex“-Operator nutzen, um einen regulären Ausdruck zu kompilieren und einer Variablen zuzuweisen. Er nutzt die gleiche Syntax wie der in Rezept 3.1 beschriebene Mustererkennungsoperator, nur dass er mit den Buchstaben qr und nicht mit m beginnt. Perl ist im Allgemeinen beim erneuten Verwenden vorher kompilierter regulärer Ausdrücke ziemlich effizient. Daher verwenden wir in diesem Kapitel in den Codebeispielen den Operator qr// nicht. Nur in Rezept 3.5 wird gezeigt, wie er funktioniert. qr// ist dann nützlich, wenn Sie Variablen im regulären Ausdruck auswerten oder wenn Sie den kompletten regulären Ausdruck als String erhalten (zum Beispiel aus einer Benutzereingabe). Mit qr/$regexstring/ können Sie Einfluss darauf nehmen, wann die Regex neu kompiliert wird, um den neuen Inhalt von $regexstring widerzuspiegeln. m/$regexstring/ würde die Regex jedes Mal neu kompilieren, während sie mit m/$regexstring/o niemals neu kompiliert würde. Rezept 3.4 beschreibt die Option /o.

Python Die Funktion compile() aus Pythons Modul re erwartet einen String mit Ihrem regulären Ausdruck und liefert ein Objekt mit der kompilierten Version zurück.

3.3 Erstellen eines Regex-Objekts | 121

Sie sollten compile() explizit aufrufen, wenn Sie die gleiche Regex mehrfach nutzen wollen. Alle Funktionen im Modul re rufen zunächst compile() und dann die von Ihnen gewünschte Funktion mit dem kompilierten Regex-Objekt auf. Die Funktion compile() merkt sich die letzten 100 regulären Ausdrücke, die sie kompiliert hat. Das erübrigt eine erneute Dictionary-Suche nach Ausdrücken, die in den 100 zuletzt genutzten regulären Ausdrücken enthalten waren. Wenn der Cache voll ist, wird er komplett geleert. Wenn die Performance kein Thema ist, ist der Cache völlig ausreichend, und Sie können die Funktionen im Modul re direkt nutzen. Aber wenn etwas zeitkritisch ist, ist ein Aufruf von compile() anzuraten.

Ruby Die in Rezept 3.2 gezeigte Notation für literale reguläre Ausdrücke erzeugt immer ein neues Regex-Objekt. Um dasselbe Objekt mehrfach verwenden zu können, weisen Sie es einfach einer Variablen zu. Wenn Sie einen regulären Ausdruck haben, der in einer String-Variablen gespeichert ist (weil Sie zum Beispiel den Anwender gebeten haben, einen regulären Ausdruck einzugeben), nutzen Sie die Regexp.new()-Fabrik oder ihr Synonym Regexp.compile(), um den regulären Ausdruck zu kompilieren. Beachten Sie, dass der reguläre Ausdruck innerhalb des Strings nicht durch Schrägstriche eingefasst ist. Diese Schrägstriche sind Teil der Notation für literale Regexp-Objekte in Ruby und gehören nicht zum regulären Ausdruck selbst. Da das Zuweisen einer literalen Regex zu einer Variablen so einfach ist, lassen die meisten Ruby-Lösungen in diesem Kapitel diese Codezeile weg und verwenden den literalen regulären Ausdruck direkt. In Ihrem eigenen Code sollten Sie die Regex einer Variablen zuweisen und diese dann verwenden, falls Sie sie mehrfach benötigen. Dadurch verbessert sich die Performance, und Ihr Code lässt sich leichter warten.

Einen regulären Ausdruck in CIL kompilieren C# Regex regexObj = new Regex("Regex-Muster", RegexOptions.Compiled);

VB.NET Dim RegexObj As New Regex("Regex-Muster", RegexOptions.Compiled)

122 | Kapitel 3: Mit regulären Ausdrücken programmieren

Diskussion Wenn Sie in .NET ein Regex-Objekt erstellen, ohne Optionen zu übergeben, wird der reguläre Ausdruck so kompiliert, wie wir es in „Diskussion“ auf Seite 118 beschrieben haben. Geben Sie als zweiten Parameter für den Regex()-Konstruktor RegexOptions.Compiled mit, verhält sich die Regex-Klasse etwas anders. Sie kompiliert dann Ihren regulären Ausdruck bis hinunter in die CIL, auch bekannt als MSIL. CIL steht für Common Intermediate Language, eine Programmiersprache, die deutlich enger an Assembler ausgerichtet ist als an C# oder Visual Basic. Alle .NET-Compiler erzeugen CIL. Wenn Ihre Anwendung das erste Mal ausgeführt wird, kompiliert das .NET Framework die CIL dann zu Maschinencode, der vom Computer direkt nutzbar ist. Der Vorteil, einen regulären Ausdruck mit RegexOptions.Compiled zu kompilieren, ist seine bis zu zehnfach höhere Ausführungsgeschwindigkeit, verglichen mit einem normal kompilierten regulären Ausdruck. Nachteil ist, dass das Kompilieren bis zu zwei Größenordnungen langsamer sein kann als das reine Parsen des Regex-Strings in einen Baum. Der CIL-Code wird zudem Teil Ihrer Anwendung, bis sie beendet ist. CIL-Code nutzt keine Garbage Collection. Verwenden Sie RegexOptions.Compiled nur dann, wenn ein regulärer Ausdruck entweder so komplex ist oder so umfangreiche Textmengen bearbeiten muss, dass der Anwender eine deutliche Wartezeit bemerkt. Der Overhead durch das Kompilieren und Assemblieren lohnt sich nicht für Regexes, die in Sekundenbruchteilen ausgeführt werden.

Siehe auch Rezepte 3.1, 3.2 und 3.4.

3.4

Optionen für reguläre Ausdrücke setzen

Problem Sie wollen einen regulären Ausdruck mit allen verfügbaren Modi kompilieren – FreiformModus sowie die Modi Groß-/Kleinschreibung ignorieren, Punkt passt zu Zeilenumbruch und Zirkumflex und Dollar passen zu Zeilenumbruch.

Lösung C# Regex regexObj = new Regex("Regex-Muster", RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Multiline);

3.4 Optionen für reguläre Ausdrücke setzen | 123

VB.NET Dim RegexObj As New Regex("Regex-Muster", RegexOptions.IgnorePatternWhitespace Or RegexOptions.IgnoreCase Or RegexOptions.Singleline Or RegexOptions.Multiline)

Java Pattern regex = Pattern.compile("Regex-Muster", Pattern.COMMENTS | Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE | Pattern.DOTALL | Pattern.MULTILINE);

JavaScript Literaler regulärer Ausdruck in Ihrem Code: var myregexp = /Regex-Muster/im;

Regulärer Ausdruck aus einer Benutzereingabe als String: var myregexp = new RegExp(userinput, "im");

PHP regexstring = '/Regex-Muster/simx';

Perl m/Regex-Muster/simx;

Python reobj = re.compile("Regex-Muster", re.VERBOSE | re.IGNORECASE | re.DOTALL | re.MULTILINE)

Ruby Literaler regulärer Ausdruck in Ihrem Code: myregexp = /Regex-Muster/mix;

Regulärer Ausdruck aus einer Benutzereingabe als String: myregexp = Regexp.new(userinput, Regexp::EXTENDED or Regexp::IGNORECASE or Regexp::MULTILINE);

Diskussion Viele der regulären Ausdrücke in diesem Buch und auch solche, die Sie woanders finden, sind dafür geschrieben, mit bestimmten Regex-Modi zu funktionieren. Es gibt vier grundlegende Basismodi, die nahezu alle modernen Regex-Varianten unterstützen. Lei-

124 | Kapitel 3: Mit regulären Ausdrücken programmieren

der nutzen einige Varianten inkonsistente und verwirrende Namen für die Optionen, durch die die Modi implementiert werden. Nutzt man einen falschen Modus, funktioniert der reguläre Ausdruck im Allgemeinen nicht mehr so, wie man sich das vorgestellt hat. Alle Lösungen in diesem Rezept nutzen Schalter oder Optionen, die von der Programmiersprache oder der Regex-Klasse zum Setzen der Modi verwendet werden. Eine andere Möglichkeit, Modi zu setzen, ist die Verwendung von Modus-Modifikatoren innerhalb der regulären Ausdrücke. Modus-Modifikatoren in der Regex überschreiben immer die Optionen oder Schalter, die außerhalb des regulären Ausdrucks gesetzt wurden.

.NET Der Konstruktor Regex() besitzt einen zweiten, optionalen Parameter für die RegexOptionen. Sie finden die verfügbaren Optionen in der Enumeration RegexOptions. Freiform: RegexOptions.IgnorePatternWhitespace Groß-/Kleinschreibung ignorieren: RegexOptions.IgnoreCase Punkt passt zu Zeilenumbruch: RegexOptions.Singleline Zirkumflex und Dollar passen zu Zeilenumbruch: RegexOptions.Multiline

Java Der Klassenfabrik Pattern.compile() kann man einen optionalen zweiten Parameter für die Regex-Optionen mitgeben. Die Klasse Pattern definiert eine Reihe von Konstanten für die verschiedenen Optionen. Sie können mehrere Optionen gleichzeitig setzen, indem Sie sie durch den bitweisen Oder-Operator | verbinden. Freiform: Pattern.COMMENTS Groß-/Kleinschreibung ignorieren: Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE Punkt passt zu Zeilenumbruch: Pattern.DOTALL Zirkumflex und Dollar passen zu Zeilenumbruch: Pattern.MULTILINE Für das Ignorieren von Groß- und Kleinschreibung gibt es tatsächlich zwei Optionen, die Sie beide setzen müssen, wenn Sie die Schreibweise komplett ignorieren wollen. Setzen Sie nur Pattern.CASE_INSENSITIVE, werden lediglich die Buchstaben A bis Z unabhängig von der Schreibweise gefunden. Setzen Sie beide Optionen, werden alle Zeichen aus allen Schriftsystemen unabhängig von der Groß-/Kleinschreibung gefunden. Der einzige Grund, Pattern.UNICODE_CASE nicht zu nutzen, ist der Performanceaspekt, wenn Sie schon im Voraus wissen, dass Sie nur mit ASCII-Text arbeiten. Wenn Sie innerhalb Ihres regulären Ausdrucks Modus-Modifikatoren verwenden, nutzen Sie ‹(?i)› für das Ignorieren von Groß- und Kleinschreibung nur für ASCII-Zeichen und ‹(?iu)› für ein vollständiges Ignorieren.

3.4 Optionen für reguläre Ausdrücke setzen | 125

JavaScript In JavaScript können Sie Optionen festlegen, indem Sie ein oder mehrere Zeichen nach dem zweiten Schrägstrich an das RegExp-Literal anhängen. Wenn in Büchern oder auf Webseiten über diese Schalter geschrieben wird, geschieht das meist in der Form /i und /m, obwohl die Schalter eigentlich nur aus dem Zeichen selbst bestehen. Es sind keine zusätzlichen Schrägstriche anzugeben, wenn man die Modusschalter nutzt. Verwenden Sie den Konstruktor RegExp(), um einen String zu einem regulären Ausdruck zu kompilieren, können Sie einen optionalen zweiten Parameter mit den Schaltern übergeben. Dabei sollte es sich um einen String mit den Buchstaben der Optionen handeln, die Sie setzen wollen. Fügen Sie hier keine Schrägstriche ein. Freiform: nicht durch JavaScript unterstützt Groß-/Kleinschreibung ignorieren: /i Punkt passt zu Zeilenumbruch: nicht durch JavaScript unterstützt Zirkumflex und Dollar passen zu Zeilenumbruch: /m

PHP In Rezept 3.1 ist beschrieben, dass literale reguläre Ausdrücke bei den preg-Funktionen von PHP durch zwei Satzzeichen begrenzt sein müssen – im Allgemeinen Schrägstriche – und dass alles als String zu formatieren ist. Sie können dabei Optionen angeben, indem Sie ein oder mehrere Zeichen als Modifikatoren an das Ende des Strings stellen. Das heißt, der Modifikator-Buchstabe kommt nach dem schließenden Regex-Begrenzer, aber er muss sich immer noch innerhalb der Anführungszeichen des Strings befinden. Wenn in Büchern oder auf Webseiten über diese Schalter geschrieben wird, geschieht das meist in der Form /x, obwohl die Schalter eigentlich nur aus dem Zeichen selbst bestehen und der Begrenzer zwischen Regex und Modifikator gar kein Schrägstrich sein muss. Freiform: /x Groß-/Kleinschreibung ignorieren: /i Punkt passt zu Zeilenumbruch: /s Zirkumflex und Dollar passen zu Zeilenumbruch: /m

Perl Sie können Optionen für reguläre Ausdrücke festlegen, indem Sie einen oder mehrere aus einem Zeichen bestehende Modifikatoren an das Ende des Operators zur Mustererkennung oder zum Ersetzen anhängen. Wenn in Büchern oder auf Webseiten über diese Schalter geschrieben wird, geschieht das meist in der Form /x, obwohl die Schalter eigentlich nur aus dem Zeichen selbst bestehen und der Begrenzer zwischen Regex und Modifikator gar kein Schrägstrich sein muss. Freiform: /x Groß-/Kleinschreibung ignorieren: /i

126 | Kapitel 3: Mit regulären Ausdrücken programmieren

Punkt passt zu Zeilenumbruch: /s Zirkumflex und Dollar passen zu Zeilenumbruch: /m

Python Der (im obigen Rezept erläuterten) Funktion compile() kann ein optionaler zweiter Parameter für die Optionen für den regulären Ausdruck mitgegeben werden. Sie können diesen Parameter aufbauen, indem Sie die im Modul re definierten Konstanten mit dem Operator | kombinieren. Viele der anderen Funktionen im Modul re, die einen literalen regulären Ausdruck als Parameter nutzen, bieten auch einen (letzten) optionalen Parameter an, mit dem die Optionen übergeben werden können. Die Konstanten für die Regex-Optionen gibt es immer paarweise. Jede Option kann entweder als Konstante mit einem vollständigen Namen oder mit einem einfachen Buchstaben genutzt werden. Die Funktionalität ist die gleiche. Einziger Unterschied ist, dass der vollständige Name Ihren Code für Entwickler leichter lesbar macht, die mit der Buchstabensuppe der Regex-Optionen nicht so vertraut sind. Die aufgeführten Ein-BuchstabenOptionen entsprechen denen von Perl. Freiform: re.VERBOSE oder re.X Groß-/Kleinschreibung ignorieren: re.IGNORECASE oder re.I Punkt passt zu Zeilenumbruch: re.DOTALL oder re.S Zirkumflex und Dollar passen zu Zeilenumbruch: re.MULTILINE oder re.M

Ruby In Ruby können Sie die Optionen angeben, indem Sie dem Regexp-Literal einen oder mehrere Buchstaben als Schalter anhängen. Wenn in Büchern oder auf Webseiten über diese Schalter geschrieben wird, geschieht das meist in der Form /i und /m, obwohl die Schalter eigentlich nur aus dem Zeichen selbst bestehen. Es sind keine zusätzlichen Schrägstriche anzugeben, wenn man die Modusschalter nutzt. Verwenden Sie die Regexp.new()-Fabrik, um einen String in einen regulären Ausdruck zu kompilieren, können Sie einen optionalen zweiten Parameter mit Schaltern an den Konstruktor übergeben. Der zweite Parameter sollte entweder den Wert nil haben, um alle Optionen abzuschalten, oder aus einer Kombination der Konstanten aus der Klasse Regexp bestehen, die mit dem Operator or kombiniert wurden. Freiform: /r oder Regexp::EXTENDED Groß-/Kleinschreibung ignorieren: /i oder Regexp::IGNORECASE Punkt passt zu Zeilenumbruch : /m oder Regexp::MULTILINE. Ruby nutzt hier tatsächlich „m“ und „Multiline“, obwohl alle anderen Varianten „s“ oder „Single Line“ für Punkt passt zu Zeilenumbruch verwenden. Zirkumflex und Dollar passen zu Zeilenumbruch: Zirkumflex und Dollar passen in Ruby immer auf Zeilenumbrüche. Sie können dieses Verhalten nicht abschalten. Mit ‹\A› und ‹\Z› finden Sie den Anfang oder das Ende des Ausgangstexts.

3.4 Optionen für reguläre Ausdrücke setzen | 127

Weitere sprachspezifische Optionen .NET RegexOptions.ExplicitCapture sorgt dafür, dass alle Gruppen, mit Ausnahme der benannten Gruppen, nicht-einfangend sind. Mit dieser Option ist ‹(Gruppe)› das Gleiche wie ‹(?:Gruppe)›. Wenn Sie Ihre einfangenden Gruppen immer benennen, schalten Sie diese Option ein, damit Ihr regulärer Ausdruck effizienter wird, ohne die ‹(?:Gruppe)›-Syntax nutzen zu müssen. Anstatt RegexOptions.ExplicitCapture zu verwenden, können Sie diese Option auch aktivieren, indem Sie ‹(?n)› an den Anfang Ihres regulären Ausdrucks

stellen. In Rezept 2.9 erfahren Sie mehr über Gruppen. Rezept 2.11 erklärt benannte Gruppen. Wenn Sie den gleichen regulären Ausdruck in Ihrem .NET-Code und in JavaScript-Code nutzen und Sie sicherstellen wollen, dass er sich in beiden Umgebungen gleich verhält, können Sie RegexOptions.ECMAScript nutzen. Das ist insbesondere dann nützlich, wenn Sie die Clientseite einer Webanwendung in JavaScript und die Serverseite in ASP.NET entwickeln. Der wichtigste Effekt, der durch diese Option eintritt, ist die Einschränkung von \w und \d auf ASCII-Zeichen, so wie es in JavaScript auch der Fall ist.

Java Eine in Java einmalige Option ist Pattern.CANON_EQ, mit der eine „kanonische Äquivalenz“ ermöglicht wird. Wie in „Unicode-Graphem“ auf Seite 56 beschrieben, gibt es in Unicode unterschiedliche Wege, diakritische Zeichen zu repräsentieren. Wenn Sie diese Option aktivieren, wird Ihre Regex ein Zeichen finden, auch wenn es im Ausgangstext anders kodiert wurde. So wird zum Beispiel die Regex ‹\u00E0› sowohl auf "\u00E0" als auch auf "\u0061\u0300" passen, weil beide kanonisch äquivalent sind. Beide zeigen auf dem Bildschirm ein „à“ an, sodass der Endanwender keinen Unterschied bemerkt. Ohne die kanonische Äquivalenz passt die Regex ‹\u00E0› nicht zum String "\u0061\u0300". In der Weise verhalten sich jedoch alle anderen Regex-Varianten aus diesem Buch. Schließlich teilt Pattern.UNIX_LINES Java noch mit, nur ‹\n› von Punkt, Zirkumflex und Dollarzeichen als echten Zeilenumbruch behandeln zu lassen. Standardmäßig werden alle Unicode-Zeilenumbrüche als solche Zeichen angesehen.

JavaScript Wenn Sie einen regulären Ausdruck wiederholt auf den gleichen String anwenden wollen – zum Beispiel um alle Fundstellen zu durchlaufen oder um nicht nur die erste, sondern alle Übereinstimmungen zu ersetzen –, geben Sie den „Global“-Schalter /g mit.

PHP /u weist die PCRE an, sowohl den regulären Ausdruck als auch den Ausgangstext als UTF-8-Strings zu interpretieren. Dieser Modifikator ermöglicht es zudem, Unicode-

128 | Kapitel 3: Mit regulären Ausdrücken programmieren

Regex-Tokens zu nutzen, wie zum Beispiel ‹\p{FFFF}› und ‹\p{L}›. Diese werden in Rezept 2.7 erklärt. Ohne den Modifikator behandelt die PCRE jedes Byte als eigenes Zeichen, und die Unicode-Regex-Tokens führen zu einem Fehler. /U vertauscht das „genügsame“ und das „gierige“ Verhalten von Quantoren mit und ohne Fragezeichen. Normalerweise ist ‹.*› gierig und ‹.*?› genügsam. Mit der Option /U ist ‹.*› genügsam und ‹.*?› gierig. Ich empfehle dringend, diese Option niemals zu verwenden, da Programmierer, die später Ihren Code lesen und den Modifikator /U übersehen, komplett verwirrt werden können. Verwechseln Sie auch nicht /U mit /u, wenn Sie

diese Optionen im Code von anderen Leuten vorfinden. Regex-Modifikatoren reagieren durchaus empfindlich auf eine unterschiedliche Schreibweise.

Perl Wenn Sie einen regulären Ausdruck wiederholt auf den gleichen String anwenden wollen – zum Beispiel um alle Fundstellen zu durchlaufen oder um nicht nur die erste, sondern alle Übereinstimmungen zu ersetzen –, geben Sie den „Global“-Schalter /g mit. Lassen Sie eine Variable in einer Regex auswerten – zum Beispiel m/Ich heiße $name/ –, wird Perl den regulären Ausdruck jedes Mal neu kompilieren, wenn er angewendet werden soll, da sich der Inhalt von $name geändert haben könnte. Sie können dieses Verhalten mit dem Modifikator /o unterbinden. m/Ich heiße $name/o wird von Perl nur kompiliert, wenn der Ausdruck das erste Mal benötigt wird, und dann immer wieder angewandt. Wenn sich der Inhalt von $name ändert, wird die Regex diese Änderung nicht widerspiegeln. In Rezept 3.3 finden Sie Informationen zum neuen Kompilieren der Regex.

Python Python bietet zwei zusätzliche Optionen an, die die Bedeutung der Wortgrenzen (siehe Rezept 2.6), der Kurzzeichenklassen ‹\w›, ‹\d› und ‹\s› und ihrer negierten Gegenspieler (siehe Rezept 2.3) ändern. Standardmäßig nutzen diese Tokens nur ASCII-Buchstaben, Ziffern und Whitespace. Die Option re.LOCALE oder re.L lässt diese Tokens abhängig vom aktuellen Locale agieren. Das Locale bestimmt dann, welche Zeichen von den Regex-Tokens als Buchstaben, Ziffern und Whitespace behandelt werden. Sie sollten diese Option angeben, wenn der Ausgangstext kein Unicode-String ist und Sie zum Beispiel diakritische Zeichen als Buchstaben behandelt wissen wollen. re.UNICODE oder re.U lässt diese Tokens auf den Unicode-Standard Rücksicht nehmen.

Alle Zeichen, die von Unicode als Buchstaben, Ziffern und Whitespace gekennzeichnet sind, werden dann von den Regex-Tokens auch als solche behandelt. Sie sollten diese Option nutzen, wenn es sich beim Ausgangstext um einen Unicode-String handelt.

3.4 Optionen für reguläre Ausdrücke setzen | 129

Ruby Der Regexp.new()-Fabrik kann ein optionaler dritter Parameter übergeben werden, um die String-Kodierung auszuwählen, die Ihr regulärer Ausdruck unterstützt. Wenn Sie keine Kodierung für Ihren regulären Ausdruck angeben, wird die genommen, die auch Ihre Quellcodedatei nutzt. Meist ist die von der Quellcodedatei genutzte Kodierung schon die richtige. Um eine Kodierung explizit auszuwählen, übergeben Sie für diesen Parameter ein einzelnes Zeichen. Groß- und Kleinschreibung ist nicht wichtig. Mögliche Werte sind: n

Das steht für „None“. Jedes Byte in Ihrem String wird als einzelnes Zeichen behandelt. Nutzen Sie diese Kodierung für ASCII-Texte. e

Verwendet die „EUC“-Kodierung für Sprachen aus dem ostasiatischen Raum. s

Verwendet die japanische „Shift-JIS“-Kodierung. u

Verwendet UTF-8, das ein bis vier Byte pro Zeichen nutzt und alle Sprachen im Unicode-Standard unterstützt (wozu alle wichtigen lebenden Sprachen gehören). Wenn Sie einen literalen regulären Ausdruck verwenden, können Sie die Kodierung durch die Modifikatoren /n, /e, /s und /u setzen. Dabei kann nur einer dieser Modifikatoren für einen einzelnen regulären Ausdruck genutzt werden. Man kann sie aber mit den Modifikatoren /x, /i und /m kombinieren. Verwechseln Sie Rubys /s nicht mit dem von Perl, Java oder .NET. In Ruby sorgt /s für die Anwendung der Shift-JIS-Kodierung. In Perl und den meisten anderen Regex-Varianten wird damit der Modus „Punkt passt zu Zeilenumbruch“ aktiviert. In Ruby erreichen Sie das wiederum mit /m.

Siehe auch Die Auswirkungen der Modi werden detailliert in Kapitel 2 erläutert. Dieser Abschnitt zeigt auch, wie man die Modus-Modifikatoren innerhalb von regulären Ausdrücken nutzt: Freiform: Rezept 2.18 Groß-/Kleinschreibung ignorieren: „Übereinstimmungen unabhängig von Groß- und Kleinschreibung“ auf Seite 29 in Rezept 2.1 Punkt passt zu Zeilenumbruch: Rezept 2.4 Zirkumflex und Dollar passen zu Zeilenumbruch: Rezept 2.5

130 | Kapitel 3: Mit regulären Ausdrücken programmieren

Die Rezepte 3.1 und 3.3 beschreiben, wie Sie literale reguläre Ausdrücke in Ihrem Quellcode verwenden und wie Sie Regex-Objekte erstellen. Die Regex-Optionen werden dabei während des Erzeugens eines regulären Ausdrucks gesetzt.

3.5

Auf eine Übereinstimmung in einem Text prüfen

Problem Sie wollen prüfen, ob für einen bestimmten regulären Ausdruck in einem bestimmten String eine Übereinstimmung gefunden werden kann. Eine teilweise Übereinstimmung ist ausreichend, zum Beispiel stimmt die Regex ‹RegexzMuster› teilweise mit Das RegexMuster kann gefunden werden überein. Sie kümmern sich nicht um die Details der Übereinstimmung, Sie wollen bloß wissen, ob die Regex im String gefunden wird.

Lösung C# Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie diesen statischen Aufruf verwenden: bool foundMatch = Regex.IsMatch(subjectString, "Regex-Muster");

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie den statischen Aufruf per Exception Handling absichern: bool foundMatch = false; try { foundMatch = Regex.IsMatch(subjectString, UserInput); } catch (ArgumentNullException ex) { // Regex und Text müssen vorhanden sein } catch (ArgumentException ex) { // Syntaxfehler im regulären Ausdruck }

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt: Regex regexObj = new Regex("Regex-Muster"); bool foundMatch = regexObj.IsMatch(subjectString);

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie das Regex-Objekt per Exception Handling absichern: bool foundMatch = false; try { Regex regexObj = new Regex(UserInput); try { foundMatch = regexObj.IsMatch(subjectString); } catch (ArgumentNullException ex) { // Regex und Text müssen vorhanden sein

3.5 Auf eine Übereinstimmung in einem Text prüfen | 131

} } catch (ArgumentException ex) { // Syntaxfehler im regulären Ausdruck }

VB.NET Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie diesen statischen Aufruf verwenden: Dim FoundMatch = Regex.IsMatch(SubjectString, "Regex-Muster")

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie den statischen Aufruf per Exception Handling absichern: Dim FoundMatch As Boolean Try FoundMatch = Regex.IsMatch(SubjectString, UserInput) Catch ex As ArgumentNullException 'Regex und Text müssen vorhanden sein Catch ex As ArgumentException 'Syntaxfehler im regulären Ausdruck End Try

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt: Dim RegexObj As New Regex("Regex-Muster") Dim FoundMatch = RegexObj.IsMatch(SubjectString)

Der Aufruf von IsMatch() sollte als Parameter nur SubjectString nutzen. Achten Sie darauf, den Aufruf für die Instanz RegexObj durchzuführen, nicht für die Klasse Regex: Dim FoundMatch = RegexObj.IsMatch(SubjectString)

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie das Regex-Objekt per Exception Handling absichern: Dim FoundMatch As Boolean Try Dim RegexObj As New Regex(UserInput) Try FoundMatch = Regex.IsMatch(SubjectString) Catch ex As ArgumentNullException 'Regex und Text müssen vorhanden sein End Try Catch ex As ArgumentException 'Syntaxfehler im regulären Ausdruck End Try

Java Um auf eine teilweise Übereinstimmung zu testen, muss man einen Matcher erzeugen: Pattern regex = Pattern.compile("Regex-Muster"); Matcher regexMatcher = regex.matcher(subjectString); boolean foundMatch = regexMatcher.find();

132 | Kapitel 3: Mit regulären Ausdrücken programmieren

Wenn die Regex vom Benutzer eingegeben wird, sollten Sie Exception Handling berücksichtigen: boolean foundMatch = false; try { Pattern regex = Pattern.compile(UserInput); Matcher regexMatcher = regex.matcher(subjectString); foundMatch = regexMatcher.find(); } catch (PatternSyntaxException ex) { // Syntaxfehler im regulären Ausdruck }

JavaScript if (/Regex-Muster/.test(subject)) { // Übereinstimmung gefunden } else { // Keine Übereinstimmung }

PHP if (preg_match('/Regex-Muster/', $subject)) { # Übereinstimmung gefunden } else { # Keine Übereinstimmung }

Perl Wenn sich der Text in der Spezial-Variablen $_ befindet: if (m/Regex-Muster/) { # Übereinstimmung gefunden } else { # Keine Übereinstimmung }

Wenn sich der Text in der Variablen $subject befindet: if ($subject =~ m/Regex-Muster/) { # Übereinstimmung gefunden } else { # Keine Übereinstimmung }

Beim Verwenden eines vorkompilierten regulären Ausdrucks: $regex = qr/Regex-Muster/; if ($subject =~ $regex) { # Übereinstimmung gefunden } else { # Keine Übereinstimmung }

3.5 Auf eine Übereinstimmung in einem Text prüfen | 133

Python Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie diese globale Funktion verwenden: if re.search("Regex-Muster", subject): # Übereinstimmung gefunden else: # Keine Übereinstimmung

Um die gleiche Regex mehrfach einzusetzen, verwenden Sie ein kompiliertes Objekt: reobj = re.compile("Regex-Muster") if reobj.search(subject): # Übereinstimmung gefunden else: # Keine Übereinstimmung

Ruby if subject =~ /Regex-Muster/ # Übereinstimmung gefunden else # Keine Übereinstimmung end

Dieser Code macht genau das Gleiche: if /Regex-Muster/ =~ subject # Übereinstimmung gefunden else # Keine Übereinstimmung end

Diskussion Die grundlegendste Aufgabe eines regulären Ausdrucks ist das Prüfen, ob in einem String eine Übereinstimmung gefunden werden kann. In den meisten Programmiersprachen reicht eine teilweise Übereinstimmung aus, damit die entsprechende Funktion einen Erfolg meldet. Die Übereinstimmungsfunktion durchläuft den gesamten Ausgangstext, um herauszufinden, ob der reguläre Ausdruck einen Teil davon findet. Sobald es eine Übereinstimmung gibt, liefert die Funktion true zurück. Den Wert false gibt sie nur dann zurück, wenn sie das Ende des Strings erreicht, ohne eine Übereinstimmung gefunden zu haben. Die Codebeispiele in diesem Rezept sind nützlich, wenn man prüfen will, ob ein String bestimmte Daten enthält. Wollen Sie jedoch testen, ob ein String in seiner Gesamtheit einem bestimmten Muster entspricht (zum Beispiel bei der Eingabekontrolle), ist das nächste Rezept das richtige für Sie.

134 | Kapitel 3: Mit regulären Ausdrücken programmieren

C# und VB.NET Die Klasse Regex stellt vier überladene Versionen der Methode IsMatch() bereit, von denen zwei statisch sind. Damit ist es möglich, IsMatch() mit unterschiedlichen Parametern aufzurufen. Der Ausgangstext ist immer der erste Parameter. In diesem String versucht der reguläre Ausdruck, eine Übereinstimmung zu finden. Dieser erste Parameter darf nicht null sein, da IsMatch() ansonsten eine ArgumentNullException wirft. Sie können den Test in einer einzelnen Codezeile durchführen, indem Sie Regex.IsMatch() aufrufen, ohne ein Regex-Objekt zu erzeugen. Übergeben Sie einfach den regulären Ausdruck als zweiten Parameter und die Regex-Optionen optional als dritten Parameter. Wenn Ihr regulärer Ausdruck einen Syntaxfehler enthält, wird eine ArgumentException von IsMatch() geworfen. Ist Ihre Regex gültig, wird der Aufruf bei einer Übereinstimmung mit einem Teil des Strings true zurückgeben. Wenn keine Übereinstimmung gefunden werden konnte, liefert die Funktion stattdessen false zurück. Möchten Sie den gleichen regulären Ausdruck auf mehrere Strings anwenden, können Sie Ihren Code effizienter machen, indem Sie zunächst ein Regex-Objekt erzeugen und dann für dieses Objekt IsMatch() aufrufen. Der erste Parameter mit dem Ausgangstext ist der einzig notwendige Parameter. Sie können optional einen zweiten Parameter angeben, in dem die Position im String angegeben wird, ab der die Suche beginnen soll. Im Prinzip handelt es sich bei diesem Wert um die Anzahl der Zeichen, die der reguläre Ausdruck am Anfang des Ausgangstexts ignorieren soll. Das kann nützlich sein, wenn Sie den String schon bis zu einer gewissen Position verarbeitet haben und nun prüfen wollen, ob der Rest auch noch zu bearbeiten ist. Wenn Sie diese Zahl angeben, muss sie größer oder gleich null und kleiner oder gleich der Länge des Strings sein. Ansonsten wirft IsMatch() eine ArgumentOutOfRangeException. Die statisch überladenen Varianten ermöglichen keine Angabe der Position, ab der im String gesucht werden soll. Es gibt auch keine Version von IsMatch(), der mitgeteilt werden kann, dass die Funktion nicht bis zum Ende des Strings zu suchen braucht. Wenn Sie das erreichen wollen, können Sie Regex.Match("subject", start, stop) aufrufen und dann die Eigenschaft Success des zurückgelieferten Match-Objekts auswerten. In Rezept 3.8 finden Sie weitere Details dazu.

Java Um zu prüfen, ob eine Regex einen String teilweise oder vollständig abbildet, instantiieren Sie einen Matcher, wie in Rezept 3.3 beschrieben. Dann rufen Sie die Methode find() für Ihren neu erstellten oder frisch zurückgesetzten Matcher auf. Verwenden Sie nicht die Methoden String.matches(), Pattern.matches() oder Matcher.matches(). Bei all diesen Methoden muss die Regex auf den gesamten String passen.

3.5 Auf eine Übereinstimmung in einem Text prüfen | 135

JavaScript Um zu prüfen, ob ein regulärer Ausdruck auf einen Teil des Strings passt, rufen Sie die Methode test() für Ihren regulären Ausdruck auf. Übergeben Sie den Ausgangs-String als einzigen Parameter. regexp.test() gibt true zurück, wenn der reguläre Ausdruck zu einem Teil oder der Gesamtheit des Strings passt. Ansonsten gibt die Funktion false zurück.

PHP Die Funktion preg_match() kann für eine ganze Reihe von Aufgaben genutzt werden. Die einfachste Variante ist der Aufruf mit den beiden notwendigen Parametern: dem String mit Ihrem regulären Ausdruck und dem String mit dem Text, auf den die Regex angewendet werden soll. preg_match() liefert 1 zurück, wenn es eine Übereinstimmung gab, und 0, wenn nichts gefunden wurde. Weitere Rezepte weiter unten in diesem Kapitel erklären die optionalen Parameter, die Sie an preg_match() übergeben können.

Perl In Perl ist m// ein echter Regex-Operator, nicht nur ein Regex-Container. Wenn Sie m// allein verwenden, greift er auf die Variable $_ als Ausgangstext zurück. Wollen Sie den Mustererkennungsoperator auf den Inhalt einer anderen Variablen anwenden, nutzen Sie den Bindungsoperator =~, um den Regex-Operator mit Ihrer Variablen zu verknüpfen. Durch das Binden der Regex an einen String wird die Regex direkt ausgeführt. Der Mustererkennungsoperator liefert true zurück, wenn die Regex zu einem Teil des Ausgangstexts passt, und false, wenn keine Übereinstimmung gefunden wurde. Wenn Sie prüfen wollen, ob ein regulärer Ausdruck nicht auf einen String passt, können Sie !~ nutzen, die negierte Version von =~.

Python Die Funktion search() aus dem Modul re durchsucht einen String, um herauszufinden, ob der reguläre Ausdruck auf einen Teil davon passt. Übergeben Sie Ihren regulären Ausdruck als ersten Parameter und den Ausgangstext als zweiten Parameter. Optional können Sie noch die Regex-Optionen als dritten Parameter mitgeben. Die Funktion re.search() ruft re.compile() und dann die Methode search() für das kompilierte Regex-Objekt auf. Diese Methode erhält nur einen einzigen Parameter: den Ausgangstext. Wenn der reguläre Ausdruck eine Übereinstimmung findet, liefert search() eine Instanz eines MatchObject zurück. Findet die Regex keine Übereinstimmung, liefert search() den Wert None zurück. Wenn Sie den zurückgegebenen Wert in einer if-Anweisung auswerten,

136 | Kapitel 3: Mit regulären Ausdrücken programmieren

führt MatchObject zu True, während None zu False wird. Weitere Rezepte in diesem Kapitel werden zeigen, wie Sie die Informationen in einem MatchObject verwenden können. Bringen Sie search() nicht mit match() durcheinander. Sie können match() nicht nutzen, um eine Übereinstimmung mitten in einem String zu finden. Das nächste Rezept verwendet match().

Ruby Der Operator =~ ist der Mustererkennungsoperator. Wenn Sie ihn zwischen einem regulären Ausdruck und einem String einsetzen, wird damit die erste Übereinstimmung durch die Regex gefunden. Der Operator gibt einen Integer-Wert mit der Position der Übereinstimmung zurück. Wenn keine Übereinstimmung gefunden wurde, liefert er stattdessen nil. Dieser Operator ist in den beiden Klassen Regexp und String implementiert. In Ruby 1.8 ist es egal, welche Klasse Sie links und welche Sie rechts des Operators verwenden. In Ruby 1.9 gibt es spezielle Nebeneffekte, die benannte einfangende Gruppen betreffen. Rezept 3.9 geht darauf näher ein. In allen anderen Ruby-Codeschnipseln in diesem Buch haben wir den Ausgangstext links vom =~-Operator und den regulären Ausdruck rechts davon platziert. Das entspricht der Vorgehensweise in Perl, aus der Ruby sich die =~-Syntax ausgeliehen hat. Zudem werden damit die Besonderheiten in Ruby 1.9 bezüglich der benannten einfangenden Gruppen vermieden, die eventuell nicht berücksichtigt weredn.

Siehe auch Rezepte 3.6 und 3.7.

3.6

Auf eine vollständige Übereinstimmung einer Regex mit einem Text prüfen

Problem Sie wollen prüfen, ob ein String komplett zu einem bestimmten Muster passt. Das heißt, Sie wollen sicherstellen, dass der reguläre Ausdruck, der das Muster enthält, den String vom Anfang bis zum Ende abdecken kann. Wenn Ihre Regex zum Beispiel ‹RegexzMuster› ist, wird der Text Regex-Muster gefunden, aber nicht der längere String Das Regex-Muster kann gefunden werden.

3.6 Auf eine vollständige Übereinstimmung einer Regex mit einem Text prüfen | 137

Lösung C# Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen Aufruf verwenden: bool foundMatch = Regex.IsMatch(subjectString, @"\ARegex-Muster\Z");

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt: Regex regexObj = new Regex(@"\ARegex-Muster\Z"); bool foundMatch = regexObj.IsMatch(subjectString);

VB.NET Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen Aufruf verwenden: Dim FoundMatch = Regex.IsMatch(SubjectString, "\ARegex-Muster\Z")

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt: Dim RegexObj As New Regex("\ARegex-Muster\Z") Dim FoundMatch = RegexObj.IsMatch(SubjectString)

Der Aufruf von IsMatch() sollte als Parameter nur SubjectString nutzen. Achten Sie darauf, den Aufruf für die Instanz RegexObj durchzuführen, nicht für die Klasse Regex: Dim FoundMatch = RegexObj.IsMatch(SubjectString)

Java Wenn Sie nur einen String prüfen wollen, können Sie den statischen Aufruf nutzen: boolean foundMatch = subjectString.matches("Regex-Muster");

Wollen Sie die gleiche Regex auf mehrere Strings anwenden, kompilieren Sie Ihre Regex und erstellen einen Matcher: Pattern regex = Pattern.compile("Regex-Muster"); Matcher regexMatcher = regex.matcher(subjectString); boolean foundMatch = regexMatcher.matches(subjectString);

JavaScript if (/^Regex-Muster$/.test(subject)) { // Übereinstimmung gefunden } else { // Keine Übereinstimmung }

138 | Kapitel 3: Mit regulären Ausdrücken programmieren

PHP if (preg_match('/\ARegex-Muster\Z/', $subject)) { # Übereinstimmung gefunden } else { # Keine Übereinstimmung }

Perl if ($subject =~ m/\ARegex-Muster\Z/) { # Übereinstimmung gefunden } else { # Keine Übereinstimmung }

Python Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie die globale Funktion verwenden: if re.match(r"Regex-Muster\Z", subject): # Übereinstimmung gefunden else: # Keine Übereinstimmung

Um die gleiche Regex mehrfach einzusetzen, verwenden Sie ein kompiliertes Objekt: reobj = re.compile(r"Regex-Muster\Z") if reobj.match(subject): # Übereinstimmung gefunden else: # Keine Übereinstimmung

Ruby if subject =~ /\ARegex-Muster\Z/ # Übereinstimmung gefunden else # Keine Übereinstimmung end

Diskussion Normalerweise erfahren Sie durch eine erfolgreiche Übereinstimmung eines regulären Ausdrucks nur, dass das Muster irgendwo innerhalb des Texts vorhanden ist. In vielen Situationen wollen Sie aber auch sicherstellen, dass der Text vollständig abgedeckt ist und nichts enthält, was nicht mit dem Muster übereinstimmt. Das kommt vor allem in Situationen vor, in denen man Eingaben von Benutzern auf Gültigkeit prüfen will. Wenn ein Anwender eine Telefonnummer oder eine IP-Adresse eingibt, aber darüber hinaus zusätzliche Zeichen hinzufügt, wollen Sie die Angaben zurückweisen.

3.6 Auf eine vollständige Übereinstimmung einer Regex mit einem Text prüfen | 139

Die Lösungen, die die Anker ‹$› und ‹\Z› nutzen, funktionieren auch, wenn Sie eine Datei Zeile für Zeile verarbeiten (Rezept 3.21) und beim Einlesen der Zeilen den Zeilenumbruch am Ende belassen. Wie in Rezept 2.5 erläutert, passen diese Anker auch vor einem abschließenden Zeilenumbruch, womit sich dieser Zeilenumbruch ignorieren lässt. In den folgenden Abschnitten erläutern wir die Lösungen für die verschiedenen Sprachen.

C# und VB.NET Die Klasse Regex() im .NET Framework bietet keine Funktion an, mit der sich testen lässt, ob eine Regex einen String komplett abdeckt. Daher muss man hier den Textanfangs-Anker ‹\A› an den Anfang der Regex und den Textende-Anker ‹\Z› an das Ende setzen. So passt der reguläre Ausdruck nur auf den vollständigen String – oder gar nicht. Wenn Ihr regulärer Ausdruck Alternationen verwendet, zum Beispiel ‹eins|zwei|drei›, müssen Sie sicherstellen, dass die Alternation in eine Gruppe gesteckt wird, bevor Sie die Anker hinzufügen: ‹\A(?:eins|zwei|drei)\Z›. Ist Ihr regulärer Ausdruck so angepasst, können Sie die gleiche Methode IsMatch() benutzen, die schon im vorhergehenden Rezept beschrieben wurde.

Java In Java gibt es drei Methoden mit dem Namen matches(). Alle drei prüfen, ob eine Regex einen String vollständig abdecken kann. Diese Methoden stellen eine einfache Möglichkeit zur Eingabeüberprüfung dar, da Sie Ihre Regex nicht extra mit Ankern am Anfang und am Ende versehen müssen. Die Klasse String enthält eine Methode matches(), die als einzigen Parameter einen regulären Ausdruck erwartet. Sie liefert true oder false zurück, um anzugeben, ob die Regex den ganzen String abdecken konnte. Die Klasse Pattern besitzt eine statische Methode matches(), die zwei Strings erwartet. Der erste ist der reguläre Ausdruck, der zweite der fragliche Text. Sie können als Text sogar eine beliebige CharSequence an Pattern. matches() übergeben. Das ist der einzige Grund dafür, Pattern.matches() statt String. matches() zu verwenden. Sowohl String.matches() als auch Pattern.matches() kompilieren den regulären Ausdruck jedes Mal, indem sie Pattern.compile("Regex").matcher(subjectString).matches() aufrufen. Da die Regex immer wieder neu kompiliert wird, sollten Sie diese Methoden nur verwenden, wenn Sie die Regex lediglich ein Mal nutzen wollen (um zum Beispiel ein Feld in einem Eingabefenster auszuwerten) oder wenn Effizienz kein Thema ist. Bei diesen Methoden kann man außerhalb des regulären Ausdrucks keine Regex-Optionen mitgeben. Wenn Ihr regulärer Ausdruck einen Syntaxfehler enthält, wird eine PatternSyntaxException geworfen.

140 | Kapitel 3: Mit regulären Ausdrücken programmieren

Möchten Sie die gleiche Regex effizient auf mehrere Strings anwenden, sollten Sie Ihre Regex kompilieren, dann einen Matcher erstellen und diesen wiederholt nutzen, wie in Rezept 3.3 beschrieben. Anschließend rufen Sie für Ihre Matcher-Instanz die Methode matches() auf. Diese Funktion erwartet keine Parameter, da Sie den Ausgangstext schon beim Erstellen oder Zurücksetzen des Matchers angegeben haben.

JavaScript JavaScript bietet keine Funktion an, mit der man testen kann, ob eine Regex einen String vollständig abdeckt. Stattdessen fügen Sie am Anfang Ihres regulären Ausdrucks ein ‹^› und am Ende ein ‹$› an. Stellen Sie sicher, dass Sie nicht die Option /m nutzen. Denn nur ohne diese Option passen Zirkumflex und Dollarzeichen lediglich am Anfang und am Ende des Ausgangstexts. Wenn Sie /m setzen, passen sie auch auf Zeilenumbrüche mitten im Text. Wenn Sie Ihren regulären Ausdruck um die Anker ergänzt haben, können Sie die gleiche Methode regexp.test() nutzen, die im vorhergehenden Rezept beschrieben wurde.

PHP PHP bietet keine Funktion an, mit der sich testen lässt, ob eine Regex einen String komplett abdeckt. Daher muss man hier den Textanfangs-Anker ‹\A› an den Anfang der Regex und den Textende-Anker ‹\Z› an das Ende setzen. So passt der reguläre Ausdruck nur auf den vollständigen String – oder gar nicht. Wenn Ihr regulärer Ausdruck Alternationen verwendet, zum Beispiel ‹eins|zwei|drei›, müssen Sie sicherstellen, dass die Alternation in eine Gruppe gesteckt wird, bevor Sie die Anker hinzufügen: ‹\A(?:eins|zwei|drei)\Z›. Wenn Sie Ihren regulären Ausdruck um die Anker ergänzt haben, können Sie die gleiche Funktion preg_match() nutzen, die im vorhergehenden Rezept beschrieben wurde.

Perl Perl hat nur einen Mustererkennungsoperator, der schon bei Übereinstimmung mit einem Teil des Texts zufrieden ist. Um also zu prüfen, ob Ihre Regex den gesamten Text abdeckt, fügen Sie den Textanfangs-Anker ‹\A› an den Anfang der Regex und den Textende-Anker ‹\Z› an das Ende ein. So passt der reguläre Ausdruck nur auf den vollständigen String – oder gar nicht. Wenn Ihr regulärer Ausdruck Alternationen verwendet, zum Beispiel ‹eins|zwei|drei›, müssen Sie sicherstellen, dass die Alternation in eine Gruppe gesteckt wird, bevor Sie die Anker hinzufügen: ‹\A(?:eins|zwei|drei)\Z›. Wenn Sie Ihren regulären Ausdruck um die Anker ergänzt haben, können Sie ihn genau so wie im vorhergehenden Rezept beschrieben anwenden.

3.6 Auf eine vollständige Übereinstimmung einer Regex mit einem Text prüfen | 141

Python Die Funktion match() ähnelt sehr der im vorigen Rezept beschriebenen Funktion search(). Hauptunterschied ist, dass match() den regulären Ausdruck nur am Anfang des Ausgangstexts auswertet. Wenn die Regex nicht am Anfang des Strings passt, liefert match() direkt den Wert None zurück. Die Funktion search() versucht hingegen, die Regex an jeder möglichen Position im String anzuwenden, bis sie entweder eine Übereinstimmung findet oder das Ende des Strings erreicht. Bei der Funktion match() ist es nicht erforderlich, dass der reguläre Ausdruck den gesamten String abdeckt. Es reicht eine Übereinstimmung mit einem Teil des Texts, solange diese am Anfang des Strings beginnt. Wenn Sie prüfen wollen, ob Ihre Regex den gesamten String abdeckt, müssen Sie einen Textende-Anker ‹\Z› an Ihren regulären Ausdruck anhängen.

Ruby Die Ruby-Klasse Regexp enthält keine Funktion, mit der sich testen lässt, ob eine Regex einen String komplett abdeckt. Daher müssen Sie hier den Textanfangs-Anker ‹\A› an den Anfang der Regex und den Textende-Anker ‹\Z› an das Ende setzen. So passt der reguläre Ausdruck nur auf den vollständigen String – oder gar nicht. Wenn Ihr regulärer Ausdruck Alternationen verwendet, zum Beispiel ‹eins|zwei|drei›, müssen Sie sicherstellen, dass die Alternation in eine Gruppe gesteckt wird, bevor Sie die Anker hinzufügen: ‹\A(?:eins|zwei|drei)\Z›. Haben Sie Ihren regulären Ausdruck um die Anker ergänzt, können Sie den gleichen Operator =~ nutzen, der schon im vorhergehenden Rezept beschrieben wurde.

Siehe auch Rezept 2.5 erklärt genauer, wie Anker funktionieren. Die Rezepte 2.8 und 2.9 erklären die Alternation und das Gruppieren. Wenn Ihre Regex eine Alternation außerhalb von Gruppen verwendet, müssen Sie sie gruppieren, bevor Sie die Anker hinzufügen. Nutzen Sie keine Alternation oder verwenden Sie sie nur innerhalb von Gruppen, brauchen Sie keine zusätzliche Gruppe, damit die Anker wie gewünscht funktionieren. Schauen Sie sich Rezept 3.5 an, wenn eine teilweise Übereinstimmung ausreichend ist.

3.7

Auslesen des übereinstimmenden Texts

Problem Sie haben einen regulären Ausdruck, der zu einem Teil des Ausgangstexts passt. Sie wollen den übereinstimmenden Text auslesen. Findet der reguläre Ausdruck im String mehr

142 | Kapitel 3: Mit regulären Ausdrücken programmieren

als eine Übereinstimmung, wollen Sie lediglich die erste auslesen. Wenn Sie zum Beispiel die Regex ‹\d+› auf den String Mögen Sie 13 oder 42? anwenden, sollte 13 zurückgegeben werden.

Lösung C# Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen Aufruf verwenden: string resultString = Regex.Match(subjectString, @"\d+").Value;

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie den statischen Aufruf per Exception Handling absichern: string resultString = null; try { resultString = Regex.Match(subjectString, @"\d+").Value; } catch (ArgumentNullException ex) { // Regex und Text müssen vorhanden sein } catch (ArgumentException ex) { // Syntaxfehler im regulären Ausdruck }

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt: Regex regexObj = new Regex(@"\d+"); string resultString = regexObj.Match(subjectString).Value;

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie das Regex-Objekt per Exception Handling absichern: string resultString = null; try { Regex regexObj = new Regex(@"\d+"); try { resultString = regexObj.Match(subjectString).Value; } catch (ArgumentNullException ex) { // Regex und Text müssen vorhanden sein } } catch (ArgumentException ex) { // Syntaxfehler im regulären Ausdruck }

VB.NET Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen Aufruf verwenden: Dim ResultString = Regex.Match(SubjectString, "\d+").Value

3.7 Auslesen des übereinstimmenden Texts | 143

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie den statischen Aufruf per Exception Handling absichern: Dim ResultString As String = Nothing Try ResultString = Regex.Match(SubjectString, "\d+").Value Catch ex As ArgumentNullException 'Regex und Text dürfen nicht Nothing sein Catch ex As ArgumentException 'Syntaxfehler im regulären Ausdruck End Try

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt: Dim RegexObj As New Regex("\d+") Dim ResultString = RegexObj.Match(SubjectString).Value

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie das Regex-Objekt per Exception Handling absichern: Dim ResultString As String = Nothing Try Dim RegexObj As New Regex("\d+") Try ResultString = RegexObj.Match(SubjectString).Value Catch ex As ArgumentNullException 'Text darf nicht Nothing sein End Try Catch ex As ArgumentException 'Syntaxfehler im regulären Ausdruck End Try

Java Erstellen Sie einen Matcher, um die Suche auszuführen und das Ergebnis zu sichern: String resultString = null; Pattern regex = Pattern.compile("\\d+"); Matcher regexMatcher = regex.matcher(subjectString); if (regexMatcher.find()) { resultString = regexMatcher.group(); }

Wenn die Regex vom Endanwender eingegeben wird, sollten Sie das Ganze mit einem kompletten Exception Handling versehen: String resultString = null; try { Pattern regex = Pattern.compile("\\d+"); Matcher regexMatcher = regex.matcher(subjectString); if (regexMatcher.find()) { resultString = regexMatcher.group(); } } catch (PatternSyntaxException ex) { // Syntaxfehler im regulären Ausdruck }

144 | Kapitel 3: Mit regulären Ausdrücken programmieren

JavaScript var result = subject.match(/\d+/); if (result) { result = result[0]; } else { result = ''; }

PHP if (preg_match('/\d+/', $subject, $groups)) { $result = $groups[0]; } else { $result = ''; }

Perl if ($subject =~ m/\d+/) { $result = $&; } else { $result = ''; }

Python Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie die globale Funktion verwenden: matchobj = re.search("Regex-Muster", subject) if matchobj: result = matchobj.group() else: result = ""

Um die gleiche Regex mehrfach einzusetzen, verwenden Sie ein kompiliertes Objekt: reobj = re.compile("Regex-Muster") matchobj = reobj.search(subject) if match: result = matchobj.group() else: result = ""

Ruby Sie können den Operator =~ und seine magische Variable $& nutzen: if subject =~ /Regex-Muster/ result = $& else result = "" end

3.7 Auslesen des übereinstimmenden Texts | 145

Alternativ können Sie auch die Methode match für ein Regexp-Objekt aufrufen: matchobj = /Regex-Muster/.match(subject) if matchobj result = matchobj[0] else result = "" end

Diskussion Das Extrahieren eines Teils eines längeren Strings, der zu einem bestimmten Muster passt, ist ein weiteres Haupteinsatzgebiet regulärer Ausdrücke. Alle in diesem Buch behandelten Programmiersprachen bieten eine einfache Möglichkeit, die erste Übereinstimmung eines regulären Ausdrucks in einem String auszulesen. Die Funktion versucht dabei, die Regex am Anfang des Strings anzuwenden, und durchläuft ihn so lange, bis der Ausdruck passt.

.NET Die .NET-Klasse Regex hat keinen Member, der den von einem regulären Ausdruck gefundenen String zurückgibt. Aber es gibt eine Methode Match(), die eine Instanz der Klasse Match zurückgibt. Dieses Match-Objekt hat eine Eigenschaft namens Value, in der der vom regulären Ausdruck gefundene Text zu finden ist. Wenn der reguläre Ausdruck nichts findet, gibt er trotzdem ein Match-Objekt zurück, aber die Eigenschaft Value enthält dann einen leeren String. Fünf verschieden überladene Versionen ermöglichen es Ihnen, die Methode Match() auf unterschiedlichste Art und Weise aufzurufen. Der erste Parameter ist immer der String, in dem sich der Ausgangstext befindet, auf den der reguläre Ausdruck angewendet werden soll. Dieser Parameter sollte nicht null sein, da Match() ansonsten eine ArgumentNullException werfen wird. Wenn Sie den regulären Ausdruck nur ein paar Mal nutzen wollen, können Sie einen statischen Aufruf durchführen. Der zweite Parameter ist dann der reguläre Ausdruck, den Sie nutzen wollen. Sie können als optionalen dritten Parameter noch Regex-Optionen angeben. Enthält Ihr regulärer Ausdruck einen Syntaxfehler, wird eine ArgumentException geworfen. Möchten Sie den gleichen regulären Ausdruck auf viele Strings anwenden, können Sie Ihren Code effizienter gestalten, indem Sie zunächst ein Regex-Objekt erstellen und dann für dieses Objekt Match() aufrufen. Der erste Parameter mit dem Text ist der einzig notwendige. Sie können einen optionalen zweiten Parameter mit der Zeichenposition angeben, an der der reguläre Ausdruck mit der Suche beginnen soll. Im Prinzip handelt es sich bei diesem Wert um die Anzahl der Zeichen, die der reguläre Ausdruck am Anfang des Ausgangstexts ignorieren soll. Das kann nützlich sein, wenn Sie den String schon bis zu einer gewissen Position verarbeitet haben und nun noch den Rest durchsuchen wollen.

146 | Kapitel 3: Mit regulären Ausdrücken programmieren

Diese Zahl muss größer oder gleich null und kleiner oder gleich der Länge des Strings sein. Ansonsten wirft IsMatch() eine ArgumentOutOfRangeException. Wenn Sie den zweiten Parameter mit der Startposition übergeben, können Sie auch einen dritten Parameter angeben, der die Länge des Substrings festlegt, in der die Regex suchen darf. Diese Zahl muss größer oder gleich null sein und darf die Länge des restlichen Strings nicht überschreiten. So versucht zum Beispiel regexObj.Match("123456", 3, 2), eine Übereinstimmung in "45" zu finden. Wenn der dritte Parameter größer als die Länge des Texts ist, wirft Match() eine ArgumentOutOfRangeException. Ist er nicht größer als die Länge des Texts, aber größer als der verbleibende Text, wird stattdessen eine IndexOutOfRangeException geworfen. Erlauben Sie dem Anwender, Anfangs- und Endpositionen anzugeben, müssen Sie sie entweder vor dem Aufruf von Match() prüfen oder zumindest sicherstellen, dass Sie beide Exceptions abfangen. Bei den statischen Methoden können keine Parameter für die Definition des zu durchsuchenden Abschnitts mitgegeben werden.

Java Um den Teil eines Strings zu erhalten, der von einem regulären Ausdruck gefunden wurde, müssen Sie einen Matcher erstellen, wie in Rezept 3.3 beschrieben. Dann rufen Sie dessen Methode find() ohne weitere Parameter auf. Wenn find() den Wert true zurückgibt, rufen Sie group() ohne weitere Parameter auf, um den von Ihrem regulären Ausdruck gefundenen Text zu erhalten. Liefert find() den Wert false, sollten Sie group() nicht aufrufen, da Sie dann nur eine IllegalStateException erhalten würden. Matcher.find() hat einen optionalen Parameter, mit dem die Startposition im Text angegeben werden kann. Sie können ihn dazu verwenden, die Suche an einer bestimmten Stelle im String beginnen zu lassen. Wenn Sie 0 angeben, wird am Anfang des Texts begonnen. Es wird eine IndexOutOfBoundsException geworfen, wenn Sie die Startposition auf einen negativen Wert setzen oder auf einen Wert, der größer ist als die Länge des Texts.

Lassen Sie den Parameter weg, beginnt find() mit dem Zeichen nach der Position, an der die letzte Übereinstimmung durch find() gefunden wurde. Wenn Sie find() das erste Mal nach Pattern.matcher() oder Matcher.reset() aufrufen, beginnt find() mit der Suche am Anfang des Strings.

JavaScript Die Methode string.match() erwartet einen regulären Ausdruck als Parameter. Sie können einen regulären Ausdruck als literale Regex, als Regex-Objekt oder als String übergeben. Wenn Sie einen String mitgeben, erzeugt string.match() ein temporäres regexpObjekt. Ist die Suche erfolglos, liefert string.match() den Wert null zurück. Damit können Sie unterscheiden zwischen einer Regex, die keine Übereinstimmungen gefunden hat, und

3.7 Auslesen des übereinstimmenden Texts | 147

einer, die eine Übereinstimmung der Länge null enthält. Das bedeutet, dass Sie das Ergebnis nicht direkt anzeigen können, da „null“ oder ein Fehler über ein Null-Objekt erscheinen kann. Wenn die Suche erfolgreich war, liefert string.match() ein Array mit den Details der Übereinstimmung zurück. Das nullte Element im Array ist ein String mit dem vom regulären Ausdruck gefundenen Text. Achten Sie darauf, nicht die Option /g zu nutzen. Denn dann verhält sich string.match() anders, wie in Rezept 3.10 geschildert wird.

PHP Der in den vorletzten beiden Rezepten besprochenen Funktion preg_match() kann ein optionaler dritter Parameter mitgegeben werden, in dem der gefundene Text und die eingefangenen Gruppen abgelegt werden. Wenn preg_match() den Wert 1 zurückgibt, enthält die Variable ein Array mit Strings. Das nullte Element des Arrays enthält den vom regulären Ausdruck gefundenen Teil des Texts. Die anderen Elemente werden in Rezept 3.9 beschrieben.

Perl Wenn der Mustererkennungsoperator m// eine Übereinstimmung findet, setzt er eine Reihe von speziellen Variablen. Eine davon ist die Variable $&, die den Teil des Strings enthält, der durch den regulären Ausdruck gefunden wurde. Die anderen speziellen Variablen werden in späteren Rezepten erläutert.

Python In Rezept 3.5 wird die Funktion search() beschrieben. Hier speichern wir die von search() zurückgegebene Instanz von MatchObject in einer Variablen. Um an den vom regulären Ausdruck gefundenen Teil des Strings zu gelangen, rufen wir die Methode group() für das Match-Objekt ohne Parameter auf.

Ruby In Rezept 3.8 sind die Variable $~ und das Objekt MatchData beschrieben. In einem String-Kontext wird aus diesem Objekt der Text, der durch den regulären Ausdruck gefunden wurde. In einem Array-Kontext wird es zu einem Array, dessen nulltes Element den von der Regex gefundenen Text enthält. $& ist eine spezielle, nur lesbare Variable. Sie ist ein Alias für $~[0], in dem ein String mit

dem vom regulären Ausdruck gefundenen Text zu finden ist.

Siehe auch Rezepte 3.5, 3.8, 3.9, 3.10 und 3.11.

148 | Kapitel 3: Mit regulären Ausdrücken programmieren

3.8

Position und Länge der Übereinstimmung ermitteln

Problem Statt den Substring auszulesen, der von einem regulären Ausdruck gefunden wurde (wie im vorherigen Rezept), wollen Sie Startposition und Länge der Übereinstimmung wissen. Mit dieser Information können Sie die Übereinstimmung in Ihrem eigenen Code ermitteln oder irgendwelche spannenden Verarbeitungsschritte mit dem ursprünglichen String anstellen.

Lösung C# Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen Aufruf verwenden: int matchstart, matchlength = -1; Match matchResult = Regex.Match(subjectString, @"\d+"); if (matchResult.Success) { matchstart = matchResult.Index; matchlength = matchResult.Length; }

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt: int matchstart, matchlength = -1; Regex regexObj = new Regex(@"\d+"); Match matchResult = regexObj.Match(subjectString).Value; if (matchResult.Success) { matchstart = matchResult.Index; matchlength = matchResult.Length; }

VB.NET Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen Aufruf verwenden: Dim MatchStart = -1 Dim MatchLength = -1 Dim MatchResult = Regex.Match(SubjectString, "\d+") If MatchResult.Success Then MatchStart = MatchResult.Index MatchLength = MatchResult.Length End If

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt: Dim MatchStart = -1 Dim MatchLength = -1

3.8 Position und Länge der Übereinstimmung ermitteln | 149

Dim RegexObj As New Regex("\d+") Dim MatchResult = Regex.Match(SubjectString, "\d+") If MatchResult.Success Then MatchStart = MatchResult.Index MatchLength = MatchResult.Length End If

Java int matchStart, matchLength = -1; Pattern regex = Pattern.compile("\\d+"); Matcher regexMatcher = regex.matcher(subjectString); if (regexMatcher.find()) { matchStart = regexMatcher.start(); matchLength = regexMatcher.end() - matchStart; }

JavaScript var matchstart = -1; var matchlength = -1; var match = /\d+/.exec(subject); if (match) { matchstart = match.index; matchlength = match[0].length; }

PHP if (preg_match('/\d+/', $subject, $groups, PREG_OFFSET_CAPTURE)) { $matchstart = $groups[0][1]; $matchlength = strlen($groups[0][0]); }

Perl if ($subject =~ m/\d+/g) { $matchlength = length($&); $matchstart = length($`); }

Python Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie die globale Funktion verwenden: matchobj = re.search(r"\d+", subject) if matchobj: matchstart = matchobj.start() matchlength = matchobj.end() - matchstart

150 | Kapitel 3: Mit regulären Ausdrücken programmieren

Um die gleiche Regex mehrfach einzusetzen, verwenden Sie ein kompiliertes Objekt: reobj = re.compile(r"\d+") matchobj = reobj.search(subject) if matchobj: matchstart = matchobj.start() matchlength = matchobj.end() - matchstart

Ruby Sie können den Operator =~ und seine magische Variable $& nutzen: if subject =~ /Regex-Muster/ matchstart = $~.begin() matchlength = $~.end() - matchstart end

Alternativ können Sie auch die Methode match für ein Regexp-Objekt aufrufen: matchobj = /Regex-Muster/.match(subject) if matchobj matchstart = matchobj.begin() matchlength = matchobj.end() - matchstart end

Diskussion .NET Um die Startposition und die Länge des Übereinstimmungsbereichs zu erhalten, nutzen wir die gleiche Methode Regex.Match(), die schon im vorhergehenden Rezept beschrieben wurde. Dieses Mal verwenden wir die Eigenschaften Index und Length des von Regex.Match() zurückgegebenen Match-Objekts. Index ist die Position im Text, an der die Regex-Übereinstimmung beginnt. Wenn dies gleich am Anfang des Strings der Fall ist, hat Index den Wert 0. Beginnt die Übereinstimmung mit dem zweiten Zeichen im String, hat Index den Wert 1. Der maximale Wert für Index ist die Länge des Strings. Das kann dann passieren, wenn die Regex eine Übereinstimmung der Länge null am Ende des Strings findet. Besteht die Regex zum Beispiel nur aus dem Textende-Anker ‹\Z›, findet sich die Übereinstimmung immer am Ende des Strings. Length gibt die Zahl der Zeichen an, für die eine Übereinstimmung besteht. Es ist mög-

lich, dass eine Übereinstimmung null Zeichen lang ist. Besteht zum Beispiel die Regex nur aus der Wortgrenze ‹\b›, wird eine Übereinstimmung der Länge null am Anfang des ersten Worts im String gefunden. Wenn es keine Übereinstimmung gibt, liefert Regex.Match() trotzdem ein Match-Objekt zurück. Dessen Eigenschaften Index und Length haben dann beide den Wert null. Diese Werte können aber auch bei einer erfolgreichen Suche vorkommen. Besteht die Regex zum Beispiel aus dem Textanfangs-Anker ‹\A›, wird eine Übereinstimmung der Länge 3.8 Position und Länge der Übereinstimmung ermitteln | 151

null am Anfang des Strings gefunden. Daher können Sie sich nicht auf Match.Index oder Match.Length verlassen, um herauszufinden, ob die Suche erfolgreich war. Verwenden Sie stattdessen besser Match.Success.

Java Um die Position und die Länge der Übereinstimmung zu finden, rufen Sie zunächst, wie schon im vorhergehenden Rezept beschrieben, Matcher.find() auf. Wenn find() true zurückgibt, rufen Sie als Nächstes Matcher.start() ohne Parameter auf, um den Index des ersten Zeichens zu erhalten, das Teil der Regex-Übereinstimmung ist. Ein Aufruf von end() ohne Parameter liefert den Index des ersten Zeichens nach der Übereinstimmung zurück. Subtrahieren Sie den Anfang vom Ende, um die Länge der Übereinstimmung zu erhalten. Diese kann durchaus null sein. Wenn Sie start() oder end() ohne einen vorherigen Aufruf von find() aufrufen, erhalten Sie eine IllegalStateException.

JavaScript Rufen Sie die Methode exec() für ein regexp-Objekt auf, um ein Array mit den Details des Suchergebnisses zu erhalten. Dieses Array besitzt ein paar zusätzliche Eigenschaften. In der Eigenschaft index ist die Position im Text abgelegt, an der die Regex-Übereinstimmung beginnt. Wenn dies der Anfang des Strings ist, hat index den Wert null. Das nullte Element des Arrays enthält einen String mit dem gesamten Suchergebnis. Die Eigenschaft length dieses Strings ist die Länge der Übereinstimmung. Konnte der reguläre Ausdruck keine Übereinstimmung finden, liefert regexp.exec() den Wert null zurück. Verwenden Sie nicht die Eigenschaft lastIndex des von exec() zurückgegebenen Arrays, um das Ende der Übereinstimmung zu erhalten. In einer strikten JavaScript-Implementierung existiert lastIndex gar nicht im zurückgegebenen Array, sondern nur im regexpObjekt selbst. Sie sollten aber ebenfalls nicht regexp.lastIndex verwenden. Es ist aufgrund von Unterschieden zwischen den Browsern nicht verlässlich nutzbar (siehe Rezept 3.11). Stattdessen addieren Sie match.index und match[0].length einfach, um herauszufinden, wo die Regex-Übereinstimmung endet.

PHP Das vorhergehende Rezept hat erläutert, wie Sie den von einem regulären Ausdruck gefundenen Text erhalten können, indem Sie preg_match() einen dritten Parameter übergeben. Sie können die Position der Übereinstimmung ermitteln, indem Sie die Konstante PREG_OFFSET_CAPTURE als vierten Parameter übergeben. Dieser Parameter beeinflusst das, was preg_match() im dritten Parameter ablegt, wenn es 1 zurückliefert. Wenn Sie den vierten Parameter weglassen oder auf null setzen, enthält die als dritter Parameter übergebene Variable ein Array mit Strings. Übergeben Sie PREG_OFFSET_CAPTURE als vierten Parameter, enthält die Variable ein Array aus Arrays. Das nullte Element im

152 | Kapitel 3: Mit regulären Ausdrücken programmieren

Hauptarray enthält weiterhin das Suchergebnis (siehe dazu das obige Rezept), während die darauffolgenden Elemente immer noch die Ergebnisse der einfangenden Gruppen enthalten. Aber statt eines Strings mit dem von der Regex oder einer einfangenden Gruppe gefundenen Text enthält das Element nun ein Array mit zwei Werten: dem Text, der gefunden wurde, und der Position im String, an der er gefunden wurde. Um die Details des gesamten Suchergebnisses zu bekommen, liefert uns das nullte Unterelement des nullten Elements den Text, der von der Regex gefunden wurde. Diesen übergeben wir an die Funktion strlen(), um seine Länge zu ermitteln. Das erste Unterelement des nullten Elements enthält einen Integer-Wert mit der Position im Text, an der die Übereinstimmung beginnt.

Perl Um die Länge der Übereinstimmung zu ermitteln, berechnen wir einfach die Länge der Variablen $&, die das vollständige Suchergebnis enthält. Um den Anfang des Übereinstimmungsbereichs herauszufinden, berechnen wir die Länge der Variablen $`, in der der Text des Strings vor dem Regex-Übereinstimmungsbereich zu finden ist.

Python Die Methode start() von MatchObject gibt die Position im String zurück, an der die Regex-Übereinstimmung beginnt. Die Methode end() gibt die Position des ersten Zeichens nach dem Übereinstimmungsbereich zurück. Beide Methoden liefern den gleichen Wert, wenn eine Übereinstimmung der Länge null gefunden wurde. Sie können an die Methoden start() und end() einen Parameter übergeben, um den Textbereich einer der einfangenden Gruppen des regulären Ausdrucks zu erhalten. Mit start(1) sprechen Sie die erste einfangende Gruppe an, mit end(2) die zweite und so weiter. Python unterstützt bis zu 99 einfangende Gruppen. Die Gruppe mit der Nummer 0 ist das gesamte Suchergebnis. Jede Zahl außerhalb des Bereichs von 0 bis zur Anzahl der einfangenden Gruppen (mit 99 als absoluter Obergrenze) führt zu einem IndexError. Wenn die Gruppennummer gültig ist, die Gruppe aber an der Regex-Übereinstimmung nicht beteiligt war, liefern sowohl start() als auch end() für diese Gruppe den Wert -1 zurück. Wollen Sie Start- und Endposition in einem Tupel speichern, rufen Sie die Methode span() für das Match-Objekt auf.

Ruby In Rezept 3.5 wird der Operator =~ genutzt, um die erste Regex-Übereinstimmung in einem String zu finden. Ein Nebeneffekt dieses Operators ist, dass er die spezielle Variable $~ mit einer Instanz der Klasse MatchData bestückt. Diese Variable ist Thread- und Methoden-lokal. Das bedeutet, Sie können den Inhalt dieser Variablen nutzen, bis Ihre Methode beendet ist oder bis Sie das nächste Mal den Operator =~ in Ihrer Methode nut-

3.8 Position und Länge der Übereinstimmung ermitteln | 153

zen. Sie müssen sich nicht darum sorgen, dass ein anderer Thread oder eine andere Methode in Ihrem Thread den Inhalt überschreibt. Wenn Sie die Details mehrerer Regex-Suchen sichern wollen, rufen Sie die Methode match() für ein Regexp-Objekt auf. Diese Methode erwartet den Text als einzigen Parameter. Sie liefert eine Instanz von MatchData zurück, wenn es eine Übereinstimmung gab, ansonsten den Wert nil. Auch sie setzt die Variable $~ auf die gleiche Instanz von MatchObject, überschreibt aber keine anderen MatchObject-Instanzen, die in anderen Variablen gespeichert waren. Das Objekt MatchData speichert alle Details zu einer Regex-Übereinstimmung. Die Rezepte 3.7 und 3.9 beschreiben, wie man an den Text gelangt, der vom regulären Ausdruck und von den einfangenden Gruppen gefunden wurde. Die Methode begin() liefert die Position im Text zurück, an der die Regex-Übereinstimmung beginnt. end() liefert die Position des ersten Zeichens nach dem Regex-Übereinstimmungsbereich zurück. offset() gibt ein Array zurück, in dem sich die Anfangs- und Endpositionen befinden. Diese drei Methoden erwarten jeweils einen Parameter. Mit 0 erhalten Sie die Positionen des gesamten Suchergebnisses. Übergeben Sie eine positive Zahl, erhalten Sie die Daten für die entsprechende einfangende Gruppe. So liefert begin(1) zum Beispiel den Anfang der ersten einfangenden Gruppe. Verwenden Sie nicht length() oder size(), um die Länge der Übereinstimmung zu ermitteln. Beide Methoden geben die Anzahl der Elemente im Array zurück, das MatchData im Array-Kontext liefert (erklärt in Rezept 3.9).

Siehe auch Rezepte 3.5 und 3.9.

3.9

Teile des übereinstimmenden Texts auslesen

Problem Wie in Rezept 3.7 haben Sie einen regulären Ausdruck, der auf einen Substring des Texts passt. Dieses Mal wollen Sie aber nur einen Teil dieses Substrings nutzen. Um den gewünschten Teil abzugrenzen, haben Sie Ihrem regulären Ausdruck eine einfangende Gruppe hinzugefügt (wie bereits in Rezept 2.9 beschrieben). So passt der reguläre Ausdruck ‹http://([a-z0-9.-]+)› zum Beispiel auf http://www. regexcookbook.com im String Auf http://www.regexcookbook.com finden Sie mehr Informationen. Der Teil der Regex innerhalb der ersten einfangenden Gruppe passt auf www.regexcookbook.com, und Sie wollen den Domainnamen aus der ersten einfangenden Gruppe in eine String-Variable auslesen.

154 | Kapitel 3: Mit regulären Ausdrücken programmieren

Wir verwenden diese einfache Regex, um das Konzept von einfangenden Gruppen deutlich zu machen. In Kapitel 7 finden Sie exaktere Regexes, die auf URLs passen.

Lösung C# Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen Aufruf verwenden: string resultString = Regex.Match(subjectString, "http://([a-z0-9.-]+)").Groups[1].Value;

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt: Regex regexObj = new Regex("http://([a-z0-9.-]+)"); string resultString = regexObj.Match(subjectString).Groups[1].Value;

VB.NET Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen Aufruf verwenden: Dim ResultString = Regex.Match(SubjectString, "http://([a-z0-9.-]+)").Groups(1).Value

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt: Dim RegexObj As New Regex("http://([a-z0-9.-]+)") Dim ResultString = RegexObj.Match(SubjectString).Groups(1).Value

Java String resultString = null; Pattern regex = Pattern.compile("http://([a-z0-9.-]+)"); Matcher regexMatcher = regex.matcher(subjectString); if (regexMatcher.find()) { resultString = regexMatcher.group(1); }

JavaScript var result = ""; var match = /http:\/\/([a-z0-9.-]+)/.exec(subject); if (match) { result = match[1]; } else { result = ''; }

3.9 Teile des übereinstimmenden Texts auslesen | 155

PHP if (preg_match('%http://([a-z0-9.-]+)%', $subject, $groups)) { $result = $groups[1]; } else { $result = ''; }

Perl if ($subject =~ m!http://([a-z0-9.-]+)!) { $result = $1; } else { $result = ''; }

Python Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie die globale Funktion verwenden: matchobj = re.search("http://([a-z0-9.-]+)", subject) if matchobj: result = matchobj.group(1) else: result = ""

Um die gleiche Regex mehrfach einzusetzen, verwenden Sie ein kompiliertes Objekt: reobj = re.compile("http://([a-z0-9.-]+)") matchobj = reobj.search(subject) if match: result = matchobj.group(1) else: result = ""

Ruby Sie können den Operator =~ und seine magischen nummerierten Variablen (wie zum Beispiel $1) nutzen: if subject =~ %r!http://([a-z0-9.-]+)! result = $1 else result = "" end

Alternativ können Sie auch die Methode match für ein Regexp-Objekt aufrufen: matchobj = %r!http://([a-z0-9.-]+)!.match(subject) if matchobj result = matchobj[1] else result = "" end

156 | Kapitel 3: Mit regulären Ausdrücken programmieren

Diskussion In Rezept 2.10 und 2.21 haben wir erklärt, wie Sie nummerierte Rückwärtsreferenzen im regulären Ausdruck und im Ersetzungstext nutzen können, um denselben Text erneut zu suchen oder um Teile der Regex-Übereinstimmung in den Ersetzungstext einzufügen. Sie können die gleichen Referenzzahlen nutzen, um den Text in Ihrem Code auszulesen, der von einer oder mehreren einfangenden Gruppen gefunden wurde. Bei regulären Ausdrücken werden einfangende Gruppen beginnend mit eins nummeriert. Programmiersprachen nummerieren Arrays und Listen im Allgemeinen bei null beginnend. Alle in diesem Buch behandelten Programmiersprachen speichern die einfangenden Gruppen in einem Array oder in einer Liste, wobei sie aber die gleiche Nummerierung wie bei den einfangenden Gruppen im regulären Ausdruck verwenden, also mit eins beginnen. Das nullte Element im Array oder in der Liste wird dafür genutzt, das gesamte Suchergebnis zu speichern. Wenn also Ihr regulärer Ausdruck drei einfangende Gruppen hat, finden sich im Array mit den Übereinstimmungen vier Elemente. Das Element null enthält das gesamte Suchergebnis, während die Elemente eins, zwei und drei den Text enthalten, der durch die drei einfangenden Gruppen gefunden wurde.

.NET Um die Details zu einfangenden Gruppen zu erhalten, greifen wir erneut auf die Member-Funktion Regex.Match() zurück, die erstmals in Rezept 3.7 beschrieben wurde. Das zurückgegebene Objekt vom Typ Match besitzt eine Eigenschaft namens Groups. Dabei handelt es sich um eine Collection des Typs GroupCollection. Die Collection enthält die Details aller einfangenden Gruppen in Ihrem regulären Ausdruck. In Groups[1] stehen die Details der ersten einfangenden Gruppe, in Groups[2] die der zweiten und so weiter. Die Collection Groups enthält für jede einfangende Gruppe ein Objekt des Typs Group. Die Klasse Group besitzt die gleichen Eigenschaften wie die Klasse Match, abgesehen von der Eigenschaft Groups. Match.Groups[1].Value liefert den von der ersten einfangenden Gruppe gefundenen Text zurück – genauso wie Match.Value das gesamte Suchergebnis liefert. Match.Groups[1].Index und Match.Groups[1].Length geben die Startposition und die Länge des von der Gruppe gefundenen Texts zurück. In Rezept 3.8, finden Sie mehr Informationen zu Index und Length. Groups[0] enthält die Details des gesamten Suchergebnisses, die auch direkt im MatchObjekt gefunden werden können. Match.Value und Match.Groups[0].Value sind äquivalent.

Die Collection Groups wirft keine Exception, wenn Sie eine ungültige Gruppennummer angeben. So liefert Groups[-1] zum Beispiel trotzdem ein Group-Objekt zurück, aber die Eigenschaften dieses Objekts zeigen dann, dass die fiktive einfangende Gruppe -1 nichts gefunden hat. Die beste Möglichkeit, das zu prüfen, ist die Eigenschaft Success. Groups [-1].Success wird den Wert false zurückgeben.

3.9 Teile des übereinstimmenden Texts auslesen | 157

Um herauszubekommen, wie viele einfangende Gruppen es gibt, schauen Sie sich Match.Groups.Count an. Die Eigenschaft Count folgt den gleichen Konventionen wie die Eigenschaft Count aller anderen Collection-Objekte in .NET: Sie gibt die Anzahl an Elementen in der Collection zurück – also den größten zulässigen Index plus eins. In unserem Beispiel gibt es in der Collection Groups die Elemente Groups[0] und Groups[1].Groups.Count ergibt damit 2.

Java Um den von einer einfangenden Gruppe gefundenen Text oder die Details der Übereinstimmung zu ermitteln, braucht man praktisch den gleichen Code wie für das gesamte Suchergebnis, der in den vorhergehenden beiden Rezepten zu finden ist. Die Methoden group(), start() und end() der Klasse Matcher besitzen alle einen optionalen Parameter. Ohne diesen Parameter oder mit dem Wert null erhalten Sie die Übereinstimmung beziehungsweise die Positionen des gesamten Suchergebnisses. Wenn Sie eine positive Zahl übergeben, erhalten Sie die Details der einfangenden Gruppe. Gruppen werden mit eins beginnend nummeriert, so wie die Rückwärtsreferenzen im regulären Ausdruck selbst. Geben Sie eine Zahl an, die größer ist als die Anzahl der einfangenden Gruppen in Ihrem regulären Ausdruck, werfen diese drei Funktionen eine IndexOutOfBoundsException. Wenn die einfangende Gruppe vorhanden ist, es aber keine Übereinstimmung für sie gibt, liefert group(n) den Wert null, während start(n) und end(n) als Ergebnis -1 ausgeben.

JavaScript Wie im vorhergehenden Rezept beschrieben, liefert die Methode exec() eines regulären Ausdrucks ein Array mit den Details über das Suchergebnis zurück. In Element null steht der gesamte Suchausdruck. Element eins enthält den Text, der durch die erste einfangende Gruppe gefunden wurde, Element zwei den der zweiten Gruppe und so weiter. Wenn ein regulärer Ausdruck gar nichts findet, liefert regexp.exec() den Wert null zurück.

PHP In Rezept 3.7 wird beschrieben, wie Sie den vom regulären Ausdruck gefundenen Text erhalten können, indem Sie preg_match() einen dritten Parameter übergeben. Wenn preg_match() den Wert 1 zurückgibt, wird der Parameter mit einem Array gefüllt. Das nullte Element enthält einen String mit dem gesamten Suchergebnis. Das erste Element enthält den Text der ersten einfangenden Gruppe, das zweite den Text der zweiten Gruppe und so weiter. Die Länge des Arrays entspricht der Anzahl der einfangenden Gruppen plus eins. Array-Indexe entsprechen den Rückwärtsverweisnummern im regulären Ausdruck.

158 | Kapitel 3: Mit regulären Ausdrücken programmieren

Wenn Sie die Konstante PREG_OFFSET_CAPTURE als vierten Parameter angeben, wie es in obigem Rezept beschrieben wurde, entspricht die Länge des Arrays immer noch der Anzahl der einfangenden Gruppen plus eins. Aber statt an jedem Index einen String zu enthalten, findet sich dort ein Unterarray mit zwei Elementen. Das nullte Unterelement ist der String mit dem von der Regex oder einfangenden Gruppe gefundenen Text. Das erste Unterelement ist eine Integer-Zahl, die die Position des gefundenen Texts im Ausgangstext angibt.

Perl Wenn der Operator m// eine Übereinstimmung findet, wird eine Reihe von speziellen Variablen gesetzt. Dazu gehören auch die nummerierten Variablen $1, $2, $3 und so weiter, die den Teil des Strings enthalten, der von der entsprechenden einfangenden Gruppe im regulären Ausdruck gefunden wurden.

Python Die Lösung für dieses Problem ist nahezu identisch mit der aus Rezept 3.7. Statt group() ohne Parameter aufzurufen, geben wir die Nummer der einfangenden Gruppe an, an der wir interessiert sind. Mit group(1) erhalten Sie den Text, der durch die erste einfangende Gruppe gefunden wurde, mit group(2) den der zweiten Gruppe und so weiter. Python unterstützt bis zu 99 einfangende Gruppen. Die Gruppe mit der Nummer 0 enthält das gesamte Suchergebnis. Übergeben Sie eine Zahl, die größer als die Anzahl der einfangenden Gruppen ist, wirft group() eine IndexError-Exception. Wenn die Gruppennummer gültig ist, die Gruppe aber an der Regex-Übereinstimmung nicht beteiligt war, liefert group() den Wert None. Sie können group() mehrere Gruppennummern übergeben, um den von mehreren einfangenden Gruppen gefundenen Text mit einem Aufruf zu erhalten. Das Ergebnis ist dann eine Liste mit Strings. Wenn Sie ein Tupel mit den von allen einfangenden Gruppen gefundenen Texten erhalten wollen, können Sie die Methode groups() von MatchObject aufrufen. Das Tupel enthält für Gruppen, die an der Übereinstimmung nicht beteiligt sind, den Wert None. Wenn Sie groups() einen Parameter mitgeben, wird für Gruppen ohne Suchergebnis dieser Wert anstelle von None genommen. Wenn Sie ein Dictionary statt eines Tupels haben wollen, rufen Sie groupdict() statt groups() auf. Sie können auch hier einen Parameter übergeben, dessen Inhalt anstelle von None für Gruppen genommen wird, die kein Suchergebnis enthalten.

Ruby In Rezept 3.8 werden die Variable $~ und das Objekt MatchData beschrieben. In einem ArrayKontext liefert dieses Objekt ein Array mit den Texten, die durch die einfangenden Gruppen gefunden wurden. Die Gruppen werden dabei wie die Rückwärtsreferenzen mit 1 beginnend nummeriert, während sich in Element 0 im Array das gesamte Suchergebnis befindet.

3.9 Teile des übereinstimmenden Texts auslesen | 159

$1, $2 und folgende sind spezielle, nur lesbare Variablen. $1 ist eine Kurzform von $~[1], in dem sich der von der ersten einfangenden Gruppe gefundene Text befindet. $2 liefert

den Text aus der zweiten Gruppe und so weiter.

Benannte Captures Wenn Ihr regulärer Ausdruck benannte einfangende Gruppen nutzt, können Sie den Namen der Gruppe verwenden, um den von ihr gefundenen Text in Ihrem Code zu verwenden.

C# Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen Aufruf verwenden: string resultString = Regex.Match(subjectString, "http://(?[a-z0-9.-]+)").Groups["domain"].Value;

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt: Regex regexObj = new Regex("http://(?[a-z0-9.-]+)"); string resultString = regexObj.Match(subjectString).Groups["domain"].Value;

In C# sieht der Code für das Ermitteln des Group-Objekts einer benannten Gruppe und einer nummerierten Gruppe gar nicht so unterschiedlich aus. Statt die Groups-Collection mit einem Integer-Wert als Index anzusprechen, nutzen Sie einen String. Auch dann wird .NET keine Exception werfen, wenn die Gruppe nicht existiert. Match.Groups ["keinegruppe"].Success liefert in diesem Fall schlicht false zurück.

VB.NET Für schnelle Tests, die nur einmal durchgeführt werden sollen, können Sie den statischen Aufruf verwenden: Dim ResultString = Regex.Match(SubjectString, "http://(?[a-z0-9.-]+)").Groups("domain").Value

Um die gleiche Regex mehrfach einzusetzen, erstellen Sie ein Regex-Objekt: Dim RegexObj As New Regex("http://(?[a-z0-9.-]+)") Dim ResultString = RegexObj.Match(SubjectString).Groups("domain").Value

In VB.NET fällt der Code für das Ermitteln des Group-Objekts einer benannten Gruppe und einer nummerierten Gruppe gar nicht so unterschiedlich aus. Statt die GroupsCollection mit einem Integer-Wert als Index anzusprechen, nutzen Sie einen String. Auch dann wird .NET keine Exception werfen, wenn die Gruppe nicht existiert. Match.Groups("keinegruppe").Success liefert in diesem Fall schlicht False zurück.

160 | Kapitel 3: Mit regulären Ausdrücken programmieren

PHP if (preg_match('%http://(?P[a-z0-9.-]+)%', $subject, $groups)) { $result = $groups['domain']; } else { $result = ''; }

Wenn Ihr regulärer Ausdruck benannte einfangende Gruppen besitzt, ist das $groups zugewiesene Array ein assoziatives Array. Der Text, der von jeder benannten einfangenden Gruppe gefunden wird, steht im Array zwei Mal zur Verfügung. Sie können den Text auslesen, indem Sie das Array entweder über die Gruppennummer oder über den Gruppennamen ansprechen. Im Codebeispiel speichert $groups[0] das gesamte Suchergebnis der Regex, während sowohl $groups[1] als auch $groups['domain'] den Text enthalten, der von der einen einfangenden Gruppe gefunden wurde, die im regulären Ausdruck enthalten ist.

Perl if ($subject =~ '!http://(?[a-z0-9.-]+)%!) { $result = $+{'domain'}; } else { $result = ''; }

Perl unterstützt benannte einfangende Gruppen seit Version 5.10. Der Hash $+ enthält den Text, der von allen einfangenden Gruppen gefunden wurde. Perl nummeriert benannte Gruppen zusammen mit den nummerierten Gruppen durch. In diesem Beispiel findet sich sowohl in $1 als auch in $+{'domain'} der Text, der von der einen einfangenden Gruppe gefunden wurde, die im regulären Ausdruck enthalten ist.

Python matchobj = re.search("http://(?P[a-z0-9.-]+)", subject) if matchobj: result = matchobj.group("domain") else: result = ""

Wenn Ihr regulärer Ausdruck benannte Gruppen besitzt, können Sie der Methode group() statt der Nummer auch den Gruppennamen übergeben.

Siehe auch Rezept 2.9 beschreibt nummerierte einfangende Gruppen. Rezept 2.11 beschreibt benannte einfangende Gruppen.

3.9 Teile des übereinstimmenden Texts auslesen | 161

3.10 Eine Liste aller Übereinstimmungen erhalten Problem Alle bisherigen Rezepte in diesem Kapitel drehen sich nur darum, die erste Übereinstimmung zu finden, die ein regulärer Ausdruck im Text ermittelt. Aber in vielen Fällen kann ein regulärer Ausdruck, der einen String nur teilweise abdeckt, auch noch eine weitere Übereinstimmung im restlichen Text ermitteln ... und vielleicht noch eine dritte und so weiter. So kann zum Beispiel die Regex ‹\d+› sechs Übereinstimmungen im Text Die Gewinnzahlen sind 7, 13, 16, 42, 65 und 99 finden: 7, 13, 16, 42, 65 und 99. Sie wollen die Liste aller Substrings ermitteln, die der reguläre Ausdruck findet, wenn er nach jeder Übereinstimmung erneut auf den Rest des Strings angewendet wird.

Lösung C# Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen regulären Ausdruck verarbeiten: MatchCollection matchlist = Regex.Matches(subjectString, @"\d+");

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck mit einer großen Zahl an Strings verarbeiten wollen: Regex regexObj = new Regex(@"\d+"); MatchCollection matchlist = regexObj.Matches(subjectString);

VB.NET Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen regulären Ausdruck verarbeiten: Dim matchlist = Regex.Matches(SubjectString, "\d+")

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck mit einer großen Zahl an Strings verarbeiten wollen: Dim RegexObj As New Regex("\d+") Dim MatchList = RegexObj.Matches(SubjectString)

Java List resultList = new ArrayList(); Pattern regex = Pattern.compile("\\d+"); Matcher regexMatcher = regex.matcher(subjectString); while (regexMatcher.find()) { resultList.add(regexMatcher.group()); }

162 | Kapitel 3: Mit regulären Ausdrücken programmieren

JavaScript var list = subject.match(/\d+/g);

PHP preg_match_all('/\d+/', $subject, $result, PREG_PATTERN_ORDER); $result = $result[0];

Perl @result = $subject =~ m/\d+/g;

Das funktioniert nur bei regulären Ausdrücken, die keine einfangenden Gruppen enthalten. Greifen Sie daher auf nicht-einfangende Gruppen zurück. Details dazu finden Sie in Rezept 2.9.

Python Wenn Sie nur wenige Strings mit dem gleichen regulären Ausdruck verarbeiten wollen, können Sie die globale Funktion nutzen: result = re.findall(r"\d+", subject)

Um die gleiche Regex mehrfach zu verwenden, nutzen Sie ein kompiliertes Objekt: reobj = re.compile(r"\d+") result = reobj.findall(subject)

Ruby result = subject.scan(/\d+/)

Diskussion .NET Die Methode Matches() der Klasse Regex wendet den regulären Ausdruck wiederholt auf den String an, bis alle Übereinstimmungen gefunden wurden. Sie liefert ein Objekt vom Typ MatchCollection zurück, in der sich alle Übereinstimmungen finden. Der Ausgangstext ist immer der erste Parameter. In diesem String versucht der reguläre Ausdruck, eine Übereinstimmung zu finden. Dieser Parameter darf nicht null sein, da Matches() sonst eine ArgumentNullException wirft. Wenn Sie die Regex-Treffer nur in ein paar wenigen Strings erhalten wollen, können Sie die statische Version von Matches() nutzen. Übergeben Sie Ihren Text als ersten und den regulären Ausdruck als zweiten Parameter. Optionen für den regulären Ausdruck können Sie als optionalen dritten Parameter mitgeben. Wenn Sie viele Strings verarbeiten, erstellen Sie zunächst ein Regex-Objekt und rufen dann dafür Matches() auf. Der Ausgangstext ist dabei der einzige notwendige Parameter. 3.10 Eine Liste aller Übereinstimmungen erhalten | 163

Mit einem optionalen zweiten Parameter können Sie festlegen, ab welcher Zeichenposition der reguläre Ausdruck seine Überprüfung beginnen soll. Im Prinzip handelt es sich bei diesem Wert um die Anzahl der Zeichen, die der reguläre Ausdruck am Anfang des Ausgangstexts ignorieren soll. Das kann nützlich sein, wenn Sie den String schon bis zu einer gewissen Position verarbeitet haben und nun noch den Rest durchsuchen wollen. Wenn Sie diese Zahl angeben, muss sie größer oder gleich null und kleiner oder gleich der Länge des Strings sein. Ansonsten wirft IsMatch() eine ArgumentOutOfRangeException. Die statische Version der Methode ermöglicht keine Angabe, ab welcher Position die Regex ihre Suche beginnen soll. Es gibt auch keine Version von Matches(), der Sie sagen können, wo die Suche vor Ende des Strings abbrechen soll. Wenn Sie das wollen, können Sie Regex.Match("Ausgangstext", start, stop) in einer Schleife aufrufen, wie dies im nächsten Rezept gezeigt wird, und alle Übereinstimmungen selbst in einer Liste sammeln.

Java Java bietet keine Funktion an, mit der Sie eine Liste von Übereinstimmungen erhalten können. Aber das lässt sich leicht mit eigenem Code erreichen, indem man Rezept 3.7 anpasst. Anstatt find() in einer if-Anweisung aufzurufen, nutzen Sie eine while-Schleife. Für die im Beispiel verwendeten Klassen List und ArrayList müssen Sie an den Anfang Ihres Codes import java.util.*; setzen.

JavaScript Dieser Code ruft genauso wie in Rezept 3.7 die Funktion string.match() auf. Es gibt jedoch einen kleinen, aber entscheidenden Unterschied – die Option /g. Regex-Optionen werden in Rezept 3.4 beschrieben. Die Option /g teilt der Funktion match() mit, über alle Übereinstimmungen im String zu iterieren und sie in einem Array abzulegen. Im Codebeispiel enthält list[0] dann die erste Regex-Übereinstimmung, list[1] die zweite und so weiter. Über list.length bekommen Sie heraus, wie viele Übereinstimmungen es gibt. Wenn es gar keine Übereinstimmungen gab, liefert string.match wie üblich null zurück. Die Elemente im Array sind Strings. Wenn Sie eine Regex mit der Option /g verwenden, liefert string.match() keine weiteren Details zu den Übereinstimmungen. Benötigen Sie weitere Informationen, müssen Sie, wie in Rezept 3.11 beschrieben, über die einzelnen Ergebnisse iterieren.

PHP In allen bisherigen Rezepten wurde bei PHP die Funktion preg_match() genutzt, die die erste Regex-Übereinstimmung in einem String findet. preg_match_all() ist eine sehr ähnliche Funktion. Der Hauptunterschied ist, dass sie alle Übereinstimmungen findet. Sie liefert eine Integer-Zahl zurück, die angibt, wie oft die Regex gefunden werden konnte.

164 | Kapitel 3: Mit regulären Ausdrücken programmieren

Die ersten drei Parameter von preg_match_all() sind die gleichen wie die ersten drei von preg_match(): ein String mit Ihrem regulären Ausdruck, der zu durchsuchende String und eine Variable, in der ein Array mit den Ergebnissen abgelegt wird. Nur ist dieses Mal der dritte Parameter nicht optional, und das Array ist immer mehrdimensional. Für den vierten Parameter geben Sie entweder die Konstante PREG_PATTERN_ORDER oder PREG_SET_ORDER an. Wenn Sie den Parameter weglassen, wird PREG_PATTERN_ORDER als Standardwert genutzt. Mit PREG_PATTERN_ORDER erhalten Sie ein Array, in dem sich die Details des gesamten Suchergebnisses im nullten Element befinden, während die Details der einfangenden Gruppen in den darauffolgenden Elementen zu finden sind. Die Länge des Arrays entspricht der Anzahl der einfangenden Gruppen plus eins. Das ist die gleiche Reihenfolge, die bei preg_match() genutzt wird. Der Unterschied liegt darin, dass jedes Element nun nicht nur einen String mit dem Suchergebnis enthält – wie es bei preg_match() der Fall ist –, sondern ein Unterarray mit allen Übereinstimmungen, die von preg_match_all() gefunden wurden. Die Länge jedes Unterarrays entspricht dem Wert, der von preg_match_all() zurückgegeben wurde. Um eine Liste aller Regex-Übereinstimmungen im String zu erhalten, ohne sich für die Texte zu interessieren, die von einfangenden Gruppen gefunden wurden, geben Sie PREG_PATTERN_ORDER an und nutzen das nullte Element im Array. Wenn Sie nur an den Texten interessiert sind, die von einer bestimmten einfangenden Gruppe gefunden wurden, nutzen Sie PREG_PATTERN_ORDER und die Gruppennummer der einfangenden Gruppe. So erhalten Sie zum Beispiel nach dem Aufruf von preg_match('%http://([a-z0-9.-]+)%', $subject, $result) in $result[1] die Liste der Domainnamen aller URLs in Ihrem Ausgangstext. PREG_SET_ORDER füllt das Array mit den gleichen Strings, aber in einer anderen Kombination. Die Länge des Arrays entspricht dem Wert, der von preg_match_all() zurückgege-

ben wird. Jedes Element in diesem Array ist ein Unterarray, in dem sich an Position null das Suchergebnis befindet und die Ergebnisse der einfangenden Gruppen an darauffolgenden Positionen. Wenn Sie PREG_SET_ORDER angeben, enthält $result[0] das gleiche Array wie bei einem Aufruf von preg_match(). Sie können PREG_OFFSET_CAPTURE mit PREG_PATTERN_ORDER oder PREG_SET_ORDER kombinieren. Das hat den gleichen Effekt wie die Übergabe von PREG_OFFSET_CAPTURE als vierten Parameter an preg_match(). Statt in den Elementen des Arrays Strings abzulegen, finden sich dort Unterarrays mit zwei Elementen – dem String und der Position, an der der String im Ausgangstext vorkommt.

Perl In Rezept 3.4 wird erklärt, dass Sie Ihre Regex um den Modifikator /g ergänzen müssen, um mehr als eine Übereinstimmung im Ausgangstext zu finden. Wenn Sie eine globale Regex in einem Listenkontext verwenden, wird sie alle Übereinstimmungen finden und zurückliefern. In diesem Rezept sorgt die List-Variable auf der linken Seite des Zuweisungsoperators für den entsprechenden Kontext. 3.10 Eine Liste aller Übereinstimmungen erhalten | 165

Wenn der reguläre Ausdruck keine einfangenden Gruppen enthält, wird die Liste alle Suchergebnisse enthalten. Sind aber einfangende Gruppen vorhanden, wird die Liste die von allen einfangenden Gruppen gefundenen Texte für alle Regex-Übereinstimmungen enthalten. Das gesamte Suchergebnis ist dann nicht enthalten, sofern Sie nicht noch eine einfangende Gruppe um die gesamte Regex legen. Möchten Sie nur eine Liste aller Suchergebnisse bekommen, ersetzen Sie alle einfangenden durch nicht-einfangende Gruppen. In Rezept 2.9 werden beide Gruppenarten beschrieben.

Python Die Funktion findall() im Modul re durchsucht wiederholt einen String, um alle Übereinstimmungen zum regulären Ausdruck zu finden. Übergeben Sie Ihren regulären Ausdruck als ersten und den Ausgangstext als zweiten Parameter. Optionen für den regulären Ausdruck können Sie optional im dritten Parameter mitliefern. Die Funktion re.findall() ruft re.compile() und dann die Methode findall() für das kompilierte Regex-Objekt auf. Diese Methode hat nur einen notwendigen Parameter – den Ausgangstext. Die Methode findall() besitzt zwei optionale Parameter, die von der globalen Funktion re.findall() nicht unterstützt werden. Nach dem Ausgangstext können Sie die Zeichenposition im String angeben, ab der findall() mit ihrer Suche beginnen soll. Lassen Sie diesen Parameter weg, verarbeitet findall() den gesamten Text. Wenn Sie eine Startposition angeben, können Sie auch eine Endposition festlegen. Geben Sie keine Endposition mit, wird die Suche bis zum Ende des Strings durchgeführt. Egal wie Sie findall() aufrufen – das Ergebnis ist immer eine Liste mit allen Übereinstimmungen, die gefunden wurden. Wenn die Regex keine einfangenden Gruppen besitzt, erhalten Sie eine Liste mit Strings. Sind welche vorhanden, bekommen Sie eine Liste mit Tupeln, in denen sich der Text aller einfangenden Gruppen für jede Regex-Übereinstimmung findet.

Ruby Die Methode scan() der Klasse String erwartet einen regulären Ausdruck als einzigen Parameter. Sie iteriert über alle Regex-Übereinstimmungen im String. Ruft man sie ohne einen Block auf, liefert scan() ein Array mit allen Regex-Übereinstimmungen zurück. Wenn Ihr regulärer Ausdruck keine einfangenden Gruppen besitzt, gibt scan() ein StringArray zurück. In diesem Array gibt es für jede Regex-Übereinstimmung ein Element mit dem gefundenen Text. Gibt es einfangende Gruppen, liefert scan() ein Array aus Arrays zurück. Das Array enthält ein Element für jede Regex-Übereinstimmung. Jedes dieser Elemente ist ein Array mit den bei jeder Regex-Übereinstimmung gefundenen Texten. Unterelement null enthält den Text, der von der ersten einfangenden Gruppe gefunden wurde, Unterelement

166 | Kapitel 3: Mit regulären Ausdrücken programmieren

eins enthält den Text der zweiten einfangenden Gruppe und so weiter. Das gesamte Suchergebnis ist nicht im Array enthalten. Wenn Sie auch dieses brauchen, müssen Sie Ihren gesamten regulären Ausdruck mit einer zusätzlichen einfangenden Gruppe umschließen. Ruby bietet keine Möglichkeit an, sich durch scan() ein Array mit Strings zurückgeben zu lassen, wenn die Regex einfangende Gruppen besitzt. Sie können dann nur alle benannten und nummerierten Gruppen durch nicht-einfangende Gruppen ersetzen.

Siehe auch Rezepte 3.7, 3.11 und 3.12.

3.11 Durch alle Übereinstimmungen iterieren Problem Das vorhergehende Rezept hat gezeigt, wie eine Regex wiederholt auf einen String angewendet werden kann, um eine Liste mit Übereinstimmungen zu erhalten. Jetzt wollen Sie über alle diese Übereinstimmungen in Ihrem eigenen Code iterieren.

Lösung C# Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen regulären Ausdruck verarbeiten: Match matchResult = Regex.Match(subjectString, @"\d+"); while (matchResult.Success) { // Hier können Sie die in matchResult abgelegten Übereinstimmungen bearbeiten matchResult = matchResult.NextMatch(); }

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck mit einer großen Zahl an Strings verarbeiten wollen: Regex regexObj = new Regex(@"\d+"); matchResult = regexObj.Match(subjectString); while (matchResult.Success) { // Hier können Sie die in matchResult abgelegten Übereinstimmungen bearbeiten matchResult = matchResult.NextMatch(); }

3.11 Durch alle Übereinstimmungen iterieren | 167

VB.NET Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen regulären Ausdruck verarbeiten: Dim MatchResult = Regex.Match(SubjectString, "\d+") While MatchResult.Success 'Hier können Sie die in MatchResult abgelegten Übereinstimmungen bearbeiten MatchResult = MatchResult.NextMatch End While

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck mit einer großen Zahl an Strings verarbeiten wollen: Dim RegexObj As New Regex("\d+") Dim MatchResult = RegexObj.Match(SubjectString) While MatchResult.Success 'Hier können Sie die in MatchResult abgelegten Übereinstimmungen bearbeiten MatchResult = MatchResult.NextMatch End While

Java Pattern regex = Pattern.compile("\\d+"); Matcher regexMatcher = regex.matcher(subjectString); while (regexMatcher.find()) { // Hier können Sie die in regexMatcher abgelegten Übereinstimmungen bearbeiten }

JavaScript Wenn Ihr regulärer Ausdruck eine Übereinstimmung der Länge null enthalten kann oder Sie sich da einfach nicht sicher sind, sollten Sie nach Möglichkeit versuchen, Probleme zwischen den verschiedenen Browsern bezüglich solcher Übereinstimmungen und exec() zu umgehen: var regex = /\d+/g; var match = null; while (match = regex.exec(subject)) { // Browser wie Firefox sollen nicht in einer Endlosschleife festsitzen if (match.index == regex.lastIndex) regex.lastIndex++; // Hier können Sie die in match abgelegten Übereinstimmungen bearbeiten }

Wenn Sie sicher sind, dass Ihre Regex niemals eine Übereinstimmung der Länge null finden wird, können Sie direkt über die Regex iterieren: var regex = /\d+/g; var match = null; while (match = regex.exec(subject)) { // Hier können Sie die in match abgelegten Übereinstimmungen bearbeiten }

168 | Kapitel 3: Mit regulären Ausdrücken programmieren

PHP preg_match_all('/\d+/', $subject, $result, PREG_PATTERN_ORDER); for ($i = 0; $i < count($result[0]); $i++) { # Gefundener Text = $result[0][$i]; }

Perl while ($subject =~ m/\d+/g) { # Gefundener Text = $& }

Python Wenn Sie nur wenige Strings mit dem gleichen regulären Ausdruck verarbeiten wollen, können Sie die globale Funktion nutzen: for matchobj in re.finditer(r"\d+", subject): # Hier können Sie die in matchobj abgelegten Übereinstimmungen bearbeiten

Um die gleiche Regex mehrfach zu verwenden, nutzen Sie ein kompiliertes Objekt: reobj = re.compile(r"\d+") for matchobj in reobj.finditer(subject): # Hier können Sie die in matchobj abgelegten Übereinstimmungen bearbeiten

Ruby subject.scan(/\d+/) {|match| # Hier können Sie die in match abgelegten Übereinstimmungen bearbeiten }

Diskussion .NET In Rezept 3.7 wird beschrieben, wie man die Member-Funktion Match() der Klasse Regex nutzt, um die erste Regex-Übereinstimmung im String zu finden. Um nach und nach alle Übereinstimmungen im String zu erhalten, rufen wir wieder die Funktion Match() auf, um die Details der ersten Übereinstimmung zu ermitteln. Diese Funktion liefert eine Instanz der Klasse Match zurück, die wir in der Variablen matchResult ablegen. Wenn die Eigenschaft Success des Objekts matchResult den Wert true besitzt, können wir mit der Schleife beginnen. Am Anfang der Schleife können Sie die Eigenschaften der Klasse Match verwenden, um die Details der ersten Übereinstimmung zu nutzen. In Rezept 3.7 wird die Eigenschaft Value beschrieben, in Rezept 3.8 die Eigenschaften Index und Length und in Rezept 3.9 die Collection Groups.

3.11 Durch alle Übereinstimmungen iterieren | 169

Wenn Sie mit der ersten Übereinstimmung fertig sind, rufen Sie die Member-Funktion NextMatch() der Variablen matchResult auf. Match.NextMatch() liefert eine Instanz der Klasse Match zurück – so wie es auch Regex.Match() tut. Die neue Instanz enthält die Details der zweiten Übereinstimmung. Durch das Zuweisen des Ergebnisses von matchResult.NextMatch() an die gleiche Variable matchResult ist es einfach, über alle Übereinstimmungen zu iterieren. Wir müssen immer nur matchResult.Success prüfen, um herauszufinden, ob NextMatch() tatsächlich noch eine Übereinstimmung gefunden hat. Wenn NextMatch() fehlschlägt, gibt es dennoch ein Match-Objekt zurück, aber dessen Eigenschaft Success ist dann auf false gesetzt. Indem Sie nur eine Variable matchResult nutzen, kann der erste Test und der Test nach dem Aufruf von NextMatch() in einer einzelnen while-Anweisung kombiniert werden. Der Aufruf von NextMatch() macht das Match-Objekt, für das Sie es aufgerufen haben, nicht ungültig. Wenn Sie möchten, können Sie das komplette Match-Objekt für jede Übereinstimmung aufheben. Der Methode NextMatch() können keine weitere Parameter übergeben werden. Sie nutzt den gleichen regulären Ausdruck und den gleichen Ausgangstext, den Sie der Methode Regex.Match() übergeben haben. Das Match-Objekt hält die Verweise auf Ihren regulären Ausdruck und Ausgangstext. Sie können die statische Version von Regex.Match() nutzen, auch wenn Ihr Ausgangstext eine große Zahl von Übereinstimmungen enthält. Regex.Match() kompiliert Ihren regulären Ausdruck einmal, und das zurückgegebene Match-Objekt merkt sich eine Referenz auf diese kompilierte Regex. Match.MatchAgain() verwendet den vorher kompilierten regulären Ausdruck, der vom Match-Objekt referenziert wurde, auch wenn Sie den statischen Aufruf von Regex.Match() genutzt haben. Sie müssen die Klasse Regex nur dann instantiieren, wenn Sie Regex.Match() wiederholt aufrufen wollen, also die gleiche Regex für verschiedene Strings verwenden.

Java Das Iterieren über alle Übereinstimmungen in einem String ist in Java sehr einfach. Rufen Sie die in Rezept 3.7 vorgestellte Methode find() in einer while-Schleife auf. Jeder Aufruf von find() aktualisiert das Matcher-Objekt mit der nächsten Übereinstimmung und der Startposition für den folgenden Versuch.

JavaScript Bevor Sie beginnen, sollten Sie sicherstellen, dass Sie die Option /g gesetzt haben, wenn Sie Ihre Regex in einer Schleife nutzen wollen. Diese Option wird in Rezept 3.4 beschrieben. while (regexp.exec()) findet alle Zahlen im Ausgangstext, wenn regexp = /\d+/g gilt. Ist regexp = /\d+/, findet while (regexp.exec()) nur die erste Zahl – und zwar wieder und wieder, bis Ihr Skript abstürzt oder vom Browser zum Aufgeben gezwungen wird.

170 | Kapitel 3: Mit regulären Ausdrücken programmieren

Beachten Sie, dass while (/\d+/g.exec()) (eine Schleife über eine literale Regex mit /g) ebenfalls in einer Endlosschleife stecken bleiben kann, zumindest bei bestimmten JavaScript-Implementierungen. Denn der reguläre Ausdruck wird mit jeder Iteration der while-Schleife neu kompiliert. Dabei wird auch die Startposition für die Übereinstimmungssuchen auf den Anfang des Strings zurückgesetzt. Weisen Sie daher den regulären Ausdruck außerhalb der Schleife einer Variablen zu, um sicherzustellen, dass er nur einmal kompiliert wird. Die Rezepte 3.8 und 3.9 beschreiben das von regexp.exec() zurückgelieferte Objekt. Hier ist es das gleiche Objekt, auch wenn Sie exec() in einer Schleife verwenden. Sie können mit diesem Objekt machen, was Sie wollen. Einziger Effekt von /g ist das Aktualisieren der Eigenschaft lastIndex des regexp-Objekts, für das Sie exec() aufrufen. Das funktioniert auch, wenn Sie einen literalen regulären Ausdruck verwenden, wie in der zweiten JavaScript-Lösung für dieses Rezept gezeigt wird. Wenn Sie exec() das nächste Mal aufrufen, wird die Übereinstimmungssuche bei lastIndex beginnen. Weisen Sie lastIndex einen neuen Wert zu, wird die Suche dort weitergehen. Es gibt bei lastIndex allerdings ein größeres Problem. Wenn Sie den ECMA-262v3-Standard für JavaScript wörtlich nehmen, sollte exec() den Wert von lastIndex auf das erste Zeichen nach der Übereinstimmung setzen. Wenn die Übereinstimmung die Länge null hat, bedeutet das, dass die nächste Suche an der gerade gefundenen Position starten wird – das führt zu einer Endlosschleife. Alle Regex-Engines, die in diesem Buch behandelt werden (mit Ausnahme von JavaScript), umgehen dieses Problem, indem sie den nächsten Suchvorgang automatisch ein Zeichen weiter beginnen, wenn die vorherige Übereinstimmung die Länge null hat. Das ist der Grund dafür, dass Rezept 3.7 beschreibt, dass Sie mit lastIndex nicht das Ende der Übereinstimmung finden können, weil Sie im Internet Explorer falsche Werte erhalten. Die Firefox-Entwickler waren bei der Implementierung des ECMA-262v3-Standards allerdings gnadenlos, obwohl das bedeutet, dass regexp.exec() damit in einer Endlosschleife landen kann. Und das ist gar nicht so unwahrscheinlich. So können Sie zum Beispiel mit re = /^.*$/gm; while (re.exec()) über alle Zeilen eines mehrzeiligen Strings iterieren – wenn der String Leerzeilen enthält, wird Firefox dort hängen bleiben. Sie können das umgehen, indem Sie lastIndex in Ihrem Code um eins erhöhen, wenn die Funktion exec() das noch nicht selbst getan hat. Die erste JavaScript-Lösung in diesem Rezept zeigt, wie’s geht. Wenn Sie unsicher sind, kopieren Sie diese Codezeile einfach in Ihren Code und verschwenden danach keine Gedanken mehr daran. Bei string.replace() (Rezept 3.14) oder beim Finden aller Übereinstimmungen mit string.match() (Rezept 3.10) gibt es dieses Problem nicht. Sie nutzen lastIndex zwar intern, aber der ECMA-262v3-Standard gibt hier an, dass lastIndex für Übereinstimmungen der Länge null erhöht werden muss.

3.11 Durch alle Übereinstimmungen iterieren | 171

PHP Der Funktion preg_match() kann ein optionaler fünfter Parameter mitgegeben werden. Dieser gibt die Position im String an, an der die Suche beginnen soll. Sie könnten Rezept 3.8 anpassen, indem Sie $matchstart + $matchlength beim zweiten Aufruf von preg_match() als fünften Parameter übergeben, um die zweite Übereinstimmung im String zu finden, und das für die weiteren Übereinstimmungen wiederholen, bis preg_match() den Wert 0 zurückgibt. Rezept 3.18 nutzt diese Methode. Neben dem zusätzlichen Code zum Berechnen der Startposition für jeden Suchvorgang ist ein wiederholtes Aufrufen von preg_match() ineffizient, da es keine Möglichkeit gibt, einen kompilierten regulären Ausdruck in einer Variablen zu speichern. preg_match() muss jedes Mal in seinem Cache nach dem kompilierten regulären Ausdruck suchen, wenn Sie es aufrufen. Einfacher und effizienter ist es, preg_match_all() aufzurufen, wie es schon im vorhergehenden Rezept beschrieben wurde, und über das Array mit den Suchergebnissen zu iterieren.

Perl In Rezept 3.4 ist beschrieben, dass Sie den Modifikator /g nutzen müssen, um mehr als eine Übereinstimmung im Ausgangstext zu finden. Wenn Sie eine globale Regex in einem skalaren Kontext verwenden, wird es die nächste Übereinstimmung suchen. In diesem Rezept sorgt die while-Anweisung für den skalaren Kontext. Alle speziellen Variablen, wie zum Beispiel $& (beschrieben in Rezept 3.7), stehen innerhalb der while-Schleife zur Verfügung.

Python Die Funktion finditer() aus re liefert einen Iterator zurück, den Sie nutzen können, um alle Übereinstimmungen des regulären Ausdrucks zu finden. Übergeben Sie Ihren regulären Ausdruck als ersten und den Ausgangstext als zweiten Parameter. Optionen für den regulären Ausdruck können Sie optional als dritten Parameter übergeben. Die Funktion re.finditer() ruft re.compile() und dann die Methode finditer() für das kompilierte Regex-Objekt auf. Diese Methode hat nur einen Pflichtparameter: den Ausgangstext. Der Methode finditer() können zwei optionale Parameter übergeben werden, die die globale Funktion re.finditer() nicht unterstützt. Nach dem Ausgangstext können Sie die Zeichenposition angeben, an der finditer() mit ihrer Suche beginnen soll. Lassen Sie diesen Parameter weg, wird der Iterator den gesamten Text verarbeiten. Wenn Sie eine Startposition angeben, können Sie auch eine Endposition festlegen. Geben Sie keine Endposition an, läuft die Suche bis zum Ende des Strings.

172 | Kapitel 3: Mit regulären Ausdrücken programmieren

Ruby Die Methode scan() der Klasse String erwartet als einzigen Parameter einen regulären Ausdruck. Dann iteriert sie über alle Übereinstimmungen im String. Wenn sie mit einem Block aufgerufen wird, können Sie jede Übereinstimmung direkt verarbeiten. Enthält Ihr regulärer Ausdruck keine einfangenden Gruppen, geben Sie im Block eine Iterator-Variable an. In dieser Variablen findet sich dann ein String mit dem vom regulären Ausdruck gefundenen Text. Wenn sich in Ihrer Regex eine oder mehrere einfangende Gruppen befinden, geben Sie eine Variable für jede Gruppe an. In der ersten Variablen steht der String mit dem von der ersten Gruppe gefundenen Text, in der zweiten der von der zweiten Gruppe und so weiter. Es wird aber keine Variable mit dem gesamten Suchergebnis gefüllt. Brauchen Sie aber das gesamte Suchergebnis, müssen Sie Ihren kompletten regulären Ausdruck mit einer zusätzlichen einfangenden Gruppe umschließen. subject.scan(/(a)(b)(c)/) {|a, b, c| # a, b und c enthalten den Text, der von den # drei einfangenden Gruppen gefunden wurde }

Geben Sie weniger Variablen an, als es einfangenden Gruppen gibt, werden Sie nur auf die Gruppen zugreifen können, für die Sie Variablen angegeben haben. Wenn Sie mehr Variablen angeben, als Gruppen vorhanden sind, werden die zusätzlichen Variablen auf nil gesetzt. Wenn Sie nur eine Iterator-Variable angeben und Ihre Regex eine oder mehrere einfangende Gruppen besitzt, wird die Variable mit einem Array aus Strings gefüllt. Das Array enthält dann einen String für jede einfangende Gruppe. Gibt es lediglich eine Gruppe, wird das Array auch nur ein Element enthalten: subject.scan(/(a)(b)(c)/) {|abc| # abc[0], abc[1] und abc[2] enthalten den von den drei # einfangenden Gruppen gefundenen Text }

Siehe auch Rezepte 3.7, 3.8, 3.10 und 3.12.

3.12 Übereinstimmungen in prozeduralem Code überprüfen Problem In Rezept 3.10 wird gezeigt, wie Sie eine Liste aller Übereinstimmungen eines regulären Ausdrucks in einem String finden können, wenn die Regex wiederholt auf den jeweils verbleibenden Rest angewendet wird. Jetzt wollen Sie eine Liste mit Übereinstimmungen

3.12 Übereinstimmungen in prozeduralem Code überprüfen | 173

haben, die bestimmte Bedingungen erfüllen sollen. Diese Bedingungen lassen sich aber nicht (einfach) mit einem regulären Ausdruck beschreiben. Wenn Sie zum Beispiel eine Liste mit Gewinnzahlen auslesen, möchten Sie nur diejenigen behalten, die sich (ohne Rest) durch 13 teilen lassen.

Lösung C# Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen regulären Ausdruck verarbeiten: StringCollection resultList = new StringCollection(); Match matchResult = Regex.Match(subjectString, @"\d+"); while (matchResult.Success) { if (int.Parse(matchResult.Value) % 13 == 0) { resultList.Add(matchResult.Value); } matchResult = matchResult.NextMatch(); }

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck mit einer großen Zahl an Strings verarbeiten wollen: StringCollection resultList = new StringCollection(); Regex regexObj = new Regex(@"\d+"); matchResult = regexObj.Match(subjectString); while (matchResult.Success) { if (int.Parse(matchResult.Value) % 13 == 0) { resultList.Add(matchResult.Value); } matchResult = matchResult.NextMatch(); }

VB.NET Sie können den statischen Aufruf nutzen, wenn Sie nur wenige Strings mit dem gleichen regulären Ausdruck verarbeiten: Dim ResultList = New StringCollection Dim MatchResult = Regex.Match(SubjectString, "\d+") While MatchResult.Success If Integer.Parse(MatchResult.Value) Mod 13 = 0 Then ResultList.Add(MatchResult.Value) End If MatchResult = MatchResult.NextMatch End While

174 | Kapitel 3: Mit regulären Ausdrücken programmieren

Erstellen Sie ein Regex-Objekt, wenn Sie den gleichen regulären Ausdruck mit einer großen Zahl an Strings verarbeiten wollen: Dim ResultList = New StringCollection Dim RegexObj As New Regex("\d+") Dim MatchResult = RegexObj.Match(SubjectString) While MatchResult.Success If Integer.Parse(MatchResult.Value) Mod 13 = 0 Then ResultList.Add(MatchResult.Value) End If MatchResult = MatchResult.NextMatch End While

Java List resultList = new ArrayList(); Pattern regex = Pattern.compile("\\d+"); Matcher regexMatcher = regex.matcher(subjectString); while (regexMatcher.find()) { if (Integer.parseInt(regexMatcher.group()) % 13 == 0) { resultList.add(regexMatcher.group()); } }

JavaScript var list = []; var regex = /\d+/g; var match = null; while (match = regex.exec(subject)) { // Browser wie Firefox sollen nicht in einer Endlosschleife hängen bleiben if (match.index == regex.lastIndex) regex.lastIndex++; // Hier können Sie die in match abgelegten Übereinstimmungen bearbeiten if (match[0] % 13 == 0) { list.push(match[0]); } }

PHP preg_match_all('/\d+/', $subject, $matchdata, PREG_PATTERN_ORDER); for ($i = 0; $i < count($matchdata[0]); $i++) { if ($matchdata[0][$i] % 13 == 0) { $list[] = $matchdata[0][$i]; } }

Perl while ($subject =~ m/\d+/g) { if ($& % 13 == 0) { push(@list, $&); } }

3.12 Übereinstimmungen in prozeduralem Code überprüfen | 175

Python Wenn Sie nur wenige Strings mit dem gleichen regulären Ausdruck verarbeiten wollen, können Sie die globale Funktion nutzen: list = [] for matchobj in re.finditer(r"\d+", subject): if int(matchobj.group()) % 13 == 0: list.append(matchobj.group())

Um die gleiche Regex mehrfach zu verwenden, nutzen Sie ein kompiliertes Objekt: list = [] reobj = re.compile(r"\d+") for matchobj in reobj.finditer(subject): if int(matchobj.group()) % 13 == 0: list.append(matchobj.group())

Ruby list = [] subject.scan(/\d+/) {|match| list = 1 && $day = 1 && $month = 1 && $day = 1 && $day = 1 && $month = 1 && $day 1[0-2]|0[1-9])$

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 ^(?P[0-9]{4})-(?P1[0-2]|0[1-9])$

Regex-Optionen: Keine Regex-Varianten: PCRE, Python Datum, zum Beispiel 2008-08-30. Die Bindestriche sind optional. Diese Regex erlaubt allerdings YYYY-MMDD und YYYYMM-DD, was nicht ISO 8601 entspricht: ^([0-9]{4})-?(1[0-2]|0[1-9])-?(3[0-1]|0[1-9]|[1-2][0-9])$

252 | Kapitel 4: Validierung und Formatierung

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ^(?[0-9]{4})-?(?1[0-2]|0[1-9])-? (?3[0-1]|0[1-9]|[1-2][0-9])$

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 Datum, zum Beispiel 2008-08-30. Die Bindestriche sind optional. Diese Regex nutzt eine Bedingung, um YYYY-MMDD und YYYYMM-DD auszuschließen. Es gibt eine zusätzliche einfangende Gruppe für den ersten Bindestrich: ^([0-9]{4})(-)?(1[0-2]|0[1-9])(?(2)-)(3[0-1]|0[1-9]|[1-2][0-9])$

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE, Perl, Python Datum, zum Beispiel 2008-08-30. Die Bindestriche sind optional. Diese Regex nutzt eine Alternation, um YYYY-MMDD und YYYYMM-DD auszuschließen. Es gibt zwei einfangende Gruppen für den Monat: ^([0-9]{4})(?:(1[0-2]|0[1-9])|-?(1[0-2]|0[1-9])-?) (3[0-1]|0[1-9]|[1-2][0-9])$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Kalenderwoche, zum Beispiel 2008-W35. Der Bindestrich ist optional: ^([0-9]{4})-?W(5[0-3]|[1-4][0-9]|0[1-9])$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ^(?[0-9]{4})-?W(?5[0-3]|[1-4][0-9]|0[1-9])$

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 Tag einer Woche, zum Beispiel 2008-W35-6. Die Bindestriche sind optional: ^([0-9]{4})-?W(5[0-3]|[1-4][0-9]|0[1-9])-?([1-7])$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ^(?[0-9]{4})-?W(?5[0-3]|[1-4][0-9]|0[1-9])-?(?[1-7])$

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 Tag eines Jahres, zum Beispiel 2008-243. Der Bindestrich ist optional: ^([0-9]{4})-?(36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9])$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

4.7 Datums- und Uhrzeitwerte im Format ISO 8601 validieren |

253

^(?[0-9]{4})-? (?36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9])$

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 Stunden und Minuten, zum Beispiel 17:21. Der Doppelpunkt ist optional: ^(2[0-3]|[01]?[0-9]):?([0-5]?[0-9])$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ^(?2[0-3]|[01]?[0-9]):?(?[0-5]?[0-9])$

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 Stunden, Minuten und Sekunden, zum Beispiel 17:21:59. Die Doppelpunkte sind optional: ^(2[0-3]|[01]?[0-9]):?([0-5]?[0-9]):?([0-5]?[0-9])$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ^(?2[0-3]|[01]?[0-9]):?(?[0-5]?[0-9]):? (?[0-5]?[0-9])$

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 Zeitzonenangabe, zum Beispiel Z, +07 oder +07:00. Der Doppelpunkt und die Minuten sind optional: ^(Z|[+-](?:2[0-3]|[01]?[0-9])(?::?(?:[0-5]?[0-9]))?)$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Stunden, Minuten und Sekunden mit Zeitzonenangabe, zum Beispiel 17:21:59+07:00. Alle Doppelpunkte sind optional. Die Minuten in der Zeitzonenangabe sind ebenfalls optional: ^(2[0-3]|[01]?[0-9]):?([0-5]?[0-9]):?([0-5]?[0-9]) (Z|[+-](?:2[0-3]|[01]?[0-9])(?::?(?:[0-5]?[0-9]))?)$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ^(?2[0-3]|[01]?[0-9]):?(?[0-5]?[0-9]):?(?[0-5]?[0-9]) (?Z|[+-](?:2[0-3]|[01]?[0-9])(?::?(?:[0-5]?[0-9]))?)$

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9

254 | Kapitel 4: Validierung und Formatierung

Datum mit optionaler Zeitzone, zum Beispiel 2008-08-30 oder 2008-08-30+07:00. Bindestriche sind erforderlich. Das ist der XML Schema-Typ date: ^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[0-1]|0[1-9]|[1-2][0-9]) (Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ^(?-?(?:[1-9][0-9]*)?[0-9]{4})-(?1[0-2]|0[1-9])(?3[0-1]|0[1-9]|[1-2][0-9]) (?Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?$

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 Uhrzeit mit optionalen Sekundenbruchteilen und Zeitzone, zum Beispiel 01:45:36 oder 01:45:36.123+07:00. Das ist der XML Schema-Typ time: ^(2[0-3]|[0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)? (Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ^(?2[0-3]|[0-1][0-9]):(?[0-5][0-9]):(?[0-5][0-9]) (?\.[0-9]+)?(?Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?$

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 Datum und Uhrzeit mit optionalen Sekundenbruchteilen und Zeitzone, zum Beispiel 2008-08-30T01:45:36 oder 2008-08-30T01:45:36.123Z. Das ist der XML Schema-Typ dateTime: ^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[0-1]|0[1-9]|[1-2][0-9]) T(2[0-3]|[0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)? (Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ^(?-?(?:[1-9][0-9]*)?[0-9]{4})-(?1[0-2]|0[1-9])(?3[0-1]|0[1-9]|[1-2][0-9])T(?2[0-3]|[0-1][0-9]): (?[0-5][0-9]):(?[0-5][0-9])(?\.[0-9]+)? (?Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?$

Regex-Optionen: Keine Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9

Diskussion Die ISO 8601 definiert eine große Anzahl von Datums- und Zeitformaten. Die hier vorgestellten regulären Ausdrücke kümmern sich um die gebräuchlichsten Formate, aber die meisten Systeme, die ISO 8601 verwenden, greifen nur auf eine Untermenge zurück. So sind zum Beispiel bei Datums- und Zeitwerten in XML Schema die Bindestriche und 4.7 Datums- und Uhrzeitwerte im Format ISO 8601 validieren |

255

Doppelpunkte nicht optional. Um diese Zeichen in den regulären Ausdrücken einzufordern, entfernen Sie einfach die Fragezeichen hinter ihnen. Passen Sie aber auf nicht-einfangende Gruppen auf, die die Syntax ‹(?:Gruppe)› verwenden. Wenn ein Fragezeichen und ein Doppelpunkt auf eine öffnende Klammer folgen, sind diese drei Zeichen der Anfang einer nicht-einfangenden Gruppe. Bei den regulären Ausdrücken sind die einzelnen Bindestriche und Doppelpunkte optional, was nicht ganz ISO 8601 entspricht. So ist zum Beispiel 1733:26 nach ISO 8601 keine gültige Zeit, sie wird aber von den Zeit-Regexes akzeptiert. Wenn alle Bindestriche und Doppelpunkte gleichzeitig vorhanden oder eben nicht vorhanden sein sollen, wird Ihre Regex ein ganzes Stück komplexer. Wir haben das als Beispiel für die Datums-Regex gezeigt, aber im Alltag sind die Trennzeichen im Allgemeinen entweder auf jeden Fall erforderlich (wie bei XML Schema) oder immer verboten und nicht optional. Wir haben um alle Zahlenelemente der Regex Klammern gelegt. Damit ist es einfach, die Werte für Jahr, Monat, Tag, Stunden, Minuten, Sekunden und Zeitzonen auszulesen. In Rezept 2.9 wird beschrieben, wie Klammern einfangende Gruppen definieren. Rezept 3.9 zeigt Ihnen, wie Sie den von diesen einfangenden Gruppen gefundenen Text mithilfe von prozeduralem Code auslesen können. Bei den meisten Regexes haben wir auch eine Alternative mit benannten Captures präsentiert. Manche dieser Datums- und Zeitformate sind Ihnen oder Ihren Kollegen vielleicht unbekannt. Benannte Captures erleichtern das Verstehen der Regex. .NET, PCRE 7, Perl 5.10 und Ruby 1.9 unterstützen die Syntax ‹(?‹Name›Gruppe)›. Alle Versionen von PCRE und Python, die in diesem Buch behandelt werden, bieten auch die alternative Syntax ‹(?P‹Name›Gruppe)› an, in der ein zusätzliches ‹P› enthalten ist. In Rezept 2.11 und Rezept 3.9 finden Sie die Details dazu. Die Zahlenbereiche in allen Regexes sind sehr strikt. So ist zum Beispiel der Kalendertag auf Werte zwischen 01 und 31 eingeschränkt. Sie werden nie einen Tag 32 oder einen Monat 13 erhalten. Allerdings versucht keine der vorgestellten Regexes, ungültige Kombinationen aus Tag und Monat auszuschließen, wie zum Beispiel den 31. Februar. In Rezept 4.5 beschreiben wir, wie Sie dieses Problem angehen können. Auch wenn manche dieser Regexes ziemlich lang sind, gibt es in ihnen dennoch keine exotischen Verrenkungen, und alle nutzen die gleichen Techniken, die in Rezept 4.4 und Rezept 4.6 erläutert wurden.

Siehe auch Rezepte 4.4, 4.5, 4.6.

256 | Kapitel 4: Validierung und Formatierung

4.8

Eingabe auf alphanumerische Zeichen beschränken

Problem Bei Ihrer Anwendung soll der Nutzer bei einer Eingabe nur alphanumerische Zeichen aus dem englischen Alphabet eingeben dürfen.

Lösung Mit den Ihnen zur Verfügung stehenden regulären Ausdrücken ist die Lösung ganz einfach. Eine Zeichenklasse kann den erlaubten Bereich mit Zeichen festlegen. Mit einem Quantor, der die Zeichenklasse ein Mal oder mehrfach zulässt, und Ankern, die die Übereinstimmung mit dem Anfang und dem Ende des Strings verbinden, sind Sie schon fertig.

Regulärer Ausdruck ^[A-Z0-9]+$

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Ruby if subject =~ /^[A-Z0-9]+$/i puts "Text ist alphanumerisch" else puts "Text ist nicht alphanumerisch" end

Andere Programmiersprachen In den Rezepten 3.4 und 3.5 finden Sie Informationen über das Implementieren dieses regulären Ausdrucks in anderen Programmiersprachen.

Diskussion Lassen Sie uns die vier Teile dieses regulären Ausdrucks nacheinander anschauen: ^ [A-Z0-9] + $

# Sicherstellen, dass die Übereinstimmung am Anfang des Texts beginnt. # Ein Zeichen zwischen "A" und "Z" oder zwischen "0" und "9" finden... # einmal bis unbegrenzt oft. # Sicherstellen, dass die Übereinstimmung am Ende des Texts endet.

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Freiform-Modus Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

4.8 Eingabe auf alphanumerische Zeichen beschränken | 257

Die Anker ‹^› und ‹$› am Anfang und Ende des regulären Ausdrucks sorgen dafür, dass die gesamte Eingabe überprüft wird. Ohne sie könnte die Regex auch Teile eines längeren Strings finden, sodass doch ungültige Zeichen übersehen werden. Durch den Quantor ‹+› kann das vorherige Element einmal oder häufiger vorkommen. Wenn Ihre Regex auch einen vollständig leeren String erkennen soll, können Sie das ‹+› durch ‹*› ersetzen. Der Stern-Quantor ‹*› erlaubt, dass ein Element null Mal oder häufiger vorkommt, wodurch dieses Element im Endeffekt optional wird.

Variationen Eingabe auf ASCII-Zeichen einschränken Der folgende reguläre Ausdruck beschränkt die Eingabe auf die 128 Zeichen in der 7-BitASCII-Tabelle. Dazu gehören auch 33 nicht sichtbare Steuerzeichen: ^[\x00-\x7F]+$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Eingabe auf ASCII-Zeichen ohne Steuerzeichen und auf Zeilenumbrüche einschränken Mit dem folgenden regulären Ausdruck beschränken Sie die Eingabe auf sichtbare Zeichen und Whitespace in der ASCII-Tabelle. Steuerzeichen werden damit ausgeschlossen. Die Zeichen für Line Feed und Carriage Return (mit den Werten 0x0A und 0x0D) sind die gebräuchlichsten Steuerzeichen, daher werden sie hier explizit durch ‹\n› (Line Feed) und ‹\r› (Carriage Return) mit aufgenommen: ^[\n\r\x20-\x7E]+$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Eingabe auf die Zeichen begrenzen, die sowohl in ISO-8859-1 als auch in Windows1252 vorkommen ISO-8859-1 und Windows-1252 (häufig als ANSI bezeichnet) sind zwei häufig genutzte Zeichenkodierungen mit 8 Bit Breite, die beide auf dem Latin-1-Standard basieren (genauer gesagt, auf ISO/IEC 8859-1). Allerdings sind die Zeichen an den Positionen von 0x80 und 0x9F inkompatibel. ISO-8859-1 nutzt diese Positionen für Steuerzeichen, während Windows-1252 dort noch mehr Buchstaben und Satzzeichen abgelegt hat. Diese Unterschiede führen manchmal zu Problemen beim Anzeigen von Zeichen, insbesondere bei Dokumenten, die ihre Kodierung nicht angeben, oder bei denen der Empfänger ein Nicht-Windows-System verwendet. Der folgende reguläre Ausdruck kann dazu verwendet werden, die Eingabe auf Zeichen zu beschränken, die in beiden Zeichentabellen ISO8859-1 und Windows-1252 vorhanden sind (einschließlich der gemeinsamen Steuerzeichen):

258 | Kapitel 4: Validierung und Formatierung

^[\x00-\x7F\xA0-\xFF]+$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Durch die hexadezimale Schreibweise ist dieser reguläre Ausdruck vielleicht etwas schlechter zu lesen, aber er arbeitet genau so wie die weiter oben gezeigte Zeichenklasse ‹[A-Z0-9]›. Die Regex passt auf Zeichen in zwei Bereichen: \x00-\x7F und \xA0-\xFF.

Eingabe auf alphanumerische Zeichen in beliebigen Sprachen einschränken Dieser reguläre Ausdruck beschränkt die Eingabe auf Buchstaben und Ziffern aus beliebigen Sprachen oder Schriften. Er verwendet eine Zeichenklasse, die Eigenschaften für alle Codepoints in den Buchstaben- und Ziffernkategorien von Unicode enthält: ^[\p{L}\p{N}]+$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Ruby 1.9 Leider werden Unicode-Eigenschaften nicht von allen in diesem Buch behandelten Regex-Varianten unterstützt. Vor allem funktioniert diese Regex nicht in JavaScript, Python und Ruby 1.8. Zudem muss die PCRE mit UTF-8-Unterstützung kompiliert werden, will man diese Regex dort nutzen. Unicode-Eigenschaften können in den preg-Funktionen von PHP genutzt werden (die auf der PCRE aufbauen), wenn an die Regex die Option /u angehängt wird. Mit der folgenden Regex kann man in Python die fehlende Unterstützung von UnicodeEigenschaften umgehen: ^[^\W_]+$

Regex-Optionen: Unicode Regex-Variante: Python Hier umgehen wir die in Python fehlenden Unicode-Eigenschaften, indem wir den Schalter UNICODE oder U beim Erstellen des regulären Ausdrucks verwenden. Dadurch wird die Bedeutung einiger Regex-Tokens so verändert, dass sie auf die Unicode-Zeichentabelle zugreifen. ‹\w› bringt uns schon recht weit, weil damit alphanumerische Zeichen und der Unterstrich gefunden werden. Durch die inverse Abkürzung ‹\W› in einer negierten Zeichenklasse können wir den Unterstrich aus dieser Menge entfernen. Solche doppelt negierten Elemente sind in regulären Ausdrücken manchmal recht nützlich, auch wenn man eventuell erst einmal seinen Grips dafür anstrengen muss.1

1

Wenn Sie noch mehr Spaß haben wollen (für sehr seltsame Definitionen von „Spaß”), können Sie versuchen, dreifache, vierfache oder noch „höher-fache“ Negierungen zu erzeugen, indem Sie negative Lookarounds (siehe Rezept 2.16) und Zeichenklassen-Subtraktionen (siehe Rezept 2.3) ins Spiel bringen.

4.8 Eingabe auf alphanumerische Zeichen beschränken | 259

Siehe auch In Rezept 4.9 wird gezeigt, wie man die Länge des Texts einschränkt.

4.9

Die Länge des Texts begrenzen

Problem Sie wollen prüfen, ob ein String zwischen einem und zehn Buchstaben von A bis Z enthält.

Lösung Alle Programmiersprachen, die in diesem Buch behandelt werden, stellen eine einfache und effiziente Möglichkeit bereit, die Länge eines Texts zu überprüfen. So haben zum Beispiel JavaScript-Strings eine Eigenschaft length mit einer Integer-Zahl, die die Länge des Strings angibt. Aber manchmal kann es auch sinnvoll sein, die Länge eines Texts mit einem regulären Ausdruck zu prüfen, insbesondere wenn die Länge nur eine von mehreren Regeln ist, die bestimmen, ob der Ausgangstext in das gewünschte Muster passt. Der folgende reguläre Ausdruck stellt sicher, dass der Text zwischen 1 und 10 Zeichen lang ist. Zudem schränkt er den Text auf die Großbuchstaben A bis Z ein. Sie können die regulären Ausdrücke so verändern, dass sie eine minimale oder maximale Textlänge definieren, aber auch andere Zeichen als A bis Z zulassen.

Regulärer Ausdruck ^[A-Z]{1,10}$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Perl if ($ARGV[0] =~ /^[A-Z]{1,10}$/) { print "Eingabe ist gültig\n"; } else { print "Eingabe ist ungültig\n"; }

Andere Programmiersprachen In Rezept 3.5 finden Sie Informationen darüber, wie Sie diesen regulären Ausdruck in anderen Programmiersprachen implementieren können.

260 | Kapitel 4: Validierung und Formatierung

Diskussion Hier ist die Aufteilung dieser sehr einfachen Regex in ihre Bestandteile: ^ [A-Z] {1,10} $

# Sicherstellen, dass die Regex am Textanfang passt. # Einen der Buchstaben von "A" bis "Z" finden ... # zwischen 1 und 10 Mal. # Sicherstellen, dass die Regex am Textende passt.

Regex-Optionen: Freiform Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Die Anker ‹^› und ‹$› stellen sicher, dass die Regex den gesamten Ausgangstext umfasst. Ansonsten könnte es sein, dass sie nur zehn Zeichen innerhalb eines längeren Texts findet. Die Zeichenklasse ‹[A-Z]› passt auf einen einzelnen Großbuchstaben von A bis Z. Der Intervall-Quantor ‹{1,10}› sorgt dafür, dass die Zeichenklasse zwischen einem und zehn Mal vorkommen kann. Indem man den Intervall-Quantor mit den Textanfangsund -ende-Ankern kombiniert, wird die Regex fehlschlagen, wenn die Länge des Ausgangstexts außerhalb des gewünschten Bereichs liegt. Beachten Sie, dass die Zeichenklasse ‹[A-Z]› explizit nur Großbuchstaben zulässt. Wenn Sie auch die Kleinbuchstaben a bis z aufnehmen wollen, können Sie entweder als Zeichenklasse ‹[A-Za-z]› verwenden oder die Option zum Ignorieren von Groß- und Kleinschreibung nutzen. In Rezept 3.4 ist nachzulesen, wie man das tut. Ein von Anfängern häufig gemachter Fehler ist, ein paar Tastendrücke dadurch zu sparen, dass man den Zeichenklassenbereich ‹[A-z]› nutzt. Auf den ersten Blick sieht das eigentlich ganz praktisch aus. Aber die ASCII-Zeichentabelle enthält zwischen den Bereichen von A bis Z und a bis z eine Reihe von Satzzeichen. Daher entspricht ‹[A-z]› in Wirklichkeit ‹[A-Z[\]^_`a-z]›.

Variationen Die Länge eines bestimmten Musters begrenzen Da Quantoren wie ‹{1,10}› nur auf das direkt vor ihnen stehende Element wirken, muss man etwas anders vorgehen, wenn man die Zeichenzahl von Mustern begrenzen will, die mehr als ein einzelnes Token enthalten. Wie in Rezept 2.16 beschrieben, sind Lookaheads (und ihre Gegenstücke, die Lookbehinds) besondere Arten von Zusicherungen, die wie ‹^› und ‹$› auf eine Position innerhalb des Ausgangstexts passen, aber keine Zeichen konsumieren. Lookaheads können entweder positiv oder negativ sein. Das bedeutet, man kann mit ihnen prüfen, ob auf die aktuelle Position ein Muster folgt – oder eben nicht. Ein positives Lookahead, das die Syntax ‹(?=...)› hat, kann am Anfang des Musters genutzt werden, um sicherzustellen, dass die Länge des Strings innerhalb des Zielbereichs liegt. Der Rest der Regex kann dann das gewünschte Muster prüfen, ohne sich darum kümmern zu müssen, wie lang der Text ist. Hier ein einfaches Beispiel:

4.9 Die Länge des Texts begrenzen | 261

^(?=.{1,10}$).*

Regex-Optionen: Punkt passt zu Zeilenumbruch Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^(?=[\S\s]{1,10}$)[\S\s]*

Regex-Optionen: Keine Regex-Variante: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Es ist wichtig, dass sich der Anker ‹$› innerhalb des Lookaheads befindet, denn das Prüfen der maximalen Länge funktioniert nur, wenn wir sicherstellen, dass es nach dem Erreichen der Grenze keine weiteren Zeichen gibt. Da das Lookahead die Länge des Bereichs am Anfang der Regex sicherstellt, kann das folgende Muster dann beliebige zusätzliche Validierungsregeln umsetzen. In diesem Fall wird das Muster ‹.*› (oder ‹[\S\s]*› für JavaScript) genutzt, um einfach den gesamten Ausgangstext zu finden – ohne zusätzliche Einschränkungen. Die erste Regex verwendet die Option Punkt passt auf Zeilenumbruch, damit der Punkt wirklich alle Zeichen findet – auch Zeilenumbrüche. In Rezept 3.4 finden Sie Details über das Anwenden dieses Modifikators in Ihrer Programmiersprache. Die Regex für JavaScript sieht anders aus, da JavaScript die Option Punkt passt auf Zeilenumbruch nicht besitzt. In „Jedes Zeichen einschließlich Zeilenumbrüchen“ auf Seite 37 in Rezept 2.4 finden Sie weitere Informationen.

Anzahl der Zeichen ohne Whitespace einschränken Die folgende Regex passt auf Strings, die zwischen 10 und 100 Nicht-Whitespace-Zeichen enthalten: ^\s*(?:\S\s*){10,100}$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby In Java, PCRE, Python und Ruby passt ‹\s› nur auf ASCII-Whitespace-Zeichen und ‹\S› auf alles andere. In Python können Sie durch die Option UNICODE oder U beim Erzeugen der Regex dafür sorgen, dass ‹\s› jeden Unicode-Whitespace berücksichtigt. Entwickler, die mit Java, der PCRE oder Ruby 1.9 arbeiten und verhindern wollen, dass UnicodeWhitespace beim Zählen berücksichtigt wird, können die folgende Version nutzen, die von den Unicode-Eigenschaften Gebrauch macht (beschrieben in Rezept 2.7): ^[\p{Z}\s]*(?:[^\p{Z}\s][\p{Z}\s]*){10,100}$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Ruby 1.9 Die PCRE muss mit UTF-8-Unterstützung kompiliert werden, damit diese Regex wie gewünscht funktioniert. In PHP müssen Sie die UTF-8-Unterstützung durch den Modifikator /u aktivieren.

262 | Kapitel 4: Validierung und Formatierung

Diese Regex kombiniert die Unicode-Eigenschaft „Separator“ ‹\p{Z}› mit der Zeichenklassenabkürzung ‹\s› für Whitespace. Das liegt daran, dass Zeichen, die von ‹\p{Z}› und ‹\s› gefunden werden, nicht unbedingt identisch sind. Zu ‹\s› gehören die Zeichen an den Positionen 0x09 bis 0x0D (Tab, Line Feed, vertikaler Tab, Form Feed und Carriage Return), die nicht der Separator-Eigenschaft im Unicode-Standard zugewiesen sind. Indem Sie ‹\p{Z}› und ‹\s› in einer Zeichenklasse kombinieren, stellen Sie sicher, dass alle Whitespace-Zeichen gefunden werden. In beiden Regexes wird der Intervall-Quantor ‹{10,100}› auf die vor ihm stehende nichteinfangende Gruppe angewendet und nicht nur auf ein einzelnes Token. Die Gruppe passt zu jedem einzelnen Nicht-Whitespace-Zeichen, dem null oder mehr WhitespaceZeichen folgen. Der Intervall-Quantor kann zuverlässig erkennen, wie viele NichtWhitespace-Zeichen gefunden wurden, da während jeder Iteration nur genau ein NichtWhitespace-Zeichen passt.

Anzahl der Wörter einschränken Die folgende Regex ähnelt dem vorigen Beispiel, bei dem die Anzahl der NichtWhitespace-Zeichen begrenzt wird. Nur passt hier jede Wiederholung auf ein ganzes Wort und nicht auf ein einzelnes Nicht-Whitespace-Zeichen. Es passt auf 10 bis 100 Wörter, wobei alle Nicht-Wortzeichen ignoriert werden – auch Satzzeichen und Whitespace: ^\W*(?:\w+\b\W*){10,100}$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby In Java, JavaScript, PCRE und Ruby passt das Wortzeichen-Token ‹\w› in dieser Regex nur auf die ASCII-Zeichen A–Z, a–z, 0–9 und _. Daher lassen sich damit Wörter mit Nicht-ASCII-Buchstaben und -Zahlen nicht korrekt zählen. In .NET und Perl basiert ‹\w› auf der Unicode-Tabelle (genauso wie das Gegen-Token ‹\W› und die Wortgrenze ‹\b›) und passt daher auf Buchstaben und Ziffern aus allen Unicode-Schriftsystemen. In Python können Sie selbst wählen, ob diese Tokens auf Unicode basieren sollen oder nicht. Das hängt davon ob, ob Sie die Option UNICODE oder U beim Erstellen der Regex mit angeben. Wenn Sie Wörter zählen wollen, die Nicht-ASCII-Buchstaben und -Zahlen enthalten, können die folgenden Regexes dies für weitere Regex-Varianten ermöglichen: ^[^\p{L}\p{N}_]*(?:[\p{L}\p{N}_]+\b[^\p{L}\p{N}_]*){10,100}$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, Perl ^[^\p{L}\p{N}_]*(?:[\p{L}\p{N}_]+(?:[^\p{L}\p{N}_]+|$)){10,100}$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Ruby 1.9

4.9 Die Länge des Texts begrenzen | 263

Die PCRE muss mit UTF-8-Unterstützung kompiliert werden, damit diese Regex dort funktioniert. In PHP schalten Sie die UTF-8-Unterstützung mit dem Muster-Modifikator /u ein. Wie schon in „Wortzeichen“ auf Seite 46 in Rezept 2.6 erwähnt, ist der Grund für diese verschiedenen (aber gleich funktionierenden) Regexes der unterschiedliche Umgang mit Wortzeichen und Wortgrenzen. Die letzten beiden Regexes nutzen Zeichenklassen, die die Unicode-Eigenschaften für Buchstaben und Zahlen verwenden (‹\p{L}› und ‹\p{N}›). Dazu wurde noch der Unterstrich mit aufgenommen, damit sich die Zeichenklassen genau so verhalten wie bei den anderen Regexes, die auf ‹\w› und ‹\W› aufbauen. Jede Wiederholung der nicht-einfangenden Gruppe in den ersten beiden dieser drei Regexes passt zu einem vollständigen Wort, auf das null oder mehr Nicht-Wortzeichen folgen. Das Token ‹\W› (oder ‹[^\p{L}\p{N}_]›) innerhalb der Gruppe ist optional, falls der String mit einem Wortzeichen endet. Da damit aber die Nicht-Wortzeichen-Folge während des Suchprozesses optional würde, brauchen wir die Wortgrenzenzusicherung ‹\b› zwischen ‹\w› und ‹\W› (oder ‹[\p{L}\p{N}_]› und ‹[^\p{L}\p{N}_]›). Damit ist sichergestellt, dass jede Wiederholung der Gruppe wirklich ein vollständiges Wort findet. Ohne die Wortgrenze würde eine einzelne Wiederholung einen beliebigen Teil eines Worts finden, und die folgenden Wiederholungen könnten dann auf zusätzliche Teile passen. Die dritte Version der Regex (durch die auch PCRE und Ruby 1.9 mitmachen können) funktioniert ein bisschen anders. Sie verwendet einen Plus- (eins oder mehr) statt eines Stern-Quantors (null oder mehr) und lässt explizit nur dann null Zeichen zu, wenn der Suchprozess schon am Ende des Strings angelangt ist. Damit wird das WortgrenzenToken vermieden, was für eine genauere Suche wichtig war, da ‹\b› in der PCRE und in Ruby nicht Unicode-kompatibel ist. In Java ist ‹\b› Unicode-kompatibel, ‹\w› allerdings nicht. Leider ist es mit keiner dieser Optionen möglich, in JavaScript oder Ruby 1.8 korrekt mit Wörtern umzugehen, die Nicht-ASCII-Zeichen verwenden. Eine mögliche Alternative ist, die Regex so umzubauen, dass sie Whitespace-Sequenzen statt Wörter zählt: ^\s*(?:\S+(?:\s+|$)){10,100}$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, Perl, PCRE, Python, Ruby Das funktioniert in vielen Fällen genau so wie die vorherigen Lösungen, ist aber nicht genau das Gleiche. So werden hier zum Beispiel Wörter mit Bindestrichen (wie „SBahn“) als ein Wort gezählt und nicht mehr als zwei Wörter.

Siehe auch Rezepte 4.8 und 4.10.

264 | Kapitel 4: Validierung und Formatierung

4.10 Die Zeilenanzahl eines Texts beschränken Problem Sie müssen prüfen, ob ein String aus fünf oder weniger Zeilen besteht, wobei die Gesamtlänge des Strings unwichtig ist.

Lösung Die Zeichen oder Zeichenfolgen, die als Zeilentrenner genutzt werden, können sehr stark von Ihrem Betriebssystem, der Anwendung oder den Einstellungen des Benutzers abhängig sein. Daher stellt sich beim Schaffen einer idealen Lösung die Frage, welche Konventionen unterstützt werden sollen, um den Anfang einer neuen Zeile zu erkennen. Die folgenden Lösungen unterstützen den Standard von MS-DOS/Windows (‹\r\n›), dem alten Mac OS (‹\r›) und Unix/Linux/OS X (‹\n›).

Regulärer Ausdruck Die folgenden drei variantenspezifischen Regexes besitzen zwei Unterschiede. Die erste Regex verwendet atomare Gruppen, die als ‹(?>...)› geschrieben werden, statt auf nicht-einfangende Gruppen zurückzugreifen (‹(?:...)›), denn dadurch können die Regex-Varianten, die sie unterstützen, eventuell einen kleinen Geschwindigkeitsvorteil erhalten. Python und JavaScript bieten keine atomaren Gruppen, daher werden sie bei diesen Varianten nicht verwendet. Der andere Unterschied sind die Tokens, die genutzt werden, um die Position am Anfang und Ende des Strings sicherzustellen (‹\A› oder ‹^› für den Anfang des Strings und ‹\z›, ‹\Z› oder ‹$› für dessen Ende). Die Gründe für diese Unterschiede werden später noch genauer beleuchtet. Alle drei variantenspezifischen Regexes passen auf genau die gleichen Strings: \A(?>(?>\r\n?|\n)?[^\r\n]*){0,5}\z

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Ruby \A(?:(?:\r\n?|\n)?[^\r\n]*){0,5}\Z

Regex-Optionen: Keine Regex-Variante: Python ^(?:(?:\r\n?|\n)?[^\r\n]*){0,5}$

Regex-Optionen: Keine Regex-Variante: JavaScript

4.10 Die Zeilenanzahl eines Texts beschränken | 265

PHP (PCRE) if (preg_match('/\A(?>(?>\r\n?|\n)?[^\r\n]*){0,5}\z/', $_POST['subject'])) { print 'Text enthält fünf oder weniger Zeilen'; } else { print 'Text enthält mehr als fünf Zeilen'; }

Andere Programmiersprachen In Rezept 3.5 finden Sie Informationen dazu, wie diese regulären Ausdrücke mit anderen Programmiersprachen implementiert werden können.

Diskussion Alle in diesem Rezept bis hierhin gezeigten regulären Ausdrücke nutzen eine Gruppe, die zu einem Zeilenumbruch in MS-DOS/Windows, dem alten Mac OS oder in Unix/Linux/OS X passen, gefolgt von einer beliebigen Zahl von Zeichen, die kein Zeilenumbruch sind. Diese Gruppe wird zwischen null und fünf Mal wiederholt, da wir bis zu fünf Zeilen finden wollen. Im folgenden Beispiel haben wir die JavaScript-Version der Regex in ihre Bestandteile zerlegt. Die JavaScript-Version haben wir hier deshalb verwendet, weil ihre Elemente vermutlich den meisten Lesern bekannt sein dürften. Wir werden die Versionen für andere Regex-Varianten im Anschluss behandeln: ^ (?: (?: \r \n ? | \n ) ? [^\r\n] * ) {0,5} $

# # # # # # # # # # # # # # #

Position am Anfang des Strings sicherstellen. Gruppieren, aber nicht einfangen ... Gruppieren, aber nicht einfangen ... Ein Carriage Return (CR, ASCII-Position 0x0D) finden. Ein Line Feed (LF, ASCII-Position 0x0A) finden ... null oder ein Mal. oder ... Ein Line Feed finden. Ende der nicht-einfangenden Gruppe. Die vorige Gruppe null oder ein Mal wiederholen. Ein beliebiges Zeichen außer CR oder LF finden ... null Mal oder öfter. Ende der nicht-einfangenden Gruppe. Vorherige Gruppe null bis fünf Mal wiederholen. Position am Ende des Strings sicherstellen.

Regex-Optionen: Freiform Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Das führende ‹^› stellt sicher, dass sich die Übereinstimmung am Anfang des Strings befindet. Damit wird dafür gesorgt, dass der gesamte String nicht mehr als fünf Zeilen enthält, denn so können sich nicht schon vor dem gefundenen Bereich Zeilen befinden. Als Nächstes umschließt eine nicht-einfangende Gruppe die Kombination einer Zeilenumbruchfolge und einer beliebigen Zahl von Zeichen, die gerade kein Zeilenumbruch

266 | Kapitel 4: Validierung und Formatierung

sind. Der direkt darauffolgende Quantor erlaubt, diese Gruppe zwischen null und fünf Mal zu finden (null Wiederholungen passen zu einem vollständig leeren String). Innerhalb der äußeren Gruppe passt eine optionale Untergruppe zu einer Zeilenumbruchfolge. Danach kommt die Zeichenklasse, die zu einer beliebigen Zahl von Zeichen passt, die kein Zeilenumbruch sind. Schauen Sie sich die Reihenfolge der Elemente der äußeren Gruppe genauer an (zuerst ein Zeilenumbruch, dann anderer Text). Wenn wir die Reihenfolge umkehren, sodass die Gruppe stattdessen als ‹(?:[^\r\n]*(?:\r\n?|\n)?)› geschrieben würde, ließe eine fünfte Wiederholung einen abschließenden Zeilenumbruch zu. Somit würde man eine leere sechste Zeile zulassen. Die Untergruppe erlaubt eine von drei Zeilenumbruchfolgen: • Ein Carriage Return, gefolgt von einem Line Feed (‹\r\n›, der normale Zeilenumbruch im MS-DOS-/Windows-Umfeld). • Ein einzelnes Carriage Return (‹\r›, der Zeilenumbruch im alten Mac OS). • Ein einzelnes Line Feed (‹\n›, der klassische Zeilenumbruch unter Unix/Linux/OS X). Lassen Sie uns nun die Unterschiede bei den Varianten anschauen. Die erste Version der Regex (die für alle Varianten außer Python und JavaScript genutzt werden kann) verwendet atomare Gruppen statt einfache nicht-einfangende Gruppen. Auch wenn die Verwendung von atomaren Gruppen einen deutlich größeren Einfluss auf die Performance haben kann, wird die Regex-Engine in diesem Fall nur vor ein bisschen unnötigem Backtracking bewahrt, das bei einer fehlschlagenden Suche auftreten kann (in Rezept 2.15 erhalten Sie mehr Informationen über atomare Gruppen). Die anderen Unterschiede zwischen den Varianten sind die Tokens, die genutzt werden, um die Position am Anfang und Ende des Strings sicherzustellen. Die auseinandergenommene Regex weiter oben hat dafür ‹^› und ‹$› genutzt. Diese Anker werden zwar von allen hier behandelten Regex-Varianten unterstützt, die anderen Regexes in diesem Abschnitt nutzen aber stattdessen ‹\A›, ‹\Z› und ‹\z›. Kurz gesagt, unterscheidet sich die Bedeutung dieser Metazeichen ein wenig bei den verschiedenen Regex-Varianten. Eine ausführlichere Erklärung lässt uns ein wenig in die Geschichte von Regexes eintauchen … Wenn man Perl nutzt, um eine Zeile aus einer Datei einzulesen, endet der sich so ergebende String mit einem Zeilenumbruch. Daher hat Perl eine „Verbesserung“ für die klassische Bedeutung von ‹$› eingeführt, die seitdem von den meisten Regex-Varianten übernommen wurde. So findet ‹$› nicht nur das absolute Ende des Strings, sondern auch einen Zeilenumbruch direkt vor dem String-Ende. Perl hat zudem zwei weitere Zusicherungen für das Ende eines Strings eingeführt: ‹\Z› und ‹\z›. Der Anker ‹\Z› hat die gleiche eigenartige Bedeutung wie ‹$›, nur dass sich diese nicht ändert, wenn die Option Zirkumflex und Dollar passen auf Zeilenumbruch für ‹^› und ‹$› eingeschaltet wird. ‹\z› passt immer nur auf das absolute Ende eines Strings – ohne Ausnahme. Da dieses Rezept explizit mit Zeilenumbrüchen hantiert, um die Zeilen in einem String zu zählen, nutzt es

4.10 Die Zeilenanzahl eines Texts beschränken | 267

die Zusicherung ‹\z› für die Regex-Varianten, in denen sie angeboten wird. Damit kann sichergestellt werden, dass es keine sechste, leere Zeile gibt. Die meisten anderen Regex-Varianten haben die Zeilenende-/String-Ende-Anker von Perl übernommen. .NET, Java, PCRE und Ruby unterstützen alle sowohl ‹\Z› als auch ‹\z› mit der gleichen Bedeutung wie in Perl. Python bietet nur das ‹\Z› (als Großbuchstabe), wobei es aber verwirrenderweise die Bedeutung ändert. Es passt nur auf das absolute Ende des Strings, so wie das kleine ‹\z› von Perl. JavaScript unterstützt überhaupt keine „z“-Anker, aber anders als die anderen Varianten passt sein Anker ‹$› nur auf das absolute Ende des Strings (wenn die Option Zirkumflex und Dollar passen auf Zeilenumbruch nicht aktiv ist). Bei ‹\A› ist das Ganze etwas übersichtlicher. Dieser Anker passt immer nur auf den Anfang eines Strings und hat überall die gleiche Bedeutung – außer in JavaScript, das ihn nicht unterstützt. Es ist zwar nicht sehr schön, dass es diese verwirrenden Inkonsistenzen gibt, aber einer der Vorteile beim Verwenden regulärer Ausdrücke in diesem Buch ist, dass Sie sich normalerweise keine Sorgen darum machen müssen. Solche unschönen Details werden nur dann relevant, wenn Sie sich doch intensiver mit den Regexes befassen wollen.

Variationen Umgang mit esoterischen Zeilentrennern Die oben gezeigten Regexes unterstützen nur die klassischen Zeilenumbrüche von MSDOS/Windows, Unix/Linux/OS X und dem alten Mac OS. Aber es gibt noch eine Reihe selten genutzter vertikaler Whitespace-Zeichen, die Ihnen gelegentlich über den Weg laufen. Die folgenden Regexes berücksichtigen diese zusätzlichen Zeichen, und schränken dabei die Übereinstimmungen auf maximal fünf Zeilen Text ein. \A(?>\R?\V*){0,5}\z

Regex-Optionen: Keine Regex-Varianten: PCRE 7 (mit der Option PCRE_BSR_UNICODE), Perl 5.10 \A(?>(?>\r\n?|[\n-\f\x85\x{2028}\x{2029}])? [^\n-\r\x85\x{2028}\x{2029}]*){0,5}\z

Regex-Optionen: Keine Regex-Varianten: PCRE, Perl \A(?>(?>\r\n?|[\n-\f\x85\u2028\u2029])?[^\n-\r\x85\u2028\u2029]*){0,5}\z

Regex-Optionen: Keine Regex-Varianten: .NET, Java, Ruby \A(?:(?:\r\n?|[\n-\f\x85\u2028\u2029])?[^\n-\r\x85\u2028\u2029]*){0,5}\Z

Regex-Optionen: Keine Regex-Variante: Python

268 | Kapitel 4: Validierung und Formatierung

^(?:(?:\r\n?|[\n-\f\x85\u2028\u2029])?[^\n-\r\x85\u2028\u2029]*){0,5}$

Regex-Optionen: Keine Regex-Variante: JavaScript Alle diese Regexes kümmern sich um die Zeilentrenner aus Tabelle 4-1, die zusammen mit ihren Unicode-Positionen und Namen aufgeführt sind. Tabelle 4-1: Zeilentrenner Unicode-Folge

Regex-Äquivalent

Name

Verwendung

U+000D U+000A

‹\r\n›

Carriage Return und Line Feed (CRLF)

Textdateien unter Windows und MS-DOS

U+000A

‹\n›

Line Feed (LF)

Textdateien unter Unix, Linux und OS X

U+000B

‹\v›

Line Tabulation (auch vertikaler Tab oder VT)

(selten)

U+000C

‹\f›

Form Feed (FF)

(selten)

U+000D

‹\r›

Carriage Return (CR)

Textdateien unter Mac OS

U+0085

‹\x85›

Next Line (NEL)

Textdateien auf IBM Mainframes (selten)

U+2028

‹\u2028› oder ‹\x{2028}›

Zeilentrenner (Line Separator)

(selten)

U+2029

‹\u2029› oder ‹\x{2029}›

Absatztrenner (Paragraph Separator)

(selten)

Siehe auch Rezept 4.9.

4.11 Antworten auswerten Problem Sie müssen eine Konfigurationsoption oder eine Eingabe an der Befehlszeile auf einen positiven Wert überprüfen. Sie wollen bei den möglichen Antworten flexibel sein, sodass true, t, yes, y, ja, j, okay, ok und 1 in beliebiger Groß- und Kleinschreibung akzeptiert werden.

Lösung Mit einer Regex, die alle akzeptablen Antworten kombiniert, können Sie die Überprüfung mit einem einfachen Test durchführen.

4.11 Antworten auswerten | 269

Regulärer Ausdruck ^(?:1|t(?:rue)?|y(?:es)?|ja?|ok(?:ay)?)$

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

JavaScript var yes = /^(?:1|t(?:rue)?|y(?:es)?|ja?|ok(?:ay)?)$/i; if (yes.test(subject)) { alert("Ja"); } else { alert("Nein"); }

Andere Programmiersprachen In den Rezepten 3.4 und 3.5 erhalten Sie Hinweise dazu, wie dieser reguläre Ausdruck in anderen Programmiersprachen implementiert werden kann.

Diskussion Die folgende Regex zeigt die einzelnen Elemente im Detail. Kombinationen von Tokens, die leicht zusammen lesbar sind, werden in einer Zeile aufgeführt: ^ (?: 1 | t(?:rue)? | y(?:es)? | ja? | ok(?:ay)? ) $

# # # # # # # # # # # # #

Position am Anfang des Strings sicherstellen. Gruppieren, aber nicht einfangen ... Eine literale "1" finden. oder ... Finde "t", optional gefolgt von "rue". oder ... Finde "y", optional gefolgt von "es". oder ... Finde "j", optional gefolgt von "a". oder ... Finde "ok", optional gefolgt von "ay". Ende der nicht-einfangenden Gruppe. Position am Ende des Strings sicherstellen.

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Freiform Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Diese Regex ist im Prinzip nur ein einfacher Test auf einen von neun literalen Werten, wobei die Groß-/Kleinschreibung ignoriert wird. Sie könnte auch anders geschrieben werden. So ist zum Beispiel ‹^(?:[1tyj]|true|yes|ja|ok(?:ay)?)$› ein ebenso guter Ansatz. Man könnte auch einfach eine Alternation mit allen neun Werten nutzen, wie zum Beispiel ‹^(?:1|t|true|y|yes|j|ja|ok|okay)$›, aber aus Performancegründen ist es im Allgemeinen besser, die Anzahl der Alternativen mit dem Pipe-Operator ‹|› zu reduzieren und Zeichenklassen und optionale Endungen (mit dem Quantor ‹?›) vorzuziehen. In diesem Fall

270 | Kapitel 4: Validierung und Formatierung

ist der Performanceunterschied vermutlich nur minimal, aber es ist nicht verkehrt, die Performance von Regexes immer im Hinterkopf zu behalten. Manchmal kann der Unterschied zwischen den verschiedenen Vorgehensweisen erstaunlich groß sein. Alle diese Beispiele umgeben die möglichen Werte mit einer nicht-einfangenden Gruppe, um die Reichweite des Alternationsoperators zu begrenzen. Würden wir die Gruppe weglassen und stattdessen so etwas wie ‹^true|yes$› verwenden, würde die RegexEngine nach „dem Anfang des Strings, gefolgt von true’, oder yes’, gefolgt vom Ende des Strings“ suchen. ‹^(?:true|yes)$› weist die Regex-Engine an, den Anfang des Strings zu finden, dann entweder „true“ oder „yes“ und dann das Ende des Strings.

Siehe auch Rezepte 5.2 und 5.3.

4.12 US-Sozialversicherungsnummern validieren Problem Sie müssen prüfen, ob jemand eine gültige US-Sozialversicherungsnummer eingegeben hat.

Lösung Wenn Sie nur sicherstellen wollen, dass sich ein String an das grundlegende Sozialversicherungsnummerformat hält und keine offensichtlich ungültigen Zahlen enthalten sind, stellt die folgende Regex eine einfache Lösung bereit. Brauchen Sie eine strengere Prüfung, die auch bei der Social Security Administration prüft, ob die Nummer zu einer lebenden Person gehört, werfen Sie einen Blick auf die Links im Abschnitt „Siehe auch“ dieses Rezepts.

Regulärer Ausdruck ^(?!000|666)(?:[0-6][0-9]{2}|7(?:[0-6][0-9]|7[0-2]))(?!00)[0-9]{2}-(?!0000)[0-9]{4}$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Python if re.match(r"^(?!000|666)(?:[0-6][0-9]{2}|7(?:[0-6][0-9]|7[0-2]))(?!00)[0-9]{2}-(?!0000)[0-9]{4}$", sys.argv[1]): print "SSN ist gültig" else: print "SSN ist ungültig"

4.12 US-Sozialversicherungsnummern validieren | 271

Andere Programmiersprachen In Rezept 3.5 finden Sie Informationen über das Implementieren dieses regulären Ausdrucks in anderen Programmiersprachen.

Diskussion Sozialversicherungsnummern in den USA sind neunstellige Nummern im Format AAA-GGSSSS: Die ersten drei Ziffern werden anhand der geografischen Region zugewiesen. Dies ist die Area Number. Die Area Number kann nicht den Wert 000 oder 666 haben, zudem gibt es aktuell keine Sozialversicherungsnummer mit einer Area Number größer als 772. Die Ziffern vier und fünf bilden die Group Number und liegen im Bereich 01 bis 99. Die letzten vier Ziffern sind Serial Numbers von 0001 bis 9999. Dieses Rezept nutzt alle diese Regeln. Hier noch einmal der reguläre Ausdruck, dieses Mal Stück für Stück erläutert: ^ (?!000|666) (?: [0-6] [0-9]{2} | 7 (?: [0-6] [0-9] | 7 [0-2] ) ) (?!00) [0-9]{2} (?!0000) [0-9]{4} $

# # # # # # # # # # # # # # # # # # # # # #

Position am Anfang des Strings sicherstellen. Weder "000" noch "666" dürfen hier vorkommen. Gruppieren, aber nicht einfangen ... Ein Zeichen im Bereich von "0" bis "6" finden. Zwei Ziffern finden. oder ... Eine literale "7" finden. Gruppieren, aber nicht einfangen ... Eine Ziffer im Bereich von "0" bis "6" finden. Eine Ziffer finden. oder ... Eine literale "7" finden. Eine Ziffer im Bereich von "0" bis "2" finden. Ende der nicht-einfangenden Gruppe. Ende der nicht-einfangenden Gruppe. Einen literalen "-" finden. Sicherstellen, dass "00" hier nicht vorkommt. Zwei Ziffern finden. Einen literalen "-" finden. Sicherstellen, dass "0000" hier nicht vorkommt. Vier Ziffern finden. Position am Ende des Strings sicherstellen.

Regex-Optionen: Freiform Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Abgesehen von den Tokens ‹^› und ‹$›, die sicherstellen, dass die Position am Anfang und Ende des Texts gefunden wird, kann diese Regex in drei Zifferngruppen aufgeteilt werden, die durch Bindestriche getrennt sind. Die erste Gruppe ist die komplexeste. Die zweite und die dritte Gruppe passen einfach auf zwei beziehungsweise vier Ziffern. Dabei

272 | Kapitel 4: Validierung und Formatierung

wird aber vorher ein negatives Lookahead genutzt, um zu verhindern, dass alle Ziffern solch einer Gruppe 0 sind. Die erste Zifferngruppe ist viel komplexer und auch schlechter lesbar, denn sie passt auf einen ganzen Bereich. Zunächst wird das negative Lookahead ‹(?!000|666)› verwendet, um die Werte „000“ und „666“ auszuschließen. Als Nächstes geht es darum, alle Zahlen größer als 772 auszuschließen. Da reguläre Ausdrücke mit Text arbeiten und nicht mit Zahlen, müssen wir den Bereich Zeichen für Zeichen unterteilen. Zunächst einmal wissen wir, dass jede dreistellige Zahl gültig ist, deren erste Ziffern im Bereich von 0 bis 6 liegt. Durch das vorherige negative Lookahead sind die ungültigen Zahlen 000 und 666 schon ausgeschlossen. Dieser erste Teil wird recht einfach durch ein paar Zeichenklassen und einen Quantor umgesetzt: ‹[0-6][0-9]{2}›. Da wir eine Alternative für Zahlen benötigen, die mit 7 beginnen, nutzen wir eine Gruppe wie in ‹(?:[0-6][0-9]{2}|7)›, um die Reichweite des Alternationsoperators einzuschränken. Nummern, die mit 7 beginnen, sind nur zulässig, wenn sie im Bereich von 700 bis 772 liegen, daher müssen wir nun die Nummern in Abhängigkeit von der zweiten Ziffern unterteilen. Liegt sie zwischen 0 und 6, ist eine beliebige dritte Ziffer erlaubt. Ist die zweite Ziffer 7, muss die dritte Ziffer zwischen 0 und 2 liegen. Bringen wir alle diese Regeln zusammen, erhalten wir ‹7(?:[0-6][0-9]|7[0-2])›. Fügen Sie das schließlich in die äußere Gruppe für die restlichen gültigen Nummern ein, erhalten Sie ‹(?:[0-6][0-9]{2}|7(?:[0-6][0-9]|7[0-2]))›. Das ist alles. Sie haben erfolgreich eine Regex erstellt, die eine dreistellige Nummer zwischen 000 und 772 findet.

Variationen Sozialversicherungsnummern in Dokumenten finden Wenn Sie in einem größeren Dokument nach Sozialversicherungsnummern suchen, ersetzen Sie die Anker ‹^› und ‹$› durch Wortgrenzen. Regex-Engines betrachten alle alphanumerischen Zeichen und den Unterstrich als Wortzeichen. \b(?!000|666)(?:[0-6][0-9]{2}|7(?:[0-6][0-9]|7[0-2]))(?!00)[0-9]{2}-(?!0000)[0-9]{4}\b

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Siehe auch Die Website der Social Security Administration (http://www.socialsecurity.gov) beantwortet die am häufigsten gestellten Fragen und führt auch aktuelle Listen mit bisher zugewiesenen Area und Group Numbers.

4.12 US-Sozialversicherungsnummern validieren | 273

Der Social Security Number Verification Service (SSNVS) unter http://www.socialsecurity.gov/employer/ssnv.htm stellt zwei Wege bereit, um zu überprüfen, ob Namen und Sozialversicherungsnummern den Daten der Social Security Administration entsprechen. Der Umgang mit Zahlenbereichen, einschließlich Beispielen für das Finden solcher Bereiche, wird in Rezept 6.5 detaillierter erläutert.

4.13 ISBN validieren Problem Sie müssen die Gültigkeit einer International Standard Book Number (ISBN) prüfen. Diese kann entweder im älteren ISBN-10- oder im aktuellen ISBN-13-Format vorliegen. Am Anfang soll optional eine ISBN-Kennung stehen, zudem können die Teile der ISBN optional durch Bindestriche oder Leerzeichen getrennt sein. ISBN 978-0-596-52068-7, ISBN-13: 978-0-596-52068-7, 978 0 596 52068 7, 9780596520687, ISBN-10 0-596-52068-9 und 0-596-52068-9 sind allesamt Beispiele für gültige Eingaben.

Lösung Sie können eine ISBN nicht allein mit einer Regex validieren, da die letzte Ziffer mit einem Prüfsummenalgorithmus berechnet wird. Die regulären Ausdrücke in diesem Abschnitt überprüfen das Format einer ISBN, während die folgenden Codebeispiele auch prüfen, ob die letzte Ziffer korrekt ist.

Reguläre Ausdrücke ISBN-10: ^(?:ISBN(?:-10)?:?z)?(?=[-0-9Xz]{13}$|[0-9X]{10}$)[0-9]{1,5}[-z]? (?:[0-9]+[-z]?){2}[0-9X]$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ISBN-13: ^(?:ISBN(?:-13)?:?z)?(?=[-0-9z]{17}$|[0-9]{13}$)97[89][-z]?[0-9]{1,5} [-z]?(?:[0-9]+[-z]?){2}[0-9]$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ISBN-10 oder ISBN-13: ^(?:ISBN(?:-1[03])?:?z)?(?=[-0-9z]{17}$|[-0-9Xz]{13}$|[0-9X]{10}$) (?:97[89][-z]?)?[0-9]{1,5}[-z]?(?:[0-9]+[-z]?){2}[0-9X]$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

274 | Kapitel 4: Validierung und Formatierung

JavaScript // `regex` prüft auf die Formate ISBN-10 oder ISBN-13 var regex = /^(?:ISBN(?:-1[03])?:? )?(?=[-0-9 ]{17}$|[-0-9X ]{13}$| [0-9X]{10}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?(?:[0-9]+[- ]?){2}[0-9X]$/; if (regex.test(subject)) { // Nicht zugehörige ISBN-Elemente entfernen, dann in ein Array aufteilen var chars = subject.replace(/[^0-9X]/g, "").split(""); // Letzte ISBN-Ziffer aus `chars` entfernen und `last` zuweisen var last = chars.pop(); var sum = 0; var digit = 10; var check; if (chars.length == 9) { // ISBN-10-Prüfziffer berechnen for (var i = 0; i < chars.length; i++) { sum += digit * parseInt(chars[i], 10); digit -= 1; } check = 11 - (sum % 11); if (check == 10) { check = "X"; } else if (check == 11) { check = "0"; } } else { // ISBN-13-Prüfziffer berechnen for (var i = 0; i < chars.length; i++) { sum += (i % 2 * 2 + 1) * parseInt(chars[i], 10); } check = 10 - (sum % 10); if (check == 10) { check = "0"; } } if (check == last) { alert("Gültige ISBN"); } else { alert("Ungültige ISBN-Prüfziffer"); } } else { alert("Ungültige ISBN"); }

Python import re import sys # `regex` prüft auf die Formate ISBN-10 oder ISBN-13 regex = re.compile("^(?:ISBN(?:-1[03])?:? )?(?=[-0-9 ]{17}$| [-0-9X ]{13}$|[0-9X]{10}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?

4.13 ISBN validieren | 275

(?:[0-9]+[- ]?){2}[0-9X]$") subject = sys.argv[1] if regex.search(subject): # Nicht zugehörige ISBN-Elemente entfernen, dann in ein Array aufteilen chars = re.sub("[^0-9X]", "", subject).split("") # Letzte ISBN-Ziffer aus `chars` entfernen und `last` zuweisen last = chars.pop() if len(chars) == 9: # ISBN-10-Prüfziffer berechnen val = sum((x + 2) * int(y) for x,y in enumerate(reversed(chars))) check = 11 - (val % 11) if check == 10: check = "X" elif check == 11: check = "0" else: # ISBN-13-Prüfziffer berechnen val = sum((x % 2 * 2 + 1) * int(y) for x,y in enumerate(chars)) check = 10 - (val % 10) if check == 10: check = "0" if (str(check) == last): print "Gültige ISBN" else: print "Ungültige ISBN-Prüfziffer" else: print "Ungültige ISBN"

Andere Programmiersprachen In Rezept 3.5 wird beschrieben, wie Sie diesen regulären Ausdruck in anderen Programmiersprachen implementieren können.

Diskussion Eine ISBN ist eine eindeutige Kennung für Bücher und bücherähnliche Produkte. Das zehnstellige ISBN-Format wurde als internationaler Standard ISO 2108 im Jahr 1970 veröffentlicht. Alle ISBN, die seit dem 1. Januar 2007 zugewiesen werden, sind 13-stellig. ISBN-10- und ISBN-13-Nummern werden in vier beziehungsweise fünf Elemente aufgeteilt. Drei der Elemente haben eine variable, die verbleibenden ein oder zwei Elemente haben eine feste Länge. Alle fünf Teile werden normalerweise durch Bindestriche oder Leerzeichen getrennt. Dabei haben die Elemente folgende Bedeutung: • 13-stellige ISBN beginnen mit dem Präfix 978 oder 979. • Die Gruppennummer steht für einen geografisch oder sprachlich zusammenhängenden Raum. Sie kann eine bis fünf Ziffern lang sein.

276 | Kapitel 4: Validierung und Formatierung

• Die Verlagsnummer kann unterschiedlich lang sein und wird von der nationalen ISBN-Agentur vergeben. • Die Titelnummer kann auch unterschiedlich lang sein und wird vom Verlag festgelegt. • Das letzte Zeichen ist die Prüfziffer. Sie wird mit einem Prüfsummenalgorithmus ermittelt. Eine ISBN-10-Prüfziffer kann entweder die Werte 0 bis 9 oder den Buchstaben X (für die römische 10) enthalten. Eine ISBN-13-Prüfziffer liegt im Bereich von 0 bis 9. Die hier verwendeten Zeichen sind unterschiedlich, weil auch unterschiedliche Prüfsummenalgorithmen genutzt werden. Die Regex für ISBN-10- und ISBN-13-Nummern wird im folgenden Beispiel in ihre Bestandteile zerlegt. Da sie hier im Freiform-Modus genutzt wird, wurden die literalen Leerzeichen in der Regex durch Backslashs maskiert. Bei Java müssen im FreiformModus Leerzeichen selbst in Zeichenklassen maskiert werden: ^ (?: ISBN (?:-1[03])? :? \ )? (?= [-0-9\ ]{17}$ | [-0-9X\ ]{13}$ | [0-9X]{10}$ ) (?: 97[89] [-\ ]? )? [0-9]{1,5} [-\ ]? (?: [0-9]+ [-\ ]? ){2} [0-9X] $

# # # # # # # # # # # # # # # # # # # # # # # # # #

Position am Anfang des Strings sicherstellen. Gruppieren, aber nicht einfangen ... Den Text "ISBN" finden. Optional den Text "-10" oder "-13" finden. Optional ein literales ":" finden. Ein (maskiertes) Leerzeichen finden. Die Gruppe null oder ein Mal finden. Sicherstellen, dass das Folgende passt ... 17 Bindestriche, Ziffern und Leerzeichen bis zum Ende des Strings finden. Oder ... 13 Bindestriche, Ziffern, X und Leerzeichen bis zum Ende des Strings finden. Oder ... 10 Ziffern und X bis zum Ende finden. Ende des positiven Lookahead. Gruppieren, aber nicht einfangen ... Den Text "978" oder "979" finden. Optional einen Bindestrich oder ein Leerzeichen finden. Die Gruppe null oder ein Mal finden. Eine Ziffer ein bis vier Mal finden. Optional einen Bindestrich oder ein Leerzeichen finden. Gruppieren, aber nicht einfangen ... Eine Ziffer ein Mal oder häufiger finden. Optional einen Bindestrich oder ein Leerzeichen finden. Die Gruppe genau zwei Mal finden. Eine Ziffer oder ein "X" finden. Position am Ende des Strings sicherstellen.

Regex-Optionen: Freiform Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Der erste Teil ‹(?:ISBN(?:-1[03])?:?z)?› hat drei optionale Elemente, durch die einer der folgenden sieben Strings passt (mit Ausnahme des leeren Strings enthalten alle ein Leerzeichen am Ende): • ISBNz • ISBN-10z • ISBN-13z

4.13 ISBN validieren | 277

• ISBN:z • ISBN-10:z • ISBN-13:z • Ein leerer String (kein Präfix) Als Nächstes sorgt das positive Lookahead ‹(?=[-0-9z]{17}$|[-0-9Xz]{13}$|[09X]{10}$)› dafür, dass eine der drei Optionen (getrennt durch den Alternationsoperator ‹|›) bezüglich der Länge und erlaubten Zeichen für den Rest der Übereinstimmung gültig ist. Alle drei Optionen enden mit dem Anker ‹$›. Dadurch ist sichergestellt, dass es keinen nachfolgenden Text gibt, der nicht zu einem der Muster passt: ‹[-0-9z]{17}$›

Erlaubt eine ISBN-13 mit vier Trennzeichen (insgesamt 17 Zeichen). ‹[-0-9Xz]{13}$›

Erlaubt eine ISBN-13 ohne Trennzeichen oder eine ISBN-10 mit drei Trennzeichen (insgesamt 13 Zeichen). ‹[0-9X]{10}$›

Erlaubt eine ISBN-10 ohne Trennzeichen (insgesamt 10 Zeichen). Nachdem das positive Lookahead die Länge und die Zeichen geprüft hat, können wir die einzelnen Elemente der ISBN auswerten, ohne uns über ihre Gesamtlänge Gedanken machen zu müssen. ‹(?:97[89][-z]?)?› passt zum Präfix „978“ oder „979“, das von einer ISBN-13 gefordert wird. Die nicht-einfangende Gruppe ist optional, weil sie bei einer ISBN-10 nicht vorkommt. ‹[0-9]{1,5}[-z]?› passt zu einer Zifferngruppe mit ein bis fünf Ziffern, denen optional ein Trennzeichen folgt. ‹(?:[0-9]+[-z]?){2}› passt zur Verlags- und Titelnummer und deren optionalen Separatoren. Schließlich passt ‹[09X]$› auf die Prüfziffer am Ende des Strings. Ein regulärer Ausdruck kann zwar prüfen, ob die letzte Ziffer ein gültiges Zeichen nutzt (eine Ziffer oder ein X), aber nicht, ob es sich dabei um die korrekte Prüfziffer handelt. Einer der beiden Prüfsummenalgorithmen (abhängig davon, ob Sie mit einer ISBN-10oder einer ISBN-13-Nummer arbeiten) wird verwendet, um wenigstens halbwegs sicher zu sein, dass die ISBN-Ziffern nicht unabsichtlich vertauscht oder anders falsch eingegeben wurden. Der weiter oben gezeigte Beispielcode für JavaScript und Python implementiert beide Algorithmen. Der folgende Abschnitt beschreibt die Prüfsummenregeln, damit Sie diese Algorithmen auch in anderen Programmiersprachen implementieren können.

ISBN-10-Prüfsumme Die Prüfziffer einer ISBN-10-Nummer kann den Wert 0 bis 10 haben (wobei die römische Zahl X statt der 10 verwendet wird). Sie wird wie folgt ermittelt: 1. Multipliziere jede der ersten 9 Ziffern mit einer Zahl in der absteigenden Folge von

10 bis 2 und addiere die Ergebnisse. 2. Teile die Summe durch 11.

278 | Kapitel 4: Validierung und Formatierung

3. Ziehe den Rest (nicht den Quotienten) von 11 ab. 4. Wenn das Ergebnis 11 ist, verwende die Ziffer 0; ist es 10, verwende den Buchsta-

ben X. Hier ein Beispiel, wie man die ISBN-10-Prüfziffer für 3-89721-957-? ermittelt: Schritt 1: sum = 10×3 + = 30 + = 305 Schritt 2: 305 ÷ 11 Schritt 3: 11 - 8 = Schritt 4: 3 [keine

9×8 + 8×9 + 7×7 + 6×2 + 5×1 + 4×9 + 3×5 + 2×7 72 + 72 + 49 + 18 + 5 + 36 + 15 + 14

= 27, Rest 8 3 Ersetzung notwendig]

Die Prüfziffer ist 3, daher ist die komplette ISBN ISBN 3-89721-957-3.

ISBN-13-Prüfsumme Eine ISBN-13-Prüfziffer liegt im Bereich von 0 bis 9 und wird in ähnlichen Schritten ermittelt. Multipliziere jede der ersten 12 Ziffern mit 1 oder 3 – immer abwechselnd von links nach rechts – und addiere die Ergebnisse. Teile die Summe durch 10. Ziehe den Rest (nicht den Quotienten) von 10 ab. Ist das Ergebnis 10, verwende die Ziffer 0. So wird zum Beispiel die ISBN-13-Prüfziffer für 978-3-89721-957-? wie folgt berechnet: Schritt 1: sum = 1×9 + 3×7 + 1×8 + 3×3 + 1×8 + 3×9 + 1×7 + 3×2 + 1×1 + 3×9 + 1×5 + 3×7 = 9 + 21 + 8 + 9 + 8 + 27 + 7 + 6 + 1 + 27 + 5 + 21 = 149 Schritt 2: 149 ÷ 10 = 14, remainder 9 Schritt 3: 10 - 9 = 1 Schritt 4: 1 [keine Ersetzung notwendig]

Die Prüfziffer ist 1, und die komplette ISBN hat den Wert ISBN 978-3-89721-957-1.

Variationen ISBNs in Dokumenten finden Diese Version der Regex für ISBN-10 und ISBN-13 nutzt statt der Anker Wortgrenzen, um ISBN in längeren Texten zu finden, dabei aber sicherzustellen, dass sie für sich ste-

4.13 ISBN validieren | 279

hen. Der Text „ISBN“ ist in dieser Regex immer erforderlich. Das hat zwei Gründe. Zum einen verhindert man so fälschlicherweise als ISBN erkannte Zahlenfolgen (denn die Regex könnte potenziell beliebige 10- oder 13-stellige Zahlen finden), und zum anderen sollen ISBN diesen Text offiziell enthalten, wenn sie ausgegeben werden: \bISBN(?:-1[03])?:?z(?=[-0-9z]{17}$|[-0-9Xz]{13}$|[0-9X]{10}$) (?:97[89][-z]?)?[0-9]{1,5}[-z]?(?:[0-9]+[-z]?){2}[0-9X]\b

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Falsche ISBN-Kennungen finden Die vorigen Regexes haben ein Problem: Man kann den Text „ISBN-13“ haben, auf den dann aber eine ISBN-10-Nummer folgt und umgekehrt. Die folgende Regex nutzt RegexBedingungen (siehe Rezept 2.17), um sicherzustellen, dass eine Kennung „ISBN-10“ oder „ISBN-13“ immer vom passenden ISBN-Typ begleitet wird. Wenn der Typ nicht explizit angegeben ist, sind beide Nummernarten möglich. Diese Regex ist in den meisten Fällen doch etwas übertrieben, da man das gleiche Ergebnis auch einfacher erreichen kann, wenn man die getrennten ISBN-10- und ISBN-13-Regexes nutzt. Sie soll hier eher gezeigt werden, um eine interessante Anwendung von regulären Ausdrücken zu demonstrieren: ^ (?:ISBN(-1(?:(0)|3))?:?\ )? (?(1) (?(2) (?=[-0-9X ]{13}$|[0-9X]{10}$) [0-9]{1,5}[- ]?(?:[0-9]+[- ]?){2}[0-9X]$ | (?=[-0-9 ]{17}$|[0-9]{13}$) 97[89][- ]?[0-9]{1,5}[- ]?(?:[0-9]+[- ]?){2}[0-9]$ ) | (?=[-0-9 ]{17}$|[-0-9X ]{13}$|[0-9X]{10}$) (?:97[89][- ]?)?[0-9]{1,5}[- ]?(?:[0-9]+[- ]?){2}[0-9X]$ ) $

Regex-Optionen: Freiform Regex-Varianten: .NET, PCRE, Perl, Python

Siehe auch Die aktuellste Version der ISBN-Dokumente lassen sich auf der Website der International ISBN Agency unter http://www.isbn-international.org finden. Die offizielle Liste mit Gruppennummern findet sich ebenfalls auf der Website der International ISBN Agency. Anhand dieser Liste können Sie das Ursprungsland eines Buchs mithilfe der ersten 1 bis 5 Ziffern der ISBN ermitteln.

280 | Kapitel 4: Validierung und Formatierung

4.14 ZIP-Codes validieren Problem Sie müssen einen ZIP-Code (eine US-Postleitzahl) validieren, wobei sowohl das fünfstellige als auch das neunstellige Format (ZIP + 4) zu erkennen ist. Die Regex sollte auf 12345 und 12345-6789 passen, aber nicht auf 1234, 123456, 123456789 oder 1234-56789.

Lösung Regulärer Ausdruck ^[0-9]{5}(?:-[0-9]{4})?$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

VB.NET If Regex.IsMatch(subjectString, "^[0-9]{5}(?:-[0-9]{4})?$") Then Console.WriteLine("Gültiger ZIP-Code") Else Console.WriteLine("Ungültiger ZIP-Code") End If

Andere Programmiersprachen In Rezept 3.5 finden Sie Informationen über das Implementieren dieses regulären Ausdrucks in anderen Programmiersprachen.

Diskussion Der reguläre Ausdruck für den ZIP-Code sieht im Freiform-Modus so aus: ^ [0-9]{5} (?: [0-9]{4} ) ? $

# # # # # # # #

Position am Anfang des Strings sicherstellen. Fünf Ziffern finden. Gruppieren, aber nicht einfangen ... Einen literalen "-" finden. Vier Ziffern finden. Ende der nicht-einfangenden Gruppe. Die vorige Gruppe null oder ein Mal finden. Position am Ende des Strings sicherstellen.

Regex-Optionen: Freiform Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Diese Regex ist ziemlich einfach, daher ist nicht sehr viel dazu zu sagen. Eine simple Änderung ermöglicht es Ihnen, ZIP-Codes in einem längeren String zu finden: Ersetzen Sie die Anker ‹^› und ‹$› durch Wortgrenzen: ‹\b[0-9]{5}(?:-[0-9]{4})?\b›.

4.14 ZIP-Codes validieren | 281

Siehe auch Rezepte 4.15, 4.16 und 4.17.

4.15 Kanadische Postleitzahlen validieren Problem Sie wollen prüfen, ob ein String eine kanadische Postleitzahl enthält.

Lösung ^(?!.*[DFIOQU])[A-VXY][0-9][A-Z]z[0-9][A-Z][0-9]$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Das negative Lookahead am Anfang dieses regulären Ausdrucks verhindert, dass sich irgendwo im Ausgangstext die Buchstaben D, F, I, O, Q oder U befinden. Die Zeichenklasse ‹[A-VXY]› verhindert darüber hinaus, dass W oder Z das erste Zeichen ist. Neben diesen beiden Ausnahmen werden für kanadische Postleitzahlen einfach abwechselnde Folgen von sechs alphanumerischen Zeichen genutzt, wobei in der Mitte ein Leerzeichen steht. So passt diese Regex zum Beispiel auf K1A 0B1. Dabei handelt es sich um die Postleitzahl für die Zentrale der kanadischen Post in Ottawa.

Siehe auch Rezepte 4.14, 4.16 und 4.17.

4.16 Britische Postleitzahlen validieren Problem Sie benötigen einen regulären Ausdruck, der britische Postleitzahlen erkennt.

Lösung ^[A-Z]{1,2}[0-9R][0-9A-Z]?z[0-9][ABD-HJLNP-UW-Z]{2}$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

282 | Kapitel 4: Validierung und Formatierung

Diskussion Postleitzahlen in Großbritannien (oder auch Postcodes, wie sie dort genannt werden) bestehen aus fünf bis sieben alphanumerischen Zeichen, die durch ein Leerzeichen unterteilt sind. Die Regeln legen fest, welche Zeichen an welcher Position stehen dürfen. Leider sind sie ziemlich kompliziert und voller Ausnahmen. Daher kümmert sich dieser reguläre Ausdruck nur um die grundlegenden Regeln.

Siehe auch British Standard BS7666, verfügbar unter http://www.govtalk.gov.uk/gdsc/html/frames/ PostCode.htm. Hier werden die Regeln für britische Postleitzahlen beschrieben. Rezepte 4.14, 4.15 und 4.17.

4.17 Deutsche Postleitzahlen validieren Problem Sie benötigen einen regulären Ausdruck, der deutsche Postleitzahlen erkennt.

Lösung ^[0-9]{5}$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Deutsche Postleitzahlen bestehen einfach aus fünf Ziffern ohne weitere Unterteilung. Beachten Sie, dass Postleitzahlen bei einer weiteren Verarbeitung nicht als Zahlen angesehen werden sollten, sondern eher als Zeichenkette. In Deutschland gibt es eine Reihe von Orten, deren Postleitzahl mit einer 0 beginnt. Speichert man Postleitzahlen als Zahl, verschwindet diese 0, was verwirrt und eventuell dafür sorgt, dass die Post nicht (direkt) ankommt.

Variationen Postleitzahlen in anderen europäischen Ländern Belgien ‹^[1-9][0-9]{3}$›

Bulgarien ‹^[1-9][0-9]{3}$›

Dänemark ‹^[1-9][0-9]{3}$›

4.17 Deutsche Postleitzahlen validieren | 283

Finnland ‹^[0-9]{5}$›

Frankreich und Monaco ‹^[0-9]{5}$›

Griechenland ‹^[1-8][0-9]{4}$›

Italien, San Marino und Vatikanstadt ‹^[0-9]{5}$›

Kroatien ‹^(?:[1-4][0-9]|5[1-3])[0-9]{3}$›

Montenegro ‹^8[145][0-9]{3}$›

Niederlande ‹^[1-9][0-9]{3}z?[A-Z]{2}$›

Norwegen ‹^[0-9]{4}$›

Österreich ‹^[1-9][0-9]{3}$›

Polen ‹^[0-9]{2}-[0-9]{3}$›

Portugal ‹^[1-9][0-9]{3}-?[0-9]{2}$›

Rumänien ‹^[0-9]{6}$›

Schweden ‹^[1-9][0-9]{2}z?[0-9]{2}$›

Schweiz und Liechtenstein ‹^[1-9][0-9]{3}$›

Slowakei ‹^[890][0-9]{2}z?[0-9]{2}$›

Spanien ‹^(?:0[1-9]|[1-4][0-9]|5[12])[0-9]{3}$›

Tschechien ‹^[1-7][0-9]{2}z?[0-9]{2}$›

Ungarn ‹^[1-9][0-9]{3}$›

Zypern ‹^[1-9][0-9]{3}$›

Siehe auch Rezepte 4.14, 4.15 und 4.16.

284 | Kapitel 4: Validierung und Formatierung

4.18 Namen von „Vorname Nachname“ nach „Nachname, Vorname“ umwandeln Problem Sie wollen Personennamen von „Vorname Nachname“ umwandeln in „Nachname, Vorname“, um so alphabetisch sortieren zu können. Zudem wollen Sie zusätzlich auf andere Namensbestandteile Rücksicht nehmen, zum Beispiel auf einen zweiten Vornamen.

Lösung Leider ist es nicht möglich, Namen mit einem regulären Ausdruck zuverlässig zu parsen. Reguläre Ausdrücke sind strikt, während Namen so flexibel gehandhabt werden, dass selbst Menschen durcheinanderkommen. Das Bestimmen der Struktur eines Namens und die richtige Einordnung in eine alphabetisch sortierte Liste erfordern häufig das Einbeziehen traditioneller und landesspezifischer Konventionen, und selbst persönliche Vorlieben können eine Rolle spielen. Wenn Sie aber dazu bereit sind, gewissen Annahmen über Ihre Daten zu treffen und auch dann und wann Fehler akzeptieren können, kann ein regulärer Ausdruck eine schnelle Lösung bieten. Der folgende reguläre Ausdruck wird eher einfach gehalten und soll nicht unbedingt alle möglichen Grenzfälle abdecken.

Regulärer Ausdruck ^(.+?)z([^\s]+)$

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Ersetzung $2,z$1

Ersetzungstextvarianten: .NET, Java, JavaScript, Perl, PHP \2,z\1

Ersetzungstextvarianten: Python, Ruby

JavaScript function formatName (name) { return name.replace(/^(.+?)z([^\s,]+)$/i, "$2, $1"); }

4.18 Namen von „Vorname Nachname“ nach „Nachname, Vorname“ umwandeln | 285

Andere Programmiersprachen In Rezept 3.15 finden Sie Informationen über das Implementieren dieses regulären Ausdrucks in anderen Programmiersprachen.

Diskussion Lassen Sie uns diesen regulären Ausdruck erst mal Stück für Stück betrachten. Danach erklären wir Ihnen, welche Teile eines Namens von welchen Regex-Elementen gefunden werden. Da die Regex hier im Freiform-Modus geschrieben ist, wurden die literalen Leerzeichen durch Backslashs maskiert: ^ ( .+? ) \ ( [^\s]+ ) $

# # # # # # # # #

Position am Anfang des Strings sicherstellen. Gruppe für Rückwärtsreferenz 1 ... Eines oder mehrere Zeichen finden, aber so wenig wie möglich. Ende der einfangenden Gruppe. Ein Leerzeichen finden. Gruppe für Rückwärtsreferenz 2 ... Eines oder mehrere Zeichen finden, die keine Leerzeichen sind. Ende der einfangenden Gruppe. Position am Ende des Strings sicherstellen.

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Freiform Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Dieser reguläre Ausdruck geht von folgenden Annahmen aus: • Der Ausgangstext enthält mindestens einen Vornamen und einen Nachnamen (weitere Bestandteile sind optional). • Der Vorname steht vor dem Nachnamen. Ein paar Probleme gibt es aber: • Der reguläre Ausdruck kann keine mehrteiligen Nachnamen erkennen, die nicht per Bindestrich verbunden sind. So würde Sacha Baron Cohen zum Beispiel durch Cohen, Sacha Baron ersetzt werden und nicht durch die korrekte Version Baron Cohen, Sacha. • Namensbestandteile vor dem Familiennamen werden auch nicht dem Nachnamen zugeordnet, obwohl dies aufgrund von Konventionen oder persönlichen Vorlieben teilweise gewünscht wird (so kann „Charles de Gaulle“ entweder als „de Gaulle, Charles“ oder als „Gaulle, Charles de“ aufgeführt sein). • Aufgrund der Anker ‹^› und ‹$›, die die Übereinstimmung mit dem Anfang und Ende des Strings verbinden, kann keine Ersetzung vorgenommen werden, wenn nicht der gesamte Ausgangstext zum Muster passt. Wird keine passende Übereinstimmung gefunden (weil zum Beispiel der Ausgangstext nur einen Namen enthält), bleibt der Name so bestehen. Der reguläre Ausdruck nutzt zwei einfangende Gruppen, um den Namen aufzuteilen. Diese Elemente werden dann über Rückwärtsreferenzen in der gewünschten Reihenfolge

286 | Kapitel 4: Validierung und Formatierung

wieder zusammengesetzt. Die erste einfangende Gruppe nutzt das ausgesprochen flexible Muster ‹.+?›, um den ersten Vornamen zusammen mit allen weiteren Vornamen und den zusätzlichen Bestandteilen des Nachnamens einzufangen, wie zum Beispiel das deutsche „von“ oder das französische, portugiesische und spanische „de“. Diese Namenselemente werden zusammen bearbeitet, da sie in der Ausgabe auch nacheinander erscheinen sollen. Die zweite einfangende Gruppe passt durch ‹[^\s]+› auf den Nachnamen. Wie beim Punkt in der ersten einfangenden Gruppe ermöglicht die Flexibilität dieser Zeichenklasse auch die Verwendung von Umlauten und anderen exotischen Zeichen. In Tabelle 4-2 werden ein paar Beispiele für mit dieser Regex und dem entsprechenden Ersetzungstext umgestellte Namen aufgeführt. Tabelle 4-2: Formatierte Namen Eingabe

Ausgabe

Robert Downey

Downey, Robert

John F. Kennedy

Kennedy, John F.

Scarlett O’Hara

O’Hara, Scarlett

Pepé Le Pew

Pew, Pepé Le

J.R.R. Tolkien

Tolkien, J.R.R.

Catherine Zeta-Jones

Zeta-Jones, Catherine

Variationen Nachnamensbestandteile am Anfang des Namens aufführen Im folgenden regulären Ausdruck haben wir ein Element ergänzt, durch das zusätzliche Bestandteile des Nachnamens bei ihm verbleiben. Diese Regex berücksichtigt „de“, „du“, „la“, „le“, „St“, „St.“, „Ste“, „Ste.“, „van“ und „von“. Dabei sind auch mehrere solcher Bestandteile möglich (zum Beispiel „de la”): ^(.+?)z((?:(?:D[eu]|L[ae]|Ste?\.?|V[ao]n)z)*[^\s]+)$

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby $2,z$1

Ersetzungstextvarianten: .NET, Java, JavaScript, Perl, PHP \2,z\1

Ersetzungstextvarianten: Python, Ruby

4.18 Namen von „Vorname Nachname“ nach „Nachname, Vorname“ umwandeln | 287

4.19 Kreditkartennummern validieren Problem Sie sollen für eine Firma ein Bestellformular bauen, das auch eine Bezahlung per Kreditkarte zulässt. Da die Karten-Servicegesellschaft für jeden Transaktionsversuch eine Gebühr erhebt – auch für fehlgeschlagene Versuche –, wollen Sie mit einem regulären Ausdruck die offensichtlich ungültigen Kreditkartennummern ausfiltern. Nebenbei verbessert das auch die Bedienungsfreundlichkeit. Ein regulärer Ausdruck kann offensichtliche Tippfehler sofort erkennen, sobald der Anwender mit dem Ausfüllen der Felder auf der Webseite fertig ist. Eine Anfrage bei der Karten-Servicegesellschaft dauert dagegen leicht einmal 10 bis 30 Sekunden.

Lösung Leerzeichen und Bindestriche entfernen Lesen Sie die vom Kunden eingegebene Kreditkartennummer aus und speichern Sie sie in einer Variablen. Bevor Sie die Gültigkeit der Nummer überprüfen, suchen Sie nach Leerzeichen und Bindestrichen und entfernen sie aus der Nummer. Ersetzen Sie diesen regulären Ausdruck global durch einen leeren Ersetzungstext: [z-]

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby In Rezept 3.14 ist beschrieben, wie Sie diese erste Ersetzung vornehmen.

Validieren der Nummer Nachdem Leerzeichen und Bindestriche aus der Eingabe entfernt wurden, prüft dieser reguläre Ausdruck, ob die Kreditkartennummer dem Format einer der sechs großen Kreditkartenfirmen entspricht. Dabei nutzt die Regex benannte Captures, um herauszufinden, was für eine Kreditkarte der Kunde hat: ^(?: (?4[0-9]{12}(?:[0-9]{3})?) | (?5[1-5][0-9]{14}) | (?6(?:011|5[0-9][0-9])[0-9]{12}) | (?3[47][0-9]{13}) | (?3(?:0[0-5]|[68][0-9])[0-9]{11}) | (?(?:2131|1800|35\d{3})\d{11}) )$

Regex-Optionen: Freiform Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9

288 | Kapitel 4: Validierung und Formatierung

^(?: (?P4[0-9]{12}(?:[0-9]{3})?) | (?P5[1-5][0-9]{14}) | (?P6(?:011|5[0-9][0-9])[0-9]{12}) | (?P3[47][0-9]{13}) | (?P3(?:0[0-5]|[68][0-9])[0-9]{11}) | (?P(?:2131|1800|35\d{3})\d{11}) )$

Regex-Optionen: Freiform Regex-Varianten: PCRE, Python Java, Perl 5.6, Perl 5.8 und Ruby 1.8 unterstützen keine benannten Captures. Hier können Sie nummerierte Captures verwenden. Gruppe 1 fängt die Visa-Karten, Gruppe 2 die MasterCards und so weiter bis zur Gruppe 6 für JCB: ^(?: (4[0-9]{12}(?:[0-9]{3})?) | (5[1-5][0-9]{14}) | (6(?:011|5[0-9][0-9])[0-9]{12}) | (3[47][0-9]{13}) | (3(?:0[0-5]|[68][0-9])[0-9]{11}) | ((?:2131|1800|35\d{3})\d{11}) )$

# # # # # #

Visa MasterCard Discover American Express Diners Club JCB

Regex-Optionen: Freiform Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby JavaScript unterstützt keinen Freiform-Modus. Entfernen wir den Leerraum und die Kommentare, erhalten wir: ^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})| (6(?:011|5[0-9][0-9])[0-9]{12})|(3[47][0-9]{13})| (3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35\d{3})\d{11}))$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Wenn Sie nicht wissen müssen, um welchen Kartentyp es geht, können Sie die unnötigen einfangenden Gruppen entfernen: ^(?: 4[0-9]{12}(?:[0-9]{3})? | 5[1-5][0-9]{14} | 6(?:011|5[0-9][0-9])[0-9]{12} | 3[47][0-9]{13} | 3(?:0[0-5]|[68][0-9])[0-9]{11} | (?:2131|1800|35\d{3})\d{11} )$

# # # # # #

Visa MasterCard Discover American Express Diners Club JCB

Regex-Optionen: Freiform Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

4.19 Kreditkartennummern validieren | 289

Für JavaScript: ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}| 3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Folgen Sie der Anleitung in Rezept 3.6, um Ihrem Bestellformular diesen regulären Ausdruck hinzuzufügen und die Kreditkartennummer zu überprüfen. Wenn Sie unterschiedliche Serviceunternehmen für verschiedene Karten nutzen oder einfach selbst eine Statistik führen wollen, können Sie wie in Rezept 3.9 prüfen, welche benannten oder nummerierten einfangenden Gruppen die Übereinstimmung enthalten. Damit erfahren Sie, von welcher Firma die Karte Ihres Kunden ist.

Beispiel-Webseite mit JavaScript

Kreditkartentest

Kreditkartentest

Bitte geben Sie Ihre Kreditkartennummer ein:

(keine Kartennummer eingegeben)





Diskussion Leerzeichen und Bindestriche entfernen Auf Kreditkarten sind die in die Karte eingestanzten Ziffern meist in Vierergruppen unterteilt. So lässt sich die Kartennummer leichter lesen. Natürlich werden viele Leute versuchen, ihre Kartennummer auch genau so auf einer Webseite einzugeben – einschließlich der Leerzeichen. Schreibt man einen regulären Ausdruck, der eine Kartennummer validieren soll und dabei Leerzeichen, Bindestriche und was auch immer berücksichtigen will, ist das deutlich schwieriger als einer, der nur Ziffern zulässt. Um daher den Kunden nicht damit zu nerven, dass er die Kartennummer nochmals ohne Leerzeichen oder Bindestriche eingeben soll, entfernen Sie sie einfach vor dem Überprüfen der Nummer und der Übermittlung an die Karten-Servicegesellschaft. Der reguläre Ausdruck ‹[z-]› passt auf ein Leerzeichen oder einen Bindestrich. Ersetzen Sie alle Übereinstimmungen dieses regulären Ausdrucks durch einen leeren String, werden damit alle Leerzeichen und Bindestriche entfernt. Kreditkartennummern können nur aus Ziffern bestehen. Statt mit ‹[z-]› lediglich Leerzeichen und Bindestriche zu entfernen, können Sie auch die Zeichenklassenabkürzung ‹\D› nutzen, um alles zu entfernen, was keine Ziffer ist.

Validieren der Nummer Jede Kreditkartenfirma verwendet ein anderes Nummernformat. Wir nutzen diese Unterschiede, damit der Anwender eine Nummer eingeben kann, ohne die Kartenfirma angeben zu müssen. Die Firma kann dann aus der Nummer ermittelt werden. Die Formate sind: Visa 13 oder 16 Ziffern, beginnend mit einer 4. MasterCard 16 Ziffern, beginnend mit 51 bis 55. Discover 16 Ziffern, beginnend mit 6011 oder 65.

4.19 Kreditkartennummern validieren | 291

American Express 15 Ziffern, beginnend mit 34 oder 37. Diners Club 14 Ziffern, beginnend mit 300 bis 305, 36 oder 38. JCB 15 Ziffern, beginnend mit 2131 oder 1800, oder 16 Ziffern, beginnend mit 35. Wenn Sie nur bestimmte Kartenfirmen akzeptieren, können Sie die Karten aus der Regex entfernen, die Sie nicht haben wollen. Beim Entfernen von JCB achten Sie darauf, auch das letzte ‹|› zu entfernen. Endet Ihr regulärer Ausdruck mit ‹||› oder ‹|)›, werden auch leere Strings als gültige Kartennummer akzeptiert. Um zum Beispiel nur Visa, MasterCard und American Express zu akzeptieren, nutzen Sie: ^(?: 4[0-9]{12}(?:[0-9]{3})? | 5[1-5][0-9]{14} | 3[47][0-9]{13} )$

# Visa # MasterCard # AMEX

Regex-Optionen: Freiform Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Oder alternativ: ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13})$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Suchen Sie in einem längeren Text nach Kreditkartennummern, ersetzen Sie die Anker durch Wortgrenzen (‹\b›).

Einbau der Lösung in eine Webseite Das Beispiel in „Beispiel-Webseite mit JavaScript“ auf Seite 290 zeigt, wie Sie diese beiden regulären Ausdrücke in Ihr Bestellformular einbauen können. Das Eingabefeld für die Kreditkartennummer hat einen Event-Handler onkeyup, der die Funktion validatecardnumber() aufruft. Diese Funktion liest die Kartennummer aus dem Eingabefeld aus, entfernt Leerzeichen und Bindestriche und führt dann mithilfe des regulären Ausdrucks mit nummerierten einfangenden Gruppen eine Validierung durch. Das Ergebnis dieser Überprüfung wird angezeigt, indem der Text im letzten Absatz auf der Seite ersetzt wird. Hat der reguläre Ausdruck keinen Erfolg, liefert regexp.exec() den Wert null zurück, und es wird (Ungültige Kartennummer) angezeigt. Passt die Regex, liefert regexp.exec() ein String-Array zurück. Das nullte Element dieses Arrays enthält das vollständige Suchergebnis. In den Elementen 1 bis 6 finden sich die Ergebnisse der sechs einfangenden Gruppen.

292 | Kapitel 4: Validierung und Formatierung

Unser regulärer Ausdruck hat sechs einfangende Gruppen, die in den Alternativen einer Alternation untergebracht sind. Das bedeutet, dass immer nur genau eine Gruppe an der Übereinstimmung beteiligt ist und die Kartennummer enthält. Die anderen Gruppen sind dann leer (entweder undefined, oder sie enthalten einen leeren String – das hängt von Ihrem Browser ab). Die Funktion prüft nacheinander die sechs einfangenden Gruppen. Findet sie eine, die nicht leer ist, wird die Kartenfirma erkannt und ausgegeben.

Zusätzliche Validierung mit dem Luhn-Algorithmus Es gibt eine zusätzliche Validierungsmöglichkeit für Kreditkartennummern, bevor die Bestellung wirklich durchgeführt wird. Die letzte Ziffer in der Kartennummer ist eine Prüfsumme, die nach dem Luhn-Algorithmus berechnet wird. Da für diesen Algorithmus ein paar (wenn auch einfache) Kalkulationen notwendig sind, können Sie ihn nicht mit einem regulären Ausdruck implementieren. Sie können Ihr Webseitenbeispiel für dieses Rezept mit dem Luhn-Algorithmus ergänzen, indem Sie vor der else-Zeile in der Funktion validatecardnumber() den Aufruf luhn(cardnumber); einfügen. So wird die Luhn-Prüfung nur dann durchgeführt, wenn der reguläre Ausdruck eine Übereinstimmung gefunden hat und nachdem die Kartenart ermittelt wurde. Allerdings ist das Bestimmen der Kartenart für die Luhn-Prüfung nicht notwendig. Alle Kreditkarten nutzen die gleiche Methode. In JavaScript können Sie den Luhn-Algorithmus wie folgt implementieren: function luhn(cardnumber) { // Aufbau eines Arrays mit den Ziffern der Kartennummer var getdigits = /\d/g; var digits = []; while (match = getdigits.exec(cardnumber)) { digits.push(parseInt(match[0], 10)); } // Luhn-Algorithmus für die Ziffern ausführen var sum = 0; var alt = false; for (var i = digits.length - 1; i >= 0; i--) { if (alt) { digits[i] *= 2; if (digits[i] > 9) { digits[i] -= 9; } } sum += digits[i]; alt = !alt; } // Prüfung der Kartennummer if (sum % 10 == 0) { document.getElementById("notice").innerHTML += '; Luhn-Prüfung erfolgreich'; } else { document.getElementById("notice").innerHTML += '; Luhn-Prüfung nicht erfolgreich'; } }

4.19 Kreditkartennummern validieren | 293

Diese Funktion übernimmt einen String mit der Kreditkartennummer als Parameter. Die Kartennummer sollte nur aus Ziffern bestehen. In unserem Beispiel hat validatecardnumber() schon Leerzeichen und Bindestriche entfernt und ermittelt, ob die Kartennummer die richtige Anzahl an Ziffern besitzt. In der Funktion wird zunächst der reguläre Ausdruck ‹\d› verwendet, um über alle Ziffern im String iterieren zu können. Beachten Sie dabei den Modifikator /g. Innerhalb der Schleife findet sich in match[0] die Ziffer. Da reguläre Ausdrücke nur mit Texten arbeiten, rufen wir parseInt() auf, um sicherzustellen, dass die Variable als Integer und nicht als String gespeichert wird. Tun wir das nicht, findet sich später in der Variablen sum eine String-Verkettung von Ziffern und nicht die aufsummierten Werte. Der eigentliche Algorithmus läuft über das Array und berechnet eine Prüfsumme. Lässt sich diese Summe ohne Rest durch 10 teilen, ist die Kartennummer gültig.

4.20 Europäische Umsatzsteuer-Identifikationsnummern Problem Sie sollen ein Onlinebestellformular für eine Firma in der Europäischen Union erstellen. Kauft eine für die Umsatzsteuer registrierte Firma (Ihr Kunde), die in einem EU-Land sitzt, von einem Verkäufer (Ihre Firma) in einem anderen EU-Land etwas, muss der Verkäufer nach EU-Steuerrecht keine Umsatzsteuer berechnen. Hat der Käufer keine Umsatzsteuer-Identifikationsnummer (USt-IdNr.) angegeben, muss der Verkäufer die Mehrwertsteuer berechnen und sie an das lokale Finanzamt abführen. Um das zu vermeiden, nutzt der Verkäufer die USt-IdNr. des Käufers als Beweis, dass keine Steuer fällig ist. Für den Verkäufer ist es also sehr wichtig, die USt-IdNr. des Käufers zu überprüfen, bevor er die Bestellung ohne Umsatzsteuer durchführt. Die häufigste Ursache für ungültige Umsatzsteuer-Identifikationsnummern sind einfache Tippfehler vom Kunden. Um den Bestellprozess schneller und einfacher zu gestalten, sollten Sie die USt-IdNr. mit einer Regex überprüfen, während der Kunde das Bestellformular ausfüllt. Das lässt sich mit etwas JavaScript-Code auf Clientseite oder im CGISkript auf Ihrem Webserver erreichen. Passt die Nummer nicht zum regulären Ausdruck, kann der Kunde den Tippfehler direkt korrigieren.

Lösung Leerzeichen, Bindestriche und Punkte entfernen Lesen Sie die vom Kunden eingegebene USt-IdNr. aus und speichern Sie sie in einer Variablen. Bevor Sie die Gültigkeit der Nummer prüfen, ersetzen Sie die durch folgenden regulären Ausdruck gefundenen Übereinstimmungen mit einem leeren String:

294 | Kapitel 4: Validierung und Formatierung

[-.z]

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby In Rezept 3.14 wird gezeigt, wie Sie diese Ersetzung durchführen können. Wir sind davon ausgegangen, dass der Kunde abgesehen von Bindestrichen, Punkten und Leerzeichen keine anderen Satzzeichen eingegeben hat. Jegliches weitere „falsche“ Zeichen wird von der nächsten Prüfung abgefangen.

Überprüfen der Nummer Nachdem Leerzeichen, Punkte und Bindestriche entfernt wurden, prüft dieser reguläre Ausdruck, ob die USt-IdNr. für einen der 27 EU-Staaten gültig ist: ^( (AT)?U[0-9]{8} | # Österreich (BE)?0?[0-9]{9} | # Belgien (BG)?[0-9]{9,10} | # Bulgarien (CY)?[0-9]{8}L | # Zypern (CZ)?[0-9]{8,10} | # Tschechische Republik (DE)?[0-9]{9} | # Deutschland (DK)?[0-9]{8} | # Dänemark (EE)?[0-9]{9} | # Estland (EL|GR)?[0-9]{9} | # Griechenland # Spanien (ES)?[0-9A-Z][0-9]{7}[0-9A-Z] | (FI)?[0-9]{8} | # Finnland (FR)?[0-9A-Z]{2}[0-9]{9} | # Frankreich (GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3}) | # Großbritannien (HU)?[0-9]{8} | # Ungarn (IE)?[0-9]S[0-9]{5}L | # Irland (IT)?[0-9]{11} | # Italien (LT)?([0-9]{9}|[0-9]{12}) | # Litauen (LU)?[0-9]{8} | # Luxemburg (LV)?[0-9]{11} | # Lettland (MT)?[0-9]{8} | # Malta (NL)?[0-9]{9}B[0-9]{2} | # Niederlande (PL)?[0-9]{10} | # Polen (PT)?[0-9]{9} | # Portugal (RO)?[0-9]{2,10} | # Rumänien (SE)?[0-9]{12} | # Schweden (SI)?[0-9]{8} | # Slowenien (SK)?[0-9]{10} # Slowakei )$

Regex-Optionen: Freiform, Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Dieser reguläre Ausdruck verwendet den Freiform-Modus, um ein späteres Bearbeiten der Regex zu vereinfachen. Schließlich nimmt die EU gelegentlich auch neue Länder auf, oder die Staaten passen ihre Regeln für die USt-IdNr. an. Leider ist in JavaScript kein Freiform-Modus möglich. Dort müssen Sie alles in einer Zeile unterbringen:

4.20 Europäische Umsatzsteuer-Identifikationsnummern | 295

^((AT)?U[0-9]{8}|(BE)?0?[0-9]{9}|(BG)?[0-9]{9,10}|(CY)?[0-9]{8}L| (CZ)?[0-9]{8,10}|(DE)?[0-9]{9}|(DK)?[0-9]{8}|(EE)?[0-9]{9}| (EL|GR)?[0-9]{9}|(ES)?[0-9A-Z][0-9]{7}[0-9A-Z]|(FI)?[0-9]{8}| (FR)?[0-9A-Z]{2}[0-9]{9}|(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3})| (HU)?[0-9]{8}|(IE)?[0-9]S[0-9]{5}L|(IT)?[0-9]{11}| (LT)?([0-9]{9}|[0-9]{12})|(LU)?[0-9]{8}|(LV)?[0-9]{11}|(MT)?[0-9]{8}| (NL)?[0-9]{9}B[0-9]{2}|(PL)?[0-9]{10}|(PT)?[0-9]{9}|(RO)?[0-9]{2,10}| (SE)?[0-9]{12}|(SI)?[0-9]{8}|(SK)?[0-9]{10})$

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby In Rezept 3.6 wird beschrieben, wie Sie diesen regulären Ausdruck auf Ihrem Bestellformular unterbringen können.

Diskussion Leerzeichen, Punkte und Bindestriche entfernen Damit die Umsatzsteuer-Identifikationsnummern für Menschen leichter lesbar sind, werden sie häufig mit zusätzlichen Trennzeichen eingegeben. So könnte ein deutscher Kunde seine USt-IdNr. DE123456789 zum Beispiel als DE 123.456.789 eingeben. Ein einzelner regulärer Ausdruck, der die Nummern aus 27 Ländern in allen möglichen Schreibweisen erkennt, ist unmöglich zu realisieren. Da die Trennzeichen nur der Lesbarkeit dienen, ist es viel einfacher, zunächst alle diese Zeichen zu entfernen und dann die reine USt-IdNr. zu analysieren. Der reguläre Ausdruck ‹[-.z]› passt zu einem Zeichen, das ein Bindestrich, ein Punkt oder ein Leerzeichen ist. Ersetzt man alle Übereinstimmungen dieses regulären Ausdrucks durch einen leeren String, werden diese Zeichen aus den Nummern entfernt. Umsatzsteuer-Identifikationsnummern bestehen nur aus Buchstaben und Ziffern. Statt lediglich die am häufigsten eingegebenen Trennzeichen mit ‹[-.z]› zu entfernen, können Sie auch alle ungültigen Zeichen mit ‹[^A-Z0-9]› eliminieren.

Validieren der Nummer Die zwei regulären Ausdrücke für das Validieren der Nummer sind identisch. Nur nutzt der erste den Freiform-Modus, damit er leichter lesbar ist. JavaScript unterstützt diesen Modus nicht, aber bei den anderen Varianten haben die Sie freie Wahl. Die Regex nutzt eine große Alternation, um die Umsatzsteuer-Identifikationsnummern aller 27 EU-Staaten berücksichtigen zu können. Die Formate sehen so aus: Belgien 999999999 oder 0999999999 Bulgarien 999999999 oder 9999999999

296 | Kapitel 4: Validierung und Formatierung

Dänemark 99999999 Deutschland 999999999 Estland 999999999 Finnland 99999999 Frankreich XX999999999 Griechenland 999999999 Großbritannien 999999999, 999999999999 oder XX999 Irland 9S99999L Italien 99999999999 Lettland 99999999999 Litauen 999999999 oder 99999999999 Luxemburg 99999999 Malta 99999999 Niederlande 999999999B99 Österreich U99999999 Polen 999999999 Portugal 999999999 Rumänien 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999 oder 9999999999 Schweden 99999999999 Slowakei 999999999 4.20 Europäische Umsatzsteuer-Identifikationsnummern | 297

Slowenien 99999999 Spanien X9999999X Tschechische Republik 99999999, 999999999 oder 9999999999 Ungarn 99999999 Zypern 99999999L Streng genommen ist der zweistellige Ländercode Teil der USt-IdNr. Aber viele lassen ihn häufig weg, da die Rechnungsanschrift schon den Staat angibt. Der reguläre Ausdruck akzeptiert daher die Nummern mit und ohne Ländercode. Wenn Sie wollen, dass er eingegeben werden muss, entfernen Sie alle Fragezeichen aus dem regulären Ausdruck. Dann sollten Sie aber auch in der Fehlermeldung, die den Anwender auf eine ungültige USt-IdNr. hinweist, erwähnen, dass der Ländercode eingegeben werden muss. Akzeptieren Sie Bestellungen nur aus bestimmten Ländern, können Sie die Länder aus der Regex weglassen, die in der Länderauswahl auf Ihrem Bestellformular vorhanden sind. Löschen Sie eine der Alternativen, müssen Sie auch den Operator ‹|› löschen, der die Alternativen untereinander trennt. Tun Sie das nicht, steht in Ihrem regulären Ausdruck ‹||›. Das führt aber zu einer Alternative, die einen leeren String akzeptiert, sodass Ihr Bestellformular letztendlich auch ohne USt-IdNr. fertiggestellt werden kann. Die 27 Alternativen sind in einer Gruppe zusammengefasst. Diese Gruppe umfasst den gesamten Bereich zwischen einem Zirkumflex und einem Dollar, wodurch der reguläre Ausdruck mit Anfang und Ende des zu überprüfenden Strings verbunden ist. Die gesamte Eingabe muss also eine gültige USt-IdNr. sein. Suchen Sie in einem längeren Text nach Umsatzsteuer-Identifikationsnummern, ersetzen Sie die Anker durch die Wortgrenzen ‹\b›.

Variationen Der Vorteil eines regulären Ausdrucks für alle 27 Staaten liegt darin, dass Sie in Ihrem Formular nur eine Regex-Überprüfung benötigen. Sie könnten aber auch 27 getrennte Regexes nutzen. Zuerst prüfen Sie das Land, das der Kunde in der Rechnungsanschrift angegeben hat. Dann validieren Sie die USt-IdNr. abhängig vom Land: Belgien ‹^(BE)?0?[0-9]{9}$›

Bulgarien ‹^(BG)?[0-9]{9,10}$›

Dänemark ‹^(DK)?[0-9]{8}$›

298 | Kapitel 4: Validierung und Formatierung

Deutschland ‹^(DE)?[0-9]{9}$›

Estland ‹^(EE)?[0-9]{9}$›

Finnland ‹^(FI)?[0-9]{8}$›

Frankreich ‹^(FR)?[0-9A-Z]{2}[0-9]{9}$›

Griechenland ‹^(EL|GR)?[0-9]{9}$›

Großbritannien ‹^(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3})$›

Irland ‹^(IE)?[0-9]S[0-9]{5}L$›

Italien ‹^(IT)?[0-9]{11}$›

Lettland ‹^(LV)?[0-9]{11}$›

Litauen ‹^(LT)?([0-9]{9}|[0-9]{12})$›

Luxemburg ‹^(LU)?[0-9]{8}$›

Malta ‹^(MT)?[0-9]{8}$›

Niederlande ‹^(NL)?[0-9]{9}B[0-9]{2}$›

Österreich ‹^(AT)?U[0-9]{8}$›

Polen ‹^(PL)?[0-9]{10}$›

Portugal ‹^(PT)?[0-9]{9}$›

Rumänien ‹^(RO)?[0-9]{2,10}$›

Schweden ‹^(SE)?[0-9]{12}$›

Slowakei ‹^(SK)?[0-9]{10}$›

Slowenien ‹^(SI)?[0-9]{8}$›

Spanien ‹^(ES)?[0-9A-Z][0-9]{7}[0-9A-Z]$›

4.20 Europäische Umsatzsteuer-Identifikationsnummern | 299

Tschechische Republik ‹^(CZ)?[0-9]{8,10}$›

Ungarn ‹^(HU)?[0-9]{8}$›

Zypern ‹^(CY)?[0-9]{8}L$›

Implementieren Sie Rezept 3.6, um die USt-IdNr. mit der ausgewählten Regex zu validieren. Damit erfahren Sie, ob die Nummer für das Land, das der Kunde angegeben hat gültig ist. Der Hauptvorteil der getrennten regulären Ausdrücke ist, dass die USt-IdNr. auf jeden Fall mit der korrekten Länderkennung beginnen kann, ohne dass der Kunde sie angeben muss. Passt der reguläre Ausdruck auf die angegebene Nummer, prüfen Sie den Inhalt der ersten einfangenden Gruppe. In Rezept 3.9 wird beschrieben, wie Sie das machen können. Ist die erste einfangende Gruppe leer, hat der Kunde den Ländercode nicht mit eingegeben. Sie können ihn dann selbst ergänzen, bevor Sie die überprüfte Nummer in Ihrer Bestelldatenbank ablegen. Griechische Umsatzsteuer-Identifikationsnummern können zwei verschiedene Ländercodes haben. EL wird traditionell für griechische Nummern verwendet, aber GR ist der ISO-Ländercode für Griechenland.

Siehe auch Der reguläre Ausdruck prüft nur, ob die Nummer wie eine gültige USt-IdNr. aussieht. Das reicht aus, um echte Fehler auszuschließen. Aber ein regulärer Ausdruck kann offensichtlich nicht prüfen, ob die Nummer der Firma zugeordnet ist, die die Bestellung aufgibt. Die Europäische Union hat eine Website (http://ec.europa.eu/taxation_customs/vies/ vieshome.do), auf der Sie prüfen können, zu welcher Firma eine bestimmte USt-IdNr. gehört – wenn sie denn überhaupt zugewiesen ist. Die Nummern werden mit der Datenbank des entsprechenden Staats verglichen. Manche Staaten bestätigen dabei allerdings nur eine Gültigkeit, ohne weitere Informationen über die entsprechende Firma herauszugeben. Die in diesem regulären Ausdruck genutzten Techniken werden in den Rezepten 2.3, 2.5 und 2.8 besprochen.

300 | Kapitel 4: Validierung und Formatierung

KAPITEL 5

Wörter, Zeilen und Sonderzeichen

Dieses Kapitel enthält Rezepte für das Finden und Bearbeiten von Texten. Mit einigen dieser Rezepte erreichen Sie Dinge, die Sie vielleicht von einer ausgefuchsten SuchEngine erwarten – so zum Beispiel das Finden eines von mehreren Wörtern oder das Finden von Wörtern, die nahe beieinanderstehen. Andere Beispiele helfen Ihnen dabei, ganze Zeilen zu finden, die bestimmte Wörter enthalten, Wortwiederholungen zu entfernen oder Meta-zeichen in regulären Ausdrücken zu maskieren. Vor allem geht es in diesem Kapitel aber darum, Regex-Konstrukte und -Techniken im echten Einsatz zu präsentieren. Lesen Sie sich die Rezepte durch, ist das wie ein Training für einen ganzen Reigen von Regex-Features. Damit fällt es Ihnen in Zukunft leichter, reguläre Ausdrücke auf eigene Probleme anzuwenden. In vielen Fällen ist das, wonach wir suchen, einfach, aber die Vorlagen, die wir in den Lösungen bereitstellen, ermöglichen es Ihnen, sie an Ihre eigenen Probleme anzupassen.

5.1

Ein bestimmtes Wort finden

Problem Sie haben die einfache Aufgabe, alle Vorkommen des Worts „rot“ zu finden, unabhängig von Groß- oder Kleinschreibung. Entscheidend ist, dass es ein vollständiges Wort sein muss. Sie wollen keine Teile längerer Wörter finden, wie zum Beispiel Brot, Karotte oder rotieren.

Lösung Tokens für Wortgrenzen sorgen für eine ganze einfache Lösung: \brot\b

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

| 301

In Rezept 3.7 wird gezeigt, wie Sie mit diesem regulären Ausdruck alle Übereinstimmungen finden können. In Rezept 3.14 ist beschrieben, wie Sie die Übereinstimmungen durch anderen Text ersetzen können.

Diskussion Die Wortgrenzen an beiden Enden des regulären Ausdrucks stellen sicher, dass rot nur dann gefunden wird, wenn es als eigenständiges Wort auftaucht. Genauer gesagt, erzwingen die Wortgrenzen, dass rot von anderem Text durch den Anfang oder das Ende des Strings, durch Whitespace, Satzzeichen oder andere Nicht-Wortzeichen getrennt ist. Regex-Engines betrachten Buchstaben, Ziffern und Unterstriche als Wortzeichen. Wortgrenzen werden detaillierter in Rezept 2.6 behandelt. Es kann aber ein Problem geben, wenn man in JavaScript, in der PCRE und in Ruby mit internationalen Texten arbeitet, da diese Regex-Varianten nur Buchstaben aus der ASCIITabelle zum Erstellen der Wortgrenzen in Betracht ziehen. Die Wortgrenzen werden also nur an Positionen zwischen ‹^|[^A-Za-z0-9_]› und ‹[A-Za-z0-9_]› oder zwischen ‹[A-Za-z0-9_]› und ‹[^A-Za-z0-9_]|$› gefunden. Das Gleiche gilt für Python, wenn die Option UNICODE oder U nicht gesetzt ist. Damit wird leider verhindert, dass man ‹\b› in Sprachen für Suchen nach ganzen Wörtern einsetzen kann, die akzentuierte Buchstaben oder Wörter mit nicht lateinischen Schriftsystemen enthalten. So wird zum Beispiel in JavaScript, in der PCRE und in Ruby durch ‹\büber\b› eine Übereinstimmung in darüber gefunden, aber nicht in dar über. In den meisten Fällen ist das genau das Gegenteil von dem, was Sie wollen. Das Problem ist, dass ü als Nicht-Wortzeichen angesehen und daher eine Wortgrenze zwischen den beiden Zeichen rü gefunden wird. Dagegen gibt es dann keine Wortgrenze zwischen einem Leerzeichen und ü, da beide als Nicht-Wortzeichen betrachtet werden. Sie können dieses Problem durch die Verwendung von Lookaheads und Lookbehinds (gemeinsam als Lookarounds bezeichnet) statt von Wortgrenzen umgehen. Wie Wortgrenzen passen Lookarounds auf Übereinstimmungen der Länge null. In der PCRE (wenn sie mit UTF-8-Unterstützung kompiliert wurde) und Ruby 1.9 können Sie auf Unicode basierende Wortgrenzen zum Beispiel durch ‹(?wort2))\W+(?:\w+\W+){0,5}?(?(w2)(?&w1)|(?&w2))\b

Regex-Optionen: Keine Regex-Varianten: PCRE 7, Perl 5.10 Hier umgeben benannte einfangende Gruppen, die die Syntax ‹(?...)› haben, die ersten Vorkommen von ‹wort1› und ‹wort2›. Damit können Sie mit der Subroutinensyntax ‹(?&name)› ein Submuster durch seinen Namen wiederverwenden. Das funktioniert jedoch anders als eine Rückwärtsreferenz einer benannten Gruppe. Mit einer benannten Rückwärtsreferenz, wie zum Beispiel ‹\k› (.NET, PCRE 7, Perl 5.10) oder ‹(?P=name)› (PCRE 4 und neuer, Perl 5.10, Python), können Sie Text erneut suchen, der schon von einer benannten einfangenden Gruppe gefunden wurde. Eine Subroutine wie ‹(?&name)› ermöglicht die Wiederverwendung des eigentlichen Musters, das sich in der entsprechenden Gruppe befindet. Sie können hier keine Rückwärtsreferenz nutzen, weil damit nur Wörter gefunden werden können, die schon gefunden wurden. Die Subroutinen innerhalb der Bedingung am Ende der Regex passen zu dem Wort aus den beiden angegebenen Optionen, das noch nicht gefunden wurde, ohne es erneut hinschreiben zu müssen. Somit gibt es in der Regex nur eine Stelle, die Sie anpassen müssen, wenn Sie sie mit anderen Wörtern verwenden wollen.

Drei oder mehr Wörter finden, die nahe beieinanderliegen Exponentiell wachsende Permutationen: Zwei Wörter zu finden, die nahebeieinander liegen, ist eine recht übersichtliche Aufgabe. Schließlich gibt es nur zwei Möglichkeiten, sie anzuordnen. Aber was, wenn Sie drei Wörter in beliebiger Reihenfolge finden wollen? Jetzt gibt es sechs mögliche Anordnungen (siehe Abbildung 5-2). Die Anzahl an Möglichkeiten, eine gegebene Menge an Wörtern zu ordnen, ist n! oder das Produkt der ganzen Zahlen von 1 bis n („n Fakultät“). Bei vier Wörtern gibt es 24 Möglichkeiten, sie anzuordnen. Geht es um zehn Wörter, wächst die Anzahl an Möglichkeiten in die Millionen. Es ist schlicht nicht machbar, mehr als ein paar beieinanderliegende Wörter mithilfe der bisher genutzten Regex-Techniken finden. Die hässliche Lösung: Eine Möglichkeit, dieses Problem zu lösen, ist das Wiederholen einer Gruppe, die die notwendigen Wörter oder ein beliebiges anderes Wort (nachdem vorher ein notwendiges Wort gefunden wurde) findet. Dann greift man auf Bedingungen zurück, die dafür sorgen, dass die Übereinstimmung erst dann erfolgreich ist, wenn alle anderen notwendigen Wörter gefunden wurden. Dies ist ein Beispiel, mit dem drei Wör-

5.7 Wörter finden, die nahe beieinanderstehen | 319

ter in beliebiger Reihenfolge gefunden werden, wobei höchstens fünf andere Wörter dazwischenliegen dürfen: \b(?:(?>(wort1)|(wort2)|(wort3)|(?(1)|(?(2)|(?(3)|(?!))))\w+)\b\W*?){3,8} (?(1)(?(2)(?(3)|(?!))|(?!))|(?!))

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, PCRE, Perl Zwei Werte: [ 12, 21 ] = 2 mögliche Anordnungen Drei Werte: [ 123, 132, 213, 231, 312, 321 ] = 6 mögliche Anordnungen Vier Werte: [ 1234, 1243, 2134, 2143, 3124, 3142, 4123, 4132, = 24 mögliche Faktorieren: 2! = 2 × 3! = 3 × 4! = 4 × 5! = 5 × ... 10! = 10

1324, 1342, 2314, 2341, 3214, 3241, 4213, 4231, Anordnungen

1423, 2413, 3412, 4312,

1432, 2432, 3421, 4321 ]

1 2 × 1 3 × 2 × 1 4 × 3 × 2 × 1

= = = =

2 6 24 120

× 9 × 8 × 7 × 6 × 5 × 4 × 3 × 2 × 1 = 3628800

Abbildung 5-2: Viele Möglichkeiten, eine Menge zu ordnen

Die gleiche Regex, diesmal ohne atomare Gruppen (siehe Rezept 2.14), dafür mit einer normalen nicht-einfangenden Gruppe, damit sie auch in Python genutzt werden kann: \b(?:(?:(wort1)|(wort2)|(wort3)|(?(1)|(?(2)|(?(3)|(?!))))\w+)\b\W*?){3,8} (?(1)(?(2)(?(3)|(?!))|(?!))|(?!))

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, PCRE, Perl, Python Die Quantoren ‹{3,8}› in den regulären Ausdrücken berücksichtigen die drei erforderlichen Wörter und ermöglichen null bis fünf andere Wörter dazwischen. Die leeren negativen Lookaheads ‹(?!)› passen niemals und werden daher genutzt, um bestimmte Pfade durch die Regex zu blockieren, bis eines oder mehrere der notwendigen Wörter gefunden wurden. Die Logik, die diese Pfade kontrolliert, ist durch zwei Sets an verschachtelten Bedingungen implementiert. Das erste Set verhindert, dass ein schon gefundenes Wort ‹\w+› gefunden wird, bis mindestens eines der notwendigen Wörter passt. Das zweite Set

320 | Kapitel 5: Wörter, Zeilen und Sonderzeichen

an Bedingungen am Ende zwingt die Regex-Engine dazu, per Backtracking zurückzugehen oder einen Misserfolg zu vermelden, bis nicht alle notwendigen Wörter gefunden wurden. Das ist nur eine kurze Beschreibung der Funktionsweise, aber statt hier noch tiefer einzusteigen und zu erklären, wie man zusätzliche notwendige Wörter ergänzt, wollen wir uns lieber eine verbesserte Implementierung anschauen, die mehr Regex-Varianten unterstützt und ein bisschen trickreicher vorgeht. Leere Rückwärtsreferenzen ausnutzen: Die hässliche Lösung funktioniert, ist aber eher ein Anwärter auf den Preis für die verwirrendste Regex, da sie sich nur schlecht lesen und handhaben lässt. Und mit jedem weiteren Wort würde es nur noch schlimmer werden. Glücklicherweise gibt es einen Regex-Hack, der deutlich einfacher zu verstehen ist und der zudem auch unter Java und Ruby läuft (die beide keine Bedingungen unterstützen). Das in diesem Abschnitt beschriebene Verhalten sollte in produktiven Anwendungen nur mit Vorsicht eingesetzt werden. Wir nutzen dabei Regex-Verhaltensweisen aus, die in den meisten Regex-Bibliotheken nicht dokumentiert sind. \b(?:(?>wort1()|wort2()|wort3()|(?>\1|\2|\3)\w+)\b\W*?){3,8}\1\2\3

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, PCRE, Perl, Ruby \b(?:(?:wort1()|wort2()|wort3()|(?:\1|\2|\3)\w+)\b\W*?){3,8}\1\2\3

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Mit diesem Konstrukt kann man auch sehr einfach weitere notwendige Wörter hinzufügen. Hier ein Beispiel mit vier erforderlichen Wörtern in beliebiger Reihenfolge, wobei höchstens fünf Wörter zwischen ihnen liegen dürfen: \b(?:(?>wort1()|wort2()|wort3()|wort4()| (?>\1|\2|\3|\4)\w+)\b\W*?){4,9}\1\2\3\4

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, PCRE, Perl, Ruby \b(?:(?:wort1()|wort2()|wort3()|wort4()| (?:\1|\2|\3|\4)\w+)\b\W*?){4,9}\1\2\3\4

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Diese regulären Ausdrücke nutzen mit Absicht leere einfangende Gruppe hinter jedem erforderlichen Wort. Da jeder Versuch, eine Rückwärtsreferenz wie ‹\1› zu finden, fehlschlägt, wenn die entsprechende einfangende Gruppe noch nicht an der Übereinstimmung beteiligt war, können Rückwärtsreferenzen auf leere Gruppen genutzt werden, um den Pfad der Regex-Engine durch ein Muster so zu kontrollieren, wie es mit den weiter

5.7 Wörter finden, die nahe beieinanderstehen | 321

oben vorgestellten umfangreicheren Bedingungen möglich war. War die entsprechende Gruppe schon an der Übereinstimmung beteiligt, wenn die Engine die Rückwärtsreferenz erreicht, wird sie einfach einen leere String finden und mit der Arbeit fortfahren. Hier verhindert die Gruppe ‹(?>\1|\2|\3)›, dass mit ‹\w+› ein Wort gefunden wird, bevor nicht mindestens eines der notwendigen Wörter passt. Die Rückwärtsreferenzen werden am Ende des Musters wiederholt, um zu verhindern, dass erfolgreich eine Übereinstimmung abgeschlossen werden kann, bevor nicht alle notwendigen Wörter gefunden wurden. Python unterstützt keine atomaren Gruppen, daher werden auch hier in dem Beispiel, das für Python genutzt werden kann, die atomaren Gruppen durch nicht-einfangende Gruppen ersetzt. Dadurch werden die Regexes zwar weniger effizient, aber die gefundenen Inhalte unterscheiden sich nicht. Die äußere Gruppe kann in keiner Variante atomar sein, denn die Regex-Engine muss innerhalb der äußeren Gruppe per Backtracking arbeiten können, wenn die Rückwärtsreferenzen am Ende des Musters keinen Erfolg haben. JavaScript-Rückwärtsreferenzen mit eigenen Regeln: Obwohl JavaScript die in der Python-Version dieses Musters genutzte Syntax vollständig unterstützt, gibt es dort zwei Unterschiede, die dafür sorgen, dass dieser Trick hier nicht funktioniert. Beim ersten geht es darum, was durch Rückwärtsreferenzen auf einfangende Gruppen gefunden wird, die noch nicht Teil einer Übereinstimmung sind. In der JavaScript-Spezifikation steht, dass solche Rückwärtsreferenzen einen leeren String finden, also immer erfolgreich sind. In so gut wie jeder anderen Regex-Variante gilt das Gegenteil: Sie passen niemals und sorgen damit dafür, dass die Regex-Engine per Backtracking zurückspringen muss, bis entweder die gesamte Suche ein Fehlschlag war oder bis die Gruppe, auf die sie verweisen, Teil der Übereinstimmung ist. Dann passt auch wieder die Rückwärtsreferenz. Der zweite Unterschied bei der JavaScript-Variante dreht sich um den Wert, der von einer einfangenden Gruppe in einer wiederholten äußeren Gruppe gefunden wird, zum Beispiel ‹((a)|(b))+›. Bei den meisten Regex-Varianten entspricht der Wert, den sich eine einfangende Gruppe innerhalb einer wiederholten Gruppe merkt, dem, was als Letztes gefunden wurde. Wenn zum Beispiel mit ‹(?:(a)|(b))+› der Text ab gefunden wurde, wird der Wert der Rückwärtsreferenz 1 auf a gesetzt sein. Nach der JavaScript-Spezifikation wird aber der Wert von Rückwärtsreferenzen in verschachtelten Gruppen jedes Mal zurückgesetzt, wenn die äußere Gruppe wiederholt wird. Findet also ‹(?:(a)|(b))+› wieder den Text ab, würde die Rückwärtsreferenz 1 hier auf eine nicht beteiligte einfangende Gruppe verweisen, was in JavaScript innerhalb der Regex selbst einem leeren String entspricht. Das von RegExp.prototype.exec zurückgegebene Array würde hier den Wert undefined liefern. Beide Verhaltensunterschiede führen in der JavaScript-Regex-Variante dazu, dass Bedingungen in einer Regex auf dem oben vorgestellten Weg nicht emuliert werden können.

322 | Kapitel 5: Wörter, Zeilen und Sonderzeichen

Mehrere Wörter, die einen beliebigen Abstand voneinander haben können Wenn Sie einfach nur prüfen wollen, ob in einem Text eine Liste von Wörtern gefunden werden kann, wobei der Abstand zwischen den Wörtern beliebig sein kann, bieten positive Lookaheads eine Möglichkeit, das in einer Suchoperation zu erledigen. In vielen Fällen ist es einfacher und effizienter, jeden Term einzeln zu suchen und zu prüfen, ob alle Tests erfolgreich waren.

\A(?=.*?\bwort1\b)(?=.*?\bwort2\b).*\Z

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, Punkt passt auf Zeilenumbruch Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^(?=[\s\S]*?\bwort1\b)(?=[\s\S]*?\bwort2\b)[\s\S]*$

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert (Zirkumflex und Dollarzeichen passen auf Zeilenumbruch darf nicht gesetzt sein) Regex-Variante: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Diese regulären Ausdrücke passen auf den gesamten String, auf den sie angewendet werden, wenn alle Ihre gesuchten Wörter darin enthalten sind. Ansonsten gibt es keine Übereinstimmung. JavaScript-Programmierer können die erste Version nicht nutzen, da JavaScript die Anker ‹\A› und ‹\Z› oder die Option Punkt passt auf Zeilenumbruch nicht unterstützt. Sie können diese regulären Ausdrücke wie in Rezept 3.6 implementieren. Ändern Sie einfach die Platzhalter ‹wort1› und ‹wort2› in die gesuchten Begriffe. Wenn Sie nach mehr als zwei Wörtern suchen, können Sie so viele Lookaheads wie nötig in den regulären Ausdruck einfügen. Mit ‹\A(?=.*?\bwort1\b)(?=.*?\bwort2\b)(?=.*?\bwort3\b).*\Z› suchen Sie zum Beispiel nach drei Wörtern.

Siehe auch Rezepte 5.5 und 5.6.

5.8

Wortwiederholungen finden

Problem Sie bearbeiten ein Dokument und würden gern prüfen, ob Sie unabsichtlich Wörter wiederholt haben. Diese doppelten Wörter sollen auch dann gefunden werden, wenn sie in unterschiedlicher Groß- und Kleinschreibung eingetippt wurden, wie zum Beispiel bei „Wer wer“. Der Whitespace zwischen den Wörtern ist Ihnen ebenfalls egal. Er kann beliebig groß sein, auch wenn die Wörter damit auf unterschiedlichen Zeilen gelandet sind.

5.8 Wortwiederholungen finden | 323

Lösung Eine Rückwärtsreferenz passt auf etwas, das vorher gefunden wurde. Damit ist sie die wichtigste Zutat für dieses Rezept: \b([A-Z]+)\s+\1\b

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Wenn Sie diesen regulären Ausdruck verwenden wollen, um das erste Wort zu behalten, die Wiederholung aber zu entfernen, ersetzen Sie alle Übereinstimmungen durch die Rückwärtsreferenz 1. Sie können die Übereinstimmungen auch mit anderen Zeichen umschließen (zum Beispiel durch ein HTML-Tag), um sie bei einer späteren Überarbeitung schneller zu erkennen. In Rezept 3.15 erfahren Sie, wie Sie Rückwärtsreferenzen in Ihrem Ersetzungstext verwenden können. Wenn Sie nur doppelte Wörter finden wollen, um manuell zu prüfen, ob sie korrigiert werden müssen, finden Sie in Rezept 3.7 den notwendigen Code. Ein Texteditor oder ein grep-ähnliches Tool – wie die in „Tools für das Arbeiten mit regulären Ausdrücken“ in Kapitel 1 erwähnten – kann Ihnen dabei helfen, doppelte Wörter zu finden, während Sie gleichzeitig den Kontext sehen, in dem die fraglichen Wörter stehen.

Diskussion Man braucht zwei Dinge, um etwas zu finden, was schon vorher gefunden wurde: eine einfangende Gruppe und eine Rückwärtsreferenz. Stecken Sie das, was Sie mehr als einmal finden wollen, in eine einfangende Gruppe und suchen Sie es dann erneut mithilfe einer Rückwärtsreferenz. Das ist etwas anderes, als ein Token oder eine Gruppe mit einem Quantor zu wiederholen. Schauen Sie sich den Unterschied zwischen den beiden vereinfachten regulären Ausdrücken ‹(\w)\1› und ‹\w{2}› an. Die erste Regex nutzt eine einfangende Gruppe und eine Rückwärtsreferenz, mit der das gleiche Wortzeichen doppelt gefunden werden kann, während die zweite Regex einen Quantor nutzt, um zwei beliebige Wortzeichen zu finden. In Rezept 2.10 wird die Faszination von Rückwärtsreferenzen im Detail beschrieben. Aber zurück zum eigentlichen Problem. Dieses Rezept findet nur doppelte Wörter, die aus den Buchstaben A bis Z und a bis z bestehen (da die Option zum Ignorieren von Groß- und Kleinschreibung aktiv ist). Um auch akzentuierte Zeichen und Zeichen aus anderen Schriftsystemen zuzulassen, können Sie die Buchstabeneigenschaft für UnicodeZeichen verwenden (‹\p{L}›), wenn Ihre Regex-Variante das unterstützt (siehe „Unicode-Eigenschaften oder -Kategorien“ auf Seite 49). Zwischen der einfangenden Gruppe und der Rückwärtsreferenz passt ‹\s+› auf beliebig viele Whitespace-Zeichen, also auf Leerzeichen, Tabs und Zeilenumbrüche. Wollen Sie die Zeichen auf solche einschränken, die horizontale Abstände darstellen (also keine Zeilenumbrüche), ersetzen Sie ‹\s› durch ‹[^\S\r\n]›. Damit wird verhindert, dass Sie dop-

324 | Kapitel 5: Wörter, Zeilen und Sonderzeichen

pelte Wörter finden, die sich nicht gemeinsam in einer Zeile befinden. Bei der PCRE 7 und in Perl 5.10 gibt es die Zeichenklassenabkürzung ‹\h›, die Sie hier vielleicht lieber einsetzen wollen, weil damit nur horizontaler Whitespace gefunden wird. Schließlich stellen die Wortgrenzen am Anfang und am Ende des regulären Ausdrucks sicher, dass die Übereinstimmungen nicht innerhalb anderer Wörter liegen, wie zum Beispiel bei „die Diebe“. Beachten Sie, dass doppelte Wörter nicht immer falsch sind, daher ist es zu gefährlich, sie einfach ohne Kontrolle zu löschen. Die Konstrukte „die die“ oder „das das“ sind durchaus korrekt. Homonyme, Namen, lautmalerische Wörter (wie zum Beispiel „oink oink“ oder „ha ha“) und einige andere Konstrukte führen auch durchaus zu bewusst wiederholten Wörtern. Daher werden Sie in den meisten Fällen jede Übereinstimmung in ihrem Kontext überprüfen müssen.

Siehe auch Rezept 2.10 behandelt Rückwärtsreferenzen im Detail. Rezept 5.9 zeigt, wie man doppelte Textzeilen findet.

5.9

Doppelte Zeilen entfernen

Problem Sie haben eine Logdatei, die Ausgabe einer Datenbankabfrage oder eine andere Art von Datei oder String mit doppelten Zeilen. Sie müssen diese doppelten Einträge mit einem Texteditor oder einem ähnlichen Tool entfernen.

Lösung Es gibt eine Reihe von Softwaretools (wie zum Beispiel das Befehlszeilentool uniq unter Unix und das Windows PowerShell Cmdlet Get-Unique), mit denen Sie doppelte Zeilen in einer Datei oder einem String entfernen können. Die folgenden Abschnitte enthalten drei Regex-basierte Ansätze, die besonders dann hilfreich sein können, wenn man versucht, diese Aufgabe in einem nicht skriptbaren Texteditor umzusetzen, der aber immerhin mithilfe von regulären Ausdrücken suchen und ersetzen kann. Beim Programmieren sollten die Optionen 2 und 3 vermieden werden, da sie im Vergleich zu anderen verfügbaren Vorgehensweisen ineffizient sind. Hier sollte man zum Beispiel eher auf ein Hash-Objekt zurückgreifen, um die Eindeutigkeit von Zeilen zu gewährleisten. Aber die erste Option (bei der Sie die Zeilen vorher sortieren müssen, sofern Sie nicht nur direkt hintereinanderliegende doppelte Zeilen entfernen wollen) kann auch hier durchaus akzeptabel sein, da sie sich schnell und einfach implementieren lässt.

5.9 Doppelte Zeilen entfernen | 325

Option 1: Zeilen sortieren und hintereinanderliegende Duplikate entfernen Wenn Sie die Zeilen in der Datei oder dem String sortieren können, sodass doppelte Zeilen direkt hintereinanderliegen, sollten Sie das tun, sofern die Reihenfolge der Zeilen nicht beibehalten werden muss. Denn damit wird das Suchen und Ersetzen der doppelten Einträge viel einfacher und effizienter. Nach dem Sortieren der Zeilen verwenden Sie die folgende Regex mit dem ErsetzungsString, um die Duplikate loszuwerden: ^(.*)(?:(?:\r?\n|\r)\1)+$

Regex-Optionen: ^ und $ passen auf Zeilenumbruch (Punkt passt auf Zeilenumbruch darf nicht gesetzt sein) Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Zu ersetzen durch: $1

Ersetzungstextvarianten: .NET, Java, JavaScript, Perl, PHP \1

Ersetzungstextvarianten: Python, Ruby Dieser reguläre Ausdruck nutzt eine einfangende Gruppe und eine Rückwärtsreferenz (neben anderen Bestandteilen), um zwei oder mehr aufeinanderfolgende doppelte Zeilen zu finden. Mit einer Rückwärtsreferenz im Ersetzungstext fügen Sie nur die erste Zeile wieder ein. In Rezept 3.15 finden Sie Beispielcode, den Sie auch hier verwenden können.

Option 2: Das letzte Vorkommen jeder doppelten Zeile in einer unsortierten Datei behalten Wenn Sie einen Texteditor nutzen, mit dem Sie Zeilen nicht sortieren können, oder wenn es wichtig ist, die ursprüngliche Reihenfolge beizubehalten, können Sie mit der folgenden Lösung doppelte Zeilen entfernen, auch wenn es zwischen ihnen andere Zeilen gibt: ^([^\r\n]*)(?:\r?\n|\r)(?=.*^\1$)

Regex-Optionen: Punkt passt auf Zeilenumbruch, ^ und $ passen auf Zeilenumbruch Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Das Gleiche noch einmal als JavaScript-kompatible Regex, bei der die Option Punkt passt auf Zeilenumbruch nicht notwendig ist: ^(.*)(?:\r?\n|\r)(?=[\s\S]*^\1$)

Regex-Optionen: ^ und $ passen auf Zeilenumbruch (Punkt passt auf Zeilenumbruch darf nicht gesetzt sein) Regex-Variante: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Zu ersetzen durch: (Einen leeren String, also nichts.) Ersetzungstextvarianten: nicht notwendig

326 | Kapitel 5: Wörter, Zeilen und Sonderzeichen

Option 3: Das erste Vorkommen jeder doppelten Zeile in einer unsortierten Datei behalten Wenn Sie das erste Vorkommen jeder doppelten Zeile behalten wollen, brauchen Sie einen etwas anderen Ansatz. Dies sind der reguläre Ausdruck und der Ersetzungstext, die wir verwenden werden: ^([^\r\n]*)$(.*?)(?:(?:\r?\n|\r)\1$)+

Regex-Optionen: Punkt passt auf Zeilenumbruch, ^ und $ passen auf Zeilenumbruch Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Auch hier sind ein paar Änderungen nötig, um diese Regex mit JavaScript nutzen zu können, da JavaScript keine Option Punkt passt auf Zeilenumbruch besitzt. ^(.*)$([\s\S]*?)(?:(?:\r?\n|\r)\1$)+

Regex-Optionen: ^ und $ passen auf Zeilenumbruch (Punkt passt auf Zeilenumbruch darf nicht gesetzt sein) Regex-Variante: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Zu ersetzen durch: $1$2

Ersetzungstextvarianten: .NET, Java, JavaScript, Perl, PHP \1\2

Ersetzungstextvarianten: Python, Ruby Anders als bei den Regexes der Optionen 1 und 2 können in dieser Version nicht alle doppelten Zeilen mit einem Suchen-und-Ersetzen-Durchlauf erledigt werden. Sie werden Alle ersetzen so lange anwenden müssen, bis die Regex keine Übereinstimmungen mehr in Ihrem String findet. Denn erst dann werden keine doppelten Zeilen mehr vorhanden sein. In „Diskussion“ erfahren Sie mehr Details dazu.

Diskussion Option 1: Zeilen sortieren und hintereinanderliegende Duplikate entfernen Diese Regex entfernt bei aufeinanderfolgenden doppelten Zeilen alle bis auf die erste. Sie entfernt keine doppelten Zeilen, die durch andere Zeilen getrennt sind. Lassen Sie uns den Prozess Schritt für Schritt durchgehen. Zuerst passt der Zirkumflex (‹^›) am Anfang des regulären Ausdrucks auf den Zeilenanfang. Normalerweise würde er nur am Anfang des Gesamttexts passen, daher müssen Sie sicherstellen, dass die Option ^ und $ passen auf Zeilenumbruch aktiv ist (in Rezept 3.4 erfahren Sie, wie Sie Regex-Optionen einschalten). Als Nächstes passt ‹.*› innerhalb der einfangenden Klammern auf den gesamten Inhalt einer Zeile (selbst wenn sie leer ist), und der Wert wird als Rückwärtsreferenz 1 gespeichert. Damit das korrekt funktioniert, darf die Option Punkt passt auf Zeilenumbruch nicht aktiv sein. Ansonsten würde die Punkt-Stern-Kombination auf alles bis zum Ende des Strings passen.

5.9 Doppelte Zeilen entfernen | 327

Innerhalb einer äußeren, nicht-einfangenden Gruppe nutzen wir ‹(?:\r?\n|\r)›, um ein Zeilenende in Windows (‹\r\n›), Unix/Linux/OS X (‹\n›) oder im alten Mac OS (‹\r›) zu finden. Die Rückwärtsreferenz ‹\1› versucht dann, die Zeile zu finden, die wir gerade gefunden hatten. Wenn an dieser Stelle nicht die gleiche Zeile vorhanden ist, gibt es hier keine Übereinstimmung, und die Regex-Engine fährt mit ihrer Suche fort. Passt die Zeile, wiederholen wir die Gruppe (bestehend aus einer Zeilenumbruchfolge und der Rückwärtsreferenz 1) mit dem Quantor ‹+›, um noch weitere doppelte Zeilen zu finden. Schließlich nutzen wir das Dollarzeichen am Ende der Regex, um sicherzustellen, dass wir uns am Zeilenende befinden. Damit finden wir auch nur wirklich identische Zeilen und nicht solche, die nur mit den gleichen Zeichen beginnen wie die vorherige Zeile. Da wir hier suchen und ersetzen, wird jede vollständige Übereinstimmung (einschließlich der Originalzeile und der Zeilentrennzeichen) aus dem String entfernt. Wir ersetzen sie mit der Rückwärtsreferenz 1, um die Ursprungszeile beizubehalten.

Option 2: Das letzte Vorkommen jeder doppelten Zeile in einer unsortierten Datei behalten Im Vergleich zur Regex aus Option 1 gibt es hier eine Reihe von Änderungen, denn die erste Regex findet nur direkt aufeinanderfolgende doppelte Zeilen. So wurde in der Nicht-JavaScript-Version der Regex aus Option 2 der Punkt innerhalb der einfangenden Gruppe durch ‹[^\r\n]› ersetzt (jedes Zeichen außer einem Zeilenumbruch) und die Option Punkt passt auf Zeilenumbruch aktiviert. Das liegt daran, dass später noch ein Punkt genutzt wird, um beliebige Zeichen zu finden – eben auch Zeilenumbrüche. Und es wurde ein Lookahead ergänzt, um nach doppelten Zeilen weiter unten im String zu suchen. Da das Lookahead keine Zeichen konsumiert, ist der von der Regex gefundene Text immer eine einzelne Zeile (zusammen mit dem folgenden Zeilenumbruch), die auf jeden Fall weiter unten im String nochmals vorkommen wird. Ersetzt man alle Übereinstimmungen durch leere Strings, werden die doppelten Zeilen entfernt, und es bleibt nur deren jeweils letztes Vorkommen.

Option 3: Das erste Vorkommen jeder doppelten Zeile in einer unsortierten Datei behalten Da Lookbehinds in nicht so vielen Varianten unterstützt werden wie Lookaheads (und man dann auch nicht immer so weit zurückschauen kann, wie man es braucht), unterscheidet sich die Regex aus Option 3 deutlich von der vorherigen. Statt Zeilen zu finden, von denen man weiß, dass sie später noch einmal vorkommen (was die Taktik in Option 2 ist), passt diese Regex auf eine Zeile in einem String, die weiter unten folgende Dublette dieser Zeile und alle Zeilen dazwischen. Die ursprüngliche Zeile wird als Rückwärtsreferenz 1 gespeichert, die Zeilen dazwischen (wenn es denn welche gibt) als Rückwärtsreferenz 2. Indem Sie jede Übereinstimmung durch die Rückwärtsreferenzen 1 und 2 ersetzen, fügen Sie nur die Teile wieder ein, die Sie behalten wollen. Die abschließende doppelte Zeile und der direkt davorliegende Zeilenumbruch werden aber verworfen.

328 | Kapitel 5: Wörter, Zeilen und Sonderzeichen

Dieses alternative Vorgehen hat allerdings ein paar Haken. Erstens: Eine Übereinstimmung mit doppelten Zeilen kann wiederum andere Zeilen enthalten, in denen es auch (andere) Dubletten gibt. Diese werden aber durch ein Alles ersetzen übersprungen. Zweitens: Wenn eine Zeile nicht nur doppelt, sondern drei- oder mehrfach vorkommt, wird die Regex die ersten beiden entsprechenden Zeilen finden, aber nicht den Rest. Dort werden (bei mehr als dreifachem Vorkommen) wiederum eventuell Dubletten gefunden. Damit wird eine einzelne Operation Alles ersetzen höchstens jede zweite Dublette einer bestimmten Zeile entfernen. Um beide Probleme zu lösen und sicherzustellen, dass alle Dubletten entfernt sind, müssen Sie wiederholt suchen und ersetzen, bis die Regex keine Übereinstimmungen mehr findet. Schauen Sie sich einmal an, wie die Regex mit folgendem Ausgangstext arbeitet: Wert1 Wert2 Wert2 Wert3 Wert3 Wert1 Wert2

Um alle Dubletten aus diesem String zu entfernen, benötigt man drei Durchläufe. In Tabelle 5-1 werden die Ergebnisse jedes Durchlaufs gezeigt. Tabelle 5-1: Ersetzungsdurchläufe Durchlauf 1

Durchlauf 2

Durchlauf 3

Ergebnis

Matchtext

Wert1

Wert1

Wert1

Wert2

Wert2

Wert2

Wert2

Wert2

Wert2

Wert3

Wert3

Wert3

Wert3

Wert2

Wert3

Wert3

Wert1

Wert2

Wert2 Eine Übereinstimmung/Ersetzung

Zwei Übereinstimmungen/Ersetzungen

Eine Übereinstimmung/Ersetzung

Keine weiteren Dubletten

Siehe auch In Rezept 2.10 werden Rückwärtsreferenzen im Detail erklärt. In Rezept 5.8 erfahren Sie, wie Sie Wortdubletten finden können.

5.9 Doppelte Zeilen entfernen | 329

5.10 Vollständige Zeilen finden, die ein bestimmtes Wort enthalten Problem Sie wollen alle Zeilen finden, die das Wort Ninja enthalten.

Lösung ^.*\bNinja\b.*$

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, ^ und $ passen auf Zeilenumbruch (Punkt passt auf Zeilenumbruch darf nicht gesetzt sein) Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Es ist häufig nützlich, vollständige Zeilen zu finden, um sie in einer Liste zu sammeln oder um sie zu entfernen. Um eine Zeile zu finden, die das Wort Ninja enthält, beginnen wir mit dem regulären Ausdruck ‹\bNinja\b›. Die Wortgrenzen-Tokens auf beiden Seiten stellen sicher, dass wir „Ninja“ nur finden, wenn es als vollständiges Wort erscheint (siehe Rezept 2.6). Um die Regex so zu erweitern, dass sie eine komplette Zeile findet, ergänzen Sie an beiden Enden ‹.*›. Die Punkt-Stern-Folge passt auf null oder mehr Zeichen innerhalb der aktuellen Zeile. Die Stern-Quantoren sind gierig, daher konsumieren sie so viel Text wie möglich. Die erste Punkt-Stern-Folge passt bis zum letzten Vorkommen von „Ninja“ auf der Zeile, während die zweite Punkt-Stern-Folge alle Zeichen danach (außer Zeilenumbrüchen) findet. Durch ein Zirkumflex am Anfang und ein Dollarzeichen am Ende des regulären Ausdrucks wird schließlich sichergestellt, dass die Übereinstimmung eine komplette Zeile umfasst. Streng genommen ist das Dollarzeichen am Ende überflüssig, weil Punkt und gieriger Stern immer nur bis zum Ende der Zeile laufen. Aber das Dollarzeichen tut nicht weh, und der reguläre Ausdruck wird dadurch etwas selbsterklärender. Durch das Hinzufügen von Zeilen- oder String-Ankern (sofern passend) lassen sich gelegentlich unerwartete Phänomene vermeiden, daher ist es keine schlechte Idee, sich dies anzugewöhnen. Beachten Sie, dass der Zirkumflex im Gegensatz zum Dollarzeichen nicht überflüssig ist, da man dadurch sicherstellt, dass die Regex die vollständige Zeile abdeckt, selbst wenn die Suche aus irgendeinem Grund erst in der Mitte der Zeile loslegt. Denken Sie daran, dass die drei wichtigsten Metazeichen zum Eingrenzen der Übereinstimmung auf eine einzelne Zeile (die Anker ‹^› und ‹$› sowie der Punkt) keine feste Bedeutung besitzen. Um sie an Zeilen zu orientieren, müssen Sie die Option aktivieren, mit der ‹^› und ‹$› an Zeilenumbrüchen passen, während die Option, mit der der Punkt

330 | Kapitel 5: Wörter, Zeilen und Sonderzeichen

auch einen Zeilenumbruch findet, eben nicht aktiviert sein darf. In Rezept 3.4 wird gezeigt, wie Sie diese Optionen in Ihrem Code setzen können. Wenn Sie JavaScript oder Ruby nutzen, müssen Sie sich nur um eine der beiden Optionen Gedanken machen, weil JavaScript keine Option anbietet, mit dem Punkt Zeilenumbrüche zu finden, und die Zirkumflex- und Dollarzeichen-Anker bei Ruby immer einen Zeilenumbruch finden.

Variationen Um nach Zeilen zu suchen, die eines von mehreren Wörtern enthalten, verwenden Sie eine Alternation: ^.*\b(eins|zwei|drei)\b.*$

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, ^ und $ passen auf Zeilenumbruch (Punkt passt auf Zeilenumbruch darf nicht aktiv sein) Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Dieser reguläre Ausdruck passt auf jede Zeile, die mindestens eines der Wörter „eins“, „zwei“ oder „drei“ enthält. Die Klammern um die Wörter erfüllen gleich zwei Zwecke. Zum einen begrenzen sie die Reichweite der Alternation, und zum anderen fangen sie das Wort in der Rückwärtsreferenz 1 ein, das tatsächlich gefunden wurde. Wenn die Zeile mehr als eines der Wörter enthält, enthält die Rückwärtsreferenz das Wort, das in der Zeile am weitesten rechts steht. Das liegt daran, dass der Stern-Quantor vor den Klammern gierig ist und mit dem Punkt so viel Text wie möglich einfängt. Machen Sie den Stern genügsam, wie in ‹^.*?\b(eins|zwei|drei)\b.*$›, enthält die Rückwärtsreferenz 1 das Wort aus Ihrer Liste, das am weitesten links steht. Um Zeilen zu finden, die mehrere Wörter enthalten müssen, verwenden Sie Lookaheads: ^(?=.*?\beins\b)(?=.*?\bzwei\b)(?=.*?\bdrei\b).+$

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, ^ und $ passen auf Zeilenumbruch (Punkt passt auf Zeilenumbruch darf nicht aktiv sein) Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Dieser reguläre Ausdruck verwendet positive Lookaheads, um Zeilen zu finden, die drei notwendige Wörter enthalten. Das ‹.+› wird genutzt, um die eigentliche Zeile zu finden, nachdem die Lookaheads festgestellt haben, dass die Zeile auch die Anforderungen erfüllt.

Siehe auch In Rezept 5.11 wird gezeigt, wie Sie vollständige Zeilen finden, die ein bestimmtes Wort nicht enthalten.

5.10 Vollständige Zeilen finden, die ein bestimmtes Wort enthalten | 331

5.11 Vollständige Zeilen finden, die ein bestimmtes Wort nicht enthalten Problem Sie wollen vollständige Zeilen finden, die nicht das Wort Ninja enthalten.

Lösung ^(?:(?!\bNinja\b).)*$

Regex-Optionen: Groß-/Kleinschreibung wird ignoriert, ^ und $ passen auf Zeilenumbruch (Punkt passt auf Zeilenumbruch darf nicht aktiv sein) Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Um eine Zeile zu finden, die etwas nicht enthält, nutzen Sie negative Lookaheads (beschrieben in Rezept 2.16). Beachten Sie, dass in diesem regulären Ausdruck ein negatives Lookahead und ein Punkt zusammen in einer nicht-einfangenden Gruppe wiederholt werden. Damit ist sichergestellt, dass die Regex ‹\bNinja\b› an jeder möglichen Position in der Zeile fehlschlägt. Die Anker ‹^› und ‹$› werden an den Anfang und das Ende des regulären Ausdrucks gesetzt, um auf jeden Fall eine vollständige Zeile zu finden. Die Optionen, die Sie auf diesen regulären Ausdruck anwenden, bestimmen, ob der gesamte Ausgangstext oder immer nur eine Zeile durchforstet wird. Mit der aktiven Option, ‹^› und ‹$› auf Zeilenumbrüche passen zu lassen, und der deaktivierten Option, Punkte auf Zeilenumbrüche passen zu lassen, arbeitet dieser reguläre Ausdruck wie gewünscht zeilenweise. Schalten Sie beide Optionen um, wird der reguläre Ausdruck jeden String finden, der nicht das Wort „Ninja“ enthält. Das Testen eines negativen Lookahead an jeder Position einer Zeile oder eines Strings ist ziemlich ineffizient. Diese Lösung sollte nur in Situationen genutzt werden, in denen ein einziger regulärer Ausdruck verwendet werden kann, zum Beispiel in Anwendungen, die nicht geskriptet werden können. Beim Programmieren ist Rezept 3.21 eine deutlich effizientere Lösung.

Siehe auch In Rezept 5.10 wird gezeigt, wie Sie vollständige Zeilen finden können, die ein bestimmtes Wort enthalten.

332 | Kapitel 5: Wörter, Zeilen und Sonderzeichen

5.12 Führenden und abschließenden Whitespace entfernen Problem Sie wollen führenden und abschließenden Whitespace aus einem String entfernen.

Lösung Um das Ganze einfach und schnell zu halten, nutzt man am besten zwei Ersetzungsdurchläufe – einen, um den führenden Whitespace zu entfernen, und einen für den abschließenden Whitespace: ^\s+

Regex-Optionen: Keine (^ und $ passen auf Zeilenumbrüche darf nicht aktiv sein) Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby \s+$

Regex-Optionen: Keine (^ und $ passen auf Zeilenumbrüche darf nicht aktiv sein) Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Ersetzen Sie die gefundenen Übereinstimmungen beider regulären Ausdrücke einfach durch einen leeren String. In Rezept 3.14 können Sie nachlesen, wie das funktioniert. Bei beiden regulären Ausdrücken müssen Sie nur die erste gefundene Übereinstimmung entfernen, da der gesamte führende beziehungsweise abschließende Whitespace in einem Schritt gefunden wird.

Diskussion Führenden und abschließenden Whitespace zu entfernen, ist eine einfache und häufig verlangte Aufgabe. Die beiden regulären Ausdrücke enthalten jeweils drei Elemente: einen Anker, um die Position am Anfang oder Ende des Strings sicherzustellen (‹^› und ‹$›), die Zeichenklassenabkürzung, um beliebige Whitespace-Zeichen zu finden (‹\s›), und den Quantor, mit dem die Klasse einmal oder mehrfach gefunden werden kann (‹+›). Viele Programmiersprachen stellen schon eine Funktion bereit, mit der führender oder abschließender Whitespace entfernt werden kann. Diese Funktionen heißen oft trim oder strip. In Tabelle 5-2 sehen Sie, wie Sie diese eingebauten Funktionen oder Methoden in einer Reihe von Programmiersprachen verwenden. Tabelle 5-2: Standardfunktionen, um führenden und abschließenden Whitespace zu entfernen Programmiersprache(n)

Beispielanwendung

C#, VB.NET

String.Trim([Zeichen])

Java

string.trim()

PHP

trim($String)

Python, Ruby

string.strip()

5.12 Führenden und abschließenden Whitespace entfernen | 333

JavaScript und Perl besitzen solche Funktionen nicht in ihren Standardbibliotheken, aber Sie können leicht eine eigene schreiben. In Perl: sub trim { my $string = shift; $string =~ s/^\s+//; $string =~ s/\s+$//; return $string; }

In JavaScript: function trim (string) { return string.replace(/^\s+/, '').replace(/\s+$/, ''); } // Alternativ trim zu einer Methode aller Strings machen: String.prototype.trim = function () { return this.replace(/^\s+/, '').replace(/\s+$/, ''); };

Sowohl in Perl als auch in JavaScript passt ‹\s› auf alle Zeichen, die durch den Unicode-Standard als Whitespace definiert sind – also mehr als nur Leerzeichen, Tab, Line Feed und Carriage Return.

Variationen Es gibt eigentlich eine ganze Reihe von Möglichkeiten, mit einem regulären Ausdruck einen String zu „trimmen“. Aber alle sind bei langen Strings (bei denen Performance durchaus ein Thema ist) langsamer als die beiden einfachen Ersetzungen. Im Folgenden finden Sie ein paar gebräuchliche Alternativen, die Sie in Betracht ziehen könnten. Alle sind in JavaScript geschrieben, und da JavaScript die Option Punkt passt auf Zeilenumbruch nicht besitzt, greifen die regulären Ausdrücke auf ‹[\s\S]› zurück, um ein beliebiges Zeichen zu finden – auch Zeilenumbrüche. In anderen Programmiersprachen verwenden Sie einen Punkt und aktivieren die Option Punkt passt auf Zeilenumbruch. string.replace(/^\s+|\s+$/g, '');

Dies ist die vermutlich gebräuchlichste Lösung. Sie kombiniert die beiden einfachen Regexes per Alternation (siehe Rezept 2.8) und nutzt die Option /g (global), um alle Übereinstimmungen zu ersetzen (wenn es sowohl führenden als auch abschließenden Whitespace gibt, passt die Regex zwei Mal). Das ist kein wirklich grässlicher Ansatz, aber er ist bei langen Strings langsamer als die Verwendung der beiden einfachen Ersetzungen. string.replace(/^\s*([\s\S]*?)\s*$/, '$1')

Dieser reguläre Ausdruck passt auf den gesamten String und fängt den Text vom ersten bis zum letzten Nicht-Whitespace-Zeichen (wenn es denn welchen gibt) in der Rückwärtsreferenz 1. Ersetzen Sie den gesamten String durch die Rückwärtsreferenz 1, erhalten Sie eine zurechtgestutzte Version des Strings. 334 | Kapitel 5: Wörter, Zeilen und Sonderzeichen

Dieser Ansatz ist prinzipiell einfach, aber der genügsame Quantor innerhalb der einfangenden Gruppe sorgt dafür, dass die Regex-Engine eine ganze Menge zu tun hat. Daher wird dieser reguläre Ausdruck bei langen Strings eher langsam sein. Hat die Regex-Engine die einfangende Gruppe erreicht, sorgt der genügsame Quantor dafür, dass die Zeichenklasse ‹[\s\S]› so wenig wie möglich gefunden wird. Daher greift die Regex-Engine immer nur auf ein Zeichen gleichzeitig zu und versucht, nach jedem Zeichen das restliche Muster (‹\s*$›) zu finden. Wenn das nicht funktioniert, weil es keine Nicht-Whitespace-Zeichen mehr nach der aktuellen Position im String gibt, nimmt die Engine ein weiteres Zeichen dazu und versucht dann erneut, das restliche Muster zu finden. string.replace(/^\s*([\s\S]*\S)?\s*$/, '$1')

Diese Regex ist der vorherigen sehr ähnlich, aber der genügsame Quantor wird aus Performancegründen durch einen gierigen ersetzt. Um sicherzustellen, dass die einfangende Gruppe trotzdem nur bis zum letzten Nicht-Whitespace-Zeichen läuft, nutzen wir ein abschließendes ‹\S›. Da die Regex dennoch auch Strings aus reinen Whitespaces erkennen muss, ist die gesamte einfangende Gruppe optional, indem ihr ein abschließender Fragezeichen-Quantor angehängt wird. Lassen Sie uns genauer anschauen, wie diese Regex arbeitet. Hier wiederholt der gierige Stern in ‹[\s\S]*› das Muster für jedes Zeichen, bis das Ende des Strings erreicht ist. Die Regex-Engine geht dann vom Ende des Strings per Backtracking ein Zeichen zurück, bis sie das folgende ‹\S› finden kann oder bis wieder das erste gefundene Zeichen innerhalb der einfangenden Gruppe erreicht wurde. Sofern es nicht mehr abschließende Whitespace-Zeichen als Text gibt, ist diese Regex im Allgemeinen schneller als die vorherige mit dem genügsamen Quantor. Aber auch sie kann die Performance der beiden einfachen Substitutionen nicht erreichen. string.replace(/^\s*(\S*(?:\s+\S+)*)\s*$/, '$1')

Das ist ein recht verbreiteter Ansatz, daher soll er hier als schlechtes Beispiel mit aufgeführt werden. Es gibt keinen guten Grund, diese Regex zu verwenden, da sie langsamer als alle anderen hier vorgestellten Regexes ist. Sie entspricht den letzten beiden Regexes, da sie den gesamten String findet und Sie ihn durch den Teil ersetzen, den Sie beibehalten wollen. Aber da die innere nicht-einfangende Gruppe immer nur ein Wort gleichzeitig findet, gibt es eine Reihe von getrennten Schritten, die die Regex-Engine durchführen muss. Beim Zurechtstutzen kurzer Strings wird man den Performanceverlust nicht bemerken, aber bei sehr langen Strings mit vielen Wörtern kann diese Regex zu einem echten Performanceproblem werden. Ein paar Regex-Implementierungen enthalten pfiffige Optimierungen, die die hier beschriebenen internen Suchprozesse verändern und damit einige der vorgestellten Regexes etwas schneller oder langsamer laufen lassen, als wir beschrieben haben. Aber die bestechende Einfachheit bei der vorgeschlagenen Lösung mit den beiden Ersetzungen führt zu einer durchgehend guten Performance – bei unterschiedlichen String-Längen und -Inhalten. Daher ist es die beste Lösung.

5.12 Führenden und abschließenden Whitespace entfernen | 335

Siehe auch Rezept 5.13.

5.13 Wiederholten Whitespace durch ein einzelnes Leerzeichen ersetzen Problem Um Benutzereingaben oder andere Daten zunächst „aufzuräumen”, wollen Sie wiederholte Whitespace-Zeichen durch ein einzelnes Leerzeichen ersetzen. Jegliche Tabs, Zeilenumbrüche oder andere Whitespace-Zeichen sollten ebenfalls durch ein Leerzeichen ersetzt werden.

Lösung Um einen der folgenden regulären Ausdrücke zu implementieren, ersetzen Sie einfach alle Übereinstimmungen durch ein einzelnes Leerzeichen. In Rezept 3.14 ist beschrieben, wie der Code dafür aussieht.

Alle Whitespace-Zeichen finden \s+

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Horizontale Whitespace-Zeichen finden [z\t]+

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Häufig soll man in einem Text alle Whitespace-Zeichen durch ein einzelnes Leerzeichen ersetzen. In HTML werden zum Beispiel wiederholte Whitespace-Zeichen beim Rendern einer Seite schlicht ignoriert (von ein paar Ausnahmen abgesehen), daher kann man durch das Entfernen von überflüssigem Whitespace die Dateigröße reduzieren, ohne sich negative Effekte einzuhandeln.

336 | Kapitel 5: Wörter, Zeilen und Sonderzeichen

Alle Whitespace-Zeichen finden In dieser Lösung wird jede Folge von Whitespace-Zeichen (Zeilenumbrüchen, Tabs, Leerzeichen und so weiter) durch ein einzelnes Leerzeichen ersetzt. Da der Quantor ‹+› die Whitespace-Klasse (‹\s›) ein Mal oder mehrfach findet, wird zum Beispiel selbst ein einfaches Tab-Zeichen durch ein Leerzeichen ersetzt. Ersetzen Sie das ‹+› durch ‹{2,}›, werden nur Folgen von zwei oder mehr Whitespace-Zeichen ersetzt. Das kann zu weniger Ersetzungsvorgängen und damit einer verbesserten Performance führen, aber so können auch Tab-Zeichen oder Zeilenumbrüche übrig bleiben, die ansonsten durch Leerzeichen ersetzt worden wären. Der bessere Ansatz hängt daher davon ab, was Sie erreichen wollen.

Horizontale Whitespace-Zeichen finden Diese Regex arbeitet genau so wie die vorherige Lösung, nur dass sie Zeilenumbrüche bestehen lässt. Lediglich Tabs und Leerzeichen werden ersetzt.

Siehe auch Rezept 5.12.

5.14 Regex-Metazeichen maskieren Problem Sie wollen einen literalen String verwenden, der von einem Anwender oder aus einer anderen Quelle stammt, um ihn in Ihren regulären Ausdruck einzubauen. Allerdings wollen Sie alle Regex-Metazeichen innerhalb des Strings maskieren, bevor Sie ihn in Ihre Regex einbetten, um unerwünschte Nebeneffekte zu verhindern.

Lösung Indem Sie vor jedem Zeichen, das in einem regulären Ausdruck potenziell eine besondere Bedeutung haben kann, einen Backslash einfügen, können Sie das sich so ergebende Muster problemlos verwenden, um eine literale Zeichenfolge zu finden. Von den in diesem Buch behandelten Programmiersprachen haben abgesehen von JavaScript alle eine eingebaute Funktion oder Methode, um diese Aufgabe zu erledigen (siehe Tabelle 5-3). Aus Gründen der Vollständigkeit zeigen wir hier aber, wie Sie das mit Ihrer eigenen Regex erreichen – auch in Sprachen, die schon eine fertige Lösung bieten.

Eingebaute Lösungen In Tabelle 5-3 sind die vorgefertigten Funktionen aufgeführt, die dieses Problem lösen.

5.14 Regex-Metazeichen maskieren | 337

Tabelle 5-3: Eingebaute Lösungen für das Maskieren von Regex-Metazeichen Sprache

Funktion

C#, VB.NET

Regex.Escape(str)

Java

Pattern.quote(str)

Perl

quotemeta(str)

PHP

preg_quote(str, [delimiter])

Python

re.escape(str)

Ruby

Regexp.escape(str)

In dieser Liste fehlt JavaScript – es hat keine eingebaute Funktion, die diesen Zweck erfüllt.

Regulärer Ausdruck Auch wenn am besten eine eingebaute Funktion genutzt wird (wenn es sie denn gibt), können Sie auch den folgenden regulären Ausdruck mit dem entsprechenden ErsetzungsString verwenden. Stellen Sie aber sicher, dass nicht nur der erste, sondern alle Übereinstimmungen ersetzt werden. In Rezept 3.15 finden Sie Code, mit dem Sie Übereinstimmungen durch Text ersetzen können, die eine Rückwärtsreferenz enthalten. Die Rückwärtsreferenz ist hier notwendig, um das gefundene Spezialzeichen zusammen mit einem führenden Backslash wieder einfügen zu können: [[\]{}()*+?.\\|^$\-,&#\s]

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Ersetzung Die folgenden Ersetzungs-Strings enthalten einen literalen Backslash. Die Strings werden ohne zusätzliche Backslashs aufgeführt, die notwendig sein können, um die Backslashs bei String-Literalen zu maskieren. In Rezept 2.19 finden Sie mehr Details über Ersetzungstextvarianten. \$&

Ersetzungstextvarianten: .NET, JavaScript \\$&

Ersetzungstextvariante: Perl \\$0

Ersetzungstextvarianten: Java, PHP \\\0

Ersetzungstextvarianten: PHP, Ruby

338 | Kapitel 5: Wörter, Zeilen und Sonderzeichen

\\\&

Ersetzungstextvariante: Ruby \\\g

Ersetzungstextvariante: Python

Beispielfunktion in JavaScript Hier sehen Sie ein Beispiel dafür, wie Sie in JavaScript mit dem regulären Ausdruck und dem Ersetzungstext eine statische Methode RegExp.escape erstellen können: RegExp.escape = function (str) { return str.replace(/[[\]{}()*+?.\\|^$\-,&#\s]/g, "\\$&"); }; // Testen var str = "Hello.World?"; var escaped_str = RegExp.escape(str); alert(escaped_str == "Hallo\\.Welt\\?"); // -> true

Diskussion Der reguläre Ausdruck in diesem Rezept steckt alle Regex-Metazeichen in eine einzelne Zeichenklasse. Lassen Sie uns jedes dieser Zeichen anschauen und erklären, warum es maskiert werden muss. Bei einigen ist das weniger offensichtlich als bei anderen: [] {} () ‹[› und ‹]› erzeugen Zeichenklassen. ‹{› und ‹}› erzeugen Intervall-Quantoren

und werden auch in verschiedenen anderen Konstrukten genutzt, wie zum Beispiel bei Unicode-Eigenschaften. ‹(› und ‹)› werden zum Gruppieren, Einfangen und in anderen Konstrukten verwendet. * + ?

Diese drei Zeichen sind Quantoren, die das vorherige Element null Mal oder häufiger, ein Mal oder häufiger oder null oder ein Mal wiederholen. Das Fragezeichen wird zudem nach einer öffnenden Klammern genutzt, um besondere Gruppen und andere Konstrukte zu erzeugen (das Gleiche gilt für den Stern in Perl 5.10 und PCRE 7). . \ |

Ein Punkt passt auf jedes Zeichen innerhalb einer Zeile oder eines Strings, ein Backslash maskiert ein Sonderzeichen oder sorgt dafür, dass ein literales Zeichen etwas Besonderes darstellt. Ein vertikaler Balken dient als Alternationsoption. ^ $

Zirkumflex und Dollar sind Anker, die den Anfang oder das Ende einer Zeile oder eines Strings finden. Der Zirkumflex kann zudem eine Zeichenklasse negieren. Die restlichen Zeichen, die von diesem regulären Ausdruck gefunden werden, haben nur in besonderen Fällen eine spezielle Bedeutung. Sie sind in der Liste enthalten, um auf Nummer sicher gehen zu können.

5.14 Regex-Metazeichen maskieren | 339

-

Ein Bindestrich erzeugt in einer Zeichenklasse einen Bereich. Er wird hier maskiert, um das unabsichtliche Erstellen von Bereichen zu vermeiden, wenn in einer Zeichenklasse Text eingefügt wird. Wenn Sie Text in eine Zeichenklasse einfügen, sollten Sie daran denken, dass damit nicht der eingebettete Text gefunden wird, sondern nur eines der Zeichen aus dem String. ,

Ein Komma wird innerhalb eines Intervall-Quantors wie zum Beispiel ‹{1,5}› verwendet. Da die meisten Regex-Varianten geschweifte Klammern als literale Zeichen betrachten, wenn sie keinen gültigen Quantor bilden, ist es möglich (wenn auch recht unwahrscheinlich), durch das Einfügen von Text ohne maskierte Kommata einen Quantor an einer Stelle zu erzeugen, an der dies so gar nicht gedacht war. &

Das Kaufmanns-Und ist in der Liste enthalten, weil zwei aufeinanderfolgende Kaufmanns-Und-Zeichen in Java eine Zeichenklassen-Schnittmenge erzeugen. In anderen Programmiersprachen kann man das Kaufmanns-Und gefahrlos aus der Liste der zu maskierenden Zeichen entfernen, aber es macht auch nichts, es drin zu lassen. # und Whitespace

Das Rautezeichen und Whitespace (durch ‹\s› gefunden) sind nur im FreiformModus Metazeichen. Auch für sie gilt, dass es nicht schadet, sie zu maskieren. Beim Ersetzungstext wird eines von fünf Tokens («$&», «\&», «$0», «\0» oder «\g») genutzt, um die gefundenen Zeichen zusammen mit einem führenden Backslash wieder einzufügen. In Perl ist $& eine echte Variable. Nutzt man sie in einem regulären Ausdruck, kann das nachteilige Auswirkungen auf alle regulären Ausdrücke haben. Wird $& irgendwo anders in Ihrem Perl-Programm verwendet, können Sie sie so viel verwenden, wie Sie wollen, weil der Performanceverlust nur einmal auftritt. Ansonsten ist es vermutlich besser, die gesamte Regex in eine einfangende Gruppe zu stecken und im Ersetzungstext $1 statt $& zu nutzen.

Variationen Wie schon in „Blockmaskierung“ auf Seite 29 in Rezept 2.1 beschrieben, können Sie in einem regulären Ausdruck eine Blockmaskierungsfolge erzeugen, indem Sie ‹\Q...\E› nutzen. Aber solche Blockmaskierungen werden nur in Java, der PCRE und Perl unterstützt, und selbst in diesen Sprachen sind sie nicht absolut idiotensicher. Um vollständig sicher zu sein, müssen Sie immer noch jedes Vorkommen von \E maskieren. In den meisten Fällen ist es vermutlich günstiger, einfach alle Regex-Metazeichen zu maskieren.

Siehe auch In Rezept 2.1 wird besprochen, wie man literale Zeichen findet. Die dort aufgeführte Liste mit zu maskierenden Zeichen ist kürzer, da sie sich nicht um Zeichen kümmert, die im Freiform-Modus oder beim Einfügen in ein längeres Muster maskiert werden müssen. 340 | Kapitel 5: Wörter, Zeilen und Sonderzeichen

KAPITEL 6

Zahlen

Reguläre Ausdrücke sind eigentlich dazu gedacht, mit Texten zu arbeiten. Sie verstehen nichts von der numerischen Bedeutung, die Menschen einer Folge von Ziffern zuordnen. Für einen regulären Ausdruck ist 56 nicht die Nummer sechsundfünfzig, sondern ein String mit zwei Zeichen, die die Ziffern 5 und 6 darstellen. Die Regex-Engine weiß, dass es sich um Ziffern handelt, da sie über die Zeichenklassenabkürzung ‹\d› gefunden werden können (siehe Rezept 2.3). Aber das war es auch schon. Die Engine weiß nicht, dass 56 eine höhere Bedeutung besitzt, so wie sie auch nicht weiß, dass man in :-) mehr sehen kann als drei Satzzeichen, die durch ‹\p{P}{3}› gefunden werden. Aber Zahlen sind ein wichtiger Bestandteil der Benutzereingaben oder Dateiinhalte, mit denen Sie arbeiten. Manchmal müssen Sie sie innerhalb eines regulären Ausdrucks finden, statt sie einfach an eine normale Programmiersprache zu geben – zum Beispiel wenn Sie herausbekommen möchten, ob eine Zahl im Bereich von 1 bis 100 liegt. Deshalb handelt dieses ganze Kapitel davon, wie man mit regulären Ausdrücken alle möglichen Arten von Zahlen finden kann. Wir beginnen mit ein paar Rezepten, die vielleicht trivial erscheinen, aber wichtige grundlegende Konzepte aufzeigen. Die folgenden Rezepte drehen sich dann um kompliziertere Regexes, und dort wird davon ausgegangen, dass Sie diese Konzepte verstanden haben.

6.1

Integer-Zahlen

Problem Sie wollen verschiedene Arten von ganzen Zahlen in einem längeren Text finden oder prüfen, ob eine String-Variable eine dezimale Ganzzahl enthält.

| 341

Lösung Positive dezimale Ganzzahlen (Integer-Zahlen) in einem längeren Text finden: \b[0-9]+\b

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Prüfen, ob ein String nur aus einer positiven dezimalen Ganzzahl besteht: \A[0-9]+\Z

Regex-Optionen: Keine Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^[0-9]+$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python Finden einer positiven dezimalen Ganzzahl, die in einem größeren Text für sich steht: (? 4, 'V' => 5, 'IX' => 9, 'X' => 10, 'XL' => 40, 'L' => 50, 'XC' => 90, 'C' => 100, 'CD' => 400, 'D' => 500, 'CM' => 900, 'M' => 1000); my $decimal = 0; while ($roman =~ m/[MDLV]|C[MD]?|X[CL]?|I[XV]?/ig) { $decimal += $r2d{uc($&)}; } return $decimal; } else { # Keine römische Zahl return 0; } }

Siehe auch Rezepte 2.3, 2.8, 2.9, 2.12, 2.16, 3.9 und 3.11.

366 | Kapitel 6: Zahlen

KAPITEL 7

URLs, Pfade und Internetadressen

Neben den Zahlen, die Thema des vorherigen Kapitels waren, sind in vielen Programmen die verschiedenen Pfade und Locators zum Finden von Daten wichtig: • URLs, URNs und entsprechende Strings • Domainnamen • IP-Adressen • Datei- und Ordnernamen in Microsoft Windows Speziell das URL-Format hat sich als so flexibel und nützlich herausgestellt, dass es für eine ganze Reihe von Ressourcen verwendet wird, die gar nichts mit dem World Wide Web zu tun haben. Damit werden sich die regulären Ausdrücke aus diesem Kapitel in erstaunlich vielen Situationen nutzen lassen.

7.1

URLs validieren

Problem Sie wollen prüfen, ob ein Text eine für Ihre Zwecke gültige URL enthält.

Lösung So gut wie jede URL zulassen: ^(https?|ftp|file)://.+$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python \A(https?|ftp|file)://.+\Z

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

| 367

Domainname ist erforderlich, Benutzername oder Passwort sind nicht zulässig: \A (https?|ftp):// [a-z0-9-]+(\.[a-z0-9-]+)+ ([/?].*)? \Z

# # # # #

Anker Schema Domain Pfad und/oder Parameter Anker

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^(https?|ftp)://[a-z0-9-]+(\.[a-z0-9-]+)+ ([/?].+)?$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Domainname ist erforderlich, Benutzername oder Passwort sind nicht zulässig. Das Schema (http oder ftp) kann weggelassen werden, wenn es sich aus der Subdomain ermitteln lässt (www oder ftp): \A ((https?|ftp)://|(www|ftp)\.) [a-z0-9-]+(\.[a-z0-9-]+)+ ([/?].*)? \Z

# # # # #

Anker Schema oder Subdomain Domain Pfad und/oder Parameter Anker

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^((https?|ftp)://|(www|ftp)\.)[a-z0-9-]+(\.[a-z0-9-]+)+([/?].*)?$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python Domainname und Pfad auf eine Bilddatei sind erforderlich. Benutzername, Passwort oder Parameter sind nicht zulässig: \A (https?|ftp):// [a-z0-9-]+(\.[a-z0-9-]+)+ (/[\w-]+)* /[\w-]+\.(gif|png|jpe?g) \Z

# # # # # #

Anker Schema Domain Pfad Datei Anker

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^(https?|ftp)://[a-z0-9-]+(\.[a-z0-9-]+)+(/[\w-]+)*/[\w-]+\.(gif|png|jpe?g)$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python

368 | Kapitel 7: URLs, Pfade und Internetadressen

Diskussion Sie können keinen regulären Ausdruck erstellen, der jede gültige URL findet und ungültige URLs immer aussortiert. Der Grund liegt darin, dass nahezu alles eine gültige URL sein kann – eventuell in noch nicht erfundenen Schemata. Das Überprüfen von URLs ist nur dann sinnvoll, wenn wir den Kontext kennen, in dem diese URLs gültig sein müssen. Wir können die zu akzeptierenden URLs auf Schemata begrenzen, die von der von uns verwendeten Software verarbeitet werden können. Alle regulären Ausdrücke aus diesem Rezept sind für URLs gedacht, die von Webbrowsern verwendet werden. Solche URLs haben die Form: scheme://user:[email protected]:80/path/file.ext?param=value¶m2 =value2#fragment

Alle diese Teile sind optional. Eine URL vom Typ file: hat nur einen Pfad. http:-URLs benötigen nur einen Domainnamen. Der erste reguläre Ausdruck in dieser Lösung prüft, ob die URL mit einem der für Webbrowser gebräuchlichen Schemata beginnt: http, https, ftp oder file. Der Zirkumflex verankert die Regex am Anfang des Strings (Rezept 2.5). Eine Alternation (Rezept 2.8) wird verwendet, um die möglichen Schemata aufzuführen. Mit ‹https?› kombiniert man ‹http|https›. Da die erste Regex recht unterschiedliche Schemata zulässt, wie zum Beispiel http und file, versucht sie gar nicht erst, den Text nach dem Schema zu überprüfen. ‹.+$› schnappt sich einfach alles bis zum Ende des Strings, sofern dieser keinen Zeilenumbruch enthält. Standardmäßig passt der Punkt (Rezept 2.4) auf alle Zeichen außer Zeilenumbrüchen, und das Dollarzeichen (Rezept 2.5) passt nicht an Zeilenumbrüchen. Ruby ist hier eine Ausnahme. Dort passen Zirkumflex und Dollar immer auf im String enthaltene Zeilenumbrüche, sodass wir stattdessen ‹\A› und ‹\Z› (Rezept 2.5) verwenden müssen. Streng genommen müssten Sie die gleichen Änderungen auch bei allen regulären Ausdrücke in diesem Rezept vornehmen – wenn Ihr Eingabe-String aus mehreren Zeilen bestehen kann und Sie vermeiden wollen, eine URL zu finden, die über mehrere Zeilen verteilt ist. Die nächsten beiden regulären Ausdrücke sind im Freiform-Modus angegeben (Rezept 2.18) und einfach andere Versionen der gleichen Regex. Die Freiform-Regex lässt sich leichter lesen, während die normale Version schneller eingetippt ist. JavaScript unterstützt keine Freiform-Regexes. Diese beiden Regexes akzeptieren nur Web- und FTP-URLs, wobei auf das HTTP- oder FTP-Schema etwas folgen muss, was wie ein gültiger Domainname aussieht. Der Domainname muss aus ASCII-Zeichen bestehen. Internationalisierte Domains (IDNs) werden nicht akzeptiert. Auf die Domain kann ein Pfad oder eine Liste mit Parametern folgen, die von der Domain entweder durch einen Schrägstrich oder ein Fragzeichen getrennt sind. Da sich das Fragezeichen innerhalb einer Zeichenklasse befindet

7.1 URLs validieren | 369

(Rezept 2.3), müssen wir es nicht maskieren, denn das Fragezeichen ist dort ein ganz normales Zeichen, während der Schrägstrich in einem regulären Ausdruck nirgendwo maskiert werden muss. (Wenn Sie ihn einmal in Quellcode maskiert sehen, liegt das daran, dass Perl und viele andere Programmiersprachen Schrägstriche nutzen, um literale reguläre Ausdrücke zu begrenzen.) Es wird nicht versucht, den Pfad oder die Parameter zu validieren. ‹.*› findet einfach alles, sofern es sich nicht um einen Zeilenumbruch handelt. Da sowohl der Pfad als auch die Parameter optional sind, wird ‹[/?].*› in eine Gruppe gesteckt, die durch ein Fragezeichen optional gemacht wird (Rezept 2.12). Diese regulären Ausdrücke und diejenigen, die noch folgen, lassen weder Benutzernamen noch Passwort als Teil der URL zu. Schon aus Sicherheitsgründen sollte man sowieso davon Abstand nehmen. Die meisten Webbrowser akzeptieren URLs, in denen das Schema nicht angegeben ist. In solchen Fällen ermitteln sie das Schema aus dem Domainnamen. So ist zum Beispiel www.regexbuddy.com eine Kurzform von http://www.regexbuddy.com. Um auch solche URLs zuzulassen, erweitern wir einfach die Liste der zulässigen Schemata so, dass auch die Subdomains www. und ftp. erlaubt sind. ‹(https?|ftp)://|(www|ftp)\.› ist dafür zuständig. Diese Liste hat zwei Alternativen, von denen jede wiederum zwei Alternativen besitzt. Die erste Alternative ermöglicht ‹https?› und ‹ftp›, auf das die Zeichen ‹://› folgen müssen. Die zweite Alternative ermöglicht ‹www› und ‹ftp›, denen ein (literaler) Punkt folgen muss. Sie können beide Listen problemlos um die Schemata und Subdomains erweitern, die die Regex akzeptieren soll.

Die letzten beiden regulären Ausdrücke erfordern ein Schema, einen ASCII-Domainnamen, einen Pfad und einen Dateinamen für eine Bilddatei vom Typ GIF, PNG oder JPEG. Pfad und Dateiname lassen alle Buchstaben und Ziffern in beliebigen Schriftsystemen zu sowie Unterstriche und Bindestriche. Die Zeichenklassenabkürzung ‹\w› enthält all diese Zeichen, abgesehen von den Bindestrichen (Rezept 2.3). Welchen dieser regulären Ausdrücke sollten Sie verwenden? Das hängt wirklich davon ab, was Sie erreichen wollen. In vielen Situationen kann die Antwort durchaus sein, gar keinen regulären Ausdruck zu verwenden. Versuchen Sie einfach, auf die URL zuzugreifen. Gibt sie einen sinnvollen Inhalt zurück, akzeptieren Sie sie. Erhalten Sie einen 404eroder einen anderen Fehler, weisen Sie sie zurück. Schließlich ist das der einzige wahre Test, um herauszufinden, ob eine URL gültig ist.

Siehe auch Rezepte 2.3, 2.8, 2.9 und 2.12.

370 | Kapitel 7: URLs, Pfade und Internetadressen

7.2

URLs in einem längeren Text finden

Problem Sie wollen URLs in einem längeren Text finden. URLs können von Satzzeichen umschlossen sein, zum Beispiel Klammern, die nicht Teil der URL sind.

Lösung URL ohne Leerzeichen: \b(https?|ftp|file)://\S+

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby URL ohne Leerzeichen oder abschließendes Satzzeichen: \b(https?|ftp|file)://[-A-Z0-9+&@#/%?=~_|$!:,.;]* [A-Z0-9+&@#/%=~_|$]

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby URL ohne Leerzeichen oder abschließendes Satzzeichen. Bei URLs, die mit der Subdomain www oder ftp beginnen, kann das Schema weggelassen werden: \b((https?|ftp|file)://|(www|ftp)\.)[-A-Z0-9+&@#/%?=~_|$!:,.;]* [A-Z0-9+&@#/%=~_|$]

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Was ist im folgenden Text die URL? Besuchen Sie http://www.somesite.com/page, um mehr Informationen zu erhalten.

Bevor Sie jetzt http://www.somesite.com/page sagen, überlegen Sie noch mal: Satzzeichen und Leerzeichen sind in URLs gültige Zeichen. Kommatas, Punkte und sogar Leerzeichen müssen nicht als %20 maskiert werden. Literale Leerzeichen sind auf jeden Fall gültig. Manche WYSIWYG-Webdesigntools machen es dem Anwender sogar sehr leicht, Leerzeichen in Datei- oder Ordnernamen zu verwenden und diese dann auch in Links darauf stehen zu haben. Nutzen wir also einen regulären Ausdruck, der alle gültigen URLs zulässt, findet er im Text folgende URL: http://www.somesite.com/page, um mehr Informationen zu erhalten.

7.2 URLs in einem längeren Text finden | 371

Die Wahrscheinlichkeit ist gering, dass die Person, die diesen Satz eingetippt hat, auch wollte, dass die Leerzeichen Teil der URL sind, da unmaskierte Leerzeichen in URLs doch recht selten sind. Der erste reguläre Ausdruck in der Lösung schließt sie mithilfe der Zeichenklassenabkürzung ‹\S› aus, wozu alle Zeichen gehören, die kein Whitespace sind. Auch wenn die Regex mit der Option Groß-/Kleinschreibung ignorieren angelegt wurde, muss das S großgeschrieben werden, da ‹\S› nicht das gleiche wie ‹\s› ist. Tatsächlich steht es sogar für genau das Gegenteil. In Rezept 2.3 erfahren Sie mehr dazu. Der erste reguläre Ausdruck ist trotzdem noch nicht so ganz das Wahre. Es ist zwar nicht ungewöhnlich, dass URLs Kommatas und andere Satzzeichen enthalten, aber diese tauchen selten am Ende der URL auf. Der nächste reguläre Ausdruck nutzt statt der Zeichenklassenabkürzung ‹\S› zwei Zeichenklassen. Die erste enthält mehr Satzzeichen als die zweite. Letztere schließt alle die Zeichen aus, die eher Teil des Satzes sind, wenn man die URL in einem normalen Satz aufführt. Die erste Zeichenklasse nutzt den Stern-Quantor (Rezept 2.12), um URLs beliebiger Länge zuzulassen. Die zweite Zeichenklasse hat keinen Quantor. Die URL muss also mit einem Zeichen aus dieser Klasse enden. Die Zeichenklassen enthalten keine Kleinbuchstaben – die Option Groß-/Kleinschreibung ignorieren kümmert sich schon darum. In Rezept 3.4 erfahren Sie, wie Sie solche Optionen in Ihrer Programmiersprache setzen. Die zweite Regex wird bei bestimmten URLs mit seltsamen Kombinationen aus Satzzeichen nicht korrekt arbeiten und diese nur teilweise finden. Aber sie löst das häufig vorkommende Problem eines Kommas oder Punkts direkt nach einer URL, während diese Zeichen trotzdem innerhalb der URL auftauchen dürfen. Die meisten Webbrowser akzeptieren URLs, in denen das Schema nicht angegeben ist. In solchen Fällen ermitteln sie das Schema aus dem Domainnamen. So ist zum Beispiel www.regexbuddy.com eine Kurzform von http://www.regexbuddy.com. Um auch solche URLs zuzulassen, erweitert die letzte Regex die Liste der zulässigen Schemata um die Subdomains www. und ftp.. ‹(https?|ftp)://|(www|ftp)\.› ist dafür zuständig. Diese Liste hat zwei Alternativen, von denen jede wiederum zwei Alternativen besitzt. Die erste Alternative ermöglicht ‹https?› und ‹ftp›, auf das die Zeichen ‹://› folgen müssen. Die zweite Alternative ermöglicht ‹www› und ‹ftp›, denen ein (literaler) Punkt folgen muss. Sie können beide Listen prob-

lemlos um die Schemata und Subdomains erweitern, die die Regex akzeptieren soll.

Siehe auch Rezepte 2.3 und 2.6.

372 | Kapitel 7: URLs, Pfade und Internetadressen

7.3

URLs in Anführungszeichen in längerem Text finden

Problem Sie wollen URLs in einem längeren Text finden. Dabei können die URLs durch Satzzeichen umschlossen sein, die zum Text und nicht zur URL gehören. Sie wollen den Anwendern die Möglichkeit geben, die URLs in Anführungszeichen zu setzen, sodass diese explizit festlegen können, ob Satzzeichen oder sogar Leerzeichen Teil der URL sein können.

Lösung \b(?:(?:https?|ftp|file)://|(www|ftp)\.)[-A-Z0-9+&@#/%?=~_|$!:,.;]* [-A-Z0-9+&@#/%=~_|$] |"(?:(?:https?|ftp|file)://|(www|ftp)\.)[^"\r\n]+" |'(?:(?:https?|ftp|file)://|(www|ftp)\.)[^'\r\n]+'

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren, Punkt passt auf Zeilenumbruch, Anker passen auf Zeilenumbruch Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Im vorhergehenden Rezept wurde beschrieben, wie man URLs mit normalem Text kombiniert und trotzdem zwischen den normalen Satzzeichen und URL-Zeichen unterscheidet. Obwohl die Lösung des vorigen Rezepts sehr nützlich ist und auch die meiste Zeit korrekte Ergebnisse liefert, kann keine Regex der Welt immer die perfekte Lösung bieten. Wird Ihre Regex für Texte verwendet, die erst noch entstehen sollen, können Sie Ihren Anwendern die Möglichkeit bieten, ihre URLs in Anführungszeichen zu setzen. Die von uns vorgestellten Lösungen lassen um die URL herum einzelne oder doppelte Anführungszeichen zu. Wenn eine URL in Anführungszeichen gesetzt wird, muss sie mit einem der Schemata ‹https?|ftp|file› oder einer von zwei Subdomains ‹www|ftp› beginnen. Nach dem Schema oder der Subdomain kann in der URL jedes Zeichen angegeben werden – mit Ausnahme von Zeilenumbrüchen –, bis das schließende Anführungszeichen folgt. Der reguläre Ausdruck ist insgesamt in drei Alternativen unterteilt. Die erste Alternative ist die Regex des vorhergehenden Rezepts, die eine nicht mit Anführungszeichen versehene URL findet und dabei versucht, zwischen normalen Satzzeichen und URL-Zeichen zu unterscheiden. Die zweite Alternative passt auf eine URL in doppelten Anführungszeichen, die dritte auf eine in einfachen Anführungszeichen. Wir nutzen lieber zwei Alternativen statt nur einer mit einer einfangenden Gruppe für das öffnende Anführungszeichen und einer Rückwärtsreferenz für das schließende Anführungszeichen, weil wir keine Rückwärtsreferenzen in der negierten Zeichenklasse verwenden können, mit der wir das Anführungszeichen von der URL ausschließen.

7.3 URLs in Anführungszeichen in längerem Text finden | 373

Wir haben uns für einfache und doppelte Anführungszeichen entschieden, weil URLs in HTML- und XHTML-Dateien häufig so auftauchen. So ist es für die meisten, die im Web unterwegs sind, ganz selbstverständlich. Sie können die Regex aber natürlich auch so anpassen, dass andere Zeichen als Begrenzung verwendet werden.

Siehe auch Rezepte 2.8 und 2.9.

7.4

URLs mit Klammern in längerem Text finden

Problem Sie wollen URLs in einem längeren Text finden. Dabei können links und rechts von den URLs Satzzeichen stehen, die aber nicht zur URL gehören. Sie wollen auch URLs finden, die Klammern enthalten, gleichzeitig aber eventuell vorhandene Klammern aus dem Suchergebnis heraushalten, die sich „außerhalb“ der URL befinden.

Lösung \b(?:(?:https?|ftp|file)://|www\.|ftp\.) (?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#/%=~_|$?!:,.])* (?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[A-Z0-9+&@#/%=~_|$])

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby \b(?:(?:https?|ftp|file)://|www\.|ftp\.)(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\) |[-A-Z0-9+&@#/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)| [A-Z0-9+&@#/%=~_|$])

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion In URLs sind so gut wie alle Zeichen zulässig – auch Klammern. Sie kommen zwar sehr selten vor, daher haben wir sie bisher in noch keiner Regex in den vorherigen Rezepten berücksichtigt, aber einige wichtige Websites verwenden sie durchaus: http://de.wikipedia.org/wiki/Zwingenberg_(Baden) http://msdn.microsoft.com/de-de/library/aa752574(en-us,VS.85).aspx

Eine Lösung bestünde darin, den Anwender solche URLs in Anführungszeichen setzen zu lassen. Eine andere sieht vor, die Regex so anzupassen, dass sie solche URLs akzeptiert. Schwierig daran ist, herauszufinden, ob eine schließende Klammer Teil der URL ist oder als Satzzeichen des normalen Texts zu betrachten ist, wie in diesem Beispiel: Die Website des RegexBuddy (unter http://www.regexbuddy.com) ist wirklich cool.

374 | Kapitel 7: URLs, Pfade und Internetadressen

Da es möglich ist, dass eine der Klammern direkt an die URL anschließt, während die andere dies nicht tut, können wir die Regexes aus dem vorherigen Rezept zum Setzen in Anführungszeichen nicht verwenden. Die einfachste Lösung ist, Klammern in URLs nur dann zuzulassen, wenn sie nicht verschachtelt sind und es immer eine öffnende und eine schließende Klammer gibt. Die URLs von Wikipedia und Microsoft erfüllen diese Anforderungen. Die beiden regulären Ausdrücke in der Lösung sind identisch, die erste nutzt jedoch den Freiform-Modus, um besser lesbar zu sein. Diese regulären Ausdrücke ähneln der letzten Regex aus der Lösung in Rezept 7.2. Sie bestehen aus drei Elementen: der Liste mit Schemata, gefolgt vom Hauptteil der URL, die mit dem Stern-Quantor eine beliebige Länge zulässt, und dem Ende der URL ohne Quantor (es muss also genau einmal vorkommen). In der ursprünglichen Regex in Rezept 7.2 bestehen sowohl der Rumpf der URL wie auch das Ende der URL jeweils aus nur einer Zeichenklasse. Die Lösungen für dieses Rezept ersetzen die beiden Zeichenklassen durch komplexere Elemente. Die mittlere Zeichenklasse [-A-Z0-9+&@#/%=~_|$?!:,.]

wurde nun zu: \([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#/%=~_|$?!:,.]

Die abschließende Zeichenklasse [A-Z0-9+&@#/%=~_|$]

wurde zu: \([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[A-Z0-9+&@#/%=~_|$]

Beide Zeichenklassen wurden durch Alternationen ersetzt (Rezept 2.8). Da eine Alternation die niedrigste Wertigkeit aller Regex-Operatoren besitzt, verwenden wir nicht-einfangende Gruppen (Rezept 2.9), um beide Alternativen zusammenzuhalten. Bei beiden Zeichenklassen haben wir die Alternative ‹\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)› hinzugefügt, die alte Klasse aber als die andere Alternative beibehalten. Die neue Alternative passt auf ein Klammernpaar mit einer beliebigen Menge an in URLs erlaubten Zeichen dazwischen. Die letzte Zeichenklasse hat die gleiche Alternative erhalten, wodurch die URL mit Text in Klammern oder aber mit einem Zeichen enden kann, das eher nicht Teil des eigentlichen Satzes ist. Zusammen führt das zu einer Regex, die URLs mit beliebig vielen Klammern findet, auch solche ohne Klammern und sogar URLs, die nur aus Klammern bestehen – solange diese Klammern paarweise auftreten. Für den Hauptteil der URL haben wir den Stern-Quantor an das Ende der nicht-einfangenden Gruppe gestellt. Damit können in der URL beliebig viele Klammernpaare auftre-

7.4 URLs mit Klammern in längerem Text finden | 375

ten. Da der Stern nun die gesamte nicht-einfangende Gruppe betrifft, brauchen wir keinen mehr für die ursprüngliche Zeichenklasse. Tatsächlich müssen wir sogar darauf achten, auf keinen Fall den Stern mit aufzunehmen. Die Regex in der Lösung hat in der Mitte die Form ‹(ab*c|d)*›, wobei ‹a› und ‹c› die literalen Klammern sind und ‹b› und ‹d› die Zeichenklassen. Würde man das als ‹(ab*c|d*)*› schreiben, wäre es ein Fehler. Auf den ersten Blick wäre es logisch, weil wir damit beliebig viele Zeichen von ‹d› zulassen würden, aber der äußere ‹*› wiederholt ‹d› schon vollkommen ausreichend. Fügen wir ‹d› noch direkt einen inneren Stern hinzu, wird die Komplexität des regulären Ausdrucks exponentiell. ‹(d*)*› kann dddd auf vielen unterschiedlichen Wegen finden. So könnte der äußere Stern zum Beispiel für vier Wiederholungen sorgen, während der innere Stern jeweils nur einmal genutzt wird. Oder der äußere Stern wird drei Mal genutzt, während der innere Stern in der Häufigkeit 2-1-1, 12-1 oder 1-1-2 zum Tragen kommt. Der äußere Stern könnte auch zwei Mal genutzt werden, während der innere mit der Häufigkeit 2-2, 1-3 oder 3-1 gerufen wird. Sie können sich vorstellen, dass die Anzahl an Kombinationen mit wachsender String-Länge geradezu explodiert. Wir nennen das „katastrophales Backtracking”, ein Begriff, den wir in Rezept 2.15 eingeführt haben. Das Problem tritt dann auf, wenn der reguläre Ausdruck keine gültige Übereinstimmung liefern kann, weil Sie zum Beispiel etwas an die Regex angehängt haben, damit URLs mit etwas Bestimmtem enden.

Siehe auch Rezepte 2.8 und 2.9.

7.5

URLs in Links umwandeln

Problem Sie haben einen Text mit einer oder mehreren URLs. Sie wollen nun die URLs in Links umwandeln, indem Sie sie mit HTML-Anker-Tags umschließen. Die URL selbst wird dann als Ziel für den Link dienen, aber auch gleichzeitig der Text sein, den man anklicken kann.

Lösung Um die URLs im Text zu finden, nutzen Sie einen der regulären Ausdrücke aus den Rezepten 7.2 oder 7.4. Als Ersetzungstext verwenden Sie: $&

Ersetzungstextvarianten: .NET, JavaScript, Perl $0

Ersetzungstextvarianten: .NET, Java, PHP

376 | Kapitel 7: URLs, Pfade und Internetadressen

\0

Ersetzungstextvarianten: PHP, Ruby \&

Ersetzungstextvariante: Ruby \g

Ersetzungstextvariante: Python Beim Programmieren können Sie diesen Ersetzungsvorgang wie in Rezept 3.15 beschrieben implementieren.

Diskussion Die Lösung für dieses Problem ist recht einfach. Wir verwenden einen regulären Ausdruck, um eine URL zu finden, und ersetzen sie dann durch «URL», wobei URL für die gefundene URL steht. In den verschiedenen Programmiersprachen werden unterschiedliche Ersetzungstextsyntaxen genutzt, daher werden hier so viele Lösungen angegeben. Aber alle tun genau das Gleiche. In Rezept 2.20 wird die Ersetzungstextsyntax genauer beschrieben.

Siehe auch Rezepte 2.21, 3.15, 7.2 und 7.4.

7.6

URNs validieren

Problem Sie wollen prüfen, ob ein String aus einem gültigen Uniform Resource Name (URN) besteht (spezifiziert im RFC 2141), oder solche URNs in einem längeren Text finden.

Lösung Prüfen, ob ein String vollständig aus einem gültigen URN besteht: \Aurn: # Namensraum [a-z0-9][a-z0-9-]{0,31}: # Namensraumspezifischer String [a-z0-9()+,\-.:=@;$_!*'%/?#]+ \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

7.6 URNs validieren | 377

^urn:[a-z0-9][a-z0-9-]{0,31}:[a-z0-9()+,\-.:=@;$_!*'%/?#]+$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python Finden eines URN in einem längeren Text: \burn: # Namensraum [a-z0-9][a-z0-9-]{0,31}: # Namensraumspezifischer String [a-z0-9()+,\-.:=@;$_!*'%/?#]+

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby \burn:[a-z0-9][a-z0-9-]{0,31}:[a-z0-9()+,\-.:=@;$_!*'%/?#]+

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Finden eines URN in einem längeren Text, wobei davon ausgegangen wird, dass Satzzeichen am Ende des URN Teil des umgebenden Texts sind und nicht zum URN gehören: \burn: # Namensraum [a-z0-9][a-z0-9-]{0,31}: # Namensraumspezifischer String [a-z0-9()+,\-.:=@;$_!*'%/?#]*[a-z0-9+=@$/]

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby \burn:[a-z0-9][a-z0-9-]{0,31}:[a-z0-9()+,\-.:=@;$_!*'%/?#]*[a-z0-9+=@$/]

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Ein URN besteht aus drei Teilen. Der erste sind die vier Zeichen urn:, die wir als literale Zeichen in den regulären Ausdruck einfügen können. Der zweite Teil ist der Namensraum (Namespace Identifier, NID). Er ist zwischen 1 und 32 Zeichen lang. Das erste Zeichen muss ein Buchstabe oder eine Ziffer sein. Bei den weiteren Zeichen kann es sich um Buchstaben, Ziffern oder Bindestriche handeln. Wir finden diese Zeichen mit zwei Zeichenklassen (Rezept 2.3): Die erste passt auf einen Buchstaben oder eine Ziffer, während die zweite zwischen 0 und 31 Buchstaben, Ziffern und Bindestriche findet. Der NID muss durch einen Doppelpunkt abgeschlossen sein, den wir wieder literal in die Regex einfügen. Der dritte Teil des URN ist der namensraumspezifische String (Namespace Specific String, NSS). Er kann eine beliebige Länge haben und neben Buchstaben und Ziffern eine

378 | Kapitel 7: URLs, Pfade und Internetadressen

ganze Reihe von Satzzeichen enthalten. Das können wir wieder problemlos durch eine weitere Zeichenklasse abbilden. Das Plus nach der Zeichenklasse wiederholt diese einmal oder mehrfach (Rezept 2.12). Wenn Sie prüfen wollen, ob ein String aus einem gültigen URN besteht, müssen Sie nur noch am Anfang und Ende der Regex Anker einfügen, um damit auch den Anfang und das Ende des Strings zu finden. Das lässt sich außer in Ruby in allen Varianten mit ‹^› und ‹$› oder außer in JavaScript mit ‹\A› und ‹\Z› erreichen. In Rezept 2.5 sind diese Anker im Detail beschrieben. Wollen Sie URNs in längeren Texten finden, wird es etwas schwieriger. Die Probleme mit Satzzeichen in URLs, die wir in Rezept 7.2 besprochen haben, bestehen auch bei URNs. Schauen Sie sich den folgenden Text an: Der URN ist urn:nid:nss, oder nicht?

Die Frage ist hier, ob das Komma Teil des URN ist. URNs, die mit Kommatas enden, sind syntaktisch korrekt, aber jeder menschliche Leser des angegebenen Satzes wird das Komma als Teil des Satzes betrachten – nicht als Teil des URN. Der letzte reguläre Ausdruck aus dem Abschnitt „Lösung“ löst dieses Problem, indem er etwas strenger ist als der RFC 2141. Er beschränkt das letzte Zeichen des URN auf ein Zeichen, das für den NSS-Teil gültig ist, aber nicht unbedingt auf normale Satzzeichen passt. Das lässt sich leicht erreichen, indem man den Plus-Quantor (einen oder mehrere) durch einen Stern (null oder mehrere) ersetzt und eine zweite Zeichenklasse für das letzte Zeichen anfügt. Würden wir die Zeichenklasse ohne einen anderen Quantor ergänzen, müsste der NSS damit mindestens zwei Zeichen lang sein, was wir natürlich nicht wollen.

Siehe auch Rezepte 2.3 und 2.12.

7.7

Generische URLs validieren

Problem Sie wollen prüfen, ob ein Stück Text eine nach dem RFC 3986 gültige URL ist.

Lösung \A (# Schema [a-z][a-z0-9+\-.]*: (# Authority & Pfad // ([a-z0-9\-._~%!$&'()*+,;=]+@)?

# Benutzer

7.7 Generische URLs validieren | 379

([a-z0-9\-._~%]+ # Named Host |\[[a-f0-9:.]+\] # IPv6 Host |\[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\]) # IPvFuture Host (:[0-9]+)? # Port (/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/? # Pfad |# Pfad ohne Authority (/?[a-z0-9\-._~%!$&'()*+,;=:@]+(/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?)? ) |# Relative URL (kein Schema und keine Authority) (# Relativer Pfad [a-z0-9\-._~%!$&'()*+,;=@]+(/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/? |# Absoluter Pfad (/[a-z0-9\-._~%!$&'()*+,;=:@]+)+/? ) ) # Query (\?[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? # Fragment (\#[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby \A (# Schema (?[a-z][a-z0-9+\-.]*): (# Authority & Pfad // (?[a-z0-9\-._~%!$&'()*+,;=]+@)? # Benutzer (?[a-z0-9\-._~%]+ # Named Host | \[[a-f0-9:.]+\] # IPv6 Host | \[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\]) # IPvFuture Host (?:[0-9]+)? # Port (?(/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?) # Pfad |# Pfad ohne Authority (?/?[a-z0-9\-._~%!$&'()*+,;=:@]+ (/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?)? ) |# Relative URL (kein Schema und keine Authority) (? # Relativer Pfad [a-z0-9\-._~%!$&'()*+,;=@]+(/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/? |# Absoluter Pfad (/[a-z0-9\-._~%!$&'()*+,;=:@]+)+/? ) ) # Query (?\?[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? # Fragment (?\#[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Variante: .NET

380 | Kapitel 7: URLs, Pfade und Internetadressen

\A (# Schema (?[a-z][a-z0-9+\-.]*): (# Authority & Pfad // (?[a-z0-9\-._~%!$&'()*+,;=]+@)? # Benutzer (?[a-z0-9\-._~%]+ # Named Host | \[[a-f0-9:.]+\] # IPv6 Host | \[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\]) # IPvFuture Host (?:[0-9]+)? # Port (?(/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?) # Pfad |# Pfad ohne Authority (?/?[a-z0-9\-._~%!$&'()*+,;=:@]+ (/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?)? ) |# Relative URL (kein Schema und keine Authority) (? # Relativer Pfad [a-z0-9\-._~%!$&'()*+,;=@]+(/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/? |# Absoluter Pfad (/[a-z0-9\-._~%!$&'()*+,;=:@]+)+/? ) ) # Query (?\?[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? # Fragment (?\#[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 \A (# Schema (?P[a-z][a-z0-9+\-.]*): (# Authority & Pfad // (?P[a-z0-9\-._~%!$&'()*+,;=]+@)? # Benutzer (?P[a-z0-9\-._~%]+ # Named Host | \[[a-f0-9:.]+\] # IPv6 Host | \[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\]) # IPvFuture Host (?P:[0-9]+)? # Port (?P(/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?) # Pfad |# Pfad ohne Authority (?P/?[a-z0-9\-._~%!$&'()*+,;=:@]+ (/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?)? ) |# Relative URL (kein Schema und keine Authority) (?P # Relativer Pfad [a-z0-9\-._~%!$&'()*+,;=@]+(/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/? |# Absoluter Pfad (/[a-z0-9\-._~%!$&'()*+,;=:@]+)+/? ) )

7.7 Generische URLs validieren | 381

# Query (?P\?[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? # Fragment (?P\#[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: PCRE 4 und neuer, Perl 5.10, Python ^([a-z][a-z0-9+\-.]*:(\/\/([a-z0-9\-._~%!$&'()*+,;=]+@)?([a-z0-9\-._~%]+| \[[a-f0-9:.]+\]|\[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\])(:[0-9]+)? (\/[a-z0-9\-._~%!$&'()*+,;=:@]+)*\/?|(\/?[a-z0-9\-._~%!$&'()*+,;=:@]+ (\/[a-z0-9\-._~%!$&'()*+,;=:@]+)*\/?)?)|([a-z0-9\-._~%!$&'()*+,;=@]+ (\/[a-z0-9\-._~%!$&'()*+,;=:@]+)*\/?|(\/[a-z0-9\-._~%!$&'()*+,;=:@]+) +\/?)) (\?[a-z0-9\-._~%!$&'()*+,;=:@\/?]*)?(#[a-z0-9\-._~%!$&'()*+,;=:@\/?]*)?$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python

Diskussion Ein Großteil der bisherigen Rezepte in diesem Kapitel kümmert sich um URLs, und die regulären Ausdrücke in den Lösungen behandeln spezielle URL-Arten. Manche der Regexes sind an bestimmte „Umgebungen“ angepasst, um zum Beispiel herauszufinden, ob ein Satzzeichen Teil der URL oder des umgebenden Texts ist. Die regulären Ausdrücke in diesem Rezept kümmern sich um generische URLs. Sie sind nicht dazu gedacht, URLs in längeren Texten zu finden, sondern um Strings zu überprüfen, die URLs enthalten sollen, und um URLs in ihre Bestandteile zu zerlegen. Das schaffen sie für jede Art von URL, aber in der Praxis werden Sie die Regexes spezifischer gestalten wollen. Die Rezepte nach diesem Rezept stellen Beispiele für spezifischere Regexes vor. Der RFC 3986 beschreibt, wie eine gültige URL aussehen sollte. Er behandelt jede mögliche URL, auch relative URLs und URLs für Schemata, die noch nicht eingeführt wurden. Im Endergebnis ist der RFC 3986 sehr umfassend und der reguläre Ausdruck, mit dem er implementiert wird, ziemlich lang. Die regulären Ausdrücke in diesem Rezept implementieren nur die Grundlagen. Sie sind ausreichend, um die URL zuverlässig in ihre verschiedenen Bestandteile zu zerlegen, aber nicht, um jeden dieser Teile zu validieren. Wollte man alle Teile überprüfen, würde man spezifisches Wissen über jedes der URL-Schemata benötigen. Der RFC 3986 deckt nicht alle URLs ab, denen Sie sich in der freien Wildbahn eventuell gegenübersehen. So akzeptieren zum Beispiel viele Browser und Webserver URLs mit literalen Leerzeichen, während diese nach dem RFC 3986 als %20 zu maskieren sind. Eine absolute URL muss mit einem Schema beginnen, wie zum Beispiel http: oder ftp:. Das erste Zeichen des Schemas muss ein Buchstabe sein. Die folgenden Zeichen können Buchstaben, Ziffern und ein paar Satzzeichen sein. Beides lässt sich bequem mit zwei Zeichenklassen abhandeln: ‹[a-z][a-z0-9+\-.]*›.

382 | Kapitel 7: URLs, Pfade und Internetadressen

Viele URL-Schemata benötigen etwas, das der RFC 3986 als „Authority“ bezeichnet. Die Authority ist der Domainname oder die IP-Adresse des Servers, vor dem oder der optional ein Benutzername steht und worauf eine Portnummer folgen kann. Der Benutzername kann aus Buchstaben, Ziffern und einer Reihe von Satzzeichen bestehen. Er muss vom Domainnamen oder der IP-Adresse durch ein @-Zeichen abgegrenzt sein. Mit ‹[a-z0-9\-._~%!$&'()*+,;=]+@› findet man den Benutzernamen und das Trennzeichen. Der RFC 3986 ist in Bezug auf den Domainnamen ziemlich großzügig. In Rezept 7.15 ist beschrieben, was im Allgemeinen für Domains akzeptiert wird: Buchstaben, Ziffern, Bindestriche und Punkte. Der RFC 3986 erlaubt auch Tilden und durch die Prozentnotation beliebige andere Zeichen. Der Domainname muss nach UTF-8 konvertiert werden, und jedes Zeichen, das weder Buchstabe noch Ziffer, Bindestrich oder Tilde ist, muss als %FF kodiert werden, wobei es sich bei FF um die hexadezimale Darstellung des Byte handelt. Um unseren regulären Ausdruck einfach zu halten, prüfen wir nicht, ob auf jedes Prozentzeichen genau zwei hexadezimale Ziffern folgen. Es ist besser, solche Kontrollen erst durchzuführen, nachdem die verschiedenen Teile der URL getrennt wurden. Also finden wir den Hostnamen einfach mit ‹[a-z0-9\-._~%]+›, was auch IPv4-Adressen zulässt (die nach RFC 3986 erlaubt sind). Statt eines Domainnamens oder einer IPv4-Adresse kann der Host auch als IPv6-Adresse in eckigen Klammern oder sogar in einer zukünftig noch festzulegenden Version von IPAdressen angegeben werden. Wir finden die IPv6-Adressen per ‹\[[a-f0-9:.]+\]› und die zukünftigen Adressen mit ‹\[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\]›. Auch wenn wir IP-Adressen einer noch nicht definierten Version nicht überprüfen können, lassen sich IPv6-Adressen dennoch strenger kontrollieren. Aber auch hier ist es besser, dafür eine zweite Regex zu verwenden, nachdem wir die Adresse aus der URL extrahiert haben. In Rezept 7.17 sieht man, wie aufwendig es ist, IPv6-Adressen zu überprüfen. Die Portnummer ist, falls sie denn angegeben wurde, einfach eine Dezimalzahl, die vom Hostnamen durch einen Doppelpunkt getrennt ist. Hier benötigen wir nur die Regex ‹:[0-9]+›. Wenn eine Authority angegeben ist, muss ihr entweder ein absoluter Pfad folgen, oder es darf gar kein Pfad angegeben sein. Ein absoluter Pfad beginnt mit einem Schrägstrich, gefolgt von einem oder mehreren Segmenten, die ebenfalls durch Schrägstriche begrenzt sind. Ein Segment besteht aus einem oder mehreren Buchstaben, Ziffern oder Satzzeichen. Es kann keine direkt aufeinanderfolgenden Schrägstriche geben. Der Pfad kann zudem mit einem Schrägstrich enden. ‹(/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?› findet solche Pfade. Wenn die URL keine Authority enthält, kann der Pfad absolut, relativ oder gar nicht vorhanden sein. Absolute Pfade beginnen mit einen Schrägstrich, relative Pfade dagegen nicht. Da der führende Schrägstrich damit optional ist, brauchen wir eine etwas längere Regex, um sowohl absolute wie auch relative Pfade zu finden: ‹/?[a-z0-9\._~%!$&'()*+,;=:@]+(/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?›. 7.7 Generische URLs validieren | 383

Relative URLs enthalten kein Schema und daher auch keine Authority. Der Pfad ist dann aber verpflichtend und kann absolut oder relativ sein. Da die URL kein Schema enthält, darf das erste Segment eines relativen Pfads keinen Doppelpunkt enthalten. Ansonsten würde er als Trennzeichen für das Schema angesehen werden. Also brauchen wir zwei reguläre Ausdrücke, um den Pfad einer relativen URL zu finden. Die erste für die relativen Pfade ist ‹[a-z0-9\-._~%!$&'()*+,;=@]+(/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?›. Das ähnelt sehr stark der Regex für Pfade mit einem Schema, aber ohne Authority. Den einzigen Unterschied bildet der optionale Schrägstrich am Anfang, der hier fehlt, und die erste Zeichenklasse, die keinen Doppelpunkt enthält. Absolute Pfade finden wir mit ‹(/[a-z09\-._~%!$&'()*+,;=:@]+)+/?›. Das ist die gleiche Regex wie die für Pfade in URLs, die ein Schema und eine Authority enthalten, nur dass der Stern, der die Segmente des Pfads wiederholt, nun einem Pluszeichen gewichen ist. In relativen URLs muss mindestens ein Pfadsegment vorhanden sein. Der Query-Teil der URL ist optional. Gibt es ihn, muss er mit einem (literalen) Fragezeichen beginnen. Das Query-Element läuft bis zum ersten Rautezeichen in der URL oder bis zu ihrem Ende. Da das Rautezeichen keines der gültigen Satzzeichen für den QueryTeil der URL ist, können wir ihn leicht mit ‹\?[a-z0-9\-._~%!$&'()*+,;=:@/?]*› finden. Beide Fragezeichen in der Regex sind literale Zeichen. Das erste befindet sich außerhalb einer Zeichenklasse und muss daher maskiert werden. Das zweite steht innerhalb einer Zeichenklasse, wo es immer ein literales Zeichen ist. Der letzte Teil einer URL ist das Fragment, das ebenfalls optional sein darf. Es beginnt mit einem Rautezeichen und läuft bis zum Ende der URL. Per ‹\#[a-z0-9\-._~%!$&'()* +,;=:@/?]*› finden Sie auch dieses. Um die Arbeit mit den verschiedenen Elementen der URL zu vereinfachen, verwenden wir benannte Captures. In Rezept 2.11 ist beschrieben, wie benannte Captures in den verschiedenen Regex-Varianten genutzt werden können. .NET ist die einzige Variante, die mehrere Gruppen mit dem gleichen Namen so verarbeiten kann, als handele es sich um ein und dieselbe Gruppe. Das ist in dieser Situation sehr praktisch, da unsere Regex mehrere Wege kennt, den Pfad der URL zu finden – abhängig davon, ob das Schema und/oder die Authority angegeben sind. Geben wir diesen drei Gruppen den gleichen Namen, können wir einfach die Gruppe „path“ abfragen, um den Pfad zu erhalten – egal ob die URL ein Schema und/oder eine Authority besitzt. Die anderen Varianten bieten diese Möglichkeit für benannte Captures nicht an, auch wenn die meisten die gleiche Syntax wie .NET unterstützen. In den anderen Varianten haben die drei einfangenden Gruppen für den Pfad unterschiedliche Namen. Nur eine von ihnen wird tatsächlich den Pfad der URL enthalten, wenn es eine Übereinstimmung gibt. Die beiden anderen sind dann daran nicht beteiligt.

Siehe auch Rezepte 2.3, 2.8, 2.9 und 2.12.

384 | Kapitel 7: URLs, Pfade und Internetadressen

7.8

Das Schema aus einer URL extrahieren

Problem Sie wollen das Schema aus einem String extrahieren, der eine URL enthält. So wollen Sie zum Beispiel den Wert http für die URL http://www.regexcookbook.com erhalten.

Lösung Extrahieren des Schema aus einer URL, die schon validiert wurde ^([a-z][a-z0-9+\-.]*):

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Extrahieren des Schemas beim Validieren der URL \A ([a-z][a-z0-9+\-.]*): (# Authority & Pfad // ([a-z0-9\-._~%!$&'()*+,;=]+@)? # Benutzer ([a-z0-9\-._~%]+ # Named Host |\[[a-f0-9:.]+\] # IPv6 Host |\[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\]) # IPvFuture Host (:[0-9]+)? # Port (/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/? # Pfad |# Pfad ohne Authority (/?[a-z0-9\-._~%!$&'()*+,;=:@]+(/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?)? ) # Query (\?[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? # Fragment (\#[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^([a-z][a-z0-9+\-.]*):(//([a-z0-9\-._~%!$&'()*+,;=]+@)?([a-z0-9\-._~%]+| \[[a-f0-9:.]+\]|\[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\])(:[0-9]+)? (/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?|(/?[a-z0-9\-._~%!$&'()*+,;=:@]+ (/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?)?)(\?[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? (#[a-z0-9\-._~%!$&'()*+,;=:@/?]*)?$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python

7.8 Das Schema aus einer URL extrahieren | 385

Diskussion Es ist sehr einfach, das Schema einer URL zu extrahieren, wenn Sie schon wissen, dass es sich beim Ausgangstext um eine gültige URL handelt. Das Schema steht immer ganz am Anfang der URL. Der Zirkumflex (Rezept 2.5) sorgt dafür, dass diese Anforderung in der Regex umgesetzt wird. Das Schema beginnt mit einem Buchstaben, gefolgt von weiteren Buchstaben, Ziffern, Pluszeichen, Bindestrichen und Punkten. Das finden wir mit den beiden Zeichenklassen ‹[a-z][a-z0-9+\-.]*› (Rezept 2.3). Das Schema wird vom Rest der URL durch einen Doppelpunkt begrenzt. Wir fügen der Regex diesen Doppelpunkt hinzu, um sicherzustellen, dass wir das Schema nur dann finden, wenn die URL wirklich damit beginnt. Relative URLs enthalten kein Schema. Die in RFC 3986 definierte URL-Syntax stellt sicher, dass relative URLs keine Doppelpunkte enthalten, solange es vor diesen Doppelpunkten nicht schon Zeichen gibt, die in einem Schema nicht zulässig sind. Darum mussten wir den Doppelpunkt aus einer der Zeichenklassen entfernen, die den Pfad in Rezept 7.7 finden. Nutzen Sie die Regexes in diesem Rezept für eine gültige, aber relative URL, wird gar kein Schema gefunden. Da die Regex auf mehr als nur das Schema passt (sie enthält auch den Doppelpunkt), haben wir dem regulären Ausdruck eine einfangende Gruppe hinzugefügt. Wenn die Regex eine Übereinstimmung findet, können Sie den Text der ersten (und einzigen) einfangenden Gruppe auslesen, um das Schema ohne den Doppelpunkt zu erhalten. In Rezept 2.9 erfahren Sie alles über einfangende Gruppen, und in Rezept 3.9 lernen Sie, wie Sie den von solchen einfangenden Gruppen gefundenen Text in Ihrer Programmiersprache auslesen. Sollten Sie nicht schon wissen, dass Ihr Ausgangstext eine gültige URL ist, können Sie eine vereinfachte Version der Regex aus Rezept 7.7 verwenden. Da wir das Schema extrahieren wollen, können wir relative URLs ausschließen, in denen es nicht angegeben ist. Damit wird der reguläre Ausdruck ein bisschen einfacher. Da diese Regex die gesamte URL findet, haben wir um den Teil der Regex, der das Schema findet, eine zusätzliche einfangende Gruppe gelegt. Lesen Sie den Text aus, der durch die erste einfangende Gruppe gefunden wurde, um das Schema der URL zu erhalten.

Siehe auch Rezepte 2.9, 3.9 und 7.7.

7.9

Den Benutzer aus einer URL extrahieren

Problem Sie wollen den Benutzer aus einem String extrahieren, der eine URL enthält. So wollen Sie zum Beispiel jan aus ftp://[email protected] erhalten.

386 | Kapitel 7: URLs, Pfade und Internetadressen

Lösung Extrahieren des Benutzers aus einer URL, die schon validiert ist ^[a-z0-9+\-.]+://([a-z0-9\-._~%!$&'()*+,;=]+)@

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Extrahieren des Benutzers während des Validierens der URL \A [a-z][a-z0-9+\-.]*:// ([a-z0-9\-._~%!$&'()*+,;=]+)@ ([a-z0-9\-._~%]+ |\[[a-f0-9:.]+\] |\[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\]) (:[0-9]+)? (/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/? (\?[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? (\#[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? \Z

# # # # # # # # #

Schema Benutzer Named Host IPv6 Host IPvFuture Host Port Pfad Query Fragment

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^[a-z][a-z0-9+\-.]*://([a-z0-9\-._~%!$&'()*+,;=]+)@([a-z0-9\-._~%]+| \[[a-f0-9:.]+\]|\[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\])(:[0-9]+)? (/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?(\?[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? (#[a-z0-9\-._~%!$&'()*+,;=:@/?]*)?$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python

Diskussion Das Extrahieren des Benutzers aus einer URL ist sehr einfach, wenn Sie schon wissen, dass Ihr Ausgangstext eine gültige URL ist. Der Benutzername steht, sofern er in der URL vorhanden ist, direkt nach dem Schema und den beiden Schrägstrichen, mit denen der „Authority“-Teil der URL beginnt. Er wird vom folgenden Hostnamen durch ein @-Zeichen getrennt. Da das @-Zeichen in Hostnamen nicht gültig ist, können wir sicher sein, dass wir den Benutzernamen aus einer URL extrahieren können, wenn wir nach den beiden ersten Schrägstrichen und vor dem folgenden Schrägstrich ein @-Zeichen finden. Schrägstriche sind in Benutzernamen nicht erlaubt, daher brauchen wir dafür keine besondere Prüfung einzubauen. Durch diese Regeln können wir sehr leicht den Benutzernamen extrahieren, wenn wir schon wissen, dass die URL gültig ist. Wir überspringen einfach das Schema mithilfe von ‹[a-z0-9+\-.]+› und den literalen Zeichen ://. Dann holen wir den folgenden Benutzer-

7.9 Den Benutzer aus einer URL extrahieren | 387

namen. Finden wir das @-Zeichen, wissen wir, dass die Zeichen davor der Benutzername sind. Die Zeichenklasse ‹[a-z0-9\-._~%!$&'()*+,;=]› führt alle Zeichen auf, die in Benutzernamen gültig sind. Diese Regex findet nur dann eine Übereinstimmung, wenn die URL auch tatsächlich einen Benutzernamen enthält. Ist das der Fall, passt die Regex sowohl auf das Schema als auch auf den Benutzerteil der URL. Daher haben wir eine einfangende Gruppe ergänzt. Bei einer Übereinstimmung können Sie den von der ersten (und einzigen) einfangenden Gruppe gefundenen Text auslesen, um den Benutzernamen ohne weitere Trennzeichen oder andere Elemente der URL zu erhalten. In Rezept 2.9 erfahren Sie alles über einfangende Gruppen, und in Rezept 3.9 lernen Sie, wie Sie den von solchen einfangenden Gruppen gefundenen Text in Ihrer Programmiersprache auslesen. Wenn Sie nicht schon wissen, dass Ihr Ausgangstext eine gültige URL ist, können Sie eine vereinfachte Version der Regex aus Rezept 7.7 nutzen. Da wir den Benutzer extrahieren wollen, können wir URLs vernachlässigen, in denen keine Authority angegeben ist. Die Regex in dieser Lösung passt nur auf URLs, die eine Authority mit einem Benutzernamen enthalten. Dadurch, dass der Authority-Teil der URL vorhanden sein muss, wird die Regex etwas einfacher. Sie ist sogar noch einfacher als die Regex aus Rezept 7.8. Da diese Regex auf die gesamte URL passt, haben wir eine zusätzliche einfangende Gruppe um den Teil eingefügt, der den Benutzer findet. Um den Benutzernamen zu erhalten, lesen Sie den von der ersten einfangenden Gruppe gefundenen Text aus. Wenn Sie eine Regex haben wollen, die alle gültigen URLs findet, auch die ohne Benutzer, können Sie eine der Regexes aus Rezept 7.7 verwenden. Die erste dieser Regexes fängt den Benutzer in der dritten einfangenden Gruppe ein (falls es ihn gibt). Die einfangende Gruppe wird das @-Zeichen ebenfalls enthalten. Sie können die Regex um eine zusätzliche einfangende Gruppe ergänzen, wenn Sie den Benutzernamen ohne das @Zeichen auslesen wollen.

Siehe auch Rezepte 2.9, 3.9 und 7.7.

7.10 Den Host aus einer URL extrahieren Problem Sie wollen den Host aus einem String extrahieren, der eine URL enthält. So wollen Sie zum Beispiel www.regexcookbook.com erhalten, wenn Sie den Text http://www.regexcookbook.com/ haben.

388 | Kapitel 7: URLs, Pfade und Internetadressen

Lösung Extrahieren des Hosts aus einer als valide bekannten URL \A [a-z][a-z0-9+\-.]*:// ([a-z0-9\-._~%!$&'()*+,;=]+@)? ([a-z0-9\-._~%]+ |\[[a-z0-9\-._~%!$&'()*+,;=:]+\])

# # # #

Schema Benutzer Named Host oder IPv4-Host IPv6+ Host

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ^[a-z][a-z0-9+\-.]*://([a-z0-9\-._~%!$&'()*+,;=]+@)?([a-z0-9\-._~%]+| \[[a-z0-9\-._~%!$&'()*+,;=:]+\])

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Extrahieren des Hosts beim Validieren der URL \A [a-z][a-z0-9+\-.]*:// ([a-z0-9\-._~%!$&'()*+,;=]+@)? ([a-z0-9\-._~%]+ |\[[a-f0-9:.]+\] |\[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\]) (:[0-9]+)? (/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/? (\?[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? (\#[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? \Z

# # # # # # # # #

Schema Benutzer Named Host IPv6 Host IPvFuture Host Port Pfad Query Fragment

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^[a-z][a-z0-9+\-.]*://([a-z0-9\-._~%!$&'()*+,;=]+@)?([a-z0-9\-._~%]+| \[[a-f0-9:.]+\]|\[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\])(:[0-9]+)? (/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?(\?[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? (#[a-z0-9\-._~%!$&'()*+,;=:@/?]*)?$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python

Diskussion Das Extrahieren des Host aus einer URL ist sehr einfach, wenn Sie schon wissen, dass Ihr Ausgangstext eine gültige URL ist. Wir verwenden ‹\A› oder ‹^›, um die Übereinstimmung mit dem Anfang des Strings zu verknüpfen. ‹[a-z][a-z0-9+\-.]*://› überspringt das Schema und ‹([a-z0-9\-._~%!$&'()*+,;=]+@)?› den optionalen Benutzernamen. Der Hostname folgt direkt darauf.

7.10 Den Host aus einer URL extrahieren | 389

Der RFC 3986 erlaubt zwei unterschiedliche Schreibweisen für den Host. Domainnamen und IPv4-Adressen werden ohne eckige Klammern angegeben, während IPv6- und zukünftige IP-Adressen mit eckigen Klammern geschrieben werden. Wir müssen beide Versionen getrennt behandeln, da die Schreibweise mit eckigen Klammern mehr Satzzeichen zulässt als die Schreibweise ohne. Insbesondere der Doppelpunkt ist in den eckigen Klammern erlaubt, aber nicht in Domainnamen oder IPv4-Adressen. Der Doppelpunkt wird auch dazu verwendet, den Hostnamen (mit oder ohne eckige Klammern) von der Portnummer zu trennen. ‹[a-z0-9\-._~%]+› passt auf Domainnamen und IPv4-Adressen. ‹\[[a-z0-9\-._~%!$&'() *+,;=:]+\]› kümmert sich um IPv6- und zukünftige IP-Adressen. Wir kombinieren beide

Regexes durch eine Alternation (Rezept 2.8) in einer Gruppe. Die einfangende Gruppe lässt uns zudem auch den Hostnamen auslesen. Diese Regex findet nur dann eine Übereinstimmung, wenn in der URL auch tatsächlich ein Host angegeben ist. In dem Fall passt die Regex auf Schema, Benutzer und Host, und Sie können den Text von der zweiten einfangenden Gruppe auslesen, um den Hostnamen ohne Begrenzungszeichen oder andere URL-Elemente zu erhalten. Die einfangende Gruppe wird für IPv6-Adressen die eckigen Klammern enthalten. In Rezept 2.9 erfahren Sie alles über einfangende Gruppen, und in Rezept 3.9 wird erläutert, wie Sie den von solchen einfangenden Gruppen gefundenen Text in Ihrer Programmiersprache auslesen. Wissen Sie noch nicht, ob Ihr Ausgangstext eine gültige URL ist, können Sie eine vereinfachte Version der Regex aus Rezept 7.7 nutzen. Da wir den Host extrahieren wollen, können wir URLs ausschließen, in denen keine Authority angegeben ist. Damit wird der reguläre Ausdruck ein bisschen einfacher. Er ähnelt der aus Rezept 7.9 genutzten Regex. Der einzige Unterschied ist, dass der Benutzerabschnitt der Authority wieder optional ist. Diese Regex nutzt ebenfalls eine Alternation für die verschiedenen Schreibweisen des Hosts, die durch eine einfangende Gruppe zusammengehalten wird. Lesen Sie den von der zweiten einfangenden Gruppe gefundenen Text aus, um den Host aus der URL zu erhalten. Wenn Sie eine Regex suchen, die eine beliebige URL findet, auch solche ohne Host, können Sie eine der Regexes aus Rezept 7.7 nutzen. Die erste Regex in diesem Rezept fängt den Host, sofern es ihn gibt, in der vierten einfangenden Gruppe ein.

Siehe auch Rezepte 2.9, 3.9 und 7.7.

7.11 Den Port aus einer URL extrahieren Problem Sie wollen die Portnummer aus einem String extrahieren, der eine URL enthält. So wollen Sie zum Beispiel 80 aus http://www.regexcookbook.com:80/ extrahieren. 390 | Kapitel 7: URLs, Pfade und Internetadressen

Lösung Extrahieren des Ports aus einer URL, die als gültig bekannt ist \A [a-z][a-z0-9+\-.]*:// ([a-z0-9\-._~%!$&'()*+,;=]+@)? ([a-z0-9\-._~%]+ |\[[a-z0-9\-._~%!$&'()*+,;=:]+\]) :(?[0-9]+)

# # # # #

Schema Benutzer Named Host oder IPv4 Host IPv6+ Host Portnummer

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ^[a-z][a-z0-9+\-.]*://([a-z0-9\-._~%!$&'()*+,;=]+@)? ([a-z0-9\-._~%]+|\[[a-z0-9\-._~%!$&'()*+,;=:]+\]):([0-9]+)

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Extrahieren des Ports beim Validieren der URL \A [a-z][a-z0-9+\-.]*:// ([a-z0-9\-._~%!$&'()*+,;=]+@)? ([a-z0-9\-._~%]+ |\[[a-f0-9:.]+\] |\[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:]+\]) :([0-9]+) (/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/? (\?[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? (\#[a-z0-9\-._~%!$&'()*+,;=:@/?]*)? \Z

# # # # # # # # #

Schema Benutzer Named Host IPv6 Host IPvFuture Host Port Pfad Query Fragment

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^[a-z][a-z0-9+\-.]*:\/\/([a-z0-9\-._~%!$&'()*+,;=]+@)? ([a-z0-9\-._~%]+|\[[a-f0-9:.]+\]|\[v[a-f0-9][a-z0-9\-._~%!$&'()*+,;=:] +\]):([0-9]+)(\/[a-z0-9\-._~%!$&'()*+,;=:@]+)*\/? (\?[a-z0-9\-._~%!$&'()*+,;=:@\/?]*)?(#[a-z0-9\-._~%!$&'()*+,;=:@\/?]*)?$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python

Diskussion Das Extrahieren der Portnummer aus einer URL ist einfach, wenn Sie schon wissen, dass es sich beim Ausgangstext um eine gültige URL handelt. Wir verwenden ‹\A› oder ‹^›, um die Übereinstimmung mit dem Anfang des Strings zu verbinden. ‹[a-z][a-z0-9+\.]*://› überspringt das Schema, ‹([a-z0-9\-._~%!$&'()*+,;=]+@)?› den optionalen Benutzer und ‹([a-z0-9\-._~%]+|\[[a-z0-9\-._~%!$&'()*+,;=:]+\])› den Hostnamen.

7.11 Den Port aus einer URL extrahieren |

391

Die Portnummer wird vom Hostnamen durch einen Doppelpunkt getrennt, den wir dem regulären Ausdruck als literales Zeichen hinzufügen. Die Portnummer selbst besteht einfach aus einer Ziffernfolge, die sich leicht durch ‹[0-9]+› finden lässt. Diese Regex findet nur dann eine Übereinstimmung, wenn in der URL auch tatsächlich eine Portnummer angegeben ist. In dem Fall passt die Regex auf Schema, Benutzer, Host und Portnummer. Dann können Sie den von der dritten einfangenden Gruppe gefundenen Text auslesen, um die Portnummer ohne Trennzeichen oder andere URL-Elemente zu erhalten. Die anderen beiden Gruppe werden genutzt, um den Benutzernamen optional zu machen und die beiden Alternativen für den Hostnamen zusammenzuhalten. In Rezept 2.9 erfahren Sie alles über einfangende Gruppen, und in Rezept 3.9 wird erläutert, wie Sie den von solchen einfangenden Gruppen gefundenen Text in Ihrer Programmiersprache auslesen. Wenn Sie noch nicht wissen, ob Ihr Ausgangstext eine gültige URL ist, können Sie eine vereinfachte Version der Regex aus Rezept 7.7 verwenden. Da wir den Port extrahieren wollen, können wir URLs ausschließen, in denen keine Authority definiert ist. Damit wird die Regex ein bisschen einfacher. Sie ähnelt der aus Rezept 7.10. Der einzige Unterschied besteht darin, dass die Portnummer nicht optional ist und wir die einfangende Gruppe für die Portnummer so verschoben haben, dass der Doppelpunkt als Trennzeichen nicht mit enthalten ist. Die Nummer der einfangenden Gruppe ist 3. Wenn Sie eine Regex haben wollen, die eine beliebige gültige URL findet, auch eine ohne Port, können Sie eine der Regexes aus Rezept 7.7 verwenden. Die erste Regex in diesem Rezept fängt den Port in der fünften einfangenden Gruppe ein – sofern er denn angegeben ist.

Siehe auch Rezepte 2.9, 3.9 und 7.7.

7.12 Den Pfad aus einer URL extrahieren Problem Sie wollen den Pfad aus einem String extrahieren, der eine URL enthält. So wollen Sie zum Beispiel /index.html erhalten, wenn Sie http://www.regexcookbook.com/index.html oder /index.html#fragment als Ausgangstext haben.

392 | Kapitel 7: URLs, Pfade und Internetadressen

Lösung Extrahieren des Pfads aus einem String, der eine gültige URL enthält. Die folgende Regex findet alle URLs, auch wenn diese keinen Pfad enthalten: \A # Schema und Authority überspringen, wenn vorhanden ([a-z][a-z0-9+\-.]*:(//[^/?#]+)?)? # Pfad ([a-z0-9\-._~%!$&'()*+,;=:@/]*)

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^([a-z][a-z0-9+\-.]*:(//[^/?#]+)?)?([a-z0-9\-._~%!$&'()*+,;=:@/]*)

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Extrahieren des Pfads aus einem String, der eine gültige URL enthält. Nur URLs finden, die auch wirklich einen Pfad enthalten: \A # Schema und Authority überspringen, wenn vorhanden ([a-z][a-z0-9+\-.]*:(//[^/?#]+)?)? # Pfad (/?[a-z0-9\-._~%!$&'()*+,;=@]+(/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?|/) # Query, Fragment oder Ende der URL ([#?]|\Z)

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^([a-z][a-z0-9+\-.]*:(//[^/?#]+)?)?(/?[a-z0-9\-._~%!$&'()*+,;=@]+ (/[a-z0-9\-._~%!$&'()*+,;=:@]+)*/?|/)([#?]|$)

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Extrahieren des Pfads aus einem String, der eine gültige URL enthält. Mit atomaren Gruppen nur die URLs finden, die auch einen Pfad enthalten: \A # Schema und Authority überspringen, wenn vorhanden (?>([a-z][a-z0-9+\-.]*:(//[^/?#]+)?)?) # Pfad ([a-z0-9\-._~%!$&'()*+,;=:@/]+)

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Ruby

7.12 Den Pfad aus einer URL extrahieren | 393

Diskussion Zum Extrahieren des Pfads können Sie einen deutlich einfacheren regulären Ausdruck verwenden, wenn Sie schon wissen, dass es sich bei Ihrem Ausgangstext um eine gültige URL handelt. Die generische Regex in Rezept 7.7 enthält in Abhängigkeit von der Anwesenheit eines Schemas und/oder einer Authority drei verschiedene Wege, den Pfad zu finden. Die spezifische Regex, mit der der Pfad aus einer schon als gültig bekannten URL extrahiert werden kann, muss den Pfad nur einmal finden. Wir beginnen mit ‹\A› oder ‹^›, um die Übereinstimmung mit dem Anfang des Strings zu verknüpfen. ‹[a-z][a-z0-9+\-.]*:› überspringt das Schema und ‹//[^/?#]+› die Authority. Wir können diese sehr einfache Regex für die Authority nutzen, weil wir schon wissen, dass sie gültig ist und wir nicht daran interessiert sind, Benutzer, Host oder Port zu ermitteln. Die Authority beginnt mit zwei Schrägstrichen und endet mit dem Beginn des Pfads (Schrägstrich), der Query (Fragezeichen) oder des Fragments (Rautezeichen). Die negierten Zeichenklassen passen auf alles bis zum ersten Schrägstrich, Fragezeichen oder Rautezeichen (Rezept 2.3). Da die Authority optional ist, stecken wir sie in eine Gruppe, auf die der FragezeichenQuantor folgt. Das Schema ist ebenfalls optional. Wird es weggelassen, darf auch die Authority nicht angegeben werden. Um das in der Regex abzubilden, packen wir die Teile für das Schema und die optionale Authority in eine weitere Gruppe, die ebenfalls mit einem Fragezeichen versehen wird. Aus dem Wissen heraus, dass die URL gültig ist, können wir den Pfad mit einer einzelnen Zeichenklasse ‹[a-z0-9\-._~%!$&'()*+,;=:@/]*› abbilden, die den Schrägstrich enthält. Wir müssen daher nicht auf direkt aufeinanderfolgende Schrägstriche prüfen, die nicht erlaubt sind. Aber wir verwenden hier nach der Zeichenklasse für den Pfad als Quantor einen Stern anstelle des Pluszeichens. Es mag seltsam sein, dass man den Pfad in einer Regex optional macht, die doch nur dafür gedacht ist, den Pfad aus einer URL zu extrahieren. Aber das ist dringend nötig, denn wir nutzen recht radikale Abkürzungen, um das Schema und die Authority zu überspringen. In der generischen Regex für URLs in Rezept 7.7 haben wir drei verschiedene Wege, den Pfad zu finden – je nachdem, ob das Schema und/oder die Authority in der URL vorhanden sind. Damit ist sichergestellt, dass das Schema nicht unabsichtlich als Pfad gefunden wird. Jetzt versuchen wir, die Dinge möglichst einfach zu halten, indem wir nur eine Zeichenklasse für den Pfad verwenden. Schauen Sie sich die URL http://www.regexcookbook.com an, die ein Schema und eine Authority hat, aber keine Pfad. Der erste Teil der Regex wird das Schema und die Authority problemlos finden. Dann versucht sich die Regex-Engine an der Zeichenklasse für den Pfad, aber es sind keine Zeichen mehr übrig. Ist der Pfad optional (mit dem Stern-Quantor), ist die Engine trotzdem glücklich, weil sie ja nicht unbedingt Zeichen für den Pfad finden muss. Sie gelangt an das Ende der Regex und erklärt, dass sie tatsächlich etwas gefunden hat. 394 | Kapitel 7: URLs, Pfade und Internetadressen

Ist die Zeichenklasse für den Pfad nicht optional, führt die Regex-Engine ein Backtracking durch (wenn Sie mit Backtracking nicht vertraut sind, werfen Sie einen Blick in Rezept 2.13). Sie erinnert sich daran, dass die Authority- und Schema-Elemente in unserer Regex optional sind. Also versucht die Engine einen neuen Durchlauf, diesmal aber sorgt sie dafür, dass ‹(//[^/?#]+)?› nichts findet. ‹[a-z0-9\-._~%!$&'()*+,;=:@/]+› passt dann für den Pfad auf //www.regexcookbook.com, was wir natürlich gar nicht wollen. Würden wir eine exaktere Regex für den Pfad nutzen, mit dem doppelte Schrägstriche verboten wären, würde die Regex-Engine per Backtracking noch weiter zurückspringen und davon ausgehen, dass die URL kein Schema enthält. Mit einer exakten Regex für den Pfad würde sie nun http finden. Um auch das zu verhindern, müssten wir eine zusätzliche Prüfung einbauen, die testet, ob auf den Pfad eine Query, ein Fragment oder gar nichts mehr folgt. Fügt man all das zusammen, landet man wieder bei regulären Ausdrücken, die nur URLs mit Pfad finden. Die sind ein ganzes Stück komplizierter als die ersten beiden, nur um dafür zu sorgen, dass die Regex keine URLs ohne Pfad findet. Wenn Ihre Regex-Variante atomare Gruppen unterstützt, gibt es einen einfacheren Weg. Alle in diesem Buch behandelten Varianten – außer JavaScript und Python – unterstützen atomare Gruppen (siehe Rezept 2.15). Solch eine Gruppe weist die Regex-Engine an, kein Backtracking durchzuführen. Stecken wir die Elemente für Schema und Authority in eine atomare Gruppe, wird die Regex-Engine dazu gezwungen, die Übereinstimmungen für das Schema und die Authority beizubehalten, auch wenn damit für die Zeichenklasse für den Pfad nichts mehr übrig bleibt. Diese Lösung ist genauso effizient wie die für den optionalen Pfad. Unabhängig davon, welchen regulären Ausdruck Sie aus diesem Rezept wählen – die dritte einfangende Gruppe wird immer den Pfad enthalten. Diese kann einen leeren String zurückgeben (oder null in JavaScript), wenn Sie eine der ersten beiden Regexes verwenden, bei denen der Pfad optional sein darf. Wenn Sie noch nicht wissen, ob Ihr Ausgangstext eine gültige URL ist, können Sie die Regex aus Rezept 7.7 verwenden. Nutzen Sie .NET, können Sie die .NET-spezifische Regex nutzen, die drei Gruppen mit dem Namen „path“ enthält, um die drei Teile der Regex einzufangen, die den Pfad der URL enthalten können. Bei anderen Varianten, die benannte Captures kennen, wird eine der drei Gruppen „hostpath“, „schemepath“ oder „relpath“ den Pfad enthalten. Da nur eine der drei Gruppen tatsächlich etwas enthält, kann man den Pfad mit einem Trick auslesen. Man konkatiniert einfach den Inhalt der drei Gruppen. Zwei davon werden den leeren String zurückgeben, sodass man immer das erhält, was man haben möchte. Wenn Ihre Regex-Variante keine benannten Captures unterstützt, können Sie die erste Regex aus Rezept 7.7 nutzen. Sie fängt den Pfad in den Gruppen 6, 7 oder 8 ein. Auch hier können Sie den Inhalt der drei Gruppen konkatenieren, da zwei von ihnen immer einen leeren String zurückgeben werden. In JavaScript funktioniert das allerdings nicht. Dort geben Gruppen, die nicht am Suchergebnis beteiligt sind, ein undefined zurück.

7.12 Den Pfad aus einer URL extrahieren | 395

In Rezept 3.9 finden Sie mehr Informationen zum Auslesen von Text, der durch benannte oder nummerierte Gruppen gefunden wurde, in Ihrer Programmiersprache.

Siehe auch Rezepte 2.9, 3.9 und 7.7.

7.13 Die Query aus einer URL extrahieren Problem Sie wollen die Query aus einem String auslesen, der eine URL enthält. So wollen Sie zum Beispiel den Wert param=value erhalten, wenn Ihr Ausgangstext http://www.regexcookbook.com?param=value oder /index.html?param=value lautet.

Lösung ^[^?#]+\?([^#]+)

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Das Extrahieren der Query aus einer URL ist trivial, wenn Sie wissen, dass es sich bei Ihrem Ausgangstext um eine gültige URL handelt. Die Query wird vom vorderen Teil der URL durch ein Fragezeichen abgetrennt. Das ist das erste Fragezeichen, das in URLs erlaubt ist. Daher können wir mit ‹^[^?#]+\?› einfach alles bis zu diesem ersten Fragezeichen überspringen. Das Fragezeichen ist nur außerhalb von Zeichenklassen ein Metazeichen, daher müssen wir es dort maskieren. Der erste ‹^› ist ein Anker (Rezept 2.5), während der zweite ‹^› die Zeichenklasse negiert (Rezept 2.3). Fragezeichen können in URLs als Teil des (optionalen) Fragments nach der Query auftauchen. Daher müssen wir ‹^[^?#]+\?› und nicht nur einfach ‹\?› verwenden, um sicherzustellen, dass wir das erste Fragezeichen in der URL erwischen und dass es sich dabei nicht um einen Teil des Fragments in einer URL handelt, die keine Query enthält. Die Query läuft bis zum Anfang des Fragments oder, wenn es keins gibt, bis zum Ende der URL. Das Fragment ist vom Rest der URL durch ein Rautezeichen abgetrennt. Da Rautezeichen nur im Fragment zulässig sind, brauchen wir zum Finden der Query lediglich ‹[^#]+›. Die negierte Zeichenklasse passt auf alles bis zum ersten Rautezeichen oder bis zum Ende des Ausgangstexts, wenn kein Rautezeichen vorhanden ist. Dieser reguläre Ausdruck findet nur dann eine Übereinstimmung, wenn die URL tatsächlich eine Query enthält. Gibt es eine Übereinstimmung, findet sich darin alles vom

396 | Kapitel 7: URLs, Pfade und Internetadressen

Anfang der URL an, daher umschließen wir den Teil ‹[^#]+› mit einer einfangenden Gruppe. Bei einem positiven Suchergebnis können Sie den von der ersten (und einzigen) einfangenden Gruppe gefundenen Text auslesen, um die Query ohne Trennzeichen oder andere URL-Elemente zu erhalten. In Rezept 2.9 erfahren Sie alles über einfangende Gruppen, und in Rezept 3.9 wird genauer erklärt, wie Sie den von solchen einfangenden Gruppen gefundenen Text in Ihrer Programmiersprache auslesen. Wenn Sie noch nicht wissen, ob Ihr Ausgangstext eine gültige URL enthält, können Sie eine der Regexes aus Rezept 7.7 verwenden. Die erste Regex in diesem Rezept fängt die Query (sofern es überhaupt eine gibt) in Gruppe Nr. 12 ein.

Siehe auch Rezepte 2.9, 3.9 und 7.7.

7.14 Das Fragment aus einer URL extrahieren Problem Sie wollen das Fragment aus einem String extrahieren, in dem sich eine URL befindet. So wollen Sie zum Beispiel den Wert top erhalten, wenn der Ausgangstext http://www.regexcookbook.com#top oder /index.html#top lautet.

Lösung #(.+)

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Das Extrahieren des Fragments aus einer URL ist einfach, wenn Sie wissen, dass es sich bei Ihrem Ausgangstext um eine gültige URL handelt. Das Fragment wird vom Rest der URL durch ein Rautezeichen abgeteilt. Da das Fragment der einzige Teil einer URL ist, in dem Rautezeichen zulässig sind, und es auch immer der letzte Teil einer URL ist, können wir es leicht extrahieren, indem wir das erste Rautezeichen finden und dann alles bis zum Ende des Strings einfangen. Das funktioniert mit ‹#.+›. Achten Sie darauf, den FreiformModus nicht aktiviert zu haben, da Sie das literale Rautezeichen ansonsten mit einem Backslash maskieren müssten. Dieser reguläre Ausdruck findet nur für URLs eine Übereinstimmung, die auch ein Fragment enthalten. Dabei wird dann auch nur das Fragment gefunden, allerdings zusammen mit dem Rautezeichen am Anfang. Die Lösung enthält eine zusätzliche einfangende Gruppe, um lediglich das Fragment ohne das begrenzende # auslesen zu können.

7.14 Das Fragment aus einer URL extrahieren | 397

Wenn Sie noch nicht wissen, ob es sich bei Ihrem Ausgangstext um eine gültige URL handelt, können Sie eine der Regexes aus Rezept 7.7 verwenden. Die erste Regex in diesem Rezept fängt das Fragment (sofern es vorhanden ist) in Gruppe Nr. 13 ein.

Siehe auch Rezepte 2.9, 3.9 und 7.7.

7.15 Domainnamen validieren Problem Sie wollen prüfen, ob ein String wie ein gültiger, vollständig qualifizierter Domainname aussieht, oder Sie wollen solche Domainnamen in einem längeren Text finden.

Lösung Prüfen, ob ein String wie ein gültiger Domainname aussieht: ^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python \A([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}\Z

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Gültige Domainnamen in längerem Text finden: \b([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}\b

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Prüfen, ob jeder Teil der Domain nicht länger als 63 Zeichen ist: \b((?=[a-z0-9-]{1,63}\.)[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Internationalisierte Domainnamen in Punycode-Notation zulassen: \b((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}\b

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

398 | Kapitel 7: URLs, Pfade und Internetadressen

Prüfen, ob jeder Teil der Domäne nicht länger als 63 Zeichen ist und internationalisierte Domainnamen in Punycode-Notation zulassen: \b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Ein Domainname hat die Form domain.tld oder subdomain.domain.tld oder eine beliebige Zahl weiterer Subdomains. Die Top-Level-Domain (tld) besteht aus zwei oder mehr Buchstaben. Das ist der einfachste Teil der Regex: ‹[a-z]{2,}›. Die Domain und alle Subdomains bestehen aus Buchstaben, Ziffern und Bindestrichen. Bindestriche können weder direkt aufeinander folgen noch dürfen sie das erste oder letzte Zeichen einer Domain sein. Das erreichen wir über den regulären Ausdruck ‹[az0-9]+(-[a-z0-9]+)*›. Damit sind beliebig viele Buchstaben und Ziffern erlaubt, optional gefolgt von beliebig vielen Gruppen, die aus einem Bindestrich, gefolgt von einer weiteren Folge von Buchstaben und Ziffern, bestehen. Denken Sie daran, dass der Bindestrich in Zeichenklassen ein Metazeichen ist (Rezept 2.3), aber außerhalb von Zeichenklassen ganz normal verwendet werden kann. Daher braucht er in dieser Regex nicht maskiert zu werden. Die Domain und die Subdomains werden durch einen literalen Punkt getrennt, den wir über ‹\.› finden. Da es neben der Domain beliebig viele Subdomains geben kann, setzen wir den Domainnamen-Teil der Regex und den literalen Punkt in eine Gruppe, die wir wiederholen: ‹([a-z0-9]+(-[a-z0-9]+)*\.)+›. Weil die Subdomains der gleichen Syntax folgen wie die Domain, kann mit dieser Gruppe beides abgedeckt werden. Wenn Sie prüfen wollen, ob ein String einem gültigen Domainnamen entspricht, müssen jetzt nur noch an den Anfang und das Ende der Regex Anker gesetzt werden, die am Anfang und Ende des Strings passen. Das erreichen wir in allen Varianten außer in Ruby mit ‹^› und ‹$›, und in allen Varianten außer in JavaScript mit ‹\A› und ‹\Z›. In Rezept 2.5 erfahren Sie mehr über diese Anker. Möchten Sie Domainnamen in einem längeren Text finden, können Sie Wortgrenzen nutzen (‹\b›; siehe Rezept 2.6). Unsere ersten regulären Ausdrücke prüfen nicht, ob jeder Teil der Domain länger als 63 Zeichen ist oder nicht. Das lässt sich nicht so leicht umsetzen, da die Regex für einen Domainteil (‹[a-z0-9]+(-[a-z0-9]+)*›) drei Quantoren enthält. Es gibt keine Möglichkeit, der Regex-Engine mitzuteilen, dass dabei höchstens 63 Zeichen herauskommen sollen. Wir könnten ‹[-a-z0-9]{1,63}› nutzen, um einen Domainteil zu finden, der 1 bis 63 Zeichen lang ist, oder ‹\b([-a-z0-9]{1,63}\.)+[a-z]{2,63}› für den gesamten Domainnamen. Aber wir schließen damit keine Domains mehr aus, die an den falschen Stellen Bindestriche enthalten.

7.15 Domainnamen validieren | 399

Aber einen Lookahead können wir verwenden, um den gleichen Text doppelt zu finden. Falls Sie mit Lookaheads nicht so vertraut sind, machen Sie sich in Rezept 2.16 schlau. Wir verwenden die gleiche Regex ‹[a-z0-9]+(-[a-z0-9]+)*\.›, um einen Domainnamen mit gültigen Bindestrich-Kombinationen zu finden, und fügen innerhalb eines Lookahead die Regex ‹[-a-z0-9]{1,63}\.› hinzu, um zu prüfen, ob die Länge der Domain zwischen 1 und 63 Zeichen liegt. Das Ergebnis ist ‹(?=[-a-z0-9]{1,63}\.)[a-z0-9]+(-[az0-9]+)*\.›. Das Lookahead ‹(?=[-a-z0-9]{1,63}\.)› prüft zunächst, ob es bis zum nächsten Punkt 1 bis 63 Buchstaben, Ziffern und Bindestriche gibt. Es ist wichtig, den Punkt mit in das Lookahead aufzunehmen. Ohne ihn würden Domains, die länger als 63 Zeichen sind, die Lookahead-Bedingungen immer noch erfüllen. Nur durch den literalen Punkt im Lookahead stellen wir sicher, dass es nicht mehr als 63 Zeichen gibt. Das Lookahead konsumiert den gefundenen Text nicht. Ist es also erfolgreich, wird ‹[az0-9]+(-[a-z0-9]+)*\.› auf den gleichen Text angewendet, der schon vom Lookahead gefunden wurde. Wir wissen schon, dass die Domain nicht mehr als 63 Zeichen enthält, jetzt können wir prüfen, ob sich auch die Bindestriche an den richtigen Stellen befinden. Internationalisierte Domainnamen (IDNs) können theoretisch so gut wie jedes Zeichen enthalten. Die Liste der tatsächlich nutzbaren Zeichen hängt von der Registry-Organisation ab, die sich um die Top-Level-Domain kümmert. So sind für .es zum Beispiel Domainnamen mit spanischen Zeichen zulässig. In der Praxis sind internationalisierte Domainnamen häufig nach dem Punycode-Schema kodiert. Auch wenn der entsprechende Algorithmus ziemlich kompliziert ist, kommt es hier lediglich darauf an, dass er zu Domainnamen führt, die aus Buchstaben, Ziffern und Bindestrichen bestehen und den gleichen Regeln folgen wie bei den „normalen“ Namen. Der einzige Unterschied ist, dass die von Punycode erzeugten Domainnamen mit xn-beginnen. Um auch solche Domains von unseren regulären Ausdrücken finden zu lassen, müssen wir die Gruppe, die den Domainnamen findet, nur um ‹(xn--)?› ergänzen.

Siehe auch Rezepte 2.3, 2.12 und 2.16.

7.16 IPv4-Adressen finden Problem Sie wollen prüfen, ob ein bestimmter String aus einer gültigen IPv4-Adresse in der Notation 255.255.255.255 besteht. Optional wollen Sie diese Adresse auch noch in eine 32Bit-Integer-Zahl umwandeln.

400 | Kapitel 7: URLs, Pfade und Internetadressen

Lösung Regulärer Ausdruck Einfache Regex, mit der eine IP-Adresse geprüft wird: ^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Exakte Regex, mit der eine IP-Adresse geprüft wird: ^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3} (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Einfache Regex, mit der IP-Adressen aus einem längeren Text geholt werden: \b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Exakte Regex, mit der IP-Adresse aus einem längeren Text geholt werden: \b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3} (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Einfache Regex, die die vier Blöcke der IP-Adresse einfängt: ^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Exakte Regex, die die vier Blöcke der IP-Adresse einfängt: ^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\. (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\. (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\. (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Perl if ($subject =~ m/^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/) { $ip = $1 |\r\n]* \Z

# Laufwerk # Ordner # Datei

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^(?:[a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)\\(?:[^\\/:*?"|\r\n]+\\)* [^\\/:*?"|\r\n]*$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python

Pfade mit Laufwerkbuchstaben, UNC-Pfade und relative Pfade \A (?:(?:[a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)\\| \\?[^\\/:*?"|\r\n]+\\?) (?:[^\\/:*?"|\r\n]+\\)* [^\\/:*?"|\r\n]* \Z

# # # #

Laufwerk Relativer Pfad Ordner Datei

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^(?:(?:[a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)\\|\\?[^\\/:*?"|\r\n]+\\?) (?:[^\\/:*?"|\r\n]+\\)*[^\\/:*?"|\r\n]*$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python

Diskussion Pfade mit Laufwerkbuchstaben Es ist sehr einfach, einen vollständigen Pfad auf eine Datei oder einen Ordner auf einem Laufwerk zu finden, das einen Laufwerkbuchstaben besitzt. Das Laufwerk wird durch einen einzelnen Buchstaben festgelegt, auf den ein Doppelpunkt und ein Backslash folgen. Dazu nutzen wir die Regex ‹[a-z]:\\›. Der Backslash ist in regulären Ausdrücken ein Metazeichen, daher müssen wir ihn durch einen weiteren Backslash maskieren, um ihn literal zu finden. Ordner und Dateinamen können unter Windows alle Zeichen enthalten außer \/:*?"|. Zeilenumbrüche sind auch nicht zugelassen. Wir können problemlos eine Folge aller zulässigen Zeichen mit der negierten Zeichenklasse ‹[^\\/:*?"|\r\n]+› finden. Der Backslash ist auch in Zeichenklassen ein Metazeichen, daher maskieren wir ihn. ‹\r› und

418 | Kapitel 7: URLs, Pfade und Internetadressen

‹\n› sind die beiden Zeichen für den Zeilenumbruch. In Rezept 2.3 erfahren Sie mehr

über (negierte) Zeichenklassen. Der Plus-Quantor (Rezept 2.12) legt fest, dass wir eines oder mehrere solcher Zeichen haben wollen. Ordner werden durch Backslashs getrennt. Wir können eine Folge von null oder mehr Ordnern mithilfe von ‹(?:[^\\/:*?"|\r\n]+\\)*› finden. Dabei wird die Regex für den Ordnernamen und einen literalen Backslash in eine nicht-einfangende Gruppe gesteckt (Rezept 2.9), die durch den Stern null Mal oder häufiger gefunden werden kann (Rezept 2.12). Um den Dateinamen zu finden, nutzen wir ‹[^\\/:*?"|\r\n]*›. Der Stern sorgt dafür, dass der Dateiname optional ist und man Pfade mit einem Backslash zulassen kann. Wenn Sie nicht wollen, dass Pfade mit einem Backslash enden, ändern Sie das letzte ‹*› in der Regex in ein ‹+›.

Pfade mit Laufwerkbuchstaben und UNC-Pfade Dateien auf Netzwerklaufwerken, die nicht auf einem Laufwerkbuchstaben abgebildet sind, können durch UNC-Pfade (Universal Naming Convention) erreicht werden. UNCPfade haben die Form \\server\freigabe\ordner\datei. Wir können die Regex für die Pfade mit Laufwerkbuchstaben problemlos so erweitern, dass sie auch UNC-Pfade unterstützt. Dazu müssen wir nur den Teil ‹[a-z]:›, der den Laufwerkbuchstaben findet, durch etwas ersetzen, dass einen Laufwerkbuchstaben oder einen Servernamen findet. Diese Aufgabe erledigt ‹(?:[a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)›. Der vertikale Balken ist der Alternationsoperator (Rezept 2.8). Damit haben Sie die Wahl zwischen einem Laufwerkbuchstaben, der durch ‹[a-z]:› gefunden wird, und einem Server mit einem Freigabenamen, auf den ‹\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+› passt. Der Alternationsoperator hat die niedrigste Wertigkeit aller Regex-Operatoren. Um die beiden Alternativen zu gruppieren, verwenden wir eine nicht-einfangende Gruppe. Wie in Rezept 2.9 erklärt, ist die Zeichenfolge ‹(?:› der Anfang einer nicht-einfangenden Gruppe. Das Fragezeichen hat hier nicht seine normale Bedeutung als Quantor. Der Rest des regulären Ausdrucks kann so bleiben. Der Freigabename in UNC-Pfaden wird durch den Teil der Regex gefunden, der auch die Ordnernamen findet.

Pfade mit Laufwerkbuchstaben, UNC-Pfade und relative Pfade Ein relativer Pfad ist einer, der mit einem Ordnernamen beginnt (eventuell mit dem speziellen Namen .., der den übergeordneten Ordner auswählt) oder nur aus einem Dateinamen besteht. Um relative Pfade zu unterstützen, ergänzen wir den „Laufwerks“Abschnitt der Regex um eine dritte Alternative. Diese passt auf den Anfang eines relativen Pfads, aber nicht auf einen Laufwerkbuchstaben oder einen Servernamen.

7.18 Einen Pfad unter Windows validieren | 419

‹\\?[^\\/:*?"|\r\n]+\\?› findet den Anfang des relativen Pfads. Der Pfad kann mit einem Backslash beginnen, er muss es aber nicht. ‹\\?› passt auf den Backslash, aber auch, wenn dieser nicht vorhanden ist. ‹[^\\/:*?"|\r\n]+› passt auf einen Ordner oder

einen Dateinamen. Wenn der relative Pfad nur aus einem Dateinamen besteht, gibt es keine Übereinstimmung mit dem abschließenden ‹\\?› – so wie es auch keine Übereinstimmung gibt mit den „Ordner“- und „Datei“-Elementen der Regex, die beide optional sind. Ist im relativen Pfad ein Ordner angegeben, passt das abschließende ‹\\?› auf den Backslash, der den ersten Ordner im relativen Pfad vom Rest des Pfads trennt. Der Ordner-Teil passt dann auf die restlichen Ordner im Pfad (sofern noch welche angegeben sind) und der Datei-Teil auf den Dateinamen. Der reguläre Ausdruck, mit dem relative Pfade gefunden werden, verwendet nicht länger unterschiedliche Elemente der Regex, um auch unterschiedliche Teile des Ausgangstexts zu finden. Der Teil „relativer Pfad“ findet nämlich einen Ordner oder Dateinamen, wenn der Pfad relativ ist. Sind im relativen Pfad ein oder mehrere Ordner angegeben, findet der Teil „relativer Pfad“ den ersten Ordner, und die Teile „Ordner“ und „Datei“ holen sich das, was übrig bleibt. Handelt es sich beim relativen Pfad nur um einen Dateinamen, wird er vom Teil „relativer Pfad“ gefunden, und für die Ordner- und Dateielemente bleibt nichts mehr übrig. Da wir nur daran interessiert sind, den Pfad zu validieren, macht das nichts aus. Die Kommentare in der Regex sollen lediglich dabei helfen, sie zu verstehen. Wenn wir die Teile des Pfads über einfangende Gruppen auslesen wollen, müssen wir mehr darauf achten, das Laufwerk, den/die Ordner und den Dateinamen getrennt zu finden. Das nächste Rezept geht auf dieses Problem ein.

Siehe auch Rezepte 2.3, 2.8, 2.9 und 2.12.

7.19 Pfade unter Windows in ihre Bestandteile aufteilen Problem Sie wollen prüfen, ob ein String wie ein gültiger Pfad auf einen Ordner oder eine Datei unter Microsoft Windows aussieht. Handelt es sich bei dem String um einen gültigen Windows-Pfad, wollen Sie auch das Laufwerk, den/die Ordner und den Dateinamen extrahieren.

420 | Kapitel 7: URLs, Pfade und Internetadressen

Lösung Pfade mit Laufwerkbuchstaben \A (?[a-z]:)\\ (?(?:[^\\/:*?"|\r\n]+\\)*) (?[^\\/:*?"|\r\n]*) \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 \A (?P[a-z]:)\\ (?P(?:[^\\/:*?"|\r\n]+\\)*) (?P[^\\/:*?"|\r\n]*) \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: PCRE 4 und neuer, Perl 5.10, Python \A ([a-z]:)\\ ((?:[^\\/:*?"|\r\n]+\\)*) ([^\\/:*?"|\r\n]*) \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^([a-z]:)\\((?:[^\\/:*?"|\r\n]+\\)*)([^\\/:*?"|\r\n]*)$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python

Pfade mit Laufwerkbuchstaben und UNC-Pfade \A (?[a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)\\ (?(?:[^\\/:*?"|\r\n]+\\)*) (?[^\\/:*?"|\r\n]*) \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 \A (?P[a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)\\ (?P(?:[^\\/:*?"|\r\n]+\\)*) (?P[^\\/:*?"|\r\n]*) \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: PCRE 4 und neuer, Perl 5.10, Python

7.19 Pfade unter Windows in ihre Bestandteile aufteilen | 421

\A ([a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)\\ ((?:[^\\/:*?"|\r\n]+\\)*) ([^\\/:*?"|\r\n]*) \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^([a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)\\((?:[^\\/:*?"|\r\n]+\\)*) ([^\\/:*?"|\r\n]*)$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python

Pfade mit Laufwerkbuchstaben, UNC-Pfade und relative Pfade Diese regulären Ausdrücke können auch den leeren String finden. Im Diskussionsabschnitt ist das detaillierter beschrieben, und es gibt eine alternative Lösung. \A (?[a-z]:\\|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+\\|\\?) (?(?:[^\\/:*?"|\r\n]+\\)*) (?[^\\/:*?"|\r\n]*) \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 \A (?P[a-z]:\\|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+\\|\\?) (?P(?:[^\\/:*?"|\r\n]+\\)*) (?P[^\\/:*?"|\r\n]*) \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: PCRE 4 und neuer, Perl 5.10, Python \A ([a-z]:\\|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+\\|\\?) ((?:[^\\/:*?"|\r\n]+\\)*) ([^\\/:*?"|\r\n]*) \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^([a-z]:\\|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+\\|\\?) ((?:[^\\/:*?"|\r\n]+\\)*)([^\\/:*?"|\r\n]*)$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python

422 | Kapitel 7: URLs, Pfade und Internetadressen

Diskussion Die regulären Ausdrücke in diesem Rezept ähneln stark denen aus dem vorhergehenden. Diese Diskussion baut daher auf der aus dem vorigen Rezept auf.

Pfade mit Laufwerkbuchstaben Wir haben an den regulären Ausdrücken für Pfade mit Laufwerkbuchstaben nur eine Änderung vorgenommen – drei einfangende Gruppen, die Sie nutzen können, um die verschiedenen Elemente des Pfads auszulesen: ‹drive›, ‹folder› und ‹file›. Sie können diese Namen verwenden, wenn Ihre Regex-Variante benannte Captures unterstützt (Rezept 2.11). Wenn nicht, müssen Sie auf die einfangenden Gruppen über ihre Nummern zugreifen: 1, 2 und 3. In Rezept 3.9 erfahren Sie, wie Sie den von benannten oder nummerierten Gruppen gefundenen Text in Ihrer Programmiersprache auslesen können.

Pfade mit Laufwerkbuchstaben und UNC-Pfade Wir haben die gleichen drei einfangenden Gruppen auch den Regexes für UNC-Pfade hinzugefügt.

Pfade mit Laufwerkbuchstaben, UNC-Pfade und relative Pfade Wenn wir auch relative Pfade zulassen wollen, wird alles ein bisschen komplizierter. In obigem Rezept konnten wir einfach eine dritte Alternative für den Laufwerk-Teil ergänzen, um den Anfang des relativen Pfads zu erwischen. Hier ist das nicht möglich. Bei einem relativen Pfad sollte die einfangende Gruppe für das Laufwerk leer bleiben. Stattdessen wird nun der literale Backslash, der im Abschnitt „Pfade mit Laufwerkbuchstaben und UNC-Pfade“ hinter der einfangenden Gruppe steht, in die Gruppe hineingezogen. Dort steht er nun am Ende der Alternativen für den Laufwerkbuchstaben und die Netzwerkfreigabe. Wir ergänzen eine dritte Alternative mit einem optionalen Backslash für relative Pfade. Da die dritte Alternative optional ist, ist auch die gesamte Gruppe für das Laufwerk optional. Der so entstandene reguläre Ausdruck findet alle Windows-Pfade. Das Problem liegt nun darin, dass wir durch den optionalen Laufwerk-Teil eine Regex haben, in der alles optional ist. Die Teile für Ordner und Dateien waren schon vorher optional. Mit anderen Worten: Unser regulärer Ausdruck passt auch auf den leeren String. Wenn wir sicherstellen wollen, dass die Regex keine leeren Strings findet, müssen wir zusätzliche Alternativen ergänzen, die relative Pfade mit einem Ordner (wo der Dateiname optional ist) und ohne Ordner (wo der Dateiname notwendig ist) finden: \A (?: (?[a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)\\ (?(?:[^\\/:*?"|\r\n]+\\)*) (?[^\\/:*?"|\r\n]*)

7.19 Pfade unter Windows in ihre Bestandteile aufteilen | 423

| | ) \Z

(?\\?(?:[^\\/:*?"|\r\n]+\\)+) (?[^\\/:*?"|\r\n]*) (?[^\\/:*?"|\r\n]+)

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, PCRE 7, Perl 5.10, Ruby 1.9 \A (?: (?P[a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)\\ (?P(?:[^\\/:*?"|\r\n]+\\)*) (?P[^\\/:*?"|\r\n]*) | (?P\\?(?:[^\\/:*?"|\r\n]+\\)+) (?P[^\\/:*?"|\r\n]*) | (?P[^\\/:*?"|\r\n]+) ) \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: PCRE 4 und neuer, Perl 5.10, Python \A (?: ([a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)\\ ((?:[^\\/:*?"|\r\n]+\\)*) ([^\\/:*?"|\r\n]*) | (\\?(?:[^\\/:*?"|\r\n]+\\)+) ([^\\/:*?"|\r\n]*) | ([^\\/:*?"|\r\n]+) ) \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby ^(?:([a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)\\((?:[^\\/:*?"|\r\n]+\\)*) ([^\\/:*?"|\r\n]*)|(\\?(?:[^\\/:*?"|\r\n]+\\)+)([^\\/:*?"|\r\n]*)| ([^\\/:*?"|\r\n]+))$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python Leider müssen wir einen Preis dafür zahlen, Strings der Länge null zu vermeiden. Jetzt haben wir sechs Gruppen, die die drei verschiedenen Teile des Pfads einfangen. Sie müssen selbst entscheiden, ob es in Ihrem Szenario einfacher ist, auf leere Strings zu prüfen oder sich mit mehreren einfangenden Gruppen herumzuschlagen. Wenn Sie die .NET-Regex-Variante nutzen, können Sie mehreren benannten Gruppen den gleichen Namen verpassen. Dies ist die einzige Variante, die Gruppen mit dem gleichen Namen so behandelt, als wäre es eine einzelne einfangende Gruppe. Mit dieser Regex in .NET können Sie einfach die Übereinstimmung für die Ordner- oder Datei-

424 | Kapitel 7: URLs, Pfade und Internetadressen

gruppe auslesen, ohne sich darum kümmern zu müssen, welche der beiden Ordnergruppen oder der drei Dateigruppen nun den Inhalt enthält: \A (?: (?[a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)\\ (?(?:[^\\/:*?"|\r\n]+\\)*) (?[^\\/:*?"|\r\n]*) | (?\\?(?:[^\\/:*?"|\r\n]+\\)+) (?[^\\/:*?"|\r\n]*) | (?[^\\/:*?"|\r\n]+) ) \Z

Regex-Optionen: Freiform, Groß-/Kleinschreibung ignorieren Regex-Variante: .NET

Siehe auch Rezepte 2.9, 2.11, 3.9 und 7.18.

7.20 Den Laufwerkbuchstaben aus einem Pfad unter Windows extrahieren Problem Sie haben einen String mit einem (syntaktisch) gültigen Pfad auf eine Datei oder einen Ordner auf einem Windows-PC oder im Netzwerk. Sie wollen den Laufwerkbuchstaben aus dem Pfad auslesen, falls einer vorhanden ist. So wollen Sie zum Beispiel c aus c:\folder\file.ext extrahieren.

Lösung ^([a-z]):

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Das Extrahieren des Laufwerkbuchstaben aus einem String, der einen gültigen Pfad enthält, ist trivial, selbst wenn Sie nicht wissen, ob der Pfad tatsächlich mit einem Laufwerkbuchstaben beginnt. Der Pfad kann auch einen relativen Pfad oder einen UNC-Pfad enthalten.

7.20 Den Laufwerkbuchstaben aus einem Pfad unter Windows extrahieren | 425

Doppelpunkte sind in Pfaden unter Windows nicht zugelassen – außer als Kennzeichen für den Laufwerkbuchstaben. Wenn wir also am Anfang des Strings einen Buchstaben gefolgt von einem String finden, wissen wir, dass es sich bei dem Buchstaben um den Laufwerkbuchstaben handelt. Der Anker ‹^› passt am Anfang des Strings (Rezept 2.5). Hier macht es auch nichts aus, dass der Zirkumflex in Ruby auch an Zeilenumbrüchen passt, da gültige Pfade keinen solchen Zeilenumbruch enthalten. Die Zeichenklasse ‹[a-z]› passt auf einen einzelnen Buchstaben (Rezept 2.3). Wir stecken die Zeichenklasse in ein paar Klammern (die eine einfangende Gruppe bilden), sodass Sie den Laufwerkbuchstaben ohne den literalen Doppelpunkt auslesen können, der ebenfalls durch den regulären Ausdruck gefunden wird. Der Doppelpunkt ist in der Regex enthalten, um sicherzustllen, dass wir wirklich einen Laufwerkbuchstaben vor uns haben und nicht den ersten Buchstaben in einem relativen Pfad.

Siehe auch In Rezept 2.9 werden einfangende Gruppen erklärt. In Rezept 3.9 erfahren Sie, wie Sie in Ihrer Programmiersprache Text aus einfangenden Gruppen auslesen können. Sollten Sie nicht sicher sein, ob Ihr String einen gültigen Windows-Pfad enthält, schauen Sie sich die Regexes aus Rezept 7.19 an.

7.21 Den Server und die Freigabe aus einem UNC-Pfad extrahieren Problem Sie haben einen String mit einem (syntaktisch) gültigen Pfad auf eine Datei oder einen Ordner auf einem Windows-PC oder im Netzwerk. Wenn es sich bei dem Pfad um einen UNC-Pfad handelt, wollen Sie den Namen des Netzwerkservers und den Freigabenamen auslesen. So wollen Sie zum Beispiel die Werte server und freigabe aus \\server\ freigabe\ordner\datei.ext ermitteln.

Lösung ^\\\\([a-z0-9_.$]+)\\([a-z0-9_.$]+)

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

426 | Kapitel 7: URLs, Pfade und Internetadressen

Diskussion Das Extrahieren des Netzwerkservers und des Freigabenamens aus einem String mit einem gültigen Pfad ist einfach, selbst wenn Sie nicht wissen, ob es sich bei dem Pfad um einen UNC-Pfad handelt. Es könnte sich auch um einen relativen Pfad oder um einen mit einem Laufwerkbuchstaben handeln. UNC-Pfade beginnen mit zwei Backslashs. In Windows-Pfaden sind zwei direkt aufeinanderfolgende Backslashs nur am Anfang eines UNC-Pfads erlaubt. Beginnt also ein als gültig bekannter Pfad mit zwei Backslashs, wissen wir, dass danach der Servername und der Freigabename folgen müssen. Der Anker ‹^› passt auf den Anfang des Strings (Rezept 2.5). Hier macht es auch nichts aus, dass der Zirkumflex in Ruby auch an Zeilenumbrüchen passt, da gültige Pfade keinen solchen Zeilenumbruch enthalten. ‹\\\\› passt auf zwei literale Backslashs. Da es sich bei einem Backslash in einem regulären Ausdruck um ein Metazeichen handelt, müssen wir ihn durch einen weiteren Backslash maskieren, um ihn als literales Zeichen nutzen zu können. Die erste Zeichenklasse ‹[a-z0-9_.$]+› passt auf den Namen des Netzwerkservers. Die zweite nach einem weiteren literalen Backslash passt auf den Freigabenamen. Wir haben beide Zeichenklassen in Klammern gesteckt, um zwei einfangende Gruppen zu erhalten. So können Sie den Servernamen aus der ersten und den Freigabenamen aus der zweiten einfangenden Gruppe auslesen. Das gesamte Suchergebnis enthält \\server\freigabe.

Siehe auch In Rezept 2.9 erfahren Sie alles über einfangende Gruppen. In Rezept 3.9 erfahren Sie, wie Sie in Ihrer Programmiersprache Text aus einfangenden Gruppen auslesen können. Sollten Sie nicht sicher sein, ob Ihr String einen gültigen Windows-Pfad enthält, schauen Sie sich die Regexes aus Rezept 7.19 an.

7.22 Die Ordnernamen aus einem Pfad unter Windows extrahieren Problem Sie haben einen String mit einem (syntaktisch) gültigen Pfad auf eine Datei oder einen Ordner auf einem Windows-PC oder im Netzwerk und wollen die Ordnernamen auslesen. So wollen Sie zum Beispiel den Wert \ordner\unterordner\ aus c:\ordner\unterordner\datei.ext oder \\server\freigabe\ordner\unterordner\datei.ext erhalten.

7.22 Die Ordnernamen aus einem Pfad unter Windows extrahieren | 427

Lösung ^([a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)?((?:\\|^) (?:[^\\/:*?"|\r\n]+\\)+)

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Das Auslesen der Ordnernamen aus einem Windows-Pfad ist ein bisschen schwieriger, wenn man auch UNC-Pfade unterstützen will, da wir nicht einfach den Teil des Pfads zwischen den Backslashs auslesen können. Denn dann würden wir auch den Server- und Freigabenamen des UNC-Pfads erhalten. Der erste Teil der Regex, ‹^([a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)?›, überspringt den Laufwerkbuchstaben oder den Netzwerkserver und den Freigabenamen am Anfang des Pfads. Dieser Teil der Regex besteht aus einer einfangenden Gruppe mit zwei Alternativen. Die erste Alternative passt auf den Laufwerkbuchstaben (siehe Rezept 7.20), die zweite auf den Server und die Freigabe in UNC-Pfaden (siehe Rezept 7.21). In Rezept 2.8 wird der Alternationsoperator näher erläutert. Das Fragezeichen nach der Gruppe sorgt dafür, dass sie optional wird. Damit können wir auch relative Pfade unterstützen, die keinen Laufwerkbuchstaben und keinen Freigabenamen enthalten. Die Ordner lassen sich dann einfach mit ‹(?:[^\\/:*?"|\r\n]+\\)+› finden. Die Zeichenklasse passt auf einen Ordnernamen. Die nicht-einfangende Gruppe passt auf einen Ordnernamen, gefolgt von einem literalen Backslash, der die Ordner untereinander und vom Dateinamen trennt. Wir wiederholen diese Gruppe ein Mal oder häufiger. Das bedeutet, dass unser regulärer Ausdruck nur solche Pfade findet, die auch tatsächlich einen Ordner enthalten. Pfade, die nur einen Dateinamen, ein Laufwerk oder eine Netzwerkfreigabe enthalten, werden nicht gefunden. Wenn der Pfad mit einem Laufwerkbuchstaben oder einer Netzwerkfreigabe beginnt, muss darauf ein Backslash folgen. Ein relativer Pfad kann mit einem Backslash beginnen, er muss es aber nicht. Daher müssen wir einen optionalen Backslash am Anfang der Gruppe einfügen, die die Ordner im Pfad findet. Wir verwenden unsere Regex nur auf gültigen Pfaden, daher müssen wir in Bezug auf Backslashs bei Laufwerkbuchstaben oder Netzwerkfreigaben nicht so streng sein. Wir müssen sie nur zulassen. Da die Regex mindestens einen Ordner finden soll, müssen wir sicherstellen, dass sie nicht den Wert e\ als Ordner in \\server\freigabe\ findet. Das ist der Grund dafür, dass wir ‹(\\|^)› statt ‹\\?› verwenden, um den optionalen Backslash am Anfang der einfangenden Gruppe nutzen.

428 | Kapitel 7: URLs, Pfade und Internetadressen

Wenn Sie sich fragen, warum \\server\freigabe als Laufwerk und e\ als Ordner gefunden werden könnten, werfen Sie einen Blick in Rezept 2.13. Regex-Engines nutzen Backtracking. Schauen Sie sich diese Regex an: ^([a-z]:|\\\\[a-z0-9_.$]+\\[a-z0-9_.$]+)? ((?:\\?(?:[^\\/:*?"|\r\n]+\\)+)

Bei der Regex aus dieser Lösung muss es mindestens ein (Nicht-Backslash-)Zeichen und einen Backslash für den Pfad geben. Wenn die Regex für das Laufwerk in \\server\ freigabe die Übereinstimmung \\server\freigabe gefunden hat und dann mit der Ordner-Gruppe nichts mehr findet, gibt sie nicht einfach auf, sondern sie versucht sich an verschiedenen Permutationen der Regex. In diesem Fall hat sich die Engine gemerkt, dass die Zeichenklasse ‹[a-z0-9_.$]+›, die die Netzwerkfreigabe findet, nicht unbedingt alle passenden Zeichen konsumieren muss. Um das ‹+› zu bedienen, reicht ein Zeichen aus. Die Engine geht also per Backtracking zurück und nötigt die Zeichenklasse, wieder ein Zeichen herzugeben. Damit versucht sie einen neuen Anlauf. Jetzt gibt es zwei verbleibende Zeichen im Ausgangstext, mit denen der Ordner gefunden werden kann: e\. Diese beiden Zeichen reichen aus, um mit ‹(?:[^\\/:*?"|\r\n]+\\)+› etwas zu finden. Damit haben wir ein erfolgreiches Gesamtergebnis, das wir aber eigentlich gar nicht wollen. Durch ‹(\\|^)› statt ‹\\?› wird dieses Problem behoben. So kann am Anfang immer noch ein optionaler Backslash stehen. Fehlt dieser aber, muss der Ordner am Anfang des Strings stehen. Falls also ein Laufwerk gefunden wurde und die Regex-Engine schon über den Anfang des Strings hinaus ist, muss es einen Backslash geben. Können keine Ordner gefunden werden, wird die Regex-Engine immer noch versuchen, per Backtracking mehr Erfolg zu haben, dieses Mal aber ohne Erfolg, weil ‹(\\|^)› nicht passt. Das Backtracking wird so lange durchgeführt, bis der Anfang des Strings erreicht ist. Die einfangende Gruppe für den Laufwerkbuchstaben und die Netzwerkfreigabe ist optional, daher wird die Engine versuchen, den Ordner am Anfang des Strings zu finden. ‹(\\|^)› wird hier zwar passen, aber nicht der Rest der Regex, weil ‹(?:[^\\/:*?"|\r\n]+\\)+› den Doppelpunkt nach dem Laufwerkbuchstaben oder den doppelten Backslash der Netzwerkfreigabe nicht zulässt. Vielleicht fragen Sie sich, warum wir diese Technik nicht in den Rezepten 7.18 und 7.19 verwendet haben. Das liegt daran, dass diese regulären Ausdrücke keinen Ordner benötigen. Da alles nach dem Teil, der das Laufwerk findet, optional ist, führt die Regex-Engine kein Backtracking durch. Natürlich kann dieses Vorgehen zu anderen Problemen führen, die auch in Rezept 7.19 besprochen werden. Findet dieser reguläre Ausdruck eine Übereinstimmung, enthält die erste einfangende Gruppe den Laufwerkbuchstaben oder die Netzwerkfreigabe und die zweite Gruppe die Ordner. Bei einem relativen Pfad wird die erste einfangende Gruppe leer sein. Die zweite Gruppe wird immer mindestens einen Ordner enthalten. Wenn Sie diese Regex auf einen Pfad anwenden, in dem kein Ordner angegeben ist, findet die Regex gar keine Übereinstimmung.

7.22 Die Ordnernamen aus einem Pfad unter Windows extrahieren | 429

Siehe auch In Rezept 2.9 erfahren Sie alles über einfangende Gruppen. In Rezept 3.9 erfahren Sie, wie Sie in Ihrer Programmiersprache Text aus einfangenden Gruppen auslesen können. Sollten Sie nicht sicher sein, ob Ihr String einen gültigen Windows-Pfad enthält, schauen Sie sich die Regexes aus Rezept 7.19 an.

7.23 Den Dateinamen aus einem Pfad unter Windows extrahieren Problem Sie haben einen String mit einem (syntaktisch) gültigen Pfad auf eine Datei oder einen Ordner auf einem Windows-PC oder im Netzwerk und wollen den Dateinamen auslesen (wenn einer angegeben ist). So wollen Sie zum Beispiel den Wert datei.ext erhalten, wenn Sie den Text c:\ordner\datei.ext als Ausgangstext haben.

Lösung [^\\/:*?"|\r\n]+$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Das Extrahieren des Dateinamens aus einem String mit einem gültigen Pfad ist einfach, selbst wenn Sie nicht wissen, ob der Pfad wirklich mit einem Dateinamen endet. Der Dateiname steht immer am Ende des Strings. Er darf keine Doppelpunkte oder Backslashs enthalten, daher kann man nicht mit Ordnern, Laufwerkbuchstaben oder Netzwerkfreigaben durcheinanderkommen. Der Anker ‹$› passt auf das Ende des Strings (Rezept 2.5). Hier macht es nichts aus, dass das Dollarzeichen in Ruby auch an Zeilenumbrüchen passt, da gültige Pfade keinen solchen Zeilenumbruch enthalten. Die negierte Zeichenklasse ‹[^\\/:*?"|\r\n]+› (Rezept 2.3) passt auf die Zeichen, die in Dateinamen vorkommen können. Obwohl die Regex-Engine den String von links nach rechts durcharbeitet, stellt der Anker am Ende der Regex sicher, dass nur das letzte Vorkommen von Dateinamen-Zeichen im String gefunden werden, womit wir unseren Dateinamen erhalten. Endet der String mit einem Backslash, wie zum Beispiel bei Pfaden, die keinen Dateinamen enthalten, findet die Regex keine Übereinstimmung. Wenn es eine gibt, wird nur

430 | Kapitel 7: URLs, Pfade und Internetadressen

der Dateiname gefunden, daher brauchen wir keine einfangenden Gruppen, mit denen der Dateiname vom restlichen Pfad getrennt ausgelesen werden kann.

Siehe auch In Rezept 3.7 erfahren Sie, wie Sie in Ihrer Programmiersprache Text aus einfangenden Gruppen auslesen können. Sollten Sie nicht sicher sein, ob Ihr String einen gültigen Windows-Pfad enthält, schauen Sie sich die Regexes aus Rezept 7.19 an.

7.24 Die Dateierweiterung aus einem Pfad unter Windows extrahieren Problem Sie haben einen String mit einem (syntaktisch) gültigen Pfad auf eine Datei oder einen Ordner auf einem Windows-PC oder im Netzwerk und wollen die Dateierweiterung auslesen (wenn eine angegeben ist). So wollen Sie zum Beispiel den Wert .ext aus dem Ausgangstext c:\folder\file.ext ziehen.

Lösung \.[^.\\/:*?"|\r\n]+$

Regex-Optionen: Groß-/Kleinschreibung ignorieren Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Um die Dateierweiterung zu erhalten, können wir die gleichen Techniken anwenden wie zum Extrahieren des gesamten Dateinamens in Rezept 7.23. Der einzige Unterschied besteht im Umgang mit den Punkten. Die Regex in Rezept 7.23 enthält keinerlei Punkte. Die negierte Zeichenklasse in dieser Regex passt einfach auf alle Punkte, die im Dateinamen vorkommen. Eine Dateierweiterung muss mit einem Punkt beginnen. Daher fügen wir der Regex ‹\.› hinzu, um am Anfang der Regex einen literalen Punkt zu finden. Dateinamen wie Version 2.0.txt können mehr als einen Punkt enthalten. Der letzte Punkt ist derjenige, der die Erweiterung vom Dateinamen trennt. Die Erweiterung selbst darf keine Punkte enthalten. Das setzen wir in der Regex um, indem wir einen Punkt in die Zeichenklasse einfügen. Dort ist er ein normales literales Zeichen, daher müssen wir ihn nicht maskieren. Der Anker ‹$› am Ende der Regex stellt sicher, dass wir .txt und nicht .0 finden.

7.24 Die Dateierweiterung aus einem Pfad unter Windows extrahieren | 431

Wenn der String mit einem Backslash endet oder mit einem Dateinamen, der keine Punkte enthält, bleibt die Suche erfolglos. Gibt es eine Übereinstimmung, ist dies auch gleich die Erweiterung zusammen mit dem Punkt, der den Dateinamen und die Erweiterung voneinander trennt.

Siehe auch Sollten Sie nicht sicher sein, ob Ihr String einen gültigen Windows-Pfad enthält, schauen Sie sich die Regexes aus Rezept 7.19 an.

7.25 Ungültige Zeichen aus Dateinamen entfernen Problem Sie wollen Zeichen aus einem String entfernen, die in Dateinamen unter Windows nicht gültig sind. Das können Sie zum Beispiel nutzen, um aus einem String mit dem Titel eines Dokuments einen Standarddateinamen abzuleiten, wenn der Anwender das erste Mal das Dokument speichern möchte.

Lösung Regulärer Ausdruck [\\/:"*?|]+

Regex-Optionen: Keine Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Ersetzung Lassen Sie den Ersetzungstext leer. Ersetzungstextvarianten: .NET, Java, JavaScript, PHP, Perl, Python, Ruby

Diskussion Die Zeichen \/:"*?| sind in Dateinamen unter Windows nicht gültig. Sie werden genutzt, um Laufwerke und Ordner zu trennen, um Pfade mit Leerzeichen nutzen zu können oder um Jokerzeichen und Umleitungen an der Befehlszeile zu ermöglichen. Wir können diese Zeichen leicht mit der Zeichenklasse ‹[\\/:"*?|]› finden. Der Backslash ist auch innerhalb von Zeichenklassen ein Metazeichen, deshalb müssen wir ihn durch einen weiteren Backslash maskieren. Alle anderen Zeichen sind innerhalb von Zeichenklassen immer literale Zeichen.

432 | Kapitel 7: URLs, Pfade und Internetadressen

Wir wiederholen die Zeichenklasse aus Effizienzgründen durch ein ‹+›. Enthält der String mehrere direkt aufeinanderfolgende ungültige Zeichen, wird die gesamte Folge auf einmal gelöscht und nicht Zeichen für Zeichen. Sie werden nicht wirklich einen Performanceunterschied bemerken, wenn Sie mit sehr kurzen Strings (wie zum Beispiel Dateinamen) arbeiten, aber man sollte diese Technik im Hinterkopf haben, wenn man mit größeren Datenmengen arbeitet, die auch eher längere Folgen von zu löschenden Zeichen enthalten. Da wir die ungültigen Zeichen einfach nur löschen wollen, führen wir ein Suchen und Ersetzen mit einem leeren String als Ersetzungstext durch.

Siehe auch In Rezept 3.14 wird erklärt, wie Sie in Ihrer Programmiersprache suchen und durch einen feststehenden Text ersetzen können.

7.25 Ungültige Zeichen aus Dateinamen entfernen | 433

KAPITEL 8

Markup und Datenaustausch

Dieses letzte Kapitel dreht sich um Aufgaben, die häufig bei der Arbeit mit Auszeichnungssprachen und -formaten auftreten: HTML, XHTML, XML, CSV und INI. Auch wenn wir davon ausgehen, dass Sie mit diesen Technologien zumindest ein bisschen vertraut sind, finden Sie direkt am Anfang des Kapitels eine kurze Beschreibung. So können wir sichergehen, dass wir uns auf Augenhöhe unterhalten. Die Beschreibungen konzentrieren sich auf die grundlegenden Syntaxregeln, die notwendig sind, um die Datenstrukturen jedes Formats korrekt durchsuchen zu können. Weitere Details werden dann vorgestellt, wenn sie für die einzelnen Themen notwendig sind. Auch wenn es nicht immer so scheint: Manche dieser Formate können erstaunlich komplex sein, wenn es um die Verarbeitung geht, zumindest in Bezug auf reguläre Ausdrücke. Im Allgemeinen ist es am besten, statt regulärer Ausdrücke dedizierte Parser und APIs zu verwenden, um viele der Aufgaben in diesem Kapitel zu erledigen, insbesondere, wenn Genauigkeit eine große Rolle spielt (zum Beispiel weil durch die Verarbeitung Sicherheitsaspekte betroffen sein könnten). Trotzdem finden Sie in diesen Rezepten nützliche Techniken, die in vielen, auch kleineren Aufgaben, eine große Hilfe sein können. Lassen Sie uns also anschauen, mit was wir es hier zu tun haben. Viele der Probleme, denen wir uns in diesem Kapitel gegenübersehen, entstehen durch die folgenden Regeln, die manchmal recht überraschend angegangen werden können. Hypertext Markup Language (HTML) HTML wird verwendet, um die Struktur, Semantik und Erscheinung von Milliarden Webseiten und anderen Dokumenten zu beschreiben. HTML wird häufig mit regulären Ausdrücken verarbeitet, aber Sie sollten sich darüber im Klaren sein, dass die Sprache nicht so richtig gut auf die Strenge und Präzision reguläre Ausdrücke ausgelegt ist. Das gilt insbesondere für das misshandelte HTML, das auf vielen Webseiten im Einsatz ist und das der außerordentlichen Toleranz der Webbrowser für schlechtes HTML geschuldet ist. In diesem Kapitel werden wir uns auf die Regeln konzentrieren, die zum Verarbeiten wohlgeformten HTML-Codes notwendig sind: Elemente (und ihre Attribute), Zeichenreferenzen, Kommentare und Document-Type-Deklarationen. Dieses Buch behandelt HTML 4.01, das im Jahr 1999 abgeschlossen wurde und damit der letzte „offizielle“ Standard ist. | 435

Die grundlegenden HTML-Bestandteile sind die Elemente. Elemente werden mithilfe von Tags geschrieben, die durch spitze Klammern umschlossen sind. Dabei gibt es die Elemente entweder als Block- (zum Beispiel Absätze, Überschriften, Listen, Tabellen und Formulare) oder als Inline-Elemente (zum Beispiel Hyperlinks, Zitate, Kursivschrift und Eingabefelder auf einem Formular). Elemente haben normalerweise ein Start-Tag (zum Beispiel ) und ein End-Tag (). Das Start-Tag eines Elements kann Attribute enthalten, die später noch beschrieben werden. Zwischen den Tags befindet sich der Inhalt des Elements, der aus Text und weiteren Elementen bestehen kann, eventuell aber auch leer bleibt. Elemente können verschachtelt werden, dürfen sich aber nicht überlappen (so ist zum Beispiel in Ordnung, aber nicht). Bei manchen Elementen (wie zum Beispiel

, durch das ein Absatz ausgezeichnet wird), ist das End-Tag optional. Elemente mit einem optionalen End-Tag werden automatisch durch neue Blockelemente geschlossen. Ein paar Elemente (unter anderem
, durch das eine Zeile beendet wird) können keinen Inhalt haben und verwenden niemals ein End-Tag. Trotzdem kann ein leeres Element immer noch Attribute besitzen. Die Namen von HTML-Elementen beginnen mit einem Buchstaben von A bis Z. Alle gültigen Elemente nutzen für ihren Namen nur Buchstaben und Ziffern, Groß- und Kleinschreibung ist unwichtig. , auch wenn dies in einem Kommentar oder einem String innerhalb des Stylesheets beziehungsweise der Skriptsprache steht. Attribute finden sich innerhalb des Start-Tags nach dem Elementnamen. Sie werden durch einen oder mehrere Whitespace-Zeichen unterteilt. Die meisten Attribute werden als Name/Wert-Paare aufgeschrieben. So zeigt das folgende Beispiel ein (Anker)-Element mit zwei Attributen und dem Inhalt „Klick mich!“: Klick mich!

Wie man sieht, werden Name und Wert eines Attributs durch ein Gleichheitszeichen und optionalen Whitespace getrennt. Der Wert wird in einfachen oder doppelten Anführungszeichen geschrieben. Um das entsprechende Anführungszeichen auch innerhalb des Werts zu verwenden, müssen Sie eine Zeichenreferenz nutzen (die gleich beschrieben wird). Besteht der Wert nur aus den Zeichen A–Z, a–z, 0–9 sowie Unterstrich, Punkt, Doppelpunkt und Bindestrich (als Regex wäre das ‹^[-.0-9:A-Z_az]+$›), sind die Anführungszeichen optional. Ein paar Attribute (wie zum Beispiel selected und checked bei bestimmten Formular-Elementen) beeinflussen das Element, in dem sie stehen, einfach durch ihre Anwesenheit und erfordern keinen Wert. In diesen Fällen wird auch das Gleichheitszeichen weggelassen, das Name und Wert eines Attributs verbindet. Alternativ können diese „minimierten“ Attribute ihren eigenen Namen als Wert verwenden (zum Beispiel selected="selected"). Attributnamen beginnen mit einem Buchstaben im Bereich A bis Z. Alle gültigen Attribute nutzen nur 436 | Kapitel 8: Markup und Datenaustausch

Buchstaben und Bindestriche in ihrem Namen. Sie können in beliebiger Reihenfolge auftreten, und Groß- und Kleinschreibung ist beim Namen unwichtig. HTML 4 definiert 252 benannte Zeichenreferenzen und über eine Million numerische Zeichenreferenzen (zusammen bezeichnen wir diese als Zeichenreferenzen). Numerische Zeichenreferenzen beziehen sich über Unicode-Codepoints auf ein Zeichen und verwenden das Format &#nnnn; oder &#xhhhh;, wobei nnnn eine oder mehrere Dezimalziffern von 0 bis 9 und hhhh eine oder mehrere hexadezimale Ziffern von 0 bis 9 und A bis F (oder a bis f) sind. Benannte Zeichenreferenzen werden als &entityname; geschrieben (anders als an den meisten anderen Stellen in HTML ist hier Groß- und Kleinschreibung durchaus relevant) und sind außerordentlich hilfreich, wenn man literale Zeichen eingeben möchte, die in einem bestimmten Kontext besondere Auswirkungen haben können. Dazu gehören zum Beispiel spitze Klammern (< und >), doppelte Anführungszeichen (") sowie das Kaufmanns-Und (&). Sehr gebräuchlich ist auch das Zeichen   (No-Break Space an der Position 0xA0), denn alle Vorkommen dieses Zeichens werden gerendert. Leerzeichen, Tabs und Zeilenumbrüche werden normalerweise als einzelnes Leerzeichen gerendet, auch wenn sie mehrfach aufeinanderfolgen. Das Kaufmanns-Und-Zeichen (&) kann nicht außerhalb von Zeichenreferenzen verwendet werden. HTML-Kommentare haben die folgende Syntax:

Text innerhalb von Kommentaren hat keine spezielle Bedeutung mehr und wird von den meisten Clients verborgen. Zwischen dem schließenden -- und > darf sich Whitespace befinden. Aus Kompatibilitätsgründen zu antiken Browsern (von vor 1995) stecken manche Entwickler den Inhalt von

Diskussion Der in diesem Rezept beschriebene Ansatz erlaubt es Ihnen, immer ein vollständiges CSV-Feld (mit eingebetteten Zeilenumbrüchen, maskierten Anführungszeichen und Kommata) auf einmal zu verarbeiten. Jede Übereinstimmung beginnt dabei direkt vor dem nächsten Feldtrennzeichen. Die erste einfangende Gruppe in der Regex, ‹(,|\r?\n|^)›, passt auf ein Komma, einen Zeilenumbruch oder auf die Position am Anfang des Ausgangstexts. Da die Regex-Engine die Alternativen von links nach rechts ausprobiert, sind die Optionen in der Reihenfolge aufgeführt, in der sie in einer normalen CSV-Datei am häufigsten vorkommen. Diese einfangende Gruppe ist der einzige Teil der Regex, der gefunden werden muss. Daher ist es durchaus möglich, dass die gesamte Regex einen leeren String findet, denn der Anker ‹^› passt immer einmal. Der von dieser ersten einfangenden Gruppe gefundene Wert muss außerhalb der Regex geprüft werden, um die Kommata durch den von Ihnen gewünschten Begrenzer zu ersetzen (zum Beispiel ein Tab). Wir haben noch gar nicht die gesamte Regex beschrieben, aber der bisherige Teil ist schon irgendwie verwirrend. Sie fragen sich vielleicht, warum die Regex nicht so geschrieben ist, dass sie nur die Kommata findet, die durch Tabs ersetzt werden sollen. Wenn Sie das tun könnten, würde eine einfache Substitution des gefundenen Texts den zusätzlichen Code um die Regex herum überflüssig machen, der prüft, ob die einfangende Gruppe 1 ein Komma oder etwas anderes gefunden hat. Es sollte doch schließlich möglich sein, per Lookahead und Lookbehind herauszufinden, ob sich ein Komma innerhalb oder außerhalb eines CSV-Felds in Anführungszeichen befindet, oder? Leider bräuchten Sie für dieses Vorgehen Lookbehinds mit unbegrenzter Länge, die nur in der .NET-Regex-Variante vorhanden sind (in „Unterschiedliche Lookbehind-Ebenen“ auf Seite 83 sind die unterschiedlichen Einschränkungen der Lookbehinds in den verschiedenen Regex-Varianten beschrieben). Selbst .NET-Entwickler sollten solch ein Vorgehen vermeiden, da die Komplexität dadurch stark steigen und die Regex langsamer werden würde. 8.10 Ändern der Feldbegrenzer in CSV-Dateien | 495

Aber zurück zur vorgestellten Regex. Die meisten Muster finden sich im nächsten Abschnitt – der einfangenden Gruppe 2. Diese zweite Gruppe passt auf ein einzelnes CSV-Feld, einschließlich der es umgebenden doppelten Anführungszeichen. Anders als die vorige Gruppe ist diese optional, um auch leere Felder zu finden. Beachten Sie, dass diese zweite Gruppe in der Regex zwei alternative Muster enthält, die durch das Metazeichen ‹|› getrennt sind. Die erste Alternative ‹[^",\r\n]+› ist eine negierte Zeichenklasse, gefolgt von einem Quantor (‹+›), der die Klasse ein Mal oder häufiger findet und damit auf ein vollständiges Feld ohne Anführungszeichen passt. Für diese Übereinstimmung darf das Feld keine doppelten Anführungszeichen, Kommata oder Zeilenumbrüche enthalten. Die zweite Alternative in der Gruppe 2, ‹"(?:[^"]|"")*"›, passt auf ein Feld, das von doppelten Anführungszeichen umgeben ist. Genauer gesagt, passt es auf ein doppeltes Anführungszeichen, gefolgt von null oder mehr Zeichen, die keine doppelten Anführungszeichen oder gerade zwei aufeinanderfolgende doppelte Anführungszeichen sind, und schließlich dem schließenden doppelten Anführungszeichen. Der Quantor ‹*› am Ende der inneren, nicht-einfangenden Gruppe wiederholt die beiden inneren Optionen so häufig wie möglich, bis Sie zu einem doppelten Anführungszeichen gelangen, das nicht maskiert ist und damit das Feld beendet. Wenn Sie mit einer gültigen CSV-Datei arbeiten, sollte sich die erste Übereinstimmung dieser Regex am Anfang des Ausgangstexts befinden und jede nächste Übereinstimmung direkt nach dem Ende der letzten Übereinstimmung folgen.

Siehe auch In Rezept 8.11 wird beschrieben, wie Sie die Regex aus diesem Rezept nutzen können, um CSV-Dateien aus einer bestimmten Spalte zu extrahieren.

8.11 CSV-Felder aus einer bestimmten Spalte extrahieren Problem Sie wollen jedes Feld aus der dritten Spalte einer CSV-Datei extrahieren.

Lösung Der reguläre Ausdruck aus Rezept 8.10 kann hier erneut verwendet werden, um über jedes Feld eines Strings mit CSV-Daten zu iterieren. Mit ein bisschen zusätzlichem Code können Sie in jeder Zeile die Anzahl der Felder von links nach rechts mitzählen und die Felder an der Position extrahieren, die Sie haben wollen. Der folgende reguläre Ausdruck (in normaler Schreibweise und im Freiform-Modus) passt auf ein einzelnes CSV-Feld und sein davor stehendes Trennzeichen in zwei einfan-

496 | Kapitel 8: Markup und Datenaustausch

genden Gruppen. Da in mit doppelten Anführungszeichen umgebenen Feldern auch Zeilenumbrüche vorkommen können, wäre es nicht korrekt, in Ihrem CSV-String einfach vom Anfang jeder Zeile aus zu suchen. Indem Sie einzelne Felder suchen und Feld für Feld weitergehen, können Sie leicht herausfinden, welche Zeilenumbrüche außerhalb von Feldern mit doppelten Anführungszeichen stehen, und damit einen neuen Datensatz beginnen. Die regulären Ausdrücke in diesem Rezept sind so entworfen, dass sie mit gültigen CSV-Dateien arbeiten können, die sich an die Formatregeln in „Kommaseparierte Daten (CSV)“ auf Seite 440 halten. (,|\r?\n|^)([^",\r\n]+|"(?:[^"]|"")*")?

Regex-Optionen: Keine (^ und $ passen auf Zeilenumbruch darf nicht gesetzt sein) Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ( , | \r?\n | ^ ) ( [^",\r\n]+ | " (?:[^"]|"")* " )?

# Einfangende Gruppe 1 für die Feldbegrenzer # oder den Anfang des Strings # Einfangende Gruppe 2 für ein einzelnes Feld: # ein Feld ohne Anführungszeichen # oder ... # ein Feld in Anführungszeichen # (evtl. mit maskierten Anführungszeichen) # Die Gruppe ist optional, weil Felder leer sein können

Regex-Optionen: Freiform (^ und $ passen auf Zeilenumbruch darf nicht gesetzt sein) Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby Bei diesen regulären Ausdrücken handelt es sich um die aus Rezept 8.10. Sie lassen sich auch für viele andere Aufgaben im CSV-Umfeld verwenden. Der folgende Beispielcode zeigt, wie Sie die Version ohne Freiform-Modus nutzen können, um eine CSV-Spalte zu extrahieren.

JavaScript-Beispiel Der folgende Code ist eine komplette Webseite mit zwei mehrzeiligen Eingabefeldern und einem Button Spalte 3 extrahieren dazwischen. Klickt man auf den Button, wird der Text genommen, den Sie in die Textbox Eingabe eingegeben haben. Mithilfe des regulären Ausdrucks wird der Wert des dritten Feldes aus jedem Datensatz kopiert und dann die gesamte Spalte in das Feld Ausgabe (wobei jeder Wert durch einen Zeilenumbruch getrennt ist). Um das zu testen, sichern Sie diesen Code in einer Datei mit der Endung .html und öffnen sie in Ihrem Webbrowser:

Dritte Spalte aus einem CSV-String extrahieren

8.11 CSV-Felder aus einer bestimmten Spalte extrahieren | 497

Eingabe:



Ausgabe:





498 | Kapitel 8: Markup und Datenaustausch

Diskussion Da die hier genutzten regulären Ausdrücke aus Rezept 8.10 übernommen wurden, werden wir nicht noch einmal beschreiben, wie sie arbeiten. Aber in diesem Rezept gibt es ein neues JavaScript-Beispiel, das die Regex verwendet, um Felder aus einer bestimmten Spalte im CSV-Text zu extrahieren. Im vorgestellten Code iteriert die Funktion get_csv_column Feld für Feld über den Ausgangstext. Nach jeder Übereinstimmung wird die Rückwärtsreferenz 1 untersucht. Enthält sie ein Komma, haben Sie nicht das erste Feld eines Datensatzes erreicht, und die Variable column_index wird erhöht, um mitzuzählen, beim wievielten Komma Sie jetzt sind. Enthält die Rückwärtsreferenz 1 etwas anderes als ein Komma (also einen leeren String oder einen Zeilenumbruch), haben Sie das erste Feld einer neuen Zeile erreicht, und column_index wird auf null zurückgesetzt. Im nächsten Schritt wird im Code geprüft, ob die Variable column_index den Index erreicht hat, der extrahiert werden soll. Wenn das der Fall ist, wird der Wert der Rückwärtsreferenz 2 (alles nach dem führenden Trennzeichen) an das Array result angehängt. Nachdem Sie den gesamten Ausgangstext durchlaufen haben, gibt die Funktion get_csv_column ein Array mit den Werten aus der angegebenen Spalte zurück (in diesem Beispiel ist das die dritte Spalte). Die Liste der Übereinstimmungen wird dann in die zweite Textbox auf der Seite ausgegeben, wobei jeder Wert durch einen Zeilenumbruch (\n) getrennt wird. Man könnte das Ganze leicht verbessern, indem man den Benutzer angeben lässt, welcher Spaltenindex genutzt werden soll. Das kann man über ein Prompt oder ein zusätzliches Textfeld erreichen. Die hier besprochene Funktion get_csv_column ist schon so geschrieben, dass sie dieses Feature berücksichtigen kann – sie erwartet den gewünschten Spaltenindex als zweiten Parameter index (der bei null beginnt).

Variationen Wenn Sie den Code nutzen, um „Feld für Feld“ über einen String zu iterieren, gewinnen Sie viel Flexibilität. Aber wenn Sie in einen Texteditor unterwegs sind, sind Sie eventuell auf ein „normales“ Suchen und Ersetzen beschränkt. In dieser Situation können Sie ein ähnliches Ergebnis erzielen, indem Sie immer einen vollständigen Datensatz finden und ihn durch den Wert des Felds an der gewünschten Position ersetzen (mithilfe einer Rückwärtsreferenz). Die folgenden Regexes zeigen, wie dies für bestimmte Spaltenindexe funktioniert. Bei all diesen Regexes gilt: Wenn ein Datensatz nicht so viele Felder enthält, wie Sie eigentlich benötigen würden, wird dieser Datensatz nicht gefunden und stehen gelassen.

8.11 CSV-Felder aus einer bestimmten Spalte extrahieren | 499

Einen CSV-Datensatz finden und das Feld in Spalte 1 in der Rückwärtsreferenz 1 einfangen ^([^",\r\n]+|"(?:[^"]|"")*")?(?:,(?:[^",\r\n]+|"(?:[^"]|"")*")?)*

Regex-Optionen: ^ und $ passen auf Zeilenumbruch Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Einen CSV-Datensatz finden und das Feld in Spalte 2 in der Rückwärtsreferenz 1 einfangen ^(?:[^",\r\n]+|"(?:[^"]|"")*")?,([^",\r\n]+|"(?:[^"]|"")*")? (?:,(?:[^",\r\n]+|"(?:[^"]|"")*")?)*

Regex-Optionen: ^ und $ passen auf Zeilenumbruch Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Einen CSV-Datensatz finden und das Feld in Spalte 3 oder größer in der Rückwärtsreferenz 1 einfangen ^(?:[^",\r\n]+|"(?:[^"]|"")*")?(?:,(?:[^",\r\n]+|"(?:[^"]|"")*")?){1}, ([^",\r\n]+|"(?:[^"]|"")*")?(?:,(?:[^",\r\n]+|"(?:[^"]|"")*")?)*

Regex-Optionen: ^ und $ passen auf Zeilenumbruch Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Erhöhen Sie die Zahl im Quantor ‹{1}›, um mit dieser letzten Regex auch Spalten einzufangen, die an einer höheren Position als 3 stehen. Ändern Sie sie zum Beispiel auf ‹{2}›, fangen Sie die Felder aus Spalte 4, mit ‹{3}› fangen Sie die Felder aus für Spalte 5 und so weiter. Wenn Sie mit Spalte 3 arbeiten, können Sie die ‹{1}› auch einfach entfernen, da sie dann keine Auswirkungen hat.

Ersetzungstext Es wird für alle diese Regexes der gleiche Ersetzungstext (Rückwärtsreferenz 1) verwendet. Ersetzen Sie jede Übereinstimmung durch die Rückwärtsreferenz 1, sollten Sie nur die Felder erhalten, nach denen Sie suchen. $1

Ersetzungstextvarianten: .NET, Java, JavaScript, Perl, PHP \1

Ersetzungstextvarianten: Python, Ruby

8.12 Sektionsüberschriften in INI-Dateien finden Problem Sie wollen alle Sektionsüberschriften in einer INI-Datei finden.

500 | Kapitel 8: Markup und Datenaustausch

Lösung Das ist einfach. INI-Sektionsüberschriften stehen am Anfang einer Zeile und zeichnen sich dadurch aus, dass ein Name in eckigen Klammern steht (zum Beispiel [Sektion1]). Diese Regeln lassen sich schnell in eine Regex übertragen: ^\[[^\]\r\n]+]

Regex-Optionen: ^ und $ passen auf Zeilenumbruch Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby

Diskussion Diese Regex ist nicht so furchtbar lang, daher lässt sie sich schnell beschreiben: • Der erste ‹^› passt auf den Anfang einer Zeile, da die Option ^ und $ passen auf Zeilenumbruch aktiv ist. • ‹\[› passt auf einen literales [. Die eckige Klammer ist durch einen Backslash maskiert, damit [ nicht als Anfang einer Zeichenklasse angesehen wird. • ‹[^\]\r\n]› ist eine negierte Zeichenklasse, die ein beliebiges Zeichen findet, solange es sich nicht um eine schließende eckige Klammer ], ein Carriage Return (\r) oder ein Line Feed (\n) handelt. Der darauffolgende Quantor ‹+› sorgt dafür, dass die Klasse einmal oder häufiger gefunden werden kann. • Das letzte ‹]› passt auf ein literales ], mit dem die Sektionsüberschrift beendet wird. Dieses Zeichen muss nicht durch einen Backslash maskiert werden, da es nicht innerhalb einer Zeichenklasse steht. Wenn Sie nur eine bestimmte Sektionsüberschrift finden wollen, ist das noch einfacher. Die folgende Regex passt auf die Überschrift für eine Sektion mit dem Namen Sektion1: ^\[Sektion1]

Regex-Optionen: ^ und $ passen auf Zeilenumbruch Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby In diesem Fall ist der einzige Unterschied zu einer normalen Textsuche nach „[Sektion1]”, dass das Suchergebnis am Anfang einer Zeile stehen muss. Damit wird verhindert, dass auskommentierte Sektionsüberschriften gefunden werden (vor denen ein Semikolon steht) oder etwas, das wie eine Überschrift aussieht, aber in Wirklichkeit Teil eines Parameterwerts (zum Beispiel Item1=[Value1]) ist.

Siehe auch In Rezept 8.13 wird beschrieben, wie man Sektionsblöcke in INI-Dateien findet. In Rezept 8.14 wird beschrieben, wie man Name/Wert-Paare in INI-Dateien findet.

8.12 Sektionsüberschriften in INI-Dateien finden | 501

8.13 Sektionsblöcke in INI-Dateien finden Problem Sie müssen jeden vollständigen INI-Sektionsblock finden (also eine Sektionsüberschrift und alle Name/Wert-Paare der Sektion), um eine INI-Datei aufzuteilen oder jeden Block getrennt zu verarbeiten.

Lösung In Rezept 8.12 wurde gezeigt, wie man die Sektions-Überschrift in einer INI-Datei findet. Um eine gesamte Sektion zu finden, beginnen wir mit dem gleichen Muster aus dem Rezept, suchen aber weiter, bis wir das Ende des Strings oder ein Zeichen [ finden, das am Anfang einer Zeile steht (da damit der Anfang einer neuen Sektion beginnt): ^\[[^\]\r\n]+](?:\r?\n(?:[^[\r\n].*)?)*

Regex-Optionen: ^ und $ passen auf Zeilenumbruch (Punkt passt auf Zeilenumbruch darf nicht gesetzt sein) Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby Oder im Freiform-Modus: ^ \[ [^\]\r\n]+ ] (?: \r?\n (?: [^[\r\n] .* )? )*

# Sekitonsüberschrift finden # Rest der Sektion ... # Finden einer Zeilenumbruchfolge # Nach jedem Zeilenumbruch ... # ein beliebiges Zeichen finden, außer "[" oder einen # Zeilenumbruch # Den Rest der Zeile finden # Die Gruppe ist optional, damit auch leere Zeilen gefunden werden # Bis zum Ende des Abschnitts weitermachen

Regex-Optionen: ^ und $ passen auf Zeilenumbruch, Freiform (Punkt passt auf Zeilenumbruch darf nicht aktiv sein) Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Diskussion Dieser reguläre Ausdruck findet zunächst mithilfe des Musters ‹^\[[^\]\r\n]+]› eine Sektionsüberschrift in einer INI-Datei. Dann sucht er Zeile für Zeile weiter, sofern sie nicht mit [ beginnt. Schauen Sie sich den folgenden Ausgangstext an: [Sektion1] Item1=Value1 Item2=[Value2] ; [SektionA] ; Die Überschrift von SektionA wurde auskommentiert

502 | Kapitel 8: Markup und Datenaustausch

ItemA=ValueA ; ItemA ist nicht auskommentiert und Teil von Sektion1 [Section2] Item3=Value3 Item4 = Value4

In diesem String findet die Regex zwei Übereinstimmungen. Die erste reicht vom Anfang des Strings bis zu der leeren Zeile vor „[Sektion2]“. Die zweite Übereinstimmung reicht vom Anfang der Überschrift von Sektion2 bis zum Ende des Strings.

Siehe auch In Rezept 8.12 wird gezeigt, wie man die Sektionsüberschriften in INI-Dateien findet. In Rezept 8.14 wird gezeigt, wie man Name/Wert-Paare in INI-Dateien findet.

8.14 Name/Wert-Paare in INI-Dateien finden Problem Sie wollen die Name/Wert-Paare eines Parameters in einer INI-Datei finden (zum Beispiel Item1=Value1), wobei beide Teile mit einfangenden Gruppen getrennt werden sollen. Die Rückwärtsreferenz 1 soll den Parameternamen (Item1) enthalten, Rückwärtsreferenz 2 den Wert (Value1).

Lösung So sieht der reguläre Ausdruck aus, mit dem man diese Aufgabe erledigen kann: ^([^=;\r\n]+)=([^;\r\n]*)

Regex-Optionen: ^ und $ passen auf Zeilenumbruch Regex-Varianten: .NET, Java, JavaScript, PCRE, Perl, Python, Ruby ^ ( [^=;\r\n]+ ) = ( [^;\r\n]* )

# # # #

Zeilenanfang Einfangen des Namens als Rückwärtsreferenz 1 Trennzeichen zwischen Name und Wert Einfangen des Werts als Rückwärtsreferenz 2

Regex-Optionen: ^ und $ passen auf Zeilenumbruch, Freiform Regex-Varianten: .NET, Java, PCRE, Perl, Python, Ruby

Diskussion Wie bei den anderen INI-Rezepten in diesem Kapitel arbeiten wir auch hier mit ziemlich einfachen Zutaten. Die Regex beginnt mit einem ‹^›, um die Position am Anfang einer Zeile zu finden (stellen Sie sicher, dass die Option ^ und $ passen auf Zeilenumbruch aktiv ist). Das ist wichtig, denn ohne die Zusicherung, dass die Übereinstimmung am Anfang einer Zeile beginnt, könnten Sie einen Teil einer auskommentierten Zeile erwischen.

8.14 Name/Wert-Paare in INI-Dateien finden |

503

Als Nächstes verwendet die Regex eine einfangende Gruppe mit einer negierten Zeichenklasse (‹[^=;\r\n]›), gefolgt von dem Quantor (‹+›), der die Klasse einmal oder mehrfach findet, um den Namen des Parameters zu erhalten und ihn als Rückwärtsreferenz 1 zu speichern. Die negierte Zeichenklasse passt auf jedes Zeichen außer auf das Gleichheitszeichen, das Semikolon, den Carriage Return (‹\r›) und den Line Feed (‹\n›). Die Zeichen Carriage Return und Line Feed werden beide genutzt, um einen INI-Parameter zu beenden. Ein Semikolon zeigt den Anfang eines Kommentars an, und ein Gleichheitszeichen trennt Namen und Wert eines Parameters. Nachdem der Parametername gefunden wurde, sucht die Regex nach einem literalen Gleichheitszeichen (dem Trennzeichen zwischen Name und Wert) und dann nach dem Parameterwert. Der Wert wird mit einer zweiten einfangenden Gruppe gefangen, die dem Muster aus der ersten Gruppe ähnelt, aber nicht so viele Einschränkungen besitzt. So dürfen hier auch Gleichheitszeichen vorkommen (es gibt also ein Zeichen weniger in der negierten Zeichenklasse). Und es wird der Quantor ‹*› verwendet, um nicht mindestens ein Zeichen finden zu müssen (da Werte, anders als Namen, leer sein dürfen). Und schon sind wir fertig.

Siehe auch In Rezept 8.12 wird beschrieben, wie man die Sektionsüberschriften in INI-Dateien findet. In Rezept 8.13 wird beschrieben, wie man an die Details von Sektionsblöcken in INIDateien gelangt.

504 | Kapitel 8: Markup und Datenaustausch

Index

Symbole - (Bindestrich) als Metazeichen 28 aus Kreditkarten-Nummern entfernen 288, 291 für Bereiche in Zeichenklassen 34 Maskieren 340 !~ (Operator) 136 # (Rautezeichen) für Kommentare 90 Maskieren 340 $ (Dollarzeichen) als Anker 40, 41, 42, 113, 232 als Metazeichen 28 Maskieren 339 Maskieren in Ersetzungstext 92 $& (Variable) 95, 100, 148, 153 $' (Variable) 100, 207 $+ (Hash) 161, 193 $_ (Variable) 100 $` (Variable) 100, 207 $~ (Variable) in Ruby 148 einfangende Gruppen 159 $~[0] (Variable) 148 ( ) (Klammern) als Metazeichen 28, 235 atomare Gruppen 76, 80, 265 einfangende Gruppen 61 Lookbehind nachbauen 86 schon gefundenen Text erneut finden 64 Maskieren 339 * (Stern) als Anker (Zusicherung) 235 als Metazeichen 28 für possessive Quantoren 231 gierig 73 Maskieren 339

+ (Plus-Zeichen) als Metazeichen 28 für possessive Quantoren 76, 231 Maskieren 339 , (Komma) als Tausender-Separator 363 im Intervall-Quantor 263 Maskieren 340 . (Punkt) als Metazeichen 28 ein beliebiges Zeichen finden 38 Finden 231 Maskieren 339 Missbrauch 38 =~ (Operator) in Perl 136, 153 =~ (Operator) in Ruby 134, 137 ? (Fragezeichen) als Metazeichen 28 für genügsame Quantoren 74 Maskieren 339 zum optionalen Finden 71 @ (At-Zeichen) Finden 231 Kodieren in Perl 113 [] (eckige Klammern) als Metazeichen 28 Maskieren 339 zum Definieren von Zeichenklassen 33 \ (Backslash) als Metazeichen 28 im Quellcode 110 Maskieren 339 im Ersetzungstext 92 zum Maskieren von Zeichen 28, 337 innerhalb von Zeichenklassen 34 ^ (Zirkumflex) als Anker 40, 42, 232, 235 als Metazeichen 28

Index | 505

Maskieren 339 Negieren in Zeichenklassen 34 { } (geschweifte Klammern) als Metazeichen 28 Maskieren 339 zur Wiederholung 70 | (vertikaler Balken) als Alternations-Operator 60, 251, 304 Maskieren 339

A \a (Bell-Zeichen) 30 \A Anker 40, 41, 232 abschließenden Whitespace entfernen 333 ActionScript-Sprache 106 ähnliche Wörter finden 306 akzentuierte Zeichen 56 alphanumerische Zeichen, Eingabe begrenzen 257 Alternation 60 Wörter aus einer Liste finden 304 Zahlen in einem Bereich finden 354 Zeitformate validieren 251 Anker 40, 41 ANSI-Zeichen, Einschränken auf 258 appendReplacement() (Methode) 198 appendTail() (Methode) 198 ASCII-Steuerzeichen (siehe Steuerzeichen) atomare Gruppen 76, 80, 265 atomares Lookaround und Lookbehind 85 Attribute (Markup-Sprachen) 436 bestimmte finden 476 Whitelist 465 At-Zeichen (@) Finden 231 Kodieren in Perl 113

B \B Nicht-Wortgrenzen 45 -Tags ersetzen durch 459 \b Wortgrenze 35, 44, 233, 237, 251, 302 (siehe auch Wörter) Backslash (\) als Metazeichen 28 im Quellcode 110 Maskieren 339 im Ersetzungstext 92

506 | Index

zum Maskieren von Zeichen 28, 337 in Zeichenklassen 34 Backtracking 73 katastrophales 79 unnötiges vermeiden 75 balanciertes Finden 180 bedingte Ausdrücke 318 Bedingungen beim Finden 87 begin() (Methode) in Ruby (MatchData-Klasse) 154 Begrenzer in CSV-Dateien ändern 493 Bell-Zeichen (\a) 30 benannte Bedingungen 88 benannte Captures 66, 99, 160 Gruppen mit gleichen Namen (.NET) 248 benannte einfangende Gruppen 192 benannte Rückwärtsreferenzen 69 Benutzer aus URLs extrahieren 386 Bereiche in Zeichenklassen 34 Binärzahlen finden 348 Bindestrich (-) als Metazeichen 28 aus Kreditkarten-Nummern entfernen 288, 291 für Bereiche in Zeichenklassen 34 Maskieren 340 Blöcke, Unicode 52 alle Zeichen aufführen 57 Block-Maskierungen 29, 340 Buchstaben, Unicode-Kategorie 49

C \cA bis \cZ Werte 31 C (Sprache) 106 C# (Sprache) 104 literale Regexes 110 Regex-Bibliothek importieren 116 Regex-Objekte erstellen 119 C++ (Sprache) 106 CacheSize (Eigenschaft) in .NET 119 CANON_EQ (Konstante) in Java 128 Carriage Return (\r) 30 CASE_INSENSITIVE (Konstante) in Java 125 CDATA-Abschnitte, XML 439 cellspacing-Attribut, zu
-Tags hinzufügen 481 CIL (Common Intermediate Language) 123

Codepoints, Unicode 49 Blöcke 52 Comma-Separated Values (siehe CSV) COMMENTS (Konstante) in Java 125 Common Intermediate Language (CIL) 123 compile() (Methode) Java (Pattern-Klasse) 111, 120, 125 Python (re-Modul) 121 Regex-Optionen 127 Ruby (Regexp-Klasse) 122 Count (Eigenschaft, Match.Groups) 158 CSV (Comma-Separated Values) 440 Feldbegrenzer ändern 493 Felder aus bestimmten Spalten extrahieren 496

D \D Token 35 \d Token 35, 343 Dateinamen aus Windows-Pfaden extrahieren 430 Erweiterung aus Windows-Pfaden extrahieren 431 ungültige Zeichen entfernen 432 Dateisystem-Pfade (Windows) Aufteilen 420 Dateierweiterungen extrahieren 431 Dateinamen extrahieren 430 Laufwerksbuchstaben extrahieren 425 Ordner extrahieren 427 Validieren 417 Datensätzen in CSV-Dateien 440 Datums 252 Datumsformate validieren 241 ISO 8601-Formate 252 ungültige Datumswerte entfernen 245 DEA-Regex-Engines 355 Delphi for Win32 106 Delphi Prism 107 Deterministischer Endlicher Automat (DEA) 2 deutsche Postleitzahlen validieren 283 dezimal, römische Zahlen konvertieren in 366 Dezimalzahlen 359 DOCTYPEs (HTML) 437 Document Type Declaration, HTML 437 Dollarzeichen ($) als Anker 40, 41, 42, 113, 232 als Metazeichen 28

Maskieren 339 Maskieren in Ersetzungstext 92 Domain-Namen validieren 398 doppelte Zeilen entfernen 325 “dot all”-Modus 38, 123 DOTALL (Konstante) in Java 125

E \E Token zum Unterdrücken von Metazeichen 29, 340 \e (Escape-Zeichen) 30 eckige Klammern [] als Metazeichen 28 Maskieren 339 zum Definieren von Zeichenklassen 33 ECMA-262-Standard 104, 106 ECMAScript (Programmiersprache) 104 ECMAScript (RegexOptions) 128 eifrig, Regex-Engine ist 354 Eigenschaften in Unicode 49 einfangende Gruppen 61, 231 Bedingungen 87 beim Aufteilen von Strings 217 benannte Captures 66, 99, 160, 192 Gruppen mit gleichen Namen (.NET) 248 Details auslesen 154 leere Rückwärtsreferenzen verwenden 321 Lookbehinds nachstellen 86 Teile der Übereinstimmung wieder einfügen 189 Verweise auf nicht-vorhandene Gruppen 98 wiederholte Wörter finden 323 zum Finden von schon gefundenem Text 64 Eingabe parsen 179 Eingabeüberprüfung 271 (siehe auch Validierung) Kreditkarten-Nummern 288 nur alphanumerische Zeichen zulassen 257 Stringlänge 260 Zeilen zählen 265 eingebettete Übereinstimmungen finden 177 Elemente, HTML 436 E-Mail-Adressen validieren 227 end() (Methode) Java (Matcher-Klasse) 152, 158 Python (MatchObject-Klasse) 153 Ruby (MatchData-Klasse) 154

Index | 507

Entfernen führende Nullen 349 ungültige Zeichen aus Dateinamen 432 Entitäten, HTML 437, 475 EPP (Extensible Provisioning Protocol) 240 ereg-Funktionen (PHP) 105 ereg_replace 7 literale reguläre Ausdrücke in 112 Ersetzungstext in Code erzeugt 194 Regex-Übereinstimmung einfügen 95 Teile der Regex-Übereinstimmung e infügen 96 Übereinstimmungskontext einfügen 100 Zeichen maskieren 92 erstes Vorkommen von Zeilen speichern 327, 328 Erweiterungen (Datei), aus Pfaden extrahieren 431 Escape-Zeichen (\e) 30 Europäische Umsatzsteuer-Nummern überprüfen 294 exec() (Methode) in JavaScript 152, 158 über die Übereinstimmungen iterieren 170 ExplicitCapture (RegexOptions) 128 Expresso-Tester 18 EXTENDED (Konstante, Regexp) 127 Extensible Hypertext Markup Language 438 Extensible Markup Language 439 Extensible Provisioning Protocol (EPP) 240 extreme Wiederholung verhindern 78

F \f (Form Feed/Seitenvorschub) 30 Felder in CSV-Dateien 440 extrahieren 496 find() (Methode) in Java (Matcher-Klasse) 135, 147, 152, 164 Iterieren über Übereinstimmungen 170 findall() (Methode) in Python (re-Modul) 166 Finden 301, 331 (siehe auch Suchen und ersetzen, Suchen) alle Übereinstimmungen ersetzen 181 am Zeilenanfang und -ende 39 ausgeglichene und rekursive Übereinstimmung 180 beliebiges Zeichen 37 eine von mehreren Alternativen 59 eines von mehreren Zeichen 33

508 | Index

ganze Wörter 44 gefundenen Text auslesen 142 Groß-/Kleinschreibung ignorieren 29, 231 Gruppieren und Einfangen 61 atomare Gruppen 76, 80, 265 benannte Captures (siehe benannte Captures) Gruppen wiederholen 71 Lookaround 81, 89, 261, 302 Lookbehind nachstellen 86 Referenzen auf nicht-vorhandene Gruppen 98 in INI-Dateien (siehe INI-Dateien) IPv4-Adressen 400 IPv6-Adressen 403 Iterieren über alle Übereinstimmungen 167 Kontext in Ersetzungstext einfügen 100 Liste mit allen Übereinstimmungen erhalten 162 literalen Text finden 28 mit Bedingungen 87 exzessive Wiederholung verhindern 78 minimale und maximale Wiederholung 72 Teile der Regex wiederholen 69 nicht-druckbare Zeichen 30 Position und Länge der Übereinstimmung ermitteln 149 Prüfen, ob der gesamte String gefunden wird 137 Prüfen, ob Teile des Strings gefunden werden 131 Tags in Markup-Sprachen 442 Teile der Übereinstimmung einfügen 189 Teile des gefundenen Texts auslesen 154 Übereinstimmungen einer anderen Regex 200 Übereinstimmungen in anderen Übereinstimmungen finden 177 Übereinstimmungen in prozeduralem Code validieren 173 Unicode-Elemente 47 vorher gefundenen Text finden 64 XML-Namen 466 finditer() (Methode) in Python (re-Modul) 172 Find-Registerkarte (myregexp.com) 17 Form Feed (\f) 30 Formatieren Datumswerte überprüfen 241 ISO 8601-Formate 252 ungültige Datumswerte entfernen 245

Telefonnummern (in Nordamerika) 233 Uhrzeitwerte überprüfen ISO 8601-Formate 252 klassische Formate 250 Vor- und Nachnamen 285 Fragezeichen (?) als Metazeichen 28 für genügsame Quantoren 74 Maskieren 339 zum optionalen Finden 71 Fragment aus einer URL extrahieren 397 Freiform-Modus 90 in Python 114 Option setzen 123 führende Nullen entfernen 349 führenden Whitespace entfernen 333

groupdict() (Methode) in Python (MatchObjectKlasse) 159 groups() (Methode) in Python (MatchObjectKlasse) 159 Gruppieren 61 (siehe auch benannte Captures) atomare Gruppen 76, 80, 265 Bedingungen 89 Gruppen wiederholen 71 leere Rückwärtsreferenzen 321 Lookaround 81, 261, 302 Lookbehind nachstellen 86 nicht-vorhandene Gruppen referenzieren 98 gsub() (Methode) in Ruby (String-Klasse) 188, 200 mit Rückwärtsreferenzen 192

G

H

ganze Wörter finden 44 Ganzzahlen Finden oder erkennen 341 führende Leerzeichen entfernen 349 in einem bestimmten Bereich finden 350 mit Punkt (Tausender-Trennzeichen) 363 gefundenen Text auslesen 142 gemischte Notation (IPv6) 404, 409, 412 komprimierte gemischte Notation 407, 416 generische URLs validieren 379 genügsame Quantoren 74 Backtracking vermeiden 75 geschweifte Klammern { } als Metazeichen 28 Maskieren 339 zur Wiederholung 70 Get-Unique-Cmdlet (PowerShell) 325 gierige Quantoren 73 Backtracking vermeiden 75 Gleitkommazahlen 359 Grapheme, Unicode 49, 56 grep-Funktion, R Project 107 grep-Tools 21 Groß-/Kleinschreibung (siehe Schreibweise) Großbuchstaben (siehe Schreibweise) Group (Eigenschaft) in .NET (Match()-Funktion) 157 Group (Klasse) in .NET 157 group() (Methode) Java (Matcher-Klasse) 158 re-Modul (Python) 159

hexadezimale Zahlen finden 345 in einem bestimmten Bereich 357 horizontales Tab-Zeichen (\t) 30 Host aus URL extrahieren 388 HTML (Hypertext Markup Language) 435 durch ersetzen 459 Attribute zu
-Tags hinzufügen 481 normalen Text um

ergänzen 473 Tags finden 442 Tags validieren 456 Hyperlinks, URLs umwandeln in 376 Hypertext Markup Language 435

I (?-i) Modus-Modifikator 30 (?i) Modus-Modifikator 30 IGNORECASE (Konstante, Regexp) 127 IgnoreCase (Option, RegexOptions) 125 IgnorePatternWhitespace (Option, RegexOptions) 125 Index (Eigenschaft) in .NET 151 index (Eigenschaft) in JavaScript 152 INI-Dateien 441 Name/Wert-Paare finden 503 Sektionsblöcke finden 502 Sektionsüberschriften finden 500 Initialisierungsdateien 441 Input in Token zerlegen 179 internationale Telefonnummern validieren 239 Intervall-Quantor 263

Index | 509

IPv4-Adressen finden 400 IPv6-Adressen finden 403 ISBNs validieren 274 IsMatch() (Methode) in .NET 135, 164 ISO 8601-Formate 252 ISO-8859-1-Zeichen, beschränken auf 258 Iterieren über alle Übereinstimmungen 167

J Java (Sprache) (s.a. Matcher (Klasse) in Java, Pattern (Klasse) in Java, String (Klasse) in Java) Ersetzungstext 7 literale reguläre Ausdrücke 111 Regex-Bibliothek importieren 116 Regex-Objekte erstellen 119 Regex-Optionen 125, 128 Regex-Unterstützung 4, 104 java.util.regex (Paket) 4, 7, 104 Verwenden der Regex-Bibliothek 116 JavaScript (Sprache) (siehe auch RegExp (Klasse) in JavaScript, String (Klasse) in JavaScript) Ersetzungstext 7 literale reguläre Ausdrücke 112 Regex-Ausdrücke erstellen 120 Regex-Bibliothek 116 Regex-Optionen 126, 128 Regex-Unterstützung 5, 104

K kanadische Postleitzahlen validieren 282 katastrophales Backtracking 79 Kategorien, Unicode 49 Kaufmannsund (&), Maskieren 340 Klammern ( ) als Metazeichen 28, 235 atomare Gruppen 76, 80, 265 einfangende Gruppen 61 (siehe auch einfangende Gruppen) benannte Captures (siehe benannte Captures) Lookbehind nachstellen 86 vorher gefundenen Text finden 64 Maskieren 339 Kleinbuchstaben (siehe Schreibweise) kombinierende Zeichen 56

510 | Index

Komma (,) als Tausender-Trenner 363 im Intervall-Quantor 263 Maskieren 340 Kommentare in HTML 437 in INI-Dateien 441 in regulären Ausdrücken 90 in XML entfernen 484 validieren 486 Wörter finden in 488 komprimierte gemischte Schreibweise (IPv6) 407, 416 komprimierte Schreibweise (IPv6) 405, 409, 413 Kontext der Übereinstimmung einfügen 100 Kreditkarten-Nummern validieren 288 ksort() (Methode) in PHP 187

L Länge der Übereinstimmung ermitteln 149 lastIndex (Eigenschaft) in JavaScript 152, 171 lastIndex (Eigenschaft) in JavaScript (RegExpKlasse) 316 Laufwerksbuchstaben aus Windows-Pfaden extrahieren 425 leere Rückwärtsreferenzen 321 Length (Eigenschaft) in .NET 151 length() (Methode) in Ruby (MatchData-Klasse) 154 letztes Vorkommen von Zeilen speichern 326, 328 Line Feed 30 linker Kontext 100 List All (Button) in RegexBuddy 9 Liste aller Übereinstimmungen erhalten 162 literale Regexes im Quellcode 109 literalen Text finden 28 lokale Modus-Modifikatoren 30 Lookahead (siehe Lookaround) Lookaround 81, 261, 302 Bedingungen 89 einmalige Zeilen speichern 328 Lookbehind durch Lookahead simulieren 314, 315 negativer 83, 310, 312 Suchen nach Zahlen 344 Wörter in gewissem Abstand finden 323 Lookaround mit begrenzter Länge 315

Lookaround mit fester Anzahl 315 Lookaround mit unbegrenzter Länge 315 Lookbehind 82, 261, 302, 313, 344 (siehe auch Lookaround) Bedingungen 89 durch Lookahead simulieren 314, 315 mit einfangenden Gruppen nachstellen 86 negativer 83, 313 Luhn-Algorithmus 293

M (?m) Modus-Modifikator 43 m// (Operator) in Perl 136, 148 einfangende Gruppen 159 Maskieren 28 in Zeichenklassen 34 wie und wann ist es passend 337 Zeichen im Ersetzungstext 92 Match (Klasse) in .NET Index und Length (Eigenschaften) 151 MatchAgain() (Methode) 170 NextMatch() (Methode) 170 match() (Methode) JavaScript (String-Klasse) 164 Ruby (Regexp-Klasse) 154 Match() (Methode) in .NET 151 Groups (Eigenschaft) 157 Iterieren über Übereinstimmungen 169 Value (Eigenschaft) 146 MatchAgain() (Methode) in .NET (MatchKlasse) 170 MatchData (Klasse) in Ruby 148 length() und size() (Methoden) 154 Matcher (Klasse) in Java 120 end() (Methode) 152 find() (Methode) 135, 147, 152, 164 group() (Methode) 158 Iterieren über Übereinstimmungen 170 reset() (Methode) 147 start() und end() (Methoden) 158 Matches() (Methode) in .NET 163 MatchEvaluator (Klasse) in .NET 196, 197 MatchObject (Klasse) in Python end() (Methoden) 153 groups() (Methode) 159 start() (Methoden) 153 MatchObject (Klasse) in Ruby begin() und end() (Methoden) 154 Match-Registerkarte (myregexp.com) 17

mathematische Regularität 2 maximale Wiederholung 72 mb_ereg-Funktionen (PHP) 105 literale reguläre Ausdrücke 112 mehrzeilige Strings

-Tags ergänzen 473 Zeilenanzahl validieren 265 Zeilenweise suchen 222 Metazeichen 28 in Zeichenklassen 33 Maskieren 337 Microsoft .NET (siehe .NET-Framework) Microsoft VBScript Scripting Library 108 Microsoft Windows-Pfade Aufteilen 420 Laufwerksbuchstaben extrahieren 425 Validieren 417 minimale Wiederholung 72 Modus-Modifikatoren 30 durch nicht-einfangende Gruppen angeben 63 MSIL 123 MULTILINE (Konstante) in Java 125 MULTILINE (Konstante, Regexp-Klasse) 127 Multiline (Option, RegexOptions) 125 Multiline-Modus 42, 43 Mustererkennungs-Operator (Java) 112 myregexp.com (Online) 15

N \n (Newline-Zeichen) 30 Kodieren in C# 111 Kodieren in Java 111 Kodieren in Python 114 wird nicht durch Punkt gefunden 38 Nachnamen umformatieren 285 Name/Wert-Paare, INI-Dateien 503 Namen umformatieren 285 Namen, XML 439 Finden 466 Namespace Identifiers (NIDs) 378 Namespace Specific String (NSS) 378 NEAR-Suchen (Wörter) 317 Negation in Zeichenklassen 34 negativer Lookaround 83, 310, 312, 313 .NET-Framework 104 (siehe auch Group (Klasse) in .NET, Match (Klasse) in .NET, MatchEvaluator (Klasse) in .NET, Regex (Klasse) in .NET)

Index | 511

Ersetzungstext 7 Erstellen von Regex-Objekten 119 Regex-Optionen 125, 128 Regex-Unterstützung 4 new() (Methode) in Ruby 122, 127 Newline (\n) 30 Kodieren in C# 111 Kodieren in Java 111 Kodieren in Python 114 nicht durch Punkt zu finden 38 NextMatch() (Methode) in .NET (Match-Klasse) 170 nicht zugewiesene Codepoints (Unicode) 51, 55 nicht-druckbare Zeichen 30 in C#-Quellcode 111 Kodieren in Python 114 nicht-einfangende Gruppen 63 Nicht-Whitespace-Zeichen, Anzahl begrenzen 262 Nordamerikanische Telefonnummern validieren 233 normalen Text nach HTML wandeln 473 nregex (Online) 13 Nullen aus Nummern entfernen 349 nummerische Zeichenreferenzen, HTML 437

O offset() (Methode) in Ruby (MatchData-Klasse) 154 Oniguruma-Bibliothek 5, 7, 105 Online-Tools 12 optionales Finden 71 Optionen für reguläre Ausdrücke setzen 123 Ordner, aus Windows-Pfaden extrahieren 427

P \P{ } für Unicode-Kategorien 51, 57 \p{ } für Unicode-Kategorien 49 Parameter von INI-Dateien 441 Pattern (Klasse) in Java 119 compile() (Methode) 111, 120, 125 Optionskonstanten 125, 128 reset() (Methode) 120 split() (Methode) 213 PatternSyntaxException (Exception) 120 PCRE (Perl-Compatible Regular Expressions)Bibliothek 4, 105 Ersetzungstext 6

512 | Index

Perl (Sprache) (siehe auch m// (Operator) in Perl) Ersetzungstext 6 literale reguläre Ausdrücke 112 Regex-Bibliothek 116 Regex-Objekte erstellen 121 Regex-Optionen 126, 129 Regex-Unterstützung 4, 105 Pfad aus URL extrahieren 392 Pfade (UNC) extrahieren 426 Pfade (Windows) Aufteilen 420 Dateiendung extrahieren 431 Dateinamen extrahieren 430 Laufwerksbuchstaben extrahieren 425 Ordner extrahieren 427 Validieren 417 PHP (Sprache) XIII (siehe auch preg-Funktionen, ereg-Funktionen, mb_ereg-Funktionen) 7 Ersetzungstext-Variante 7 literale reguläre Ausdrücke 112 Regex-Bibliothek importieren 116 Regex-Funktionen 105 Regex-Objekte erstellen 121 Regex-Optionen 126, 128 Pipe-Symbol (siehe vertikaler Balken (|)) Plus-Zeichen (+) als Metazeichen 28 für possessive Quantoren 76, 231 Maskieren 339 Ports aus URLs extrahieren 390 Position der Übereinstimmung ermitteln 149 positiver Lookahead 83 Wörter in gewisser Entfernung finden 323 positiver Lookbehind 82 POSIX-kompatible Regex-Engines 355 possessive Quantoren 76, 231 Postleitzahlen validieren Deutschland 283 Großbrittanien 282, 283 Kanada 282 USA (ZIP-Codes) 281 PowerGREP-Tool 21 PowerShell Scripting Language 107 PREG_OFFSET_CAPTURE (Konstante) 152, 159, 165 PREG_PATTERN_ORDER (Konstante) 165 PREG_SET_ORDER (Konstante) 165

PREG_SPLIT_DELIM_CAPTURE (Konstante) 220 PREG_SPLIT_NO_EMPTY (Konstante) 215, 220 preg-Funktionen (PHP) literale reguläre Ausdrücke 112 preg_match() 136, 148, 165 Iterieren über Übereinstimmungen 172 preg_match_all() 164, 172 preg_matches() 165 preg_replace() 7, 105, 186 benannte Captures 193 mit Rückwärtsreferenzen 191 preg_replace_callback() 199 preg_split() 214 mit einfangenden Gruppen 220 Regex-Objekte erstellen 121 Regex-Optionen 126 Verfügbarkeit 116 Prüfsumme (ISBN) 274 Punkt (.) als Metazeichen 28 ein beliebiges Zeichen finden 38 Finden 231 Maskieren 339 Missbrauch 38 Punkt passt auf Zeilenumbruch (Option) 38, 123 Python (Sprache) (siehe auch MatchObject (Klasse) in Python, re-Modul (Python)) Ersetzungstext 7 literale reguläre Ausdrücke 113 Regex-Bibliothek importieren 116 Regex-Objekte erstellen 121 Regex-Optionen 127, 129 Regex-Unterstützung 5, 105

Q \Q Token zum Unterdrücken von Metazeichen 29, 340 qr//-Operator (Perl) 121 Quantoren 236 für fixe Wiederholungen 70 für unbegrenzte Wiederholungen 71 für variable Wiederholungen 70 verschachtelt 487 Quellcode, literale reguläre Ausdrücke in 109 Queries aus URLs extrahieren 396 Quote-Regex-Operator (Perl) 121

R \r (Carriage Return-Zeichen) 30 R Project 107 Rautezeichen (#) für Kommentare 90 Maskieren 340 Raw-Strings, Python 113 REALbasic (Sprache) 108 reAnimator (Online) 17 rechter Kontext 100 Regex (Klasse) in .NET IsMatch() (Methode) 135, 164 Match() (siehe Match() (Methode) in .NET) Matches() (Methode) 163 Replace() (Methode) 184, 196, 197 mit Rückwärtsreferenzen 191 Split() (Methode) 211 mit Rückwärtsreferenzen 219 Regex() (Konstruktor) in .NET 111, 119 RegexOptions (Enumeration für Parameter) 123, 125, 128 regex.larsolavtorvik.com (Online) 12 Regex-Bibliothek importieren 115 RegexBuddy-Tool 8 Regex-gesteuerte Engines 60 RegEx-Klasse (REALbasic) 108 Regex-Objekte erstellen 117 Regex-Optionen setzen 123 RegexOptions (Enumeration) 125, 128 Compiled (Wert) 123 RegExp (Klasse) in JavaScript exec() (Methode) 158 Iterieren über Übereinstimmungen 170 index (Eigenschaft) 152 lastIndex (Eigenschaft) 152, 171, 316 test() (Methode) 136 Regexp (Klasse) in Ruby compile() (Methode) 122 match() (siehe match() (Methode), Ruby (Regexp-Klasse)) new() (Methode) 122, 127 RegExp() (Konstruktor) in JavaScript 121 Optionen 126 RegexPal-Tool 11 regexpr (Funktion), R Project 107 RegexRenamer-Tool 23 Regex-Tester 8 Desktop 18 grep-Tools 21

Index | 513

Online 12 RegexBuddy 8 RegexPal 11 Texteditoren 24 Regex-Tester für den Desktop 18 Regex-Tools 8 bekannte Texteditoren 24 Desktop 18 grep-Tools 21 Online 12 RegexBuddy 8 RegexPal 11 Regex-Varianten 2 Reguläre Ausdrücke im Perl-Stil 3 Reguläre Ausdrücke, Definition 1 Regularität (mathematisch) 2 rekursive Übereinstimmung 180 relative Windows-Pfade 419, 423 re-Modul (Python) 5, 105, 113 compile() (Funktion) 121 findall() (Methode) 166 finditer() (Methode) 172 group() (Methode) 159 importieren 116 Regex-Optionen 127, 129 search() (Funktion) 136, 148 split() (Methode) 216 mit einfangenden Gruppen 221 sub() (Methode) 188, 199 mit Rückwärtsreferenzen 191 replace() (Funktion), String (Klasse) in JavaScript 186 mit Rückwärtsreferenzen 191 Whitespace entfernen 334 Replace() (Methode) in .NET 184, 196, 197 mit Rückwärtsreferenzen 191 replaceAll() (Methode) in Java (String-Klasse) 186 mit Rückwärtsreferenzen 191 Replace-Button (RegexBuddy) 10 replaceFirst() (Methode) in Java (String-Klasse) 186 mit Rückwärtsreferenzen 191 Replace-Registerkarte (myregexp.com) 17 reset() (Methode) in Java 120 reset() (Methode) in Java (Matcher-Klasse) 147 RFC 3986-Standard 382 RFC 4180-Standard 440 römische Zahlen 364

514 | Index

rubular (Online) 15 Ruby (Sprache) (siehe auch MatchData (Klasse) in Ruby, MatchObject (Klasse) in Ruby, Regexp (Klasse) in Ruby, String (Klasse) in Ruby Ersetzungstext 7 literale reguläre Ausdrücke 114 Regex-Bibliothek 116 Regex-Objekte erstellen 122 Regex-Optionen 127, 130 Regex-Unterstützung 5, 105 Rückwärtsreferenzen 64 auf nicht-vorhandene Gruppen 98 beim Aufteilen von Strings 217 benannte 69 benannte Captures für (siehe benannte Captures) leere verwenden 321 Teile der Übereinstimmung wieder einfügen 189 wiederholte Wörter finden 323

S \S Token 35, 231 \s Token 35, 262 (siehe auch Whitespace) s///-Operator (Perl) 187 /e-Modifikator 199 mit Rückwärtsreferenzen 191 Satzzeichen, Unicode-Kategorie 51 Scala, Regex-Unterstützung 108 scala.util.matching (Paket) 108 scan() (Methode) in Ruby (String-Klasse) 166, 173 Schemata aus URLs extrahieren 385 Schnittmenge von Zeichenklassen 36 Schreibweise Groß-/Kleinschreibung ignorieren 29, 231 Groß-/Kleinschreibung ignorieren in Zeichenklassen 35 Groß-/Kleinschreibung ignorieren, Option setzen 123 Schriftsysteme in Unicode 55 alle Zeichen aufführen 57 -Elemente (HTML) 436 search() (Funktion) in Python (re-Modul) 136, 148

Sektionen von INI-Dateien 441 Sektionsblöcke finden 502 Sektionsüberschriften finden 500 Server aus UNC-Pfad extrahieren 426 7-Bit-Zeichensatz 32 Singleline (Option, RegexOptions) 125 singleline-Modus 38, 42, 123 size() (Methode) in Ruby (MatchData-Klasse) 154 Sozialversicherungsnummern validieren 271 span() (Methode) in Python (MatchObjectKlasse) 153 split() (Funktion) in Perl 215 mit einfangenden Gruppen 220 Split() (Methode) in .NET 211 mit Rückwärtsreferenzen 219 split() (Methode) in Java 213 split() (Methode) in Java (String-Klasse) 213, 220 split() (Methode) in JavaScript (String-Klasse) 214 split() (Methode) in Python (re-Modul) 216, 221 mit einfangenden Gruppen 221 Split-Button (RegexBuddy) 10 Split-Registerkarte (myregexp.com) 17 Sprachen, Unicode-Schriftsysteme 55 start() (Methode) in Java (Matcher-Klasse) 158 start()(Methode) in Python (MatchObjectKlasse) 153 Stern (*) als Anker (Zusicherung) 235 als Metazeichen 28 für possessive Quantoren 231 gierig 73 Maskieren 339 Stern-Quantor 258 Steuerzeichen Maskierungsfolgen 30 Unicode-Kategorie 51 String (Klasse) in Java replaceAll() und replaceFirst() (Methoden) 186 mit Rückwärtsreferenzen 191 split() (Methode) 213, 220 String (Klasse) in JavaScript match() (Methode) 147, 164 replace() (Funktion) 186 mit Rückwärtsreferenzen 191 Whitespace entfernen 334 split() (Methode) 214

String (Klasse) in Ruby gsub() (Methode) 188, 200 mit Rückwärtsreferenzen 192 scan() (Methode) 166, 173 split() (Methode) 216, 221 Stringlänge überprüfen 260 Stringliterale 111 Strings als URLs validieren 367 als URNs validieren 377 Aufteilen 208 führenden und abschließenden Whitespace entfernen 333 Länge bei der Eingabe validieren 260 Metazeichen maskieren 337 wiederholten Whitespace entfernen 336 Strings in doppelten Anführungszeichen C# 111 Java 111 Perl 113 PHP 112 VB.NET 111 Strings in dreifachen Anführungszeichen (Python) 114 Strings in einfachen Anführungszeichen (PHP) 112 strip-Funktion 333 strlen() (Funktion) 153 -Tags, ersetzen durch 459 -Elemente (HTML) 436 sub (Funktion), R Project 107 sub() (Methode) in Python (re-Modul) 188, 199 mit Rückwärtsreferenzen 191 sub() Methode, re-Modul 7 Substitutions-Operator, Java 112 Substitutions-Operator, Perl 187 Subtraktion in Zeichenklassen 36, 84 Suchen 331 (siehe auch Suchen und Ersetzen, Finden) nach Wörtern 301 aus einer Wortliste 304 außer ein bestimmtes Wort 310 gleiche Wörter 306 in der Nähe anderer Wörter 317 in Kommentaren in XML-Stil 488 nicht nach bestimmten Wörtern 313 nicht vor bestimmten Wörtern 312 vollständige Zeilen finden 330 Wiederholungen 323

Index | 515

nach XML-Attributen 476 nach Zahlen Binärzahlen 348 Ganzzahlen 341 Hexadezimalzahlen 345 nach Zeilen Duplikate 325 mit einem Wort 330 ohne ein Wort 332 URLs in längerem Text 371 in Anführungszeichen 373 in Klammern 374 Zeile für Zeile in mehrzeiligen Strings 222 Suchen und Ersetzen (siehe auch Finden, Suchen) alle Übereinstimmungen ersetzen 181 alle Übereinstimmungen in anderen RegexÜbereinstimmungen 200 Ersetzungstext 6 Ersetzungstext aus Code erzeugt 194 HTML-Sonderzeichen durch Entitäten 475 literalen Text einfügen 92 Tags in Markup-Sprachen 459 Teile der Übereinstimmung erneut einfügen 189 Teile der Übereinstimmung in den Ersetzungstext einfügen 96 Übereinstimmung in Ersetzungstext einfügen 95 Übereinstimmungskontext in Ersetzungstext einfügen 100 zwischen Übereinstimmungen einer anderen Regex 203 System.Text.RegularExpressions (Paket) 7 System.Text.RegularExpressions.Regex (Klasse) 119

T \t (horizontaler Tab) 30

-Tags, Attribute ergänzen 481 Tags (Markup-Sprachen) 436 alle bis auf ausgewählte entfernen 463 eines durch ein anderes ersetzen 459 Finden 442 Teile der Übereinstimmung einsetzen 189 Teile des gefundenen Textes auslesen 154 Telefonnummern validieren internationale Telefonnummern 239 Nordamerikanische Telefonnummern 233

516 | Index

test() (Methode) in JavaScript 136 Text ersetzen XIII Texteditoren 24 Text-geleitete Engines 60 The Regulator (Tool) 20 Token 35 Token in Zeichenklassen 262 Tools für die Regex-Programmierung 8 Desktop 18 grep-Tools 21 Online 12 RegexBuddy 8 RegexPal 11 Texteditoren 24 TPerlRegEx (Komponente) 107 Trademark-Symbol 47 trim-Funktion 333

U \u für Unicode-Codepoints 49 Kodieren in Java-Strings 111 Übereinstimmungen der Länge null 43 Übereinstimmungen im prozeduralen Code validieren 173 unbegrenzte Wiederholung 71 UNC-Pfade 419, 423 Laufwerksbuchstaben extrahieren 426 UNICODE (oder U)-Option 35, 259 UNICODE_CASE (Konstante) in Java 125 Unicode-Blöcke, alle Zeichen aufführen 57 Unicode-Eigenschaften 49 Unicode-Grapheme 49, 56 Unicode-Kategorien 49 alle Zeichen aufführen 57 Unicode-Schriftsysteme 55 alle Zeichen aufführen 57 uniq-Tool (Unix) 325 UNIX_LINES (Konstante) in Java 128 URLs (Uniform Resource Locators) Benutzer extrahieren 386 Fragmente extrahieren 397 generische validieren 379 Hosts extrahieren 388 in längerem Text finden 371 in Anführungszeichen 373 in Klammern 374 in Links umwandeln 376 Pfade extrahieren 392 Ports extrahieren 390

Queries extrahieren 396 Schemata extrahieren 385 Validieren 367 URNs (Uniform Resource Names) validieren 377

V \v (vertikaler Tab) 30 Validieren Datumsformate 241 ISO 8601 252 klassisch 250 Domain-Namen 398 Eingaben auf alphanumerische Zeichen begrenzen 257 Kommentare im XML-Stil 486 Postleitzahlen (Deutschland) 283 Postleitzahlen (Großbritannien) 282 Postleitzahlen (Kanada) 282 Postleitzahlen (USA) 281 Sozialversicherungsnummern 271 Stringlänge 260 Telefonnummern (international) 239 Telefonnummern (nordamerikanisch) 233 URLs 367 URLs, generisch 379 URNs 377 Windows-Pfade 417 Zeilenzahl 265 E-Mail-Adressen 227 Europäische VAT-Nummern 294 HTML-Elemente 456 ISBNs 274 ISO 8601-Formate 252 Kreditkarten-Nummern 288 mit dem Luhn-Algorithmus 293 ungültige Datumswerte entfernen 245 Value (Eigenschaft) in .NET (Match-Objekt) 146 variable Wiederholung 70 Varianten regulärer Ausdrücke 2 Varianten von Ersetzungstexten 6 VAT-Nummern validieren 294 VB.NET (Sprache) 104, 108 literale reguläre Ausdrücke 111 Regex-Bibliothek importieren 116 Regex-Objekte erstellen 119 VBScript Scripting Library 108 Vereinigungsmengen in Zeichenklassen 36

verschachtelt Quantoren 487 Zeichenklassen 36 vertikaler Balken (|) als Alternations-Operator 60, 251, 304 Maskieren 339 vertikaler Tab (\v) 30 Visual Basic 6 (Sprache) 108 Vorkommen von Zeilen speichern 326, 328 Vornamen umformatieren 285

W \W Token 35, 46 Anzahl der Wörter einschränken 263 \w Token 46, 231 Anzahl der Wörter einschränken 263 Währungsblock (Unicode) 55 Whitelist 465 Whitespace Anzahl an Nicht-Whitespace-Zeichen begrenzen 262 aus Kreditkarten-Nummern entfernen 288, 291 aus Strings entfernen 333 aus VAT-Nummern entfernen 294, 296 im Freiform-Modus ignoriert 91 in Zeichenklassen finden 35 Unicode-Kategorie 50 wiederholten entfernen 336 wiederholten Whitespace entfernen 336 Wiederholung in der Übereinstimmung exzessive Wiederholung vermeiden 78 minimale und maximale Wiederholung 72 Teile der Regex wiederholen 69 Wiederholung mit fester Anzahl 70 Windows Grep (Tool) 22 Windows-1252-Zeichen, Einschränken auf 258 Windows-Pfade Aufteilen 420 Dateierweiterungen extrahieren 431 Dateinamen extrahieren 430 Laufwerksbuchstaben extrahieren 425 Ordner extrahieren 427 Validieren 417 Windows-Pfade aufteilen 420 Wörter 325 (siehe auch Zeilen) ähnliche finden 306

Index | 517

alle außer einem bestimmten finden 310 Anzahl einschränken 263 bestimmte finden 301 eines von mehreren finden 304 ganze Wörter finden 44 in der Nähe von anderen finden 317 in Kommentaren im XML-Stil finden 488 Metazeichen maskieren 337 nicht einem bestimmten folgend 313 nicht von einem bestimmten gefolgt 312 Whitespace entfernen 333 wiederholte finden 323 Zeilen mit einem bestimmten finden 330 Zeilen ohne ein bestimmtes finden 332 Wortgrenzen 44, 251 (siehe auch \b Wortgrenzen) in Zeichenklassen finden 35 zum Suchen nach Zahlen 344 Wortwiederholungen finden 323 Wortzeichen 35, 46

X \x für Unicode-Codepoints 49 \X Token für Unicode-Grapheme 57 \x Werte (7-Bit-Zeichensatz) 32 XHTML (Extensible Hypertext Markup Language) 438 durch austauschen 459 Attribute zu
-Tags hinzufügen 481 Tags finden 442 XML (Extensible Markup Language) 439 alle Tags außer bestimmte entfernen 463 Attribute zu
-Tags hinzufügen 481 bestimmte Attribute finden 476 Kommentare entfernen 484 Kommentare validieren 486 Namen finden 466 Tags finden 444, 452 Wörter in Kommentaren finden 488 XML 1.0-Spezifikation 468, 469 XML 1.1-Spezifikation 468, 471

Z \Z Anker 40, 41, 232 \z Anker 40, 41 Zahlen XIII Binärzahlen finden 348 Datumsformat validieren 241 ungültige Datumswerte entfernen 245 518 | Index

Deutschland 283 führende Nullen entfernen 349 Ganzzahlen finden 341 in einem bestimmten Bereich 350 Gleitkommazahlen 359 hexadezimal 345 in einem bestimmten Bereich 357 IPv4-Adressen 400 IPv6-Adressen 403 ISBNs 274 Kreditkarten-Nummern validieren 288 mit Punkt (Tausender-Trennzeichen) 363 Postleitzahlen validieren Deutschland 283 Großbrittanien 282 Kanada 282 USA (ZIP-Codes) 281 römische Zahlen 364 Sozialversicherungsnummern validieren 271 Telefonnummern validieren internationale Telefonnummern 239 Nordamerikanische Telefonnummern 233 Uhrzeitformate validieren ISO 8601-Formate 252 klassische Formate 250 Unicode-Kategorie 50 VAT-Nummern 294 Zeichen finden (siehe Finden) Zeichenklassen 33, 235 Abkürzungen 35 Groß-/Kleinschreibung ignorieren 35 im Freiform-Modus 91 Subtraktion 36, 84 Unicode-Token 57 Vereinigungen und Schnittmengen 36 Zeichenklassen-Abkürzungen 35 Zeichenreferenzen, HTML 437, 475 Zeilen 325 (siehe auch Wörter) Definition 40 die bestimmte Wörter nicht enthalten 332 doppelte entfernen 325 Metazeichen maskieren 337 mit bestimmten Wörtern 330 sortieren 326, 327 sortieren, um Duplikate zu entfernen 326, 327 Whitespace entfernen 333 wiederholten Whitespace entfernen 336 Zeilenanfang 39

Zeilenanfang oder -ende, Zeichen finden 39 (siehe auch Wortgrenzen) Zeilenanzahl überprüfen 265 Zeilenende, Zeichen finden am 39 Zeilenumbrüche 45, 268 in HTML-Dokumenten 475 Zeilenweise Suche 222 Zeitformate validieren ISO 8601-Formate 252 klassische Formate 250

Ziffern (Zahlen) 343 in Zeichenklassen finden 35 Unicode-Kategorie 50 ZIP-Codes 282, 283 Zirkumflex (^) als Anker 40, 42, 232, 235 als Metazeichen 28 Maskieren 339 Negieren in Zeichenklassen 34 Zusicherungen 235 Zusicherungen mit der Länge null 82

Index | 519

Über die Autoren Das Reguläre Ausdrücke Kochbuch wurde von Jan Goyvaerts und Steven Levithan geschrieben, zwei der weltweit anerkanntesten Experten auf dem Gebiet der regulären Ausdrücke. Jan Goyvaerts betreibt das Unternehmen Just Great Software, aus dem einige der beliebtesten Software-Tools für reguläre Ausdrücke stammen. Dazu gehören RegexBuddy, der weltweit einzige Regex-Editor, der die Besonderheiten von 15 Regex-Varianten abbildet, und PowerGREP, das umfassendste Grep-Tool für Windows. Steven Levithan ist ein führender Experte für reguläre Ausdrücke im JavaScript-Umfeld und betreibt einen Blog zum Thema reguläre Ausdrücke: http://blog.stevenlevithan.com. Seit Jahren betrachtet er es als sein Hobby, sein Wissen über die verschiedenen Varianten der regulären Ausdrücke und die dazugehörigen Libraries auszubauen.

Kolophon Auf dem Cover zu Reguläre Ausdrücke Kochbuch ist eine Spitzmaus abgebildet (Soricidae). Spitzmäuse gehören zur Ordnung der Insektenfresser (Eulipotyphla), auch wenn sie rein äußerlich viel mehr Ähnlichkeit mit Nagetieren haben. Die typischen Merkmale von Insektenfressern, zu denen auch Igel und Maulwürfe gehören, sind auch bei den Spitzmäusen ausgeprägt, als da wären die lange, spitz zulaufende, rüsselartige Schnauze, kräftige Zähne, kleine Augen und zurückgebildete Ohren. Tasthaare im Gesicht helfen bei der Orientierung. Die Größe kann von 3 Zentimetern (die Etruskerspitzmaus ist das kleinste Säugetier der Erde) bis zu 18 Zentimetern variieren. Das kurze, samtartige Fell kann farblich von grau über braun bis schwarz reichen. Spitzmäuse haben einige Fähigkeiten entwickelt, die ihnen beim Überleben gute Dienste tun: Wie bei den Fledermäusen hat man auch bei Spitzmäusen die Fähigkeit nachgewiesen, mittels Echolotung ihre Umwelt zu erfassen, sodass sie sich nicht allein auf ihren Geruchssinn verlassen müssen. Außerdem gibt es einige Arten, die einen giftigen Speichel produzieren, mit dem sie ihre Beute lähmen. Auf diese Weise sind sie sogar fähig, kleine Nagetiere und Frösche zu jagen. Eine weitere Besonderheit ist der durchdringende Moschus-Geruch, den die Tiere aus Drüsen am Körper aussondern. Dieser Geruch schützt sie vor Fraßfeinden, da viele Tiere sie für ungenießbar halten. Spitzmäuse sind in fast allen Weltregionen zu finden, außer an den Polen, in Australien und Ozeanien und in großen Teilen Südamerikas. Einige haben sich an die Trockenheit von Wüsten angepasst, andere leben aquatisch in Flüssen und Sümpfen. Die meisten Arten leben aber im Grasland und in den Wäldern der gemäßigten Breiten, wo sie Insekten, Würmer und kleine Säugetiere – ihre bevorzugte Beute – finden.