Einführung in die Verwendung von regulären Ausdrücken
Das Vorhandensein von Regulären Ausgrücken (engl. regular expressions) ist einer der wesentlichen Vorteile vieler Skriptsprachen - inzwischen jedoch auch von vielen "herkömmlichen" Programmiersprachen. Am Beispiel von Java werden wir in dieses heutzutage wichtiges Thema einen Einblick in die Verwendung regulärer Ausdrücke nehmen.
Im wesentlichen besteht dieses Dokument aus drei Teilen. Im ersten Teil werden die allgemeinen Grundlagen dargelegt, während sich der zweite Teil der Grundlagen der Java Implementation beschäftigt. Der dritte Teil letztlich widmet sich der praktischen Anwendung mit Java.
Die regulären Ausdrücke von Java sind denen von Perl sehr ähnlich. Wenn Sie reguläre Ausdrücke in Perl definieren können, werden Sie daher kaum Schwierigkeiten mit Java haben. Sie sollten jedoch die Dokumentation zur Klasse Pattern genau studieren, da es einige kleine Abweichungen gibt.
Reguläre Ausdrücke
Reguläre Ausdrücke sind Muster, welche in einer speziellen festen Syntax gehalten sind und auf Daten angewandt werden. Diese Muster sind für Einsteiger oft verwirrend oder gänzlich unverständlich. Nach etwas Einarbeitszeit werden jedoch auch Sie mit regulären Ausdrücken arbeiten können.
Bausteine von regulären Ausdrücken
Damit Sie reguläre Ausdrücke formulieren können, müssen Sie die einzelnen Bausteine kennen. Sobald Sie diese beherrschen geht es lediglich noch darum, diese richtig zusammenzubauen. Letztendlich sind Sie der Architekt einer Baustelle, die es zu konstruieren gilt. Reguläre Ausdrücke bestehen dabei aus drei Teilen:
Die atomaren Bausteine (Atoms)
Die atomaren Bausteine sind die kleinsten unteilbaren regulären Ausdrücke. Jeder reguläre Ausdruck läßt sich als eine Kombination dieser Bausteine betrachten.
| Atomare Bausteine regulärer Ausdrücke | ||
|---|---|---|
| Definition | Beispiel / Syntax | Erläuterung |
| ein einzelnes Zeichen | s | entspricht dem Zeichen s |
| ein beliebiges Zeichen | . | der Punkt entspricht jedem Zeichen |
| ein Zeilenanfang | ^ | Das Caret entspricht dem Beginn einer Zeile |
| ein Zeilenende | $ | Das Dollar-Zeichen entspricht dem Ende einer Zeile. Als Zeilenendezeichen / Zeilentrenner werden dabei "\n", "\r\n", "\r", "\u0085", "\u2028" und "\u2029" aktzeptiert. |
| ein maskiertes Zeichen | \ | Durch den Backslash können Sie auch die Sonderzeichen in Ihr Muster aufnehmen |
| gruppierte eingeschlossen Muster | (ei) | Durch gruppierte Muster lassen sich Ihre Muster weiter speziallisieren (s.a. Gruppierung). |
| Gruppierungstrenner | | | Durch den Trenner oder auch OR-Operator (engl.delimiter) läßt sich die Gruppierung weiter spezifizieren. |
| Übersicht der atomaren Bausteine regulärer Ausdrücke | ||
Die Bauteile (Pieces)
Die Bauteile (pieces) sind die atomaren Bausteine der regulären Ausdrücke, erweitert um Quantifizierer. Diese Quantifizierer geben an, wie oft ein bestimmter atomarer Ausdruck vorkommen darf, soll oder muss.
| Bauteile reguläre Ausdrücke | ||
|---|---|---|
| Definition | Beispiel / Syntax | Erläuterung |
| ein weiterer Baustein | bastie | Entspricht sechs aneinandergereihten Bausteinen. Die automaren Bausteine werden aneinander angefügt. |
| null (0) oder beliebig viele der Bausteine | * | Dem vorangegangenen Baustein wird ein Asterix (*) angefügt. |
| ein (1) oder beliebig viele der Bausteine | + | Dem vorangegangenen Baustein wird ein Plus (+) angefügt. |
| null (0) oder ein (1) Baustein | ? | Dem vorangegangenen Baustein wird ein Asterix (?) angefügt. |
| eine genaue Anzahl der Bausteine | [0-9]{2} | Entspricht dem regulären Ausdruck "[0-9][0-9]". In geschweifen Klammern wird dem Baustein die gewünschte Anzahl angefügt. |
| ein Anzahl in einem bestimmte Wertebereich | [0-9]{1,3} | Der Baustein muss hier mindestens ein mal, darf jedoch höchstens jedoch drei mal vorkommen. In geschweiften Klammern wird dem Baustein das gewünschte Minimum un Maximum getrennt durch ein Kommata angefügt. |
| Übersicht der Bauteile regulärer Ausdrücke | ||
Die Bereiche (Ranges)
Die "Faulheit" der Programmierer hat sich letztlich in den Bereichen niedergeschlagen. Diese helfen Ihnen eine Menge schreibarbeit zu vermeiden.
| Bereiche reguläre Ausdrücke | ||
|---|---|---|
| Definition | Beispiel / Syntax | Erläuterung |
| eine Menge von Zeichen | [äöüßÄÖÜ] | Jedes dieser Zeichen in dem Bereich erfüllt den regulären Ausdruck. |
| einen Bereich von Zeichen | [a-f] | Die Zeichen a b c d e f erfüllen den regulären Ausdruck. |
| nicht dieses Zeichen | [^,] | Das dem Caret nachgestellte Zeichen erfüllt nicht den regulären Ausdruck. |
| nicht die Zeichen des Bereichs | [^0-9] | Der Bereich der Zeichen nach dem Caret erfüllt nicht den regulären Ausdruck. |
| Übersicht der Bereiche regulärer Ausdrücke | ||
Die Gruppierung
Mit Hilfe der Gruppierung haben Sie die Möglichkeit häufig benötigte Aufgabenstellungen umzusetzen. Eine Gruppierung wird dabei durch rund Klammern spezifiziert z.B. "(ä|ü|ö)". Zusammen mit dem Delimiter / OR-Operator ermöglicht dies spezielle Muster z.B. zur Suche nach ähnlichen Ausdrücken. Der reguläre Ausdruck "M(ey|ei|ay|ai)er" stellt somit vier verschiedene Schreibweise von Herrn oder Frau Meier dar. Gleiches ließe sich auch mit dem kürzeren regulären Ausdruck "M(e|a)(y|i)er" realisieren.
Besondere Notationen
Die schon erwähnte Faulheit der Programmierer hat zu weiteren Abkürzungen geführt, die noch kurz dargestellt werden sollen.
| Bereiche reguläre Ausdrücke | ||
|---|---|---|
| Definition | Abkürzung | normale Definition |
| eine Ziffer | \d | [0-9] |
| keine Ziffer | \D | [^0-9] |
| ein Buchstabe oder eine Zahl (ohne landesspezifische Zeichen) inkl. Kleinbuchstaben | \w | [a-zA-Z0-9] |
| kein Buchstabe und keine Zahl (ohne landesspezifische Zeichen) inkl. Kleinbuchstaben | \W | [^a-zA-Z0-9] |
| ein nicht druckbares Zeichen (Leerraum) | \s | [\t\n\r\f] |
| kein nicht druckbares Zeichen (Leerraum) | \S | [^\t\n\r\f] |
| Übersicht der Bereiche regulärer Ausdrücke | ||
Reguläre Ausdrücke in Java
Mit dem J2SDK 1.4.0 hat Sun Microsystems den Schritt Richtung reguläre Ausdrücke gewagt. Vor dieser Version war die Verwendung von regulären Ausdrücken nur mit Hilfe von Drittanbieter APIs oder eigener Implementation z.B. mit Hilfe der Klasse StringTokenizer möglich. Das kleine zugehörige Framework findet sich im Paket java.util.regex und besteht lediglich aus den Typen Pattern, Matcher und PatternSyntaxException. Zu diesen kommt jedoch noch das Interface CharSequence aus dem java.lang Paket.
Die Klassen Pattern und Matcher sind dabei als final deklariert. Die Klasse Pattern beinhaltet den regulären Ausdruck, während die Klasse Matcher sich um die Auswertung des selben kümmert.
Die Klasse Pattern
Da die Klasse selbst keinen öffentlichen Konstruktor hat, müssen wir uns der Fabrikmethode compile bedienen. Dieser übergeben Sie Ihr konstruiertes Muster, ggf. gefolgt von speziellen Optionen (flags).
Die Optionen selbst sind dabei als statische Konstanten der Klasse Pattern erreichbar.
| Optionen | |
|---|---|
| Definition | Erläuterung |
| CANON_EQ | |
| UNICODE_CASE | Mit Hilfe dieses Flags können Sie auch in Unicode-Zeichenketten die Groß- und Kleinschreibung berücksichtigen. Die Performance Ihrer Javaanwendung kann darunter jedoch erheblich leiden. |
| DOTALL | In diesem Modus bekommt der Punkt (.) einen erweiterten Wertebereich, so dass auch Zeilentrennzeichen berücksichtigt werden. |
| MULTILINE | Diese Option ermöglicht die Suche unter Berücksichtigung von Zeilentrenner ("\n", "\r\n", "\r", "\u0085", "\u2028" und "\u2029"). Sofern Sie das Caret (^) oder Dollar ($) -Muster nutzen wollen ist diese Option meist einzuschalten. |
| COMMENTS | Diese Option erlaubt innerhalb des regulären Ausdrucken Kommentare einzufügen. Diese beginnen mit dem Wert-Zeichen (#) und enden mit einem Zeilentrenner. |
| CASE_INSENSITIVE | Bei der Prüfung des Musters / regulären Ausdrucks wird keine Rücksicht auf die Groß- / Kleinschreibung genommen. |
| UNIX_LINES | Hierbei wird als Zeilentrenner lediglich "\n" berücksichtigt. |
| Optionen für die Verarbeitung regulärer Ausdrücke | |
Alternativ dazu können wir auf ein Objekt vom Typ CharSequence mit Hilfe der Klassenmethode matches direkt unser Muster prüfen. Sie können so prüfen, ob Ihr Muster / regulärer Ausdruck innerhalb des CharSequence-Objektes gefunden wurde.
Die Klasse Matcher
Auch wenn Sie mit der Klasse Pattern Ihr Muster definieren, so ist der wahre Künstler die Klasse Matcher. Mit Hilfe dieser Klasse können Sie Muster finden, zählen, ersetzen...
Ein Objekt dieses Typs erzeugen Sie, in dem Sie die Fabirkethode matches eines Pattern-Objektes aufrufen. Als Parameter wird die Zeichenkette erwartet, auf welcher Sie Ihr Muster anwendenden wollen. Anschliessend stehen Ihnen verschiedenen Methoden zur Verfügung.
Ihr Matcher-Objekt stellt Ihnen nunmehr zahlreiche Operationen zum Testen, Aufbereitung und Ändern bereit. Zu diesen gehören auch:
- matches()
für die Prüfung ob Ihre Zeichenkette exakt Ihrem definierten Muster entspricht. - lookingAt()
für die Prüfung, ob Ihre Zeichenkette mit Ihrem definierten Muster beginnt. - find()
für die Prüfung, ob nach der aktuellen Suchposition eine weitere Musterübereinstimmung in Ihrer Zeichenkette vorkommt. In diesem Zusammenhang gibt Ihnen die Operation start() die aktuelle Suchposition zurück. - group()
für die Aufbereitung / Ausgabe Ihrer gefundenen Übereinstimmgung. - group(int gruppe)
für die Aufbereitung / Ausgabe Ihrer gefundenen Übereinstimmung für ein spezielles gruppiertes eingebettetes Ihrer Muster. Durch die Übergabe von 0 erreichen Sie die Funktionalität der Operation group() - Matcher appendReplacement (StringBuffer, String)
Diese Methode fügt alle Zeichen bis zur nächsten Übereinstimmung dem StringBuffer hinzu. Zusätzlich wird das Muster noch durch den übergebenen String ersetzt und ebenfalls zum StringBuffer hinzugefügt. - StringBuffer appendTail (StringBuffer)
Diese Methode fügt den Rest der Zeichenkette, ab der letzten Position einer Übereinstimmung, dem StringBuffer hinzu. - String replaceAll (String)
Ersetzt jedes gefundene Muster durch die übergebene Zeichenkette. - String replaceFirst (String)
Diese Methode ersetzt die erste Übereinstimmung des Musters durch die übergebene Zeichenkette und liefert das Ergebnis als String zurück.
Das Interface CharSequence
Das Interface CharSequence legt fest, welche Operationen entsprechende Typen bieten müssen. Es bietet einen lesenden Zugriff auf wesentliche Eigenschaften einer Zeichenkette. Neben Ihren Implementationen sind Objekte der Klassen CharBuffer, String und StringBuffer ebenfalls vom Typ CharSequence. CharSequence Objekte stellen folgende Operationen zur Verfügung:
- char charAt (int index)
Gibt das Zeichen am Offset "index" zurück. - int length ()
Gibt die Länge der Zeichenkette zurück. - CharSequence subSequence (int start, int ende)
Erstellt eine neue Zeichenkette, die eine Kopie der aktuellen Zeichenkette von "start"-Index (inklusive) bis zum "ende"-Index (exklusive) darstellt. - String toString ()
Erstellt ein String-Objekt, welches eine genaue Kopie der Zeichenkette darstellt.
Die PatternSyntaxException
Die PatternSyntaxException wird dann geworfen, wenn Sie versuchen ein Muster zu kompilieren ("Pattern.compile(String muster)") welches nicht der Syntax für Muster entspricht. Sie können diese jedoch sinnvoll nutzen, um z.B. nur zulässige Muster als Benutzereingabe zuzulassen oder auch nur um Ihre eigenen Muster zu prüfen.
Reguläre Ausdrücke mit Java anwenden
Nach der ganzen grauen Theorie - nun gut soviel war es ja doch nicht - nun aber zur Praxis! Zu diesem Zweck werden Sie hier einige Beispiele sehen, die (hoffentlich zumindes teilweise auch bei Ihnen) praktisch und nutzbar sind.
- Splitter für CSV-Dateien - Zeichenketten aufteilen
- Textsuche
- HTML zu Text konvertieren - Zeichenketten ersetzen
- Vor- und Nachmustersuche
Splitter für CSV-Dateien
Ausgangspunkt für dieses Beispiel soll eine alte Batch-Implementation sein, die diesen Zweck für zweispaltige Datencontainer erfüllte:
for /F "eol=# tokens=1-2 delims=;,|" %%i in (input.csv) do (
set %%i=%%j
)
Batchprogrammierer werden gleich Zweck und Erweiterung des Formats erkennen. Dies soll uns jedoch nicht interessieren.Wichtig für unsere Betrachtung sind die Trennzeichen (delimiters), welche hier das Semikolion ";", das Kommata "," und das "|" sind.
Pattern csvPattern = Pattern.compile("(,|\\||;)");
String [] csvPieces = csvPattern.split (inputString);
for (int i = 0; i < csvPieces.length; i++) {
System.out.println (csvPieces[i]);
}
Eigentlich ganz einfach, oder? Lediglich das Muster wollen wir nochmal genauer betrachten.Zuerst nehmen wir eine Gruppierung vor, daher beginnt und endet unser Muster mit einer runden Klammer "()". Anschließend geben wir unser Muster separiert vom Gruppierungstrenner "|" ein. Das mittlere Muster muß zudem noch doppelt maskiert werden: Da wir den Trenner als Muster benötigen müssen wir diesen mit Hilfe des Backslash "\" maskieren. Damit wir jedoch einen Backslash in einem String-Objekt erhalten müssen wir diesen wiederum mit einem Backslash maskieren. Das Muster "(;|,|\\|)" ist mit dem obigen funktionsgleich aber vielleicht etwas übersichtlicher.
Natürlich läßt sich selbiger Effekt auch mit Hilfe der Klasse StringTokenizer erledigen. Eine Implementation würde dementsprechend etwa folgendes aussehen haben.
StringTokenizer tokenizer = new StringTokenizer (input, ";,|", false);
while (tokenizer.hasMoreTokens ()) {
System.out.println (tokenizer.nextToken ());
}
Wo liegt nun der Vorteil? Eine kleine Übung wird Ihnen hier helfen. - Erstellen Sie eine Javaanwendung ohne Verwendung von Mustern, um einen nach HTML-Syntax formulierten String anhand der HTML-Tags zu trennen!
- Überlegen Sie sich ein passendes Muster zu diesem Zweck und testen Sie dieses!
Textsuche
Eine der wesentlichesten Aufgaben ist das Finden von Zeichenketten, welche mit dem Muster übereinstimmen. Dabei wird nicht wie bei String.indexOf(String) eine genaue Übereinstimmung sondern ein Übereinstimmung mit einem Muster gesucht. Warum? Nicht immer können Sie im voraus alle Einzelheiten spezifizieren. Angesprochen wurde bereits der Hr. Meyer oder war es Maier? Das nachfolgende Beispiel zeigt das Auffinden aller HTML-Tags, die eine Referenz auf eine weitere Resource in Form eines A-Tags darstellen.
final String input = "<h1>Linkliste</h1>"
+ "<a href="http://www.ritter.biz/java/howto/index.html">Make Java - How To</a>"
+ "<a href="http://www.ritter.biz/java/mjregular/index.html">Make Java - Regular expression</a>"
+ "<a href="http://www.ritter.biz/java/mjperformance/chapter0.html">Make Java - Performance</a>"
+ "";
Pattern p = Pattern.compile ("<a>]*>", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher (input);
while (m.find()) {
System.out.println (m.group());
}
Mit Hilfe der Methode "group()" lassen wir uns schließlich die Übereinstimmungen zurückgeben.
HTML zu Text konvertieren
Die Klasse Matcher stellt zwei Methoden zur Verfügung um Ersetzungen vorzunehmen. Diese (replaceFirst und replaceAll) werden generell gleich verwendet. Wenn Sie aus einer HTML-Datei eine Text-Datei erstellen wollen müssen Sie u.a. die maskierten Sonderzeichen wie z.B. die deutschen Umlaute umsetzen. Dazu dient uns die Methode replaceAll. Dazu zuerst der Java-Quelltext:
Properties replacingProperties = new Properties ();
try {
replacingProperties.load (
new BufferedInputStream (
this.getClass().getResourceAsStream("html2txt.rpl")));
}
catch (final IOException exitException) {
System.err.println ("Fehler beim Laden der Ersetzungstabelle.");
System.exit (-1);
}
StringBuffer inputString = new StringBuffer ("<h1>Make Java <span lang='lang'>"
+ "regular expression</span></h1> <p>Eine "
+ "Einführung in Java und reguläre "
+ "Ausdrücke</p>");
Enumeration replacingStrings = replacingProperties.keys();
while (replacingStrings.hasMoreElements()) {
final String codingString = replacingStrings.nextElement().toString();
final Pattern p = Pattern.compile (codingString);
Matcher m = p.matcher(inputString);
if (m.find()) {
inputString = new StringBuffer (m.replaceAll (replacingProperties.getProperty(codingString)));
}
}
Pattern htmlTagPattern = Pattern.compile("]*>");
String [] preText = htmlTagPattern.split (inputString);
for (int i = 0; i < preText.length; i++) {
System.out.print (preText[i]);
}
In dem Beispiel wurde unsere Ersetzungtabelle bereits ausgelagert. Diese sieht ausschnittsweise so aus:
#Ersetzungstabelle von html nach txt #Sat Aug 31 19:12:31 CEST 2002 ™=(tm) &=\u00DF ®=(r) Ä=\u00C4 ¶= ü=\u00FC ©=(c) »=>> Ü=\u00DC ö=\u00F6 <br />=\n\r <=< "=" «=<< Ö=\u00D6 ß=\u00DF ä=\u00E4 <p>=\n\r\n\rDie wesentliche Zeile ist "m.replaceAll (replacingProperties.getProperty(codingString))", welches sich z.B. auf "m.replaceAll ("\u00DC")" reduziert, wenn das Muster "Ü" entspricht. Einfach oder?
Vor- und Nachmustersuche
Die Vor- und Nachmustersuche ist leider nicht direkt implementiert. Mit Hilfe der Operationen "start()" und "end()" können Sie jedoch die Position eines gefunden Musters extrahieren. Daneben stellt die Methode "split()" diese Funktionalität bereit (siehe Splitter für CSV-Dateien);
Hier folgen jetzt noch zwei Übungsaufgaben, wobei eine mögliche Umsetzung im Abschnitt Lösungen zu finden ist.
- Extrahieren Sie aus einem HTML-String alle externen Links (ohne Anker) mit Hilfe der "split()" Methode!
- Überlegen Sie sich, wie eine Umsetzung mit Hilfe der "start()" und "end()" Methoden zu implementieren wäre!
Musterbeispiele
| Muster | Aufgabe |
|---|---|
| Musterbeispiele | |
| (;|,|\\|) | Findet alle CSV Delimiter (siehe Splitter für CSV-Dateien). |
| ]*> | Findet alle HTML-Tags in einem HTML Dokument (sieheTextsuche). |
| <a>]*href=\"?[^(>| )]*\"?[^>]*>[^<]*</a> | Findet alle Links in einem HTML-Dokument, jedoch ohne die Anker zu berücksichtigen. Der umschließende HTML-Tag wird dabei mitgeliefert. |
Lösungen
- Muster-String-Objekt: "]*>"
Eine Beispielanwendung könnte somit in etwa so aussehen:package biz.ritter.regex.sample; import java.util.regex.*; /**Überschrift: Pattern und Matcher * Copyright: Copyright © 2009 * Organisation: http://www.ritter.biz * @author Copyright © 2009 Sebastian Ritter * @licence BSD * @version 1.0 */ public class SimpleHtml2Txt implements Runnable{ /** Defaultconstruktor */ public SimpleHtml2Txt() { } /** * Threadoperation */ public void run () { String inputString = "<body><h1>Make Java<span lang="lang">" + "regular expression</span> <p>Eine " + "Einführung in Java und reguläre Ausdrücke</p>" + ""; Pattern htmlTagPattern = Pattern.compile("]*>"); String [] preText = htmlTagPattern.split (inputString); for (int i = 0; i < preText.length; i++) { System.out.print (preText[i]); } } /** * Java-startoperation * @param args */ public static void main(String[] args) { SimpleHtml2Txt html2Txt = new SimpleHtml2Txt(); html2Txt.run(); } } - Eine Umsetzung mit Hilfe der "split()" Methode die externen Links einer HTML-Datei zu extrahieren könnte so aussehen:
input = "<h1>Meine kleine Linkliste</h1><a href="http://www.ritter.biz/java/mjperformance/chapter0.html">" + "Make Java - Performance</a><br />" + "<a href="/href">Make Java - How To</a>" + "<a href="/href" target="target">Make Java - How To</a>" + "<a href="/href">Make Java - Regular expression</a>" + "<a href="/href">oben</a>"; Pattern linkPattern = Pattern.compile ("</a>]*href=\"?[^(>| )]*\"?[^>]*>[^)"); Matcher linkMatcher = linkPattern.matcher (input); LinkedList linkLinkedList = new LinkedList (); while (linkMatcher.find ()) { linkLinkedList.addLast (linkMatcher.group ()); } for (int i = 0; i < linkLinkedList.size (); i++) { CharSequence cs = prePattern.split ((CharSequence)linkLinkedList.get(i))[1]; cs = postPattern.split (cs)[0]; System.out.println (cs); }
- Erweitern Sie den HTML zu Text Konvertern, so dass dieser praktisch anwendbar ist.
- Erstellen Sie eine Anwendung, die einen Java Quelltext in ein HTML Dokument umwandelt. Dabei sollen, Schlüsselwörter, Operatoren, Variablen etc. durch entsprechende HTML-CSS-Klassen näher bestimmt werden können. Alternativ könnten Sie auch einen Syntaxchecker oder Konverter für eine beliebige andere Sprache bauen.
- Wer etwas mehr Zeit hat, könnte auch einen JDBC-Treiber für CSV Datenbanken schreiben.
- Schreiben Sie eine kleine Adressenverwaltung, die nur gültige eMail Adressen zuläßt.
Performante reguläre Ausdrücke
Informationen zu performanten regulären Ausdrücken finden Sie im Performance Handbuch unter

