Teil 4
Bislang, in den ersten beiden Teilen, war es eigentlich recht geruhsam. Dieses Kapitel allerdings hat es in sich. Arbeitet es langsam und mit Geduld durch. Es ist der anspruchsvollere Bereich, der aber auch der interessantere ist, da man nun endlich vernünftige Regexe bauen kann. Testet nach Möglichkeit selber mit dem Regex-Tester die Beispiele durch.
4.1. Quantifizierer
So,
bislang konnten wir nach einzelnen Zeichen, Zeichengruppen oder
Zeichenklassen und Bereichen suchen. Wir haben es geschafft, alternativ
nach dem Einen oder Anderen zu suchen. Aber eines fehlt uns noch:
nämlich nach Zeichenwiederholungen zu suchen.
Weiter oben gab es das Beispiel, mit dem nach einem deutschen Datum gesucht wurde:
„\d\d\.\d\d\.\d\d\d\d“
Für jede Ziffer musste man „\d“ schreiben. Gibt es da nichts
einfacheres? Doch, klar! Natürlich kann man so etwas weniger aufwändig
schreiben und mit Zeichenwiederholungen arbeiten. Aber fangen wir mal
vorn bei den Quantifizierern an:
+ * ? sind die wichtigsten Quantifizierer.
Das „+“-Zeichen bedeutet, dass das Zeichen vor dem Plus-Zeichen
mindestens einmal an der Stelle in der Zeichenkette vorkommen muss!
„pa+r“ passt somit auf ‚paar‘, aber auch auf ‚par‘ oder ‚paaaaar‘.
„Re:\s+“ heißt zum Beispiel, dass mindestens ein Whitespace-Zeichen auf
das ‚Re:‘ folgen muss, um gefunden zu werden. Ich höre da schon wieder
Experten rufen, dass die Benutzung von Quantifizierern nicht nur auf
Zeichen beschränkt ist. Stimmt, man kann sie auch auf Metazeichen,
Zeichenklassen oder anderen Elementen anwenden, die wir aber erst noch
lernen müssen.
Mit dem Stern wird festgelegt, dass das Zeichen vor dem Stern beliebig
häufig oder gar nicht existieren muss! Ooops, wofür soll das denn gut
sein: ‚gar nicht existieren muss‚?
Am besten schauen wir uns das an einem Beispiel an: „Re:\s*\w+“
Dies ist ein Regex, mit dem wir den Anfang von Betreffzeilen aus Mails
erkennen wollen (wir werden später erkennen, wofür wir uns eigentlich so
häufig um die Betreffzeile kümmern und was wir mit Regexen daraus
machen wollen. Ihr müsst euch leider gedulden) und dabei können einige
fehlerhafte Formatierungen vorliegen, die wir natürlich mit dem Regex
erkennen müssen. Na, sieht ja schon langsam so aus, wie die kryptischen
Teile der Profis. Was will das Regex?
Finde ein ‚Re‘ gefolgt von einem Doppelpunkt. Danach können beliebig
viele Leerzeichen (oder Whitespaces) folgen oder auch gar keine. Warum
das? Da gehört doch normalerweise sowieso ein Leerzeichen hin! Na, wir
wollen auch manuell verhunzte Reply-Zähler finden und sollte der
Schreiber das Leerzeichen gelöscht haben, so soll das Ganze trotzdem von
der Regex-Maschine gefunden werden; es soll uns nicht stören. Danach
aber muss mindestens ein alphanumerisches Zeichen folgen.
Achtung: hier wird gern ein Fehler gemacht. Nehmen wir
mal die folgende Aufgabe: Es sollen nur Zeilen gefunden werden, in denen
beliebig viele Ziffern stehen. Man kann folgendes hin und wieder als
Lösung sehen: „^[0-9]*$“
Tatsächlich aber findet das Regex auch leere Zeilen, denn der Stern
steht ja auch für ‚Häufigkeit 0‘. Will man also sicherstellen, dass
mindestens eine Ziffer in der Zeile auftritt, so muss das „+“-Zeichen
verwendet werden: „^[0-9]+$“.
Das Fragezeichen bedeutet dagegen, dass das Zeichen davor höchstens
einmal vorkommen darf, aber nicht muss, also mit der ‚Häufigkeit 0‘.
„H..?se“ findet ‚Hase‘, aber auch ‚Hirse‘.
Zeichenwiederholungen lassen sich aber auch anders angeben: „{x,y}“
Hiermit lassen sich detailliert Häufigkeiten definieren. „x“ steht für
die Mindestanzahl und „y“ für die Maximalzahl, die ein Zeichen vorkommen
darf. „\d{2,4}“ bedeutet, dass nur das gefunden wird, was mindestens
zwei aber höchstens vier Ziffern hat.
Lässt man das „y“ weg, also „{x,}“, so muss das Zeichen mindestens x-mal vorkommen, ohne obere Grenze.
Lässt man „,y“ weg, also in der Form „{x}“, so wird dies als absolute
Zahl gewertet: nicht mehr, aber auch nicht weniger häufig darf das
Zeichen vorkommen. Nun können wir unser Datumssuchmuster umschreiben:
„\d{2}\.\d{2}\.\d{4}“
Die zu Beginn beschriebenen Quantifizierer „?+*“ sind eigentlich Spezialschreibweisen der folgenden:
{0,1} = ?
{1,} = +
{0,} = *
Bevor wir zu einem weiteren Problem der Quantifizierer kommen, müssen
wir erst die runden Klammern als Gruppierungsmöglichkeit einführen:
4.2. Gruppierung, Subpattern und noch mal Quantifizierer
Gruppierung
Im
Abschnitt über Alternativen begegnete uns erstmalig die runde Klammer
als Metazeichen. Sie wurde dort wie in der Mathematik verwendet: gleiche
Elemente wurden ausgeklammert.
Sie kann aber auch dazu genutzt werden, Suchmuster zu gruppieren, zum
Beispiel um darauf einen Quantifizierer anzuwenden: „foo(bar)?“ findet
‚foo‘ und auch ‚foobar‘.
Anderes Beispiel:
„Re\s*(\[\d+\])?:“ Schon wieder unsere Betreffzeile! Aber diesmal schon
recht professionell. Wir suchen nach der Zeichenkette ‚Re‘. Dieser kann,
nach beliebigen Whitespace-Zeichen, die eckige Klammer mit dem Zähler
folgen, sie muss aber nicht. Die Whitespace-Zeichen haben wir mit dem
Stern versehen, damit die Zeichenkette auch dann gefunden wird, wenn der
Verfasser einfach das Leerzeichen manuell eingefügt hat, seine
Leertaste prellt und mehr als ein Leerzeichen folgt oder er gar nichts
gemacht hat und deswegen dort auch kein Leerzeichen steht. Normalerweise
dürfte dort nämlich gar kein Leerzeichen stehen. Aber was heißt hier
schon ’normalerweise‘? Zusätzlich erlauben wir sogar, dass der Zähler
astronomisch groß werden darf, denn wir geben mit dem „+“-Zeichen an,
dass mindestens eine Ziffer in der Klammer stehen muss, allerdings
dürfen es auch beliebig viele sein.
Gefunden wird also:
‚Re:‘
‚Re [1]:‘
‚Re[123]:‘
Nicht gefunden wird dagegen ‚Re []:’ Aufgabe für zwischendurch: Was
müsste am Regex geändert werden, damit auch diese Reply-Zähler gefunden
werden?
Lösung: Das „+“-Zeichen müsste nur gegen einen Stern ausgetauscht werden: „Re\s*(\[\d*\])?:“
Aus der Zeichenkette ‚Re [1]: [3]:‘ findet das Regex allerdings ‚Re
[1]:‘ Der zweite Teil passt nicht auf das gesamte Suchmuster. Wollen wir
solche verkorksten Reply-Zähler finden, müssen wir uns noch mal an das
Regex machen. Es soll nun zusätzlich beliebig viele oder keine eckige
Klammern finden, die auch noch einen Doppelpunkt haben können
„(\[\d+\]:\s*)*“. Am Schluß soll noch ein Doppelpunkt folgen „:?“
„Re\s*(\[\d+\]:\s*)*:?“
Damit werden nicht alle kaputten Reply-Zähler gefunden. Da gibt es eine
Unmenge an denkbaren Kombinationen. Wer will, kann dies mal selbst
erarbeiten. Aber immerhin haben wir jetzt den Vorspann eines Betreffs
schon ganz gut im Griff und könnten uns auf das konzentrieren, was nach
diesem Vorspann kommt: der eigentliche Betreff!
An dieser Stelle sei aber darauf hingewiesen, dass es nicht immer Sinn
macht, das perfekte Regex zu suchen. Der Aufwand steigt sehr, nur um die
unwahrscheinlichsten Zeichenkombinationen zu finden, die so gut wie nie
auftreten. Man erkauft sich den Perfektionismus mit unlesbaren
Regex-Code und einer größeren Fehleranfälligkeit, sollte man mal das
Regex geringfügig ändern wollen. Wie immer im Leben, gilt auch hier:
„Man sollte mal Fünfe gerade sein lassen!“
Als weiteres Beispiel soll wieder unsere Datumssuche dienen:
„\d{2}\.\d{2}\.\d{4}“ Wie zu sehen ist, wiederholt sich der Anfang
„\d{2}\.“ Genau das lässt sich mit Klammern verkürzen:
„(\d{2}\.){2}\d{4}“ Das erste Element des Suchmusters – in den runden
Klammern – soll nun mindestens zweimal vorkommen. Dies entspricht genau
der Kombination ‚01.02.‘
Auch weiterhin gilt, dass dies nicht die optimale Version ist, da die
Tages- und Monatsangaben zweistellig sein müssen und außerdem unsinnige
Zahlenkombinationen als Datum erkannt werden könnten. Aber lasst uns
noch ein paar weitere Sachen kennen lernen, dann werden wir das Problem
in einer der Aufgaben angehen.
Subpattern
Die
Klammersetzung hat noch einen ganz anderen Effekt, der in vielen Regexe
zum Einsatz kommt. Zeichenketten, die von eingeklammerten Suchmustern
gefunden wurden, werden in eine temporäre Variable (Subpattern) für
spätere Zwecke zwischengespeichert. Das schaut man sich am besten in
einigen Beispielen an:
‚erika.mustermann@beispiel.de‘
Wir verwenden das folgende Regex „(\w+)\.(\w+)@.*“. Die erste Klammer
passt auf ‚erika‘, die zweite ‚mustermann‘. Und genau diese beiden
Zeichenketten landen in den Subpattern1 bzw. 2.
Oder:
„(\d+\.)(\d+\.)“ Dieses Regex wenden wir auf die Zeichenkette ‚22.02.‘
an. Im ersten Subpattern wird ’22.‘ gespeichert, im zweiten ’02.‘.
Wie stellt man eigentlich fest, was das erste Subpattern ist? In obigen
Fall ist das ja einfach, was aber, wenn das Regex so aussieht:
„Re\s*(\[(\d+)\])*:“ Das was von dem ersten runden Klammerpaar umfasst
wird, wird in Subpattern 1 gespeichert, das von dem zweiten im
Subpattern 2 und so weiter. In unserem Beispiel würde aus dem String ‚Re
[4]:‘
Subpattern 1 = ‚[4]‘
Subpattern 2 = ‚4‘
D.h.: mit jeder neuen geöffneten Klammer wird eine neue Variable
erzeugt. Will man das verhindern, also eine Gruppierung vornehmen, ohne
dass deren Inhalt in einem Subpattern landet, so kann dies durch
Einfügen von „?:“ hinter der öffnenden Klammer erreicht werden:
„(?:…)“
Was steht denn in der Subpattern-Variable, wenn eine Klammer mit einem
Quantifizierer versehen ist? Nehmen wir mal wieder unser Datums-Regex:
„(\d{2}\.){2}\d{4}“
Wird es nun auf das Datum ‚19.02.2001‘ angewendet, so wird das erste
Subpattern bei der ’19.‘ gefunden. Die Regex-Maschine versucht nun einen
weiteren Teil der Zeichenkette mit dem gleichen Muster (Subpattern) zu
finden. Ist sie erfolgreich, dann legt sie diesen Wert erneut in das
Subpattern ab. Mit anderen Worten: der zweite Fund überschreibt den
ersten. Das Ergebnis ist, dass in unserem Beispiel ’02.‘ im Subpattern
enthalten ist.
Der Regex-Tester zeigt euch die Subpattern an. Der kleine Karteireiter
unten mit der ‚0‘ zeigt den gesamten ‚Fund‘ an, der mit der ‚1‘ den
ersten Subpattern usw. Tatsächlich werden bei den Programmiersprachen
die gefundenen Elemente in ein Array abgelegt. Das Element mit dem Index
0 beinhaltet in der Tat den gesamten Match; die Subpattern sind in den
Elementen mit der korrespondierenden Index-Ziffer.
Und noch mal Quantifizierer
Kommen
wir zu einer Besonderheit der Quantifizierer. Einige von ihnen haben
eine gewisse ‚menschliche‘ Neigung: sie sind gierig! Was das bedeutet?
Schauen wir uns mal den folgenden String an:
„Die Abkürzung ‚ISP‘ heißt ‚Internet Service Provider‘.“
Wir wollen ein Regex, das beliebigen Text in Hochkommata findet und als Subpattern ablegt.
„(.*)'(.*)‘.*“
Und was steht im Subpattern 2? „Internet Service Provider“ Ooops, ich
hätte eigentlich erwartet, dass „ISP“ darin steht, denn das erscheint
schließlich auch als erstes im Text zwischen den Hochkommata. Aber hier
wird schon deutlich, dass das erste (.*) sehr gierig alles genommen hat,
was es finden konnte und nur soviel für die anderen Elemente des Regex
ließ, wie unbedingt nötig sind. Damit blieb nur der zweite in
Hochkommata befindliche Text für Subpattern 2 übrig. Dies hängt im
gewählten Beispiel aber auch am letzten „.*“, denn dieser Teil ist auch
dann erfüllt, wenn „nichts“ für diesen Teil bleibt.
Nehmen wir mal ein weiteres Beispiel:
Wir wollen aus einer beliebigen Mailadresse möglichst viele Elemente extrahieren.
Für den Namen hatten wir schon weiter oben eine Lösung, die aber nicht
perfekt ist, da sie nur Wortzeichen akzeptierte. Wir werden das jetzt
allgemeiner fassen. Wir nehmen (.*) für den ersten Teil. Der zweite Teil
wird durch einen Punkt abgetrennt mit einem weiteren Textzusatz. Dieses
kann mehrfach vorkommen bevor das @ erscheint, muss aber nicht. Dieses
Regex sollte folgende Beispiele erfassen:
‚1234abc@mail.de‘
‚1234.abc@mail.de‘
’12-34.abc.def@mail.de‘
Das Regex beginnt mit „(.*)\.?(.*)*@“ Danach folgt beliebiger Text,
möglicherweise getrennt von mehreren Punkten. Uns soll aber von diesem
Rest nur das, was nach dem letzten Punkt kommt, wichtig sein, um das
Beispiel nicht unnötig komplizierter zu gestalten. Das sollte man mit
„(.*)\.(.*)“ erhalten können.
„(.*)\.(.*)*@(.*)\.(.*)“
Was erwarten wir als Ergebnis, wenn man als Zeichenkette ’12-34.abc.def@mail.de‘ eingeben?
Subpattern 1 = ’12-34′ ?
Subpattern 2 = ‚.abc‘ oder ‚.def‘ oder ‚abc.def‘ ?
Subpattern 3 = ‚mail‘ ?
Subpattern 4 = ‚de‘ ?
Lasst uns mal den Regextester fragen.
Subpattern 1 = ’12-34.abc‘
Subpattern 2 = ‚ def ‚
Subpattern 3 = ‚mail‘
Subpattern 4 = ‚de‘
Das Subpattern 1 hat fast den gesamten Vorspann erhalten, das Subpattern
2 dagegen nur die letzten drei Zeichen vor dem @! Klar, denn das * ist
gierig und hat soviel wie möglich im ersten Subpattern „gefressen“.
Vorsicht: nicht nur das Sternchen ist gierig, Pluszeichen sind es auch!
Gebt mal ’12-34.abc.def@mail.test.de‘ ein. Auch hier ist das Suchmuster
„(.*)“ im dritten Subpattern gierig. Es frisst sofort alles nach dem
@-Zeichen bis zum letzten Punkt und speichert ‚mail.test‘ im Subpattern
und nicht, wie man vielleicht vermuten könnte, nur ‚mail‘.
Wie lässt sich das vermeiden? Wir lernen wir eine neue Bedeutung des
Fragezeichens kennen (immer mit der Ruhe: es ist erst die zweite
Bedeutung, es kommen noch mehr. Aber das sollte erkären, warum es
soviele Fragezeichen in Regexe gibt *gg*): durch Anfügen des ? an das
gierige Suchmuster wird das Regex veranlasst, weniger gierig zu sein.
Konkret für das erste Subpattern:
„(.*?)\.(.*)@(.*)\.(.*)“
Subpattern 1= ’12-34′
Subpattern 2= ‚abc.def‘
Subpattern 3= ‚mail.test‘
Subpattern 4=’de‘
Ein kleiner Exkurs um die Vorgehensweise des Regex zu verstehen: nennen
wir das, was die Regexe ausführt mal „Regex-Maschine“. Die
Regex-Maschine lässt der Gier des (.*) normalerweise freien Lauf. In dem
Augenblick, in dem es aber auf die Kombination (.*?) trifft, passiert
folgendes: es wird soviel wie möglich von der Regex-Maschine in das
Subpattern (.*?) gesteckt, als wäre das Fragezeichen gar nicht da und
nun Zeichen für Zeichen rückwärts gehend wieder frei gegeben, bis das
gesamte Regex insgesamt gerade wieder passt.
Wie sieht das praktisch aus:
„(.*?)\.(.*)*“ ist der betreffende Part, den wir auf den String
’12-34.abc.def‘ anwenden. Die Regex-Maschine frisst erst mal ’12-34.abc‘
für das erste Muster. Weil danach ein Punkt und noch Text folgt, ist
dies das Maximum, denn damit wäre das Regex (ohne ?) erfüllbar. Es
stellt aber fest, dass wir mit dem ? die Gier unterdrücken wollen. Also
gibt es nun erst das ‚c‘ frei, was aber noch nicht ausreicht, denn nach
dem gefundenen Muster folgt nun kein Punkt, sondern das ‚c‘. Dann das
‚b‘, was auch nicht reicht. Anschließend noch das ‚a‘. Nun stellt die
Maschine fest, dass dies auch zu einem Treffer führt, da sich vor dem
‚a‘ noch ein Punkt befindet und somit das Minimum für einen Treffer
erfüllt. In Realität geht die Regex-Maschine noch weiter zurück, um
festzustellen, diese Position die letzte war, die mit einem Minimum an
Zeichen einen positiven Match erlaubt. Ihr könnt euch denken, dass dies
die Regex-Maschine erheblich beansprucht und es die Performance drückt,
aber manchmal ist es halt nötig.
Wie funktioniert das beim ersten Beispiel mit den Hochkommata?
Das Regex war: „(.*)'(.*)‘.*“ und der Text: „Die Abkürzung ‚ISP‘ heißt ‚Internet Service Provider‘.“
Ändern wir es mal in „(.*?)'(.*?)‘.*“ Hier müssen beide geklammerten
Suchmuster mit Fragezeichen versehen werden, damit nicht das zweite den
Text “ ISP‘ heißt ‚Internet Service Provider“ findet! Nur das zweite
Muster mit Fragezeichen zu versehen, nützt natürlich auch nichts, da
schon das erste gefräßig ist!
4.3. Überblick über dieses Kapitel
Dies
war ein sehr schwieriger Teil. Nicht nur für das Verständnis, sondern
auch zum Schreiben. Allerdings ist es ein grundlegender Bereich der
Regexe und wird häufig benötigt.
Wir haben folgende Elemente kennen gelernt:
- Zeichen, mit denen vorangegangene Suchmusterzeichen wiederholt werden können, auch Quantifizierer genannt:
- + das Zeichen davor muss mindestens einmal vorkommen, darf aber häufiger
- ? das Zeichen davor darf höchstens einmal oder aber gar nicht vorkommen
- * das Zeichen davor darf beliebig häufig oder auch gar nicht vorkommen.
- Es gibt Quantifizierer, die Bereiche für Häufigkeiten angeben: {x,y} das Zeichen davor muss mindestens x-mal, aber höchstens y-mal vorkommen. Man kann dabei Teile des Bereiches weglassen: {x,} heißt beliebig häufig, aber mindestens x-mal. {x} heißt genau x-mal
- Runde Klammern können zur Gruppierung von Suchmustern verwendet werden, um zum Beispiel Quantifizierer auf sie anzuwenden: „(ab)+“ heißt, die Buchstabenkombination ‚ab‘ muss mindestens einmal vorkommen.
- Suchmuster in runden Klammern werden „gemerkt“ und in Variablen für spätere Verwendung gespeichert. Sie werden Subpattern genannt. Bei geschachtelten Klammern wird das gesamte Ergebnis eines vom Klammerpaar umgebenen Suchmusters in eine Variable gespeichert; die Ergebnisse der inneren Klammern sind dann Teilmengen der jeweils äußeren. Die erste öffnende Klammer erzeugt die erste Variable, die zweite erzeugt die zweite Variable usw…
- Quantifizierer ohne obere Grenze sind in bestimmten Suchmustern gierig. + und * hinter einem Punkt veranlassen das Regex, alles zu finden, was möglich ist, damit das Suchmuster gerade soeben noch passt. Das Regex (.*)(.*) wird jede Zeichenkette komplett in das erste Subpattern legen.
- Durch Anfügen eines Fragezeichens an gierige Suchmuster (.*?) wird das Regex veranlasst, nicht gierig zu sein. Zwar wird zunächst dennoch alles „gefressen“, aber dann Zeichen für Zeichen zurückgegeben, bis das Regex gerade noch passt.
Aufgaben
1:
Das Regex für die Datumsangabe ist immer noch nicht perfekt, weil ja
einstellige Tages- oder Monatszahlen und ggf. zweistellige Jahreszahlen
nicht gefunden werden. Wäre doch mal ’ne Übung wert, oder?
2: So, die Lösung für das obige Problem ist ganz interessant, aber nun
wollen wir ein Regex bauen, dass etwas besser nach dem Datum sucht. Es
soll nach Möglichkeit nur etwas gefunden werden, was nach einem Datum
aussieht. Es muss ja nicht übertrieben werden: wenn es laut Regex auch
den 29.2. in einem Nicht-Schaltjahr gibt, ist das nicht so schlimm.
Wichtig ist: TT.MM.JJJJ oder T.M.JJ sollen gefunden werden.
3: Über ein Online-Fehlermeldesystem kommen standardisierte Meldungen,
die mit einem Regex in seine Bestandteile zerlegt werden soll. Die
Meldungen haben die Form:
Absender: vorname.nachname@amt.de
Datum: TT.MM.JJJJ
Fehlernr.: xyz123
Bitte erstellt ein Regex, dass die einzelnen Felder (Vorname, Name, Amt,
Datum, Fehlernummer) als Subpattern findet. Stellt euch vor, dass die
Informationen in einem einzigen String stehen und die Zeilenumbrüche gar
nicht existieren.
4. Erstellt ein Regex, das Uhrzeiten in der Form hh:mm:ss findet. Dabei
sollen nach Möglichkeit nur gültige Kombinationen gefunden werden.
Zu 1:
„\d{1,2}\.\d{1,2}\.(\d{4}|\d{2})“
Ihr habt etwas anderes? Macht nichts, muss nicht falsch sein. Es gibt immer mehrere Wege, es richtig zu machen:
„(\d?\d\.){2}(\d{4}|\d{2})“
wäre die elegante Lösung. Was ich weniger gelungen fände, wäre „\d{2,4}“
für die Jahreszahl, da dann auch dreistellige Jahreszahlen akzeptiert
würden.
Zu 2:
Dies ist sicherlich der kompliziertere Fall. Zerlegen wir das mal in Teilprobleme. Die möglichen Tage sind:
a)01-09, wobei die führende Null fehlen könnte.
b)10-29, alle Monate haben 29 Tage. Ok, gewollter Fehler: der Februar
hat eben nur alle 4 Jahre 29 Tage, aber damit wollen wir jetzt mal
leben, sonst wird es nahezu unmöglich.
c)30, haben alle Monate außer Februar
d)31, haben nur die Monate Januar, März, Mai, Juli, August, Oktober, Dezember
Die möglichen Monate sind 01-10, wobei die Null fehlen darf und 11, 12.
Wir wollen nur beliebige zweistellige oder vierstellige Jahreszahlen
zulassen. Im letzteren Fall sollte die Jahresangabe aber schon mit 19xx
oder 20xx beginnen.
Na, dann wollen wir mal:
Fall a) und b) mit den Monaten
„(0?[1-9]|[12][0-9])\.(0?[1-9]|1[0-2])\.“
Fall c) mit den Monaten
„30\.((0?[13-9])|(1[0-2]))\.“
und dann noch Fall d) mit seinen Monaten
„31\.(0?[13578]|1[02])\.“
Zu den Jahren:
„(\d{2}|(19|20)\d{2})“
Die ersten drei Elemente sind Alternativen, die Jahreszahl ist
obligatorisch. Damit nicht aus längeren Ziffernfolgen zufällige
Datumsangaben gefunden werden, umgeben wir das Regex noch mit dem
\b-Operator. Dann muss das also ungefähr so aussehen:
„\b(((0?[1-9]|[12][0-9])\.(0?[1-9]|1[0-2])\.)|(30\.((0?[13-9])|(1[0-2]))\.)
|(31\.(0?[13578]|1[02])\.))(\d{2}|(19|20)\d{2})\b“
[Anm.: Das Regex wird aus Layout-Gründen umgebrochen. Tatsächlich muss sich alles in einer Zeile befinden!]
Wahnsinn: ganz schön lang. Ihr habt was anderes? Sogar was besseres? Das
würde ich als „normal“ bezeichnen. Das obige Regex lässt sich natürlich
bei gleichem Ergebnis anders schreiben. Und „besser“ geht fast immer
😉 Meine Lösung oben zeigt nur, wie ich das Problem angegangen habe.
Ich hoffe, dies ist nachvollziehbar gewesen.
Zu 3.
Das sieht gar nicht so wild aus. Auch hier wieder in Teilen:
Vorname und Name sowie Amt ergeben sich aus der Mailadresse. Damit sollte
„Absender:\s*(.*?)\.(.*?)@(.*?)\.\w+\s*“
ausreichen. Das ? im zweiten Subpattern ist vielleicht gar nicht nötig,
weil danach ohnehin das @-Zeichen folgen muss. Aber es schadet auch nix.
Datum: welch Glück, das Format ist fest. Man muss also nicht das mörderische Regex aus Aufgabe 2 verwenden:
„Datum:\s*((\d{1,2}\.){2}\d{4})\s*“
Und nun noch Fehlernummer:
„Fehlernr.:\s*(.*)“
Um sicher zu gehen, dass auch der ganze String abgefragt wird,
veranlassen wir mit \A und \Z, dass der ganze Text ausgewertet wird.
„\AAbsender:\s*(.*?)\.(.*?)@(.*?)\.\w+\s*Datum:\s*
((\d{1,2}\.){2}\d{4})\s*Fehlernr.:\s*(.*)\Z“
[Anm.: Das Regex wird aus Layout-Gründen umgebrochen. Tatsächlich muss sich alles in einer Zeile befinden!]
Die Subpattern 1,2,3,4 und 6 sollten die gewünschten Felder liefern.
Zu 4.
Nun haben wir ja schon gewisse Übung darin, Probleme in Teilprobleme zu
zerlegen und das Uhrzeit-Problem ist erneut so eines. Es sollte uns also
eigentlich leicht von der Hand gehen. Es macht es geringfügig
einfacher, dass das Format feststeht!
Stunden gibt es von 00-19 Uhr und von 20-23 (24 Uhr ist 00 Uhr!):
„([01][0-9]|2[0-3]):“
Minuten und Sekunden verwenden die gleichen Ziffernkombinationen, nämlich 00 bis 59:
„([0-5][0-9]:){2}“
Zusammen, umgeben von Wortbegrenzern:
„\b([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\b“