StringRegExp Tutorial / Reguläre Ausdrücke in AutoIt

  • Zitat

    Auf Wunsch von Pee poste ich mein StringRegExp Tutorial nun auch hier. Ich bitte zu beachten, dass die Stellen, welche Beispiele oder Aufgaben beinhalten, welche sich auf Browsergame Quelltexte beziehen, werden die nächsten Tagen abgeändert.

    Inhaltsverzeichnis:
    1. Vorwort
    2. Was ist ein Regulärer Ausdruck?
    3. Unser erster RegExp
    4. Komplexere RegExp
    5. Quantifizer, Fangende- und nicht Fangende Gruppierungen
    6. Konditionales RegExp
    7. Assertionen, Backreference
    Fortsetzung folgt...)


    1. Vorwort
    Hallo und herzlich Willkommen zu meinem aller ersten Tutorial. Ich möchte euch in diesem Tutorial die Regulären Ausdrücke näher bringen. Aber nicht nur das: Ich möchte euch genauso den Spaß am "RegExen" vermitteln, und hoffe natürlich alles genauestens erklären zu können. Solltet ihr etwas nicht verstehen, oder noch offene Fragen haben, dann möchte ich euch bitten, natürlich jederzeit im Thread zu posten, um mich auf evtl. Schwachstellen des Tutorials aufmerksam zu machen oder um selbstverständlich Antworten auf offene Fragen zu bekommen.
    Mein Dank gilt an dieser Stelle einerseits den Machern der Webseite https://autoit.de/www.regenechsen.de, deren Workshop mich seinerzeit die Regulären Ausdrücke ( nachfolgend RegEx oder RegExp genannt ) gelehrt hat, aber auch der Firma JGSoft, ohne deren RegExBuddy(Ein Leistungsstarkes RegEx-Tool mit Syntaxhighlighting uvm.) ich wohl oft verzweifelt wäre. Genug der vielen Worte. Ich hoffe ihr seid bereit, denn wir heben in Kürze ab ;)

    2. RegExp?!

    2.1Was ist ein Regulärer Ausruck?

    Zitat von Wikipedia

    In der Informatik ist ein regulärer Ausdruck (engl. regular expression, Abk. RegExp oder Regex) eine Zeichenkette, die der Beschreibung von Mengen beziehungsweise Untermengen von Zeichenketten mit Hilfe bestimmter syntaktischer Regeln dient.

    Das sagt zumindest Wikipedia. Ich werde mein bestes geben, dieses Kauderwelsch für euch zu entschlüsseln. Zuerst solltet ihr wissen: Ihr habt wahrscheinlich alle schonmal einen RegExp benutzt. Zum Beispiel bei der Suche nach mp3-Dateien auf euren Computern: Man öffnet die Windows-Suche und sucht nach

    *.mp3

    Gefunden werden alle Dateien, deren Name auf ".mp3" endet. Das Sternchen steht für eine beliebige Anzahl unbekannter Zeichen.
    Möchten wir alle Dateien finden, deren Namen ein "S" beinhaltet, worauf 2 Zeichen später ein "G" folgt und auf .txt endet, schreiben wir

    *s??g.txt

    Hier werden also ? und * als Platzhalter verwendet. Diese "RegExe" sind aber eher infantil; und keinesfalls so mächtig wie ein richtiger Regulärer Ausdruck - Denn in eben jenem sind die Zeichen nicht nur Platzhalter.
    Ein RegExp ist also ein Suchmuster. Zum Beispiel ist es leicht, alle Worte in einem Text zu finden, die auf E beginnen, und mit n enden. Zu meinem Lieblingsbeispiel komme ich gleich.

    2.2 Was bringt mir ein RegExp überhaupt?!
    Wir nehmen mal folgenden Quelltextausschnitt. WIr wollen den Lagerstand herausfinden.

    Spoiler anzeigen
    PHP
    <td><img src="img/x.gif" class="r1" alt="Holz" title="Holz"></td>
    <td id="l4" title="600">132411/240000</td>
    <td><img src="img/x.gif" class="r2" alt="Lehm" title="Lehm"></td>
    <td id="l3" title="600">168007/240000</td>
    <td><img src="img/x.gif" class="r3" alt="Eisen" title="Eisen"></td>
    <td id="l2" title="750">32743/240000</td>
    <td><img src="img/x.gif" class="r4" alt="Getreide" title="Getreide"></td>
    <td id="l1" title="79">78451/240000</td>

    Mit _Stringbetween müsste man jetzt nach allem zwischen "> und </td> suchen. Ich kann mir aber vorstellen, dass es da massig Ergebnisse geben wird.
    Das heißt wir müssten uns erst mit Stringbefehlen ein Stück aus dem Quellcode zurechtschneiden, hier mal trimmen, da mal etwas ersetzen, nur um dann am Ende den Lagerstand herauszufinden. Das sind wieder 10 Zeilen unnötiger Code. Per RegExp macht man einfach folgendes:

    [autoit]

    $aResult = StringRegExp($HTML,"l[1-4][^>]+>(\d+\/\d+)",3)
    _Arraydisplay($aResult)

    [/autoit]

    Ich glaube das sollte als Erklärung reichen ;)

    3. Unser erster RegExp
    3.1 Vorbereitung
    Um das Tutorial effektiv zu gestalten, möchte ich mich mit euch auf eine Darstellungsform einigen. Ich werde Die RegExe IMMER in [ Code ]-Tags hüllen, damit man Sie besser erkennt.

    Code
    Also so!

    Ich möchte euch bitten, den RegExBuddy zu installieren, damit ihr die RegExe testen, und Aufgaben im Tutorial leichter verwirklichen könnt. Die Demo Version könnt ihr hier herunterladen.

    Der RegExpbuddy ist für den RegExer, wie der Pinsel für den Maler. Wie der Taktstock für einen Dirigenten, oder der Kugelschreiber für den Rapper - Ein Werkzeug um Kunst zu erstellen! (Na gut, beim Rapper müsst ihr mir nicht zwingend zustimmen :D). Der RegExbuddy hat vieles was nützlich ist, und noch viel mehr, was man schon garnicht mehr braucht. Hauptsächlich interessant sind allerdings die gute Verlässlichkeit, sowie das Syntaxhighlighting. Mal ganz zu schweigen von der Fehlererkennung (zb bei falscher Klammersetzung). Was er alles kann werdet ihr selbst herausfinden müssen. Ich kann euch jedoch nur raten, euch das Ding anzuschaffen.

    Hier habe ich mal die wichtigsten Bedienelemente im RegExbuddy hervorgehoben:
    (sorry, man kann scheinbar keine bilder in spoiler-tags einfügen)

    [Blockierte Grafik: http://i48.tinypic.com/xbjmef.jpg]

    ACHTUNG: Schaltet die RegExp Engine (Im Bild auf "Perl") auf "PCRE". Das ist die RegExp Engine, die in AutoIt verwendet wird. (Weiß ich auch erst seit ca. 1 Stunde.)


    3.2 Das erste Suchmuster
    Unser erster RegEx wird simpel, denn bekanntlich muss jeder einmal klein anfangen.

    Code
    grund oder folge


    Ja, das ist schon ein Regulärer Ausdruck! Gesucht wird nun nach der Zeichenkette "grund oder folge". Und die RegEx-Maschine ist stur und erbarmungslos! Sie sucht genau das was man ihr aufträgt; Gefunden wird nicht nur bei

    • grund oder folge der Armut?

    sondern auch bei

    • Fahre ich zum Abgrund oder folge ich der Straße?


    3.3 Case Sensitivity in AutoIt
    Ganz wichtig (und Grund dafür, dass RegExe bei mir früher nicht immer Funktioniert haben), ist, dass AutoIt-RegExp immer Case-sensitive arbeiten. Soll heißen: Groß- und Kleinschreibung werden unterschieden. Um dieses Verhalten zu unterdrücken, gibt es verschiedene Wege. Ich werde aber, da wir gerade erst mit dem Tutorial begonnen haben, nur eine erläutern:

    Code
    (?i)


    (?i) sorgt dafür, dass ab dem Punkt, wo es im RegExp steht, Groß- und Kleinschreibung ignoriert wird. (?-i) Macht das genaue Gegenteil

    BEISPIEL:
    "SciTE ist die Standard AutoIt-IDE"
    Lassen wir nun den folgenden RegExp auf diesen String los, werden wir keinen Treffer landen:

    Code
    scite


    Machen wir das ganze aber mit unserem gerade gelernten (?i), so finden wir das Wort "SciTE":

    Code
    (?i)scite

    Ihr solltet deswegen im RegExpBuddy die Buttons "Case insensitive" zu deaktiveren, und "^$ match at linebreaks" zu aktivieren. Zu letzterem komm ich später.

    3.3 Literale Suche nach Metazeichen
    Mit einem Regex lässt sich alles suchen: Alphanumerische (a-Z, 0-9), Hexadezimale, Binäre Zeichen uvm.
    Eine kleine Ausnahme bilden allerdings die Metazeichen einer RegEx-Maschine. Diesen sind nämlich besondere Aufgaben innerhalb eines RegEx zugewiesen.

    Zitat

    * + ? . ( ) [ ] { } \ / | ^ $


    Metazeichen haben bestimmte Funktionen innerhalb eines RegExp. Als Beispiel wären da ^ und $, welche Zeilenanfang und -ende repräsentieren. Oder *,+,? die als Wiederholungszeichen dienen. (Dazu kommen wir in Kapitel 5)

    Einige Experten werden nun aufschreien oder vom Stuhl kippen. Ja, nicht alle dieser Zeichen sind tatsächlich Metazeichen. (Zum Beispiel sind die geschweiften Klammern 'eigentlich' keine Metazeichen). Aber nehmen wir mal an, es wäre so.
    Ich habe all diese Zeichen hier aufgelistet, weil man sie "escapen" muss um sie zu finden. Möchte man also nach einem Fragezeichen suchen, so schreibt man \?.
    Sucht man nach einer schließenden geschweiften Klammer, so schreibt man \}. Sucht man nach einem Backslash, so schreibt man \\.

    Eine andere Möglichkeit ist das benutzen von

    Code
    \Q...\E


    Alles was zwischen \Q und \E steht, wird auch genauso gesucht. Die "Funktionen" der Metazeichen werden also nicht aktiv.
    Allerdings ist das normale escapen i.d.R. einfacher, schneller und überischtlicher.

    Achtung: Wenn ihr mal nicht wisst, ob ihr etwas escapen sollt oder nicht, dann escaped lieber einmal mehr als zu wenig.
    Ein

    Code
    \:

    wird zu keinem Fehler führen

    3.4 Der Punkt unter der Lupe.
    Unser erstes Metazeichen, welches wir uns anschauen ist der Punkt "."
    Ein Punkt steht für ein beliebiges, unbekanntes Zeichen. Ein Punkt kann für jedes Zeichen stehen. Standartmäßig aber nicht für Zeilenumbrüche. Das kann allerdings auch mit einem speziellen RegExp-Flag (wie schon bei der Groß- Kleinschreibung) geändert werden.

    Code
    M.ier

    findet "Maier" und "Meier" , aber nicht "Meyer". Außerdem findet es "Meierling" bis zum "r".

    Code
    H..d


    findet "Hand", "Herd", "Hardrock" aber nicht "Hausdach" oder "Haende"

    Später im Tutorial zeige ich euch, wie man u.a. nach mehreren unbekannten Zeichen suchen kann, ohne deren Anzahl explizit angeben zu müssen.


    3.5 Zeichenklassen
    Langsam geht es los! In diesem Abschnitt lernt ihr etwas über Zeichenklassen. Zeichenklassen sind eine "Bündelung von Zeichen", besser erklären kann ich es in einem Beispiel.

    Code
    \d

    sucht nach einer Zahl von 0-9. \d\d sucht folglich nach 2 aufeinanderfolgenden Zahlen.

    Code
    \w

    sucht nach einem Alphanumerischen Zeichen, sprich einem Klein oder Großbuchstaben, einer Zahl oder einem Unterstrich "_"

    Damit lassen sich schon schwierigere RegExe zusammenbauen:

    Code
    \w\w, \d\d\. \w\w\w


    Dieser RegExp sucht nach 2 Alphanumerischen Zeichen gefolgt von einem Komma, einem Leerzeichen, zwei Ziffern und einem Punkt, gefolgt von einem Leerzeichen und 3 alphanumerischen Zeichen.
    Gefunden wird zB:
    "Mo, 12. Jan", "Sa, 03. Mai" und "Fr, 13. Dezember" aber nicht "Do, 4 Apr" oder "Mi 25. Jul"

    Eine sehr elegante Methode um eigene Zeichenklassen zu erstellen, sind eckige Klammern "[]".
    Mit diesen eckigen klammern wird EIN ZEICHEN gesucht, egal wie viele in den klammern stehen.

    Code
    \d[ABCDEFG]

    sucht nach einer Ziffer, gefolgt von einem Großbuchstaben(!) von A-G.
    Gefunden wird also "9B", "7D", "3F" aber nicht "6X" oder "5yA".
    Anstatt jedes mal [ABCDEFG] schreiben zu müssen, eröffnet uns RegExp die Möglichkeit einfach
    [A-G] zu schreiben. Das ist kürzer und sieht gleich noch viel cooler aus!

    Begeben wir uns mal an einen etwas schwereren RegExp:

    Code
    [0-3][0-9]\.[0-1][0-9]\.

    findet alle Datumsangaben im Format "TT.MM." Komplett falsche Daten wie zb "66.29." werden hier bereits ausgeschlossen.
    Der ein oder andere hat vielleicht schon gemerkt: Ein Datum wie zb "39.19." wird trotzdem gefunden. Verbessern wir den RegExp später, wenn wir etwas mehr Erfahrung im Bereich der logischen Operatoren gesammelt haben, okey?

    Die so erstellten Zeichenklassen lassen sich ohne Probleme umdrehen:

    Code
    [^A-G]

    sucht nach allen Zeichen, die keine Großbuchstaben von A-G sind.
    Achtung: Das Zirkumflex "^" hat eine vollkommen andere Bedeutung, wenn es außerhalb der eckigen Klammern steht. Das solltet ihr euch merken. Aber dazu später mehr.

    3.6 Überblick über Kapitel 3
    Wir haben einfache RegExe und Metazeichen kennengelernt.

    • Eine im RegExp vorkommende Zeichenkette wird auch als solche gesucht! "scite" sucht nach dem Wort "scite". Groß und Kleinschreibung wird unterschieden.
    • Die RegEx Maschine in AutoIt arbeitet Case Sensitive. Soll Groß- und Kleinschreibung ignoriert werden, so nutzt man den Flag (?i).
    • Regexe verwenden Metazeichen, nach denen man nur dann literal suchen kann, wenn man ein Backslash voranstellt: * + ? . ( ) [ ] { } \ / | ^ $
    • der Punkt "." dient dazu, nach einem beliebigen unbekannten Zeichen zu suchen. Sucht man nach dem Punkt als Zeichen, so stellt man ein Backslash voran "\."
    • Zeichenklassen können durch Angabe in eckigen Klammern selbst definiert werden "[A-Z]" Diese Angabe kann durch ein ^ als erstes Zeichen in den eckigen Klammern umgekehrt werden.

    3.7 Aufgaben
    Was suchen die folgenden RegExe:

    Code
    \d\d:\d\d
    Lösung Aufgabe 1

    Zwei Ziffern gefolgt von einem Doppelpunkt und zwei weiteren Ziffern. Dies trifft auf die Darstellung einer Uhrzeit zu. (Bsp: 14:52)

    Code
    \w\w\w, \d\d \w\w\w \d\d\d\d
    Lösung Aufgabe 2

    Diesen RegExp hatten wir so ähnlich schon bei Abschnitt 3.5. Gesucht werden 3 alphanumerische Zeichen, gefolgt von einem Komma und einem Leerzeichen, gefolgt von 2 Ziffern, einem Leerzeichen und 3 weiteren alphanumerischen Zeichen. Darauf folgen noch ein Leerzeichen und 4 Ziffern. Auch das sieht nach einem Datum aus, aber in der anglo-amerikanischen Schreibweise: Tue, 19 Feb 2002. Leider ist dieses Regex nicht optimal: es erkennt nur Daten mit zweistelligen Tageszahlen. Wir werden etwas später sehen, wie man das Regex modifiziert, um sowohl einstellige als auch zweistellige Tageszahlen zu finden.

    Code
    "Re \[\d\]"
    Lösung Aufgabe 3

    Gesucht wird das Wort "Re" gefolgt von einem Leerzeichen, einer öffnenden eckigen Klammer, dann eine Ziffer von 0-9 und abschließend eine schließende eckige Klammer.

    4. Komplexere RegExp
    Das war ja kinderleicht oder? Genau! Aber leider waren viele RegExe die ich eben gezeigt habe sehr unflexibel. Wäre das schon die volle Macht der RegExp gewesen, so hätte es sich nicht gelohnt ein Tutorial dafür zu schreiben. RegExe können noch mehr. Noch viel viel mehr! Und ich zeige euch jetzt auch WAS sie alles können!

    4.1 Wortgrenzen und die Verwendung von RegExp in AutoIt
    Erinnert ihr euch an unser erstes Beispiel?

    Fahre ich in den Abgrund oder folge ich der Straße?
    Grund oder Folge der Armut?

    Code
    grund oder folge


    Wenn wir nun mit der sog. Wortgrenzen-Zeichenklasse \b arbeiten, erhalten wir

    Code
    (?i)\bgrund oder folge\b

    Dieses "Pattern", so wie der RegExp-String in Autoit genannt wird, findet nur noch "Grund oder Folge der Armut", aber nicht mehr "Fahre ich in den Abgrund oder folge ich der Straße?"

    Da ich noch garnicht erwähnt habe, wie RegExp in AutoIt angewand wird, werde ich das nun nachholen. Das eben gezeigte Beispiel wird nun mal in AutoIt eingebaut.

    [autoit]


    #include <Array.au3>
    $sString = _
    "Grund oder Folge der Armut?" & @CRLF & _
    "Fahre ich in den Abgrund oder folge ich der Straße?"
    $aResult = StringRegExp($sString,"(?i)\bgrund oder folge\b",3)
    _ArrayDisplay($aResult,"StringRegExp Results")

    [/autoit]


    Ihr könnt ja mal testweise die Wortgrenzen \b entfernen ;)
    Die Parameter sollten verständlich sein. $sString ist der Ausgangsstring, in dem wir suchen. Danach folgt unser RegExp-Pattern. Der letzte Parameter gibt an, in welcher Form der Array $aResult zurückgegeben werden soll. In 99% der Fällen reicht Flag 3 vollkommen aus. (Ich habe glaub ich selbst noch nie ein anderes Flag benutzt.)

    Hier allerdings mal ein kleines Script, nur um die verschiedenen Rückgabeformen zu verdeutlichen:

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>
    $sString = _
    '<td><img src="img/x.gif" class="r1" alt="Holz" title="Holz"></td>' & @CRLF & _
    '<td id="l4" title="600">132411/240000</td>' & @CRLF & _
    '<td><img src="img/x.gif" class="r2" alt="Lehm" title="Lehm"></td>' & @CRLF & _
    '<td id="l3" title="600">168007/240000</td>' & @CRLF & _
    '<td><img src="img/x.gif" class="r3" alt="Eisen" title="Eisen"></td>' & @CRLF & _
    '<td id="l2" title="750">32743/240000</td>' & @CRLF & _
    '<td><img src="img/x.gif" class="r4" alt="Getreide" title="Getreide"></td>' & @CRLF & _
    '<td id="l1" title="79">78451/240000</td>'

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    $aFlag1 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 1)
    $aFlag2 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 2)
    $aFlag3 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 3)
    $aFlag4 = StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)", 4)

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, "", "Flag 0 gibt 1 (matched) oder 0 (no match) zurück.")
    MsgBox(0, "Flag 0", StringRegExp($sString, "l[1-4][^>]+>(\d+\/\d+)"))

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, "", "Flag 1 zeigt das erste Match (nur Subpatterns)")
    _ArrayDisplay($aFlag1, "Flag 1")

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, "", "Flag 2 zeigt das erste Match (Full Match + Subpatterns)")
    _ArrayDisplay($aFlag2, "Flag 2")

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, "", "Flag 3 zeigt alle Matches (nur Subpatterns)")
    _ArrayDisplay($aFlag3, "Flag 3")

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, "", "Flag 4 gibt einen Array voller Arrays zurück.")
    _ArrayDisplay($aFlag4, "Flag 4")
    MsgBox(0, "", "Die einzelnen Arrays beinhalten Full Match + Subpatterns." & @CRLF & _
    "Leider kann man Arrays in Arrays nicht direkt ansprechen" & @CRLF & _
    "(ohne sie zwischenzuspeichern), deswegen benutze ich statt Flag 4 immer Flag 3." & @CRLF & _
    "Hier mal alle Arrays in $aFlag4:")

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 0 To UBound($aFlag4) - 1
    $aDummy = $aFlag4[$i]
    _ArrayDisplay($aDummy, "$aFlag4[" & $i & "]")
    Next

    [/autoit]

    4.2 Zeilen- oder Zeichenkettengrenzen
    Manchmal will man, dass ein bestimmter Text explizit am Zeilenanfang, oder vielleicht sogar am absoluten Stringanfang gefunden wird. Dafür gibt es bestimmte Metazeichen, von denen wir eines schon im letzen Kapitel kennengelernt haben. ^ und $ stehen für Zeilenanfang und -ende.

    Code
    ^AutoIt


    Findet "AutoIt ist total cool!", aber nicht "Ich finde AutoIt toll!".

    Code
    AutoIt\.$

    findet "Mein liebstes Hobby ist AutoIt.", aber nicht "Sag mir: Was ist besser als AutoIt?"
    Das gleiche gibt es statt für Zeilenanfang und -ende auch als Metazeichen für den kompletten String. \A steht für den Stringanfang, \z steht für das Stringende. Eine Besonderheit gibt es hier auch: \Z ist eine Mischung aus \z und $. Denn es steht für Stingende ODER Zeilenende.

    Diese Metazeichen helfen uns auch, unseren RegExp zu beschleunigen! Stellen wir uns die Zeichenkette "AutoIt ist sehr praktisch" vor. Wir wenden nun diesen RegExp darauf an:

    Code
    ^RegExp


    Die RegExp Maschine überprüft ob das erste Zeichen der Zeilenanfang ist -> Wahr
    Nun wird geprüft ob das zweite Zeichen ein R ist -> Falsch.
    Die RegExp Maschine bricht ab. Hätten wir ^ nicht benutzt, so würde die RegEx Maschine nun den kompletten String durchgehen, und nach "RegExp" suchen, nur um festzustellen, dass der String nicht vorhanden ist.

    4.3 Whitespaces
    Nachdem wir eben Wort-, Zeilen,- und Stringgrenzen kennengelernt haben, kommen wir zu einer anderen sehr häufig genutzten Zeichenklasse. Den Whitespaces. Mit \s wird ein Whitespace gesucht - Also ein Zeichen, welches eine weiße Fläche hinterlässt. Dazu gehören @CRLF, @CR, @LF, @TAB, und das Leerzeichen.

    Beispiel
    [autoit]

    #include <Array.au3>
    $sString = "Grund"&@TAB&"oder"&@CR&"Folge"&@CRLF&"der Armut?" & @CRLF & _
    "Fahre ich in den Abgrund oder folge ich der Straße?"
    $aResult = StringRegExp($sString,"(?i)grund\soder\sfolge",3)
    ;~ _ArrayDisplay($aResult,"StringRegExp Results") ; Da _ArrayDisplay keine Tabs & co darstellen kann,
    ; Zeige ich euch die Ergebnisse in einer MsgBox.
    MsgBox(0,"",$aResult[0])
    MsgBox(0,"",$aResult[1])

    [/autoit]

    4.4 Logischer Operator: OR
    Also logische Operatoren bezeichnet man in der Informatik u.a AND und OR.
    OR, also einen Oder-Operator gibt es in RegExp auch: Mehrer oder-Blöcke werden durch eine sog. Pipe "|" voneinander getrennt. Das geht sowohl außerhalb einer Klammer als auch darin. Zu den Klammern komme ich im Nächsten Kapitel, wenn wir mit Subpatterns arbeiten. Ich hoffe aber dass das Beispiel auch ohne vorherige Erklärung ersichtlich ist.

    Beispiel

    Ignoriert das "?:" bitte vorerst.

    [autoit]

    #include <Array.au3>
    $sString = "Mein Name ist Peter"& @CRLF
    $sString &= "Mein Name ist Karl"& @CRLF
    $sString &= "Mein Name ist Christian"& @CRLF
    $sString &= "Mein Name ist Max"& @CRLF
    $sString &= "Mein Name ist Paul"& @CRLF
    $sString &= "Mein Name ist Manfred"& @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $aResult = StringRegExp($sString,"Mein Name ist (?:Karl|Max|Paul)",3)
    _ArrayDisplay($aResult)

    [/autoit]



    4.5 Übersicht über Kapitel 4
    Wir haben komplexer Patterns, erweiterte Metazeichen und den OR-kennengelernt.

    • Mit \b suchen wir noch Wortgrenzen. ^ steht für den Zeilenanfang, $ für das Zeilenende. \A und \z stehen für Stringanfang und -ende
    • \s findet Whitespace Zeichen. Also alle Zeichen die leere Stellen erzeugen. Tabs, Zeilenumbrüche, Leerzeichen usw.
    • RegExp werden in AutoIt mit StringRegExp() aufgerufen. Die Funktion gibt einen Array zurück.
    • RegExp verfügen in bedingtem Maße über logische Operatoren Operatoren. OR kann mit einer Pipe "|" verwendet werden.

    4.6 Aufgaben.
    Schreibe ein Pattern, welches alle korrekten Uhrzeiten im Teststring findet.

    Teststring für Regexbuddy

    15:44
    12:37
    99:73
    64:33
    00:00
    24:00
    17:12
    (die roten sind falsch!)


    Benutze dieses Script als vorlage:

    Vorlagescript Aufgabe 1
    [autoit]

    #include <Array.au3>
    $sString = _
    "15:44" & @CRLF & _
    "12:37" & @CRLF & _
    "99:73" & @CRLF & _
    "64:33" & @CRLF & _
    "00:00" & @CRLF & _
    "24:00" & @CRLF & _
    "17:12" & @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $aArray = StringRegExp($sString,"",3)
    _ArrayDisplay($aArray)

    [/autoit]
    Lösung Aufgabe 1
    Code
    [0-1][0-9]:[0-5][0-9]|2[0-3]:[0-5][0-9]

    Schreibe einen Pattern, der nach einer Seriennummer sucht. 4 Blocks a 4 Zeichen von A-Z und Zahlen von 0-6, mit Bindestrichen getrennt.

    Teststring Aufgabe 2

    KKJD-4WR6-1562-Z31A
    22JE-2XAB-RRY4-U24U
    22JE-2XAB-R9Y4-U24U
    5PLM-4GHS-2TZ6-1132
    99A5-7777-ABCD-9786

    (die Roten sind falsche Seriennummern)

    Lösung Aufgabe 2
    Code
    [A-Z0-6][A-Z0-6][A-Z0-6][A-Z0-6]-[A-Z0-6][A-Z0-6][A-Z0-6][A-Z0-6]-[A-Z0-6][A-Z0-6][A-Z0-6][A-Z0-6]-[A-Z0-6][A-Z0-6][A-Z0-6][A-Z0-6]


    Im nächsten Kapitel kommen wir zu Quantifizern; Dann seht ihr auch wie man diesen RegExp effizient (und lesbar) gestalten kann.

    Was bedeuten die folgenden Suchmuster?

    Code
    ^
    Code
    ^x$
    Code
    ^$
    Spoiler anzeigen

    Das erste Muster sucht nach allen Texten, die einen Zeilenanfang haben oder am Zeilenanfang beginnen. Es werden also alle Strings, sogar der leere String gefunden!

    Das zweite Muster sucht nach Zeilen, in denen nur der Buchstabe x steht, direkt nach Zeilenbeginn und am Ende der Zeile.

    Und das letzte Muster sucht einfach nach allen Texten, die Zeilenanfang und -ende haben, aber nichts dazwischen. Es werden leere Zeilen gesucht.

    5. Quantifizer, Fangende- und nicht Fangende Gruppierungen
    In diesem Kapitel fängt der wirklich harte Stoff an. Am besten ihr lest es langsam und sorgfältig durch, und testet so viele der Beispiele im RegexBuddy.
    Sobald wir mit dem Kapitel fertig sind, macht ihr es am besten noch einmal.

    5.1 Quantifizer
    Das ist der Part, an dem RegExe interessant werden, da man nun endlich "richtige" Patterns bauen kann. Ein Quantifizer ist ein Wiederholungszeichen. Es gibt an, wie oft etwas wiederholt werden soll, damit der RegExp erfolgreich ist. Dabei ist es egal, ob der Quantifizer auf Zeichenklassen, einzeilne Buchstaben, Subpatterns (dazu kommen wir im nächsten Abschnitt) oder sonstwas angewandt wird.
    Es gibt 3 wichtige Quantifizer: ? + *

    Das Malzeichen "*" gibt an, dass etwas beliebig oft, aber auch keinmal gefunden werden soll. Dieser Quantifizer ist gierig.
    Moment..., Habe ich da gerade "gierig" gesagt? Ja! Man unterscheidet nämlich zwischen gierig (greedy) und faul (lazy) bei Quantifizern. Aber dazu gleich mehr.

    Beispiel zu *-Quantifizer
    Code
    Ham*er

    Findet "Hammer", "Hammmmmmmmmmer", "Haer"


    Das Pluszeichen "+" gibt an, dass etwas beliebig oft, mindestens aber ein mal(!) vorkommen soll. Dieser Quantifizer ist gierig.

    Beispiel zu +-Quantifizer
    Code
    Ham+er

    Findet "Hammer", "Hammmmmmmmmmer", "Hamer" aber nicht mehr "Haer"


    Das Fragezeichen "?" gibt an, dass etwas einmal, oder keinmal gefunden werden soll. Dieser Quantifizer ist gierig.

    Beispiel zu ?-Quantifizer
    Code
    Ham?er

    Findet "Hamer", Haer" aber nicht mehr "Hammer", und schon garnicht "Hammmmmer"

    Wie schon bei den Zeichenklassen, sind wir aber nicht darauf beschränkt was RegExp uns von Haus aus bietet. WIr können eigene Quantifizer erstellen.
    Diese sehen zB. so aus: {1,2}
    Rate doch mal was dieser Quantifizer macht!

    Lösung

    Genau! Er gibt an, dass die Zeichenklasse, das Subpattern oder sonst was Mindestens 1 mal, höchstens aber 2 mal gefunden werden soll.


    Man kann die zweite Zahl auch weglassen. (Das Komma muss aber bleiben) Das bedeutet dann so viel wie "ohne Grenze nach oben". {3,} sucht also mindestens 3 vorkommen, findet es mehr ist das auch i.O. Die oben genannten Quantifizer + * ? sind nur Kurzformen für diese Art der Quantifizierung.
    + = {1,}
    * = {0,}
    ? = {0,1}

    Wenn wir das Komma "," auch noch weglassen, dann wird nach einer exakten übereinstimmung gesucht.

    Code
    \w{4}


    Sucht nach allen 4-Stelligen Strings.

    Mit diesem Wissen können wir schon mal versuchen unser Seriennummern Pattern aus Kapitel 4 aufzubessern. Das sah ja wirklich elendig lang aus.

    Seriennummern

    KKJD-4WR6-1562-Z31A
    22JE-2XAB-RRY4-U24U
    22JE-2XAB-R9Y4-U24U
    5PLM-4GHS-2TZ6-1132
    99A5-7777-ABCD-9786

    (die Roten sind falsche Seriennummern)

    Code
    [A-Z0-6][A-Z0-6][A-Z0-6][A-Z0-6]-[A-Z0-6][A-Z0-6][A-Z0-6][A-Z0-6]-[A-Z0-6][A-Z0-6][A-Z0-6][A-Z0-6]-[A-Z0-6][A-Z0-6][A-Z0-6][A-Z0-6]

    Wie müsste man das Pattern umschreiben, damit es zwar immer noch alle Seriennummern findet, aber kürzer und besser aussieht?

    Lösung Zwischenaufgabe Quantifizer

    Wir haben also 4 Zeichen in jeder Gruppe. Das heißt wir können diesen Rattenschwanz hier:

    Code
    [A-Z0-6][A-Z0-6][A-Z0-6][A-Z0-6]

    schonmal durch

    Code
    [A-Z0-6]{4}

    ersetzen. Wir suchen nach 4 "Blöcken" a 4 Zeichen gefolgt von einem Bindestrich.

    Code
    ([A-Z0-6]{4}-){4}

    Aber... Moment?! Warum wird denn jetzt nichts gefunden?
    Habt ihr den Fehler entdeckt? Richtig! Hinter dem letzten Block folgt ja garkein Bindestrich.
    Das heißt wir suchen nur nach 3 Blöcken mit bindestrich, gefolgt von einem ohne Bindestrich.

    Code
    ([A-Z0-6]{4}-){3}[A-Z0-6]{4}


    Das sieht doch schon wesentlich besser aus als unser Pattern von eben, oder?
    Alternativ geht auch dieses Pattern:

    Code
    ([A-Z0-6]{4}-?){4}


    Allerdings werden hier auch Blöcke ohne Bindestiche gefunden. Also wie verbessern wir das?

    Code
    ([A-Z0-6]{4}(-|$)){4}


    Sucht nun nach 4 Blocks a 4 Zeichen von A-Z bzw 0-6, gefolgt von entweder einem Bindestrich, oder dem Zeilenende.


    5.1 Gruppierung
    Ihr habt es jetzt schon in ein paar Patterns gesehen und von der Mathematik sollte man es auch bereits kennen: Mit Klammern kann man Gruppieren. Das dient nicht nur dem Zweck, dass der Term, oder hier beim Regexen das Pattern hübsch aussieht, sondern man kann explizit angeben, welcher Teil eines Terms (Patterns) zueinander gehört. Das ist beim RegExen praktisch, da man auf die Abschnitte in Klammern ua. Quantifizer benutzen kann. Man kann den OR-Operator sinnvoll nutzen uvm.

    Beispiel Quantifizer auf Klammern
    Code
    AutoIt(Bot)?\.de

    findet AutoIt.de und AutoItBot.de

    Code
    AutoIt(Bot|script)?\.(de|com)

    Findet AutoIt.de, AutoIt.com, AutoItBot.de, AutoItScript.de, AutoItBot.com usw.

    Man unterscheided zwischen Fangender Gruppierung und Nicht fangender Gruppierung.

    5.1.1 Fangende Gruppierungen aka. Subpatterns.
    Grundlegend, wird alles was wir einklammern in sog. Subpatterns gespeichert. Subpattern sind temporäre Variablen, die wir für spätere Zwecke noch verwenden können.
    Hier mal ein Beispiel:

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>
    $sString = "SEuBo Email: SEuBo@web.de" & @CRLF
    $sString &= "SEuBo Email: Peter@live.de" & @CRLF
    $sString &= "Acanis Email: Acanis@gmx.net" & @CRLF
    $sString &= "Acanis Email: Coolertyp@googlemail.com" & @CRLF
    $aRegExp = StringRegExp($sString,"(\w+) Email: (\1@\w+\.\w+)",3)
    _ArrayDisplay($aRegExp)

    [/autoit]

    Wie ihr seht, wird das erste Wort (auf welches " Email: " folgt) in einer temporären Variable gespeichert ( weil eingeklammert )
    Diese Variable wird mit \1 abgerufen. Die 1 steht dabei für die Nummer des Subpatterns.


    Subpatterns haben außerdem den Vorteil, dass jedes SubPattern in einem eigenen Array Element gespeichert wird. Das hilft uns unter anderem besonders wenn es um Arbeit mit Quelltexten geht.
    Hier mal ein Beispiel mit einem etwas fortgeschritteneren RegExp.

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>
    Global $sQuellCode
    QuellCode()

    [/autoit] [autoit][/autoit] [autoit]

    $aRes = StringRegExp($sQuellCode,'href="([^"]+).+?>([^<]+)[\W\w]+?cox">\((\d+)[\W\w]+?coy">(\d+)',3)
    For $i = 0 to UBound($aRes)-1 Step 4
    MsgBox(0,"","Dorfname: "&@TAB&$aRes[$i+1] & @CRLF & _
    "Link: "&@TAB&@TAB&$aRes[$i] & @CRLF & _
    "X: "&@TAB&@TAB&$aRes[$i+2] & @CRLF & _
    "Y: "&@TAB&@TAB&$aRes[$i+3] & @CRLF)
    Next

    [/autoit] [autoit][/autoit] [autoit]

    Func QuellCode()
    $sQuellCode = ''
    $sQuellCode &= '<tr><td class="dot hl">?</td><td class="link"><a href="?newdid=82867">01 ? Panic Room[VO]</a></td><td class="aligned_coords">' & @CRLF
    $sQuellCode &= '<div class="cox">(17</div>' & @CRLF
    $sQuellCode &= '<div class="pi">|</div>' & @CRLF
    $sQuellCode &= '<div class="coy">113)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=89034" accesskey="n">02 ? Das 9er[RK]</a></td><td class="aligned_coords">' & @CRLF
    $sQuellCode &= '<div class="cox">(244</div>' & @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $sQuellCode &= '<div class="pi">|</div>' & @CRLF
    $sQuellCode &= '<div class="coy">64)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=92071">03 ? Hilf dem HD[DD]</a></td><td class="aligned_coords">' & @CRLF
    $sQuellCode &= '<div class="cox">(244</div>' & @CRLF
    $sQuellCode &= '<div class="pi">|</div>' & @CRLF
    $sQuellCode &= '<div class="coy">63)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=93854">04 ? Nummer 2[DD]</a></td><td class="aligned_coords">' & @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $sQuellCode &= '<div class="cox">(242</div>' & @CRLF
    $sQuellCode &= '<div class="pi">|</div>' & @CRLF
    $sQuellCode &= '<div class="coy">63)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=96194">05 ? Laberbacke[DD]</a></td><td class="aligned_coords">' & @CRLF
    $sQuellCode &= '<div class="cox">(240</div>' & @CRLF
    $sQuellCode &= '<div class="pi">|</div>' & @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $sQuellCode &= '<div class="coy">63)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=100593">06 ? Noch n Dorf[DD]</a></td><td class="aligned_coords">' & @CRLF
    $sQuellCode &= '<div class="cox">(241</div>' & @CRLF
    $sQuellCode &= '<div class="pi">|</div>' & @CRLF
    $sQuellCode &= '<div class="coy">63)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=105638">07 ? 0-8-15[DD]</a></td><td class="aligned_coords">' & @CRLF
    $sQuellCode &= '<div class="cox">(241</div>' & @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $sQuellCode &= '<div class="pi">|</div>' & @CRLF
    $sQuellCode &= '<div class="coy">64)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=107915">08 ? Notgeil</a></td><td class="aligned_coords">' & @CRLF
    $sQuellCode &= '<div class="cox">(244</div>' & @CRLF
    $sQuellCode &= '<div class="pi">|</div>' & @CRLF
    $sQuellCode &= '<div class="coy">66)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=74485">09 ** FrisBy</a></td><td class="aligned_coords">' & @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $sQuellCode &= '<div class="cox">(13</div>' & @CRLF
    $sQuellCode &= '<div class="pi">|</div>' & @CRLF
    $sQuellCode &= '<div class="coy">108)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=111417">10 ? Langeweile</a></td><td class="aligned_coords">' & @CRLF
    $sQuellCode &= '<div class="cox">(238</div>' & @CRLF
    $sQuellCode &= '<div class="pi">|</div>' & @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $sQuellCode &= '<div class="coy">69)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=114355">11 ? Monkey Island</a></td><td class="aligned_coords">' & @CRLF
    $sQuellCode &= '<div class="cox">(240</div>' & @CRLF
    $sQuellCode &= '<div class="pi">|</div>' & @CRLF
    $sQuellCode &= '<div class="coy">65)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=114359">12 ? Süchtig</a></td><td class="aligned_coords">' & @CRLF
    $sQuellCode &= '<div class="cox">(237</div>' & @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $sQuellCode &= '<div class="pi">|</div>' & @CRLF
    $sQuellCode &= '<div class="coy">68)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=114372">13 ? Schon wieder</a></td><td class="aligned_coords">' & @CRLF
    $sQuellCode &= '<div class="cox">(237</div>' & @CRLF
    $sQuellCode &= '<div class="pi">|</div>' & @CRLF
    $sQuellCode &= '<div class="coy">70)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=114548">14 ? Blood Island</a></td><td class="aligned_coords">' & @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $sQuellCode &= '<div class="cox">(243</div>' & @CRLF
    $sQuellCode &= '<div class="pi">|</div>' & @CRLF
    $sQuellCode &= '<div class="coy">66)</div></td></tr><tr><td class="dot">?</td><td class="link"><a href="?newdid=114547">15 ? Enteninsel</a></td><td class="aligned_coords">' & @CRLF
    $sQuellCode &= '<div class="cox">(243</div>' & @CRLF
    $sQuellCode &= '<div class="pi">|</div>' & @CRLF
    EndFunc ;==>QuellCode

    [/autoit]

    5.1.2 Gierige und faule Quantifizer
    Jetzt komme ich nochmal auf das Verhalten der Quantifizer zurück. Ich habe ja eben schon angemerkt, dass die Quantifizer "gierig" sind.
    Ich nehme jetzt mal das Beispiel aus dem Workshop von Regenechsen.de

    Wir wollen ein Regex, das beliebigen Text in Hochkommata findet und als Subpattern ablegt. Wir benutzen den *-Quantifizer, der ja bekanntlich gierig ist.

    [autoit]

    #include <Array.au3>
    $aRegExp = StringRegExp("Die Abkürzung 'ISP' heißt 'Internet Service Provider'.",".*'(.*)'.*",3)
    _ArrayDisplay($aRegExp)

    [/autoit]


    Und was wurde in unserem Subpattern gespeichert?
    'Internet Service Provider'. Dabei steht doch 'ISP' zuerst im Text. Das liegt daran, dass das * so gierig ist, dass es dem hinteren Teil alles wegfrisst. Es lässt nur so viel für die anderen Elemente des RegExp übrig, wie unbedingt nötig.

    Hier ein anderes Beispiel:

    [autoit]

    #include <Array.au3>
    $aRegExp = StringRegExp("12-34.abc.def@mail.de","(.*)\.(.*)*@(.*)\.(.*)",3)
    _ArrayDisplay($aRegExp)

    [/autoit]


    Das erste Subpattern ist wieder sehr gierig, und nimmt sich 12-34.abc weg. Danach kommt der Punkt "\.".
    Zuguterletzt folgt vor dem @ Zeichen noch ein SubPattern, welches das def aufnimmt.

    Vorsicht: Da das 2te Subpattern noch einmal Quantifiziert wird, wird das "def" mit dem neuen Fund überschrieben. Da dieses mal aber Nichts "" übrig bleibt (Denn * ist auch mit Nichts zufrieden), wird das 2te Subpattern durch Nichts "" überschrieben.

    Wir wollen also dem RegEx irgendwie klarmachen, dass das "*" nicht so gierig sein soll, sondern eher faul. Und das geht ganz leicht mit einem Fragezeichen "?" nach dem Quantifizer.

    +? = Mindestens ein Treffer, ohne Grenze nach oben. Dieser Quantifizer ist nun faul, und wird versuchen so viel wie möglich für die anderen Elemente übrig zu lassen
    *? = Mindestens null Treffer, ohne Grenze nach oben. Auch dieser Quantifizer ist nun faul.
    ?? = Ein Treffer oder überhaupt keiner. Wenn es möglich ist, findet dieser Quantifizer nichts. Wenn das nicht klappt "muss" er versuchen etwas zu finden. Ein ?? wird sehr selten bis nie benutzt. Ich wollte es der vollständigkeit halber aber mal auflisten.

    Nachdem wir unsere faulen Quantifizer kennengelernt haben, nehmen wir uns das Beispiel von gerade nochmal zur Hand. Diesmal mit einem lazy "*" im ersten Subpattern.

    [autoit]

    #include <Array.au3>
    $aRegExp = StringRegExp("12-34.abc.def@mail.de","(.*?)\.(.*)*@(.*)\.(.*)",3)
    _ArrayDisplay($aRegExp)

    [/autoit]


    Das erste Subpattern frisst jetzt nur 12-34. Super oder? :thumbup:



    5.1.3 Nicht-aufzeichnende Gruppierungen.
    Eben haben wir ja schon Subpatterns kennengelernt. Manchmal möchte man allerdings RegEx-Elemente gruppieren, ohne dass sie in einem SubPattern gespeichert werden. Dazu verwendet man "?:" am Anfang einer Klammer. Ich nehme noch einmal das Beispiel zur Hand, welches wir bei dem OR-Operator hatten.

    Beispiel zu &quot;?:&quot;


    Wir haben unser Pattern, bei dem wir nur Sätze finden wollen, in an deren Ende Karl, Max oder Paul steht.

    Code
    Mein Name ist (Karl|Max|Paul)


    Testen wir dieses Pattern, wird uns immer nur der Name angezeigt, (Da er in ein SubPattern geschrieben wird)

    [autoit]

    $sString = "Mein Name ist Peter"& @CRLF
    $sString &= "Mein Name ist Karl"& @CRLF
    $sString &= "Mein Name ist Christian"& @CRLF
    $sString &= "Mein Name ist Max"& @CRLF
    $sString &= "Mein Name ist Paul"& @CRLF
    $sString &= "Mein Name ist Manfred"& @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $aResult = StringRegExp($sString,"Mein Name ist (Karl|Max|Paul)",3)
    _ArrayDisplay($aResult)

    [/autoit]


    Fügen wir jedoch das "?:"-Flag am Anfang der Klammer ein,

    Code
    Mein Name ist (?:Karl|Max|Paul)


    so wird kein Subpattern mehr erstellt. Wir erhalten den vollen Satz als Ergebnis:

    [autoit]

    $sString = "Mein Name ist Peter"& @CRLF
    $sString &= "Mein Name ist Karl"& @CRLF
    $sString &= "Mein Name ist Christian"& @CRLF
    $sString &= "Mein Name ist Max"& @CRLF
    $sString &= "Mein Name ist Paul"& @CRLF
    $sString &= "Mein Name ist Manfred"& @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $aResult = StringRegExp($sString,"Mein Name ist (?:Karl|Max|Paul)",3)
    _ArrayDisplay($aResult)

    [/autoit]

    5.2 Übersicht über Kapitel 5
    Wir haben Quantifizer und Subpatterns kennengelernt.

    • Es gibt 3 Standard-Quantifizer "+", "*", "?". Diese sind gierig. Gieriege (greedy) Quantifizer können mit dem ?-Flag lazy, also faul gemacht werden. "*?", "+?", "??".
    • Alles was eingeklammert wird, wird in SubPatterns geschrieben. diese kann man mit \1,\2...,\n abrufen.
    • Möchte man RegExp-Elemente gruppieren, aber nicht in ein neues Subpattern schreiben, so verwendet man "?:" hinter der öffnenden Klammer.

    5.3 Aufgaben
    Schreibe ein Pattern, welches alle Funktionen einer .au3-Datei auflistet, und die Parameter für den Funktionsaufruf in ein Subpattern speichert.
    Nutze dieses Script als vorlage:

    Vorlagescript
    [autoit]

    #include <Array.au3>
    $sRead = FileRead(StringTrimRight(@AutoItExe,11)&"Include\Array.au3")
    ;~ ClipPut($sRead)
    $aRegExp = StringRegExp($sRead,"",3)
    _ArrayDisplay($aRegExp)

    [/autoit]
    So sollte der Array aussehen:

    [0] _ArrayAdd(ByRef $avArray, $vValue)
    [1] ByRef $avArray, $vValue
    [2] _ArrayBinarySearch(Const ByRef $avArray, $vValue, $iStart = 0, $iEnd = 0)
    [3] Const ByRef $avArray, $vValue, $iStart = 0, $iEnd = 0
    [4] _ArrayCombinations(ByRef $avArray, $iSet, $sDelim = "")
    [5] ByRef $avArray, $iSet, $sDelim = ""
    [6] _ArrayConcatenate(ByRef $avArrayTarget, Const ByRef $avArraySource, $iStart = 0)
    [7] ByRef $avArrayTarget, Const ByRef $avArraySource, $iStart = 0
    usw...

    Lösung
    Code
    Func\s+(\w+\((.*)\))


    Achtung: Man kann es immer unterschiedlich lösen. Ihr habt ein anderes Pattern heraus? Nicht schlimm! Es ist sogar noch kürzer? Perfekt!

    Filtere aus dem gegebenen Quelltext die Angaben über Rang, Geld und Einwohner und lege sie in SubPatterns ab.
    Benutze den gegebenen RegExp-Tester. (Ich hatte mit Regexbuddy selbst Probleme, da die Regexbuddy eine mächtigere Regexp-Engine hat als AutoIt.)

    Vorlagescript


    Der wichtige Teil im Quelltext:

    PHP
    title='Medal Gold ' /> Rang: 250<br /><br /> <img src='http://stargods.de/images/icons/money.png' height='16' width='16' alt='Money' title='Money' /> <span class='bold' id='cash'>5.500.000</span> Gold<br /> <img src='http://stargods.de/images/icons/money_add.png' height='16' width='16' alt='Money Add' title='Money Add' /> 4.710.190 Gold / Std<br /><br /> <img src='http://stargods.de/images/icons/group.png' height='16' width='16' alt='Group' title='Group' /> <span class='bold' id='volk'>12.821.614</span> Volk<br />
    [autoit]

    #include <Array.au3>
    #include <GuiRichEdit.au3>
    #include <WindowsConstants.au3>
    Global $bYellow ; If False, use Blue as next Color
    Global $sIniPath = @AppDataDir & "\RegExpIt\Settings.ini"

    [/autoit] [autoit][/autoit] [autoit]

    $sString = "<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.1//EN' 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'> <html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' > <head> <title>Kostenloses Browsergame | - </title> <meta name='keywords' content='browsergame, browsergames, stargods, online game, online games' /> <meta name='description' content='' /> <meta http-equiv='Content-Script-Type' content='text/javascript' /> <link rel='stylesheet' href='http://stargods.de/templates/style.css' type='text/css' /> <script type='text/javascript' src='http://stargods.de/templates/scripts/global.js'></script> <!--[if lt IE 7]><script defer='defer' type='text/javascript' src='http://stargods.de/templates/scripts/pngfix.js'></script><![endif]--> </head> <body onload='updateStats(4710190.12363,6650,5500000,12821613.8194)'> <script type='text/javascript' src='http://cf.xtremegn.de/scripts/wz_tooltip.js'></script> <div id='page'> <div id='top'> <div class='float' style='wid"
    $sString &= "th:740px'> <iframe id='Footer728' name='Footer728' src='http://stargods.de/advertising/footer728.html' framespacing='0' frameborder='no' scrolling='no' width='728' height='90' allowtransparency='true' border='0' target='_blank' alt='' /></a></iframe> </div> <div class='float'> WEITERE BROWSERGAMES VON XGN <div id='gameselect'> <a href='http://kingofelements.de' target='_blank' title = 'King of Elements'><img src='http://stargods.de/images/spacer.gif' height='50%' width='33%' alt='King of Elements' title='King of Elements' /></a><a href='http://darkstory.de' target='_blank' title = 'Darkstory'><img src='http://stargods.de/images/spacer.gif' height='50%' width='33%' alt='Darkstory' title='Darkstory' /></a><a href='http://crazyfarm.de' target='_blank' title = 'Crazyfarm'><img src='http://stargods.de/images/spacer.gif' height='50%' width='33%' alt='Crazyfarm' title='Crazyfarm' /></a> <a href='http://inselcompany.de' target='_blank' title = 'Inselcompany'><img src='http://stargods.de/images"
    $sString &= "/spacer.gif' height='50%' width='33%' alt='Inselcompany' title='Inselcompany' /></a><a href='http://oceansgarden.de' target='_blank' title = 'Oceansgarden'><img src='http://stargods.de/images/spacer.gif' height='50%' width='33%' alt='Oceansgarden' title='Oceansgarden' /></a><a href='http://dinoplanet.de' target='_blank' title = 'Dinoplanet'><img src='http://stargods.de/images/spacer.gif' height='50%' width='33%' alt='Dinoplanet' title='Dinoplanet' /></a> </div> </div><div class='clearer'></div> </div> <div style='position:absolute;width:1000px;background:#333;padding:0;filter:Alpha(opacity=50);opacity:0.75;moz-opacity:0.75'><marquee>+++ 18:11 Alfred_No_1 greift panther an +++ 18:10 Alfred_No_1 greift Wasabi an +++ 18:04 Heracles greift patrick228 an +++ 17:58 Heracles greift bartolomero an +++ 17:54 Jobi greift panther an +++ </marquee></div> <div id='left_city'> <a href='http://s1.stargods.de?nextCity=1' title = '?nextCity=1'><img src='http://stargods.de/images/spacer.gif' height='100"
    $sString &= "' width='100%' alt='?nextCity=1' title='?nextCity=1' /></a> Grambranx </div> <div id='header'> <a href='http://s1.stargods.de/' title = 'Http://s1.stargods.de/'><img src='http://stargods.de/images/spacer.gif' height='226' width='100%' alt='Http://s1.stargods.de/' title='Http://s1.stargods.de/' /></a> <div style='height:15px;margin-left:82px;width:468px'> <iframe id='Linkblock' name='Linkblock' src='http://stargods.de/advertising/linkblock.html' framespacing='0' frameborder='no' scrolling='no' width='468' height='15' allowtransparency='true' border='0' target='_blank' alt='' /></a></iframe> </div> </div> <div id='left'> <div id='left_logo_4'> Daishi </div> <div id='left_ubersicht'> <img src='http://stargods.de/images/icons/star.png' height='16' width='16' alt='Star' title='Star' /> <b>548.168</b> Punkte<br /> <img src='http://stargods.de/images/icons/medal_gold_1.png' height='16' width='16' alt='Medal Gold ' title='Medal Gold ' /> Rang: 250<br /><br /> <img src='http://stargods.de/image"
    $sString &= "s/icons/money.png' height='16' width='16' alt='Money' title='Money' /> <span class='bold' id='cash'>5.500.000</span> Gold<br /> <img src='http://stargods.de/images/icons/money_add.png' height='16' width='16' alt='Money Add' title='Money Add' /> 4.710.190 Gold / Std<br /><br /> <img src='http://stargods.de/images/icons/group.png' height='16' width='16' alt='Group' title='Group' /> <span class='bold' id='volk'>12.821.614</span> Volk<br /> <img src='http://stargods.de/images/icons/group_add.png' height='16' width='16' alt='Group Add' title='Group Add' /> 6.650 Zulauf / Std<br /><br /> Zufriedenheit:<br /> <div class='progressbar'><div style='width:100%'>100%</div></div><br /> Politische Zufriedenheit:<br /> <div class='progressbar'><div style='width:100%'>100%</div></div><br /> Milit&auml;rische Zufriedenheit:<br /> <div class='progressbar'><div style='width:86%'>86%</div></div> </div> <div id='left_support'></div> <a href='http://board.xtremegn.de' title = 'Http://board.xtremegn.de'><img"
    $sString &= " src='http://stargods.de/images/spacer.gif' id='left_forum' alt='Http://board.xtremegn.de' title='Http://board.xtremegn.de' /></a> <a href='http://s1.stargods.de/contact' title = 'Kontakt'><img src='http://stargods.de/images/spacer.gif' id='left_kontakt' alt='Kontakt' title='Kontakt' /></a> </div> <div id='content'> <ul id='topmenu'> <li><a href='http://s1.stargods.de/einheiten'>Einheiten</a></li><li><a href='http://s1.stargods.de/gebaude'>Geb&auml;ude</a></li><li><a href='http://s1.stargods.de/forschung'>Forschung</a></li><li><a href='http://s1.stargods.de/status'>Status&nbsp;</a></li><li><a href='http://s1.stargods.de/staat'>Staat</a></li><li><a href='http://s1.stargods.de/krieg'>Krieg</a></li><li><a href='http://s1.stargods.de/groups'>Allianzen</a></li><li><a href='http://s1.stargods.de/rangliste'>Rangliste</a></li><li><a href='http://s1.stargods.de/welt'>Welt</a></li> </ul> <div style='padding:16px 10px 10px'> <!-- <div class='title'><h2>Vorsicht!</h2></div><div class='box'><div cl"
    $sString &= "ass='float center' style='width:100px'><img src='http://stargods.de/images/symbols/error64.png' height='64' width='64' alt='Vorsicht!' title='Vorsicht!' /></div><div class='float75'><div style='margin:15px 10px 5px;font-size:105%'>Wir haben in dieser Version viele &Auml;nderungen am Grundsystem, sowie 'spontane' Erweiterungen vorgenommen. Auf Grund des kurzen Zeitrahmens k&ouml;nnen wir eine Fehlerfreiheit leider nicht garantieren! Spielt deshalb die ersten Tage bitte besonders aufmerksam und teilt uns Fehler &uuml;ber den Kontakt mit!<br /><br />Danke, das Stargods Team</div></div><div class='clearer'></div></div><div class='box_bottom'></div> --> <div class='title'><h2>&nbsp;</h2></div><div class='box'><div class='float' style='width:52.99%;padding-top:5px'> <iframe id='Footer300links' name='Footer300links' src='http://stargods.de/advertising/footer300.html' framespacing='0' frameborder='no' scrolling='no' width='300' height='250' allowtransparency='true' border='0' target='_blank' a"
    $sString &= "lt='' /></a></iframe> </div><div class='float justify' style='width:46.99%'> <h1>Willkommen zur&uuml;ck!</h1><br /> Hier hast du eine kleine &Uuml;bersicht &uuml;ber den aktuellen Status deiner Stadt und deines Volkes. Um n&auml;here Informationen zu den jeweiligen Punkten zu erlangen, benutze bitte die Hauptnavigation.<br /><br /> <div id='belohnungen'></div> </div><div class='clearer'></div></div><div class='box_bottom'></div><br /> <div class='title'><h2>Unterst&uuml;tzt Stargods</h2></div><div class='box bold center'>Helft mit einfachen Mitteln!<br /><br /> <b><font color='#ffff00' face='Arial' size='2'>Z&auml;hle zu unserem stargods-Team.</font></b><br /> <a href='http://www.kostenlose-browsergames.de' target='_blank'><img src='http://stargods.de/images/kostenlosebrowsergames.gif' alt='Kostenlose Browsergames' style='border: 1px solid #212121;margin-top:5px;' width='88' height='31' /></a> <br /><font face='Arial' size='1' color='#FFFF00'>Vote <b>1x am Tag</b> f&uuml;r uns.</font><"
    $sString &= "br /><br /> Und nun w&uuml;nsche ich euch eine erfolgreiche Zeit.</div><div class='box_bottom'></div><br /> <div class='title'><h2>Neuigkeiten</h2></div><div class='box'>Es sind noch keine Neuigkeiten vorhanden</div><div class='box_bottom'></div> <script type='text/javascript' src='http://stargods.de/layer.js'></script> </div></div> <div id='right'> <p style='margin-bottom:7px'><img src='http://stargods.de/images/icons/layout_delete.png' height='16' width='16' alt='Layout Delete' title='Layout Delete' /> Werbefrei: <a href='http://s1.stargods.de/ucp/adfree'>Aktivieren</a></p> <p style='margin-bottom:8px'><img src='http://stargods.de/images/icons/user_add.png' height='16' width='16' alt='Freundesliste (0)' /> <a href='http://s1.stargods.de/messages/contacts'>Freundesliste (0)</a><br /> <img src='http://stargods.de/images/icons/user_delete.png' height='16' width='16' alt='Blockierliste' /> <a href='http://s1.stargods.de/messages/blocked'>Blockierliste</a><br /> <img src='http://stargods."
    $sString &= "de/images/icons/group.png' height='16' width='16' alt='Allianz: IT-World' /> <a href='http://s1.stargods.de/groups/389'>Allianz: <b>IT-World</b></a> </p> <p><img src='http://stargods.de/images/icons/user.png' height='16' width='16' alt='Profil betrachten' /> <a href='http://s1.stargods.de/profil'>Profil betrachten</a><br /> <img src='http://stargods.de/images/icons/user_edit.png' height='16' width='16' alt='Profil bearbeiten' /> <a href='http://s1.stargods.de/ucp'>Profil bearbeiten</a><br /> <img src='http://stargods.de/images/icons/email.png' height='16' width='16' alt='Nachrichten (2)' /> <a href='http://s1.stargods.de/messages'>Nachrichten (2)</a><br /> <img src='http://stargods.de/images/icons/delete.png' height='16' width='16' alt='Ausloggen' /> <a href='http://s1.stargods.de/ucp/logout'>Ausloggen</a></p> <div style='margin-top:55px'> <iframe id='Sidebar' name='Sidebar' src='http://stargods.de/advertising/sidebar.html' framespacing='0' frameborder='no' scrolling='no' width='160' h"
    $sString &= "eight='600' allowtransparency='true' border='0' target='_blank' alt='' /></a></iframe> </div> </div><div class='clearer'></div> <div id='footer'> <p class='bold'><a href='http://s1.stargods.de/contact'>Kontakt</a> &middot; <a href='http://s1.stargods.de/'>Neuigkeiten</a> &middot; <a href='http://s1.stargods.de/agb'>AGB</a> &middot; <a href='http://s1.stargods.de/datenschutz'>Datenschutz</a> &middot; <a href='http://s1.stargods.de/impressum'>Impressum</a></p> Copyright &copy; 2008-10 XtremeGN.de - Alle Rechte vorbehalten. Webdesign von <a href='http://gnomdesign.de' target='_blank'>Gnomdesign.de</a> </div> </div> </body> </html>"

    [/autoit] [autoit][/autoit] [autoit]

    $aGUIRes = StringSplit(IniRead($sIniPath, "General Settings", "Resolution", "800x600"), "x", 2)
    $hGUI = GUICreate("RegExpIt", $aGUIRes[0], $aGUIRes[1], -1, -1, $WS_OVERLAPPEDWINDOW)
    GUISetFont(12)

    [/autoit] [autoit][/autoit] [autoit]

    $hRE_RegExp = _GUICtrlRichEdit_Create($hGUI, "", 10, 10, 445, 120, BitOR($ES_MULTILINE, $WS_VSCROLL, $ES_AUTOVSCROLL))
    _GUICtrlRichEdit_SetFont($hRE_RegExp, 12)

    [/autoit] [autoit][/autoit] [autoit]

    $hRE_TestString = _GUICtrlRichEdit_Create($hGUI, $sString, 10, 170, 780, 200)
    _GUICtrlRichEdit_SetFont($hRE_TestString, 12)

    [/autoit] [autoit][/autoit] [autoit]

    $hRE_Matches = _GUICtrlRichEdit_Create($hGUI, "", 10, 390, 780, 200)
    _GUICtrlRichEdit_SetFont($hRE_Matches, 15)

    [/autoit] [autoit][/autoit] [autoit]

    GUISetState()

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
    Case -3
    GUIDelete($hGUI) ;GUI löschen, wegen RichEdit.
    Exit
    EndSwitch
    _RichEdit_ApplyRegExp($hRE_RegExp)
    WEnd

    [/autoit] [autoit][/autoit] [autoit]

    Func _RichEdit_ApplyRegExp(ByRef $hRichEdit)
    If _GUICtrlRichEdit_IsModified($hRichEdit) Then
    _GUICtrlRichEdit_SetModified($hRichEdit, False)

    [/autoit] [autoit][/autoit] [autoit]

    $aTmp = StringRegExp(_RichEdit_GetText($hRE_TestString), _RichEdit_GetText($hRE_RegExp), 3)
    If Not @error Then
    _GUICtrlRichEdit_SetText($hRE_Matches, _ArrayToString($aTmp, @CRLF))
    Global $bYellow = True
    $aSel = _GUICtrlRichEdit_GetSel($hRichEdit) ; Get curren Cursor Pos.

    [/autoit] [autoit][/autoit] [autoit]

    __RichEdit_ColorViaRegExp($hRE_Matches)
    ;~ __RichEdit_ColorViaRegExp($hRE_TestString)

    [/autoit] [autoit][/autoit] [autoit]

    _GUICtrlRichEdit_SetSel($hRichEdit, $aSel[0], $aSel[1]) ;restore Cursor Pos
    Else
    _GUICtrlRichEdit_SetText($hRE_Matches, "")
    EndIf
    EndIf
    EndFunc ;==>_RichEdit_ApplyRegExp

    [/autoit] [autoit][/autoit] [autoit]

    Func _RichEdit_GetText($hRichEdit)
    ;SEuBo
    $aPos = _GUICtrlRichEdit_GetSel($hRichEdit)
    _GUICtrlRichEdit_AppendText($hRichEdit, " ")
    $sRet = StringReplace(_GUICtrlRichEdit_GetText($hRichEdit, True), @CR & " ", @CR)
    _GUICtrlRichEdit_SetText($hRichEdit, $sRet)
    _GUICtrlRichEdit_SetSel($hRichEdit, $aPos[0], $aPos[1])
    _GUICtrlRichEdit_SetModified($hRichEdit, False)
    Return $sRet
    EndFunc ;==>_RichEdit_GetText

    [/autoit] [autoit][/autoit] [autoit]

    Func __RichEdit_ColorViaRegExp($hRichEdit)
    $aTmp = StringRegExp(_RichEdit_GetText($hRE_TestString), _RichEdit_GetText($hRE_RegExp), 3)
    _GUICtrlRichEdit_SetSel($hRichEdit, 0, -1, True)
    _GUICtrlRichEdit_SetCharBkColor($hRichEdit, 0xFFFFFF)
    If _RichEdit_GetText($hRE_RegExp) <> "" Then
    Local $sText = _RichEdit_GetText($hRichEdit), $iCounter = 0
    Global $bYellow = True
    If $aTmp[0] = "" Then Return SetError(@error,@extended,"")
    For $i = 0 To UBound($aTmp) - 1
    $bYellow = Not $bYellow
    Local $iPos = StringInStr($sText, $aTmp[$i], 1) - 1
    _GUICtrlRichEdit_SetSel($hRichEdit, $iPos + $iCounter, $iPos + $iCounter + StringLen($aTmp[$i]), True)
    If $bYellow Then _GUICtrlRichEdit_SetCharBkColor($hRichEdit, 0xFFF000)
    If Not $bYellow Then _GUICtrlRichEdit_SetCharBkColor($hRichEdit, 0x80C0FF)
    $sText = StringTrimLeft($sText, $iPos + 1)

    [/autoit] [autoit][/autoit] [autoit]

    $iCounter += $iPos + ($hRichEdit <> $hRE_Matches)
    Next
    EndIf
    _GUICtrlRichEdit_SetModified($hRichEdit, False)
    EndFunc ;==>__RichEdit_ColorViaRegExp

    [/autoit]
    Lösung Aufgabe 2
    Code
    Rang:\s*(\d+).+cash["']>([\d\..]+).+volk["']>([\d\.]+)

    Schreibe ein Pattern, dass alle Daten in der Liste findet. Falsche Daten wie 31.02.2008 können vorerst ignoriert werden. Dazu kommen wir dann beim konditionalen RegExp.

    Teststring für Regexbuddy

    22.12.2004
    01.05.10
    6.11.2003
    28.2.07
    16.06.06
    11.11.11
    20.02.2002

    Vorlage
    [autoit]


    #include <Array.au3>
    $sString = _
    "22.12.2004" & @CRLF & _
    "01.05.10" & @CRLF & _
    "6.11.2003" & @CRLF & _
    "28.2.07" & @CRLF & _
    "16.06.06" & @CRLF & _
    "11.11.11" & @CRLF & _
    "20.02.2002" & @CRLF

    [/autoit] [autoit][/autoit] [autoit]

    $aArray = StringRegExp($sString,"",3)
    _ArrayDisplay($aArray)

    [/autoit]
    Lösung Aufgabe 3
    Code
    \b(?:3[0-1]|[0-2]?\d)\.(?:1[0-2]|0?\d)\.(?:(?:19|20)?\d\d)\b

    6. Konditionales RegExp
    Zuerst einmal: Wir sind über den Berg! Den schwersten Teil haben wir jetzt hinter uns.
    Das soll aber nicht heißen, dass das kommende Kapitel es nicht in sich hat ;)

    Vorbereitung: Stell die RegEx Engine in RegExBuddy (Combobox oben links) auf "PCRE" !


    Konditionales RegExp arbeitet nach einem If-Then-Else Prinzip. Der Syntaxaufruf lautet

    Code
    (?(?=if)then|else)

    Jetzt kommt erstmal ein ausführliches Beispiel. Ich werde den kompletten "Zusammenbau" des Patterns erklären.
    So haben wir auch noch etwas Wiederholungsstoff :P

    Wir haben folgenden String:

    Beispielstring

    DE: 02.01.2009
    US: 11-19-2008
    UK: 5-10-2004
    DE: 27.3.99
    DE: 04.12.2002
    US: 9-8-98
    UK: 08-12-08

    Wie ihr seht, sind das Daten. Einmal in US/UK-Schreibweise, und einmal in der deutschen Schreibweise.
    Wir möchten diese Daten finden. Wir möchten aber nur ein Arrayelement pro gefundenem Datum.
    Jedes Arrayelement soll nur das Datum, nicht aber "DE,US oder UK" enthalten.

    Beispiel Konditionales RegExp


    Was wir machen müssen ist also folgendes:
    Falls die Zeile mit "DE:" beginnt, suchen wir nach deutschem Datumsformat,
    Falls nicht nach dem Englischen.

    Code
    (?(?=if DE)Deutsches Datum|else Englisches Datum)


    Dann schreiben wir zuerst einen RegExp für das deutsche Format.
    Natürlich erstellen wir kein Subpattern dafür.

    Beginnend mit "DE: " sollen Zahlen von 0-31 gefunden werden. Egal ob 1 oder 2-stellig.

    Code
    (?:3[0-1]|[0-2]?\d)


    Danach folgt ein literaler Punkt

    Code
    (?:3[0-1]|[0-2]?\d)\.


    Nun Zahlen von 0-12 gefolgt von einem Punkt.

    Code
    (?:3[0-1]|[0-2]?\d)\.(?:1[0-2]|0?\d)\.


    Nun nur noch die Jahreszahl.

    Code
    (?:3[0-1]|[0-2]?\d)\.(?:1[0-2]|0?\d)\.(?:19|20)?\d\d

    Dieses Pattern soll gefunden werden, wenn der String mit "DE: " anfängt.
    Das DE soll aber nicht gefunden werden. Deswegen kommt es in ein nicht-fangendes klammerpaar (im Then-Part)

    Code
    (?(?=^DE: )[^ ]+ Deutsches Datum|Englisches Datum)
    Code
    (?(?=^DE: )[^ ]+ ((?:3[0-1]|[0-2]?\d)\.(?:1[0-2]|0?\d)\.(?:19|20)?\d\d)|englisches datum)


    Ist euch auf aufgefallen, dass wir "^DE\: " 2 mal schreiben mussten? Das könnte man zwar durch ein Subpattern lösen, welches man per \1 ansteuert (Backtracking - dazu komme ich später nochmal näher), aber wir wollten ja explizit nur ein Array Element. Im nächtsen Teil bei den Assertionen sehen wir, dass es auch einfach geht, denn Assertionen sind mit Konditionen verknüpfbar.

    Jetzt also das englische Datum:
    Am Zeilenanfang sollte "UK: " oder "US: " zu finden sein.

    Code
    ^(?:UK|US):


    gefolgt von Monat, einem Bindestrich dem Tag, Bindestrich und jahreszahl.
    Das ganze soll in ein Subpattern (damit wir UK oder US nicht im Ergebnis haben)

    Code
    ((?:1[0-2]|0?\d)\-(?:3[0-1]|[0-2]?\d)\-(?:19|20)?\d\d)

    Das ganze sieht jetzt so aus:

    Code
    (?(?=^DE\: )[^ ]+ ((?:3[0-1]|[0-2]?\d)\.(?:1[0-2]|0?\d)\.(?:19|20)?\d\d)|(?(?=^(?:UK|US))[^ ]+ ((?:1[0-2]|0?\d)\-(?:3[0-1]|[0-2]?\d)\-(?:19|20)?\d\d)))

    Aber es werden ja jetzt immer noch der 30.02 und 31.02 gefunden?! ?(

    Tja selbst schuld! Dann halt auf die harte Tour ;)

    Code
    (?(?=^DE: )[^ ]+ ((?(?=\d{2}\.0?2)[0-2]?\d\.0?2\.(?:19|20)?\d\d|(?:3[01]|[0-2]?\d)\.(?:1[0-2]|0?\d)\.(?:19|20)?\d\d))|(?(?=^U[SK])[^ ]+ ((?(?=0?2\-)0?2\-[0-2]?\d\-(?:19|20)?\d\d|(?:0?\d|1[0-2])\-(?:3[01]|[0-2]?\d)\-(?:19|20)?\d\d))))


    Dieses Pattern hat mich übrigens ca 35 mins gekostet - also selbst für mich geht sowas nicht mal "eben so von der Hand".

    Dieses Pattern überprüft nun, ob der Monat ein Februar ist. Wenn ja, werden Tage von 0-29 gesucht. Wenn nicht, Tage von 0-31.
    Und jetzt baut ihr das Ding mal ohne RegExbuddy zusammen 8o

    Verschachtelter Konditionaler Regexp unter der Lupe


    6.1 Übersicht über Kapitel 6
    Wir haben konditionales RegExp kennengelernt.

    • Konditionales RegExp wird mit
      Code
      (?(?=if RegExp)Then RegExp|Else RegExp)

      aufgerufen.

    • Der Else-Teil ist optional.
    • Konditionales RegExp kann verschachtelt werden
    • Man braucht es eigentlich nie :D


    6.2 Aufgaben
    Baue das Pattern aus Aufgabe 5.3.3 (Datums-Checker) so um, dass es den 30 & 31 Februar ausschließt!

    Lösung Aufgabe 1
    Code
    \b((?(?=\d{2}\.0?2)(?:[01]?\d|2[0-8])\.0?2\.(?:19|20)?\d\d|(?:3[01]|[0-2]?\d)\.(?:1[0-2]|0?\d)\.(?:19|20)?\d\d))\b


    Aufgabe 2:
    Baue ein Pattern, das korrekte IPv4-Adressen findet. Von 0.0.0.0 bis 255.255.255.255

    Lösung
    Code
    \b(?:(?:25[0-5]|2[0-4]\d|[01]?\d?\d)(?:\b|\.)){4}\b


    7. Assertionen und Backreference
    7.1 Assertionen
    Mit einer Assertion kann man angeben, dass etwas vor (Lookahead) oder hinter (Lookbehind) einem anderen RegExp-Abschnitt stehen soll.
    Eine Assertion hat den Vorteil, dass der Fund nicht mit ins Ergebnis übernommen wird. Schaut euch am besten mal das Beispiel an.

    Wir schauen uns mal einen positiven Lookahead an:

    foobar

    Code
    (?<=foo)bar


    findet "bar", weil "foo" davor gefunden wurde. Würde der RegEx allerdings

    Code
    (?<=zoo)bar


    heißen, würde die RegExp Maschine nichts finden. Weil dem "bar" kein "zoo" sondern ein "foo" vorangeht.

    Das ganze kann man auch verneinen, und zu einem Negativen Lookahead umwanden. Aus

    Code
    (?<=zoo)bar


    wird dann

    Code
    (?<!zoo)bar


    Mit diesem Pattern finden wir im String "foobar" auch wieder bar, weil dem "bar" kein "zoo" vorangeht.

    Das gleiche gibts natürlich auch als sog. Lookbehind.
    Diesmal suchen wir "foo", wenn "bar" folgt

    Code
    foo(?=bar)


    oder auch "foo", wenn kein "boo" folgt:

    Code
    foo(?!boo)
    Assertionen im Überblick
    [autoit]

    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    ; Positiver Lookahead (?<=)
    $aResult = StringRegExp("foobar","(?<=foo)bar", 3)
    _ArrayDisplay($aResult,"(?<=foo)bar")
    ; Negativer Lookahead (?<!)
    $aResult = StringRegExp("foobar","(?<!zoo)bar", 3)
    _ArrayDisplay($aResult,"(?<!zoo)bar")

    [/autoit] [autoit][/autoit] [autoit]

    ; Positiver Lookbehind (?=)
    $aResult = StringRegExp("foobar","foo(?=bar)", 3)
    _ArrayDisplay($aResult,"foo(?=bar)")
    ; Negativer Lookbehind (?!)
    $aResult = StringRegExp("foobar","foo(?!boo)", 3)
    _ArrayDisplay($aResult,"foo(?!bar)")

    [/autoit] [autoit][/autoit] [autoit][/autoit]

    Achtung: Eine Bedingung ist an den Suchbegriffen im Assertion allerdings zu stellen: ihre Länge muss definiert sein, das heißt, es können keine Quantifizierer verwendet werden! Die Assertion "(?<=\d+,)\d\d" führt zu einem Laufzeitfehler.

    Assertionen understützen den OR-Operator (allerdings nicht in tiefere Klammerebenen).

    Code
    (?<=123|abc)test


    Ist erlaubt. Das hier hingegen nicht:

    Code
    (?<=000(123|abc))test

    Assertionen können mit konditionalem RegExp verknüpft werden. Schaut euch das Kapitel noch mal an und ihr werdet sehen, dass wir immer einen positiven Lookbehind (?=) benutzt haben. Statt

    Code
    (?(?=If)Then|Else)

    geht also auch

    Code
    (?(?<=If)Then|Else)
    (?(?<!If)Then|Else)
    (?(?!If)Then|Else)


    Je nachdem was man gerade braucht.

    Assertionen sind verschachtelbar und auch hintereinander verwendbar.

    7.2 Backreference
    Ich habe es ja schon im Teil über Subpatterns erwähnt. Man kann sich auf das Ergebnis eines Subpatterns, oder besser gesagt auf dessen Inhalt zurückbeziehen.
    Lange Rede Kurzer Sinn:

    RegExBuddy

    Rad und Handschlag
    Rad und Radschlag_
    Hand und Handschlag
    Hand und Anschlag

    Code
    (Rad|Hand) und \1schlag


    Es wird also immer mittels \1 das Ergebnis des ersten Subpattern geprüft und später im RegExp verwendet. Deswegen findet dieses Pattern auch nur "Hand und Handschlag " und "Rad und Radschlag"

    Möchte man die Subpattern-nummer von einer "richtigen" zahl trennen, so benutzt man geschweifte klammern.
    Bsp: Wir wollen uns per Backreference auf das 4te Subpattern beziehen. Danach soll eine 9 stehen.
    \49 findet allerdings das 49te Subpattern. Deswegen machen wir folgendes:
    \{4}9

    Backreference kann aber auch mit konditionalem RegExp verwendet werden. Der Syntaxaufruf ist dann

    Code
    (?(Backreference)Then RegExp|Else RegExp)

    Beispiel:

    [autoit]

    #include <Array.au3>
    $sString = "Test dieser Satz ist ein Test"
    $aTmp = StringRegExp($sString,"(e)?(?(1)(r)|in)",3)
    _ArrayDisplay($aTmp)

    [/autoit]

    Wir suchen also erst nach "e" und schreiben es in ein Subpattern.
    Nun kommt die Kondition (?(1)...|...). Wenn Subpattern 1 am Match teilgenommen hat, dann soll nach "r" gesucht werden. (Damit wir es in den Ergebnissen angezeigt bekommen, müssen wir das natürlich auch in einem Subpattern speichern. Sollte das 1. Subpattern nciht am Match teilgenommen haben, so wird der Else-Teil der Kondition aktiv, und es wird nach "in" gesucht.

    Wer's braucht :rolleyes:

    7.3 Übersicht über Kapitel 7
    In diesem Teil lernten wir etwas über Assertionen. Desweiteren haben wir unser Wissen zu Backreferencen aufgefrischt, und das über konditionalen RegExp erweitert.

    • Es gibt 4 Arten von Assertionen: Positiver und Negativer Lookahead (?<=...), (?<!...). (Man erkennt sie an den "Pfeilen" "<".) Und positiven / negativen lookbehind. (?=...), (?!...)
    • Assertionen müssen eine feste Länge haben! Quantifizer in Assertionen sind NICHT möglich!
    • Wir können Backreference nutzen, um uns auf ein bereits gefundenes Subpattern zu beziehen. Dies funktioniert mit \1-99 oder $1-99
    • Backreference kann als kondition verwendet werden. (wenn Subpattern gefunden, dann..., ansonsten....)

    7.4 Kapitel 7 - Aufgaben
    1. Wir haben ein einfaches Verschlüsselungsprogramm geschrieben, in dem ein Buchstabe in einem Text durch einen anderen ersetzt wird.

    Spoiler anzeigen
    [autoit]

    $hGUI = GUICreate("",120,60)
    $hInput = GUICtrlCreateInput("",10,10,100,20)
    $hButton = GUICtrlCreateButton("OK",10,35,100,20)
    GUISetState()

    [/autoit] [autoit][/autoit] [autoit]

    While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
    Case -3
    Exit
    Case $hButton
    GUICtrlSetData($hInput, _Crypt(GUICtrlRead($hInput)))
    EndSwitch
    WEnd

    [/autoit] [autoit][/autoit] [autoit]

    Func _Crypt($sString)
    $sString = StringReplace($sString,"a","z")
    $sString = StringReplace($sString,"b","y")
    $sString = StringReplace($sString,"c","x")
    $sString = StringReplace($sString,"d","w")
    $sString = StringReplace($sString,"e","v")
    $sString = StringReplace($sString,"f","u")
    $sString = StringReplace($sString,"g","t")
    $sString = StringReplace($sString,"h","s")
    $sString = StringReplace($sString,"i","r")
    $sString = StringReplace($sString,"j","q")
    $sString = StringReplace($sString,"k","p")
    $sString = StringReplace($sString,"l","o")
    $sString = StringReplace($sString,"m","n")
    $sString = StringReplace($sString,"n","m")
    $sString = StringReplace($sString,"o","l")
    $sString = StringReplace($sString,"p","k")
    $sString = StringReplace($sString,"q","j")
    $sString = StringReplace($sString,"r","i")
    $sString = StringReplace($sString,"s","h")
    $sString = StringReplace($sString,"t","g")
    $sString = StringReplace($sString,"u","f")
    $sString = StringReplace($sString,"v","e")
    $sString = StringReplace($sString,"w","d")
    $sString = StringReplace($sString,"x","c")
    $sString = StringReplace($sString,"y","b")
    $sString = StringReplace($sString,"z","a")
    Return $sString
    EndFunc

    [/autoit]

    Nun haben wir allerdings ein Problem: Die Buchstaben, die ersetzt werden, werden sofort wieder zurück übersetzt. (logisch!). Finde eine Lösung für dieses Problem. (Da es ein RegExp Tutorial ist, sollte die Lösung mit StringRegExp/StringRegExpReplace arbeiten.)

    Lösung Aufgabe 1


    Wir schreiben die Buchstaben, die ersetzt wurden, in eckige Klammern. Ersetzt werden sollen dann natürlich auch nur die Buchstaben, die NICHT in eckigen Klammern stehen. (Eine Assertion ist hier von Vorteil!)

    Code
    Bsp: b -> [y]
    a[b]c -> [z][b][x].


    Danach werden alle eckigen Klammern entfernt.

    [autoit]

    $hGUI = GUICreate("", 120, 60)
    $hInput = GUICtrlCreateInput("", 10, 10, 100, 20)
    $hButton = GUICtrlCreateButton("OK", 10, 35, 100, 20)
    GUISetState()

    [/autoit] [autoit][/autoit] [autoit]

    While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
    Case -3
    Exit
    Case $hButton
    GUICtrlSetData($hInput, _Crypt(GUICtrlRead($hInput)))
    EndSwitch
    WEnd

    [/autoit] [autoit][/autoit] [autoit]

    Func _Crypt($sString)
    ; Ja ich weiß, mit einem 2D-Array und einer For-Next Schleife wäre es 200 mal kürzer. Aber ich wollte es nicht unnötig kompliziert machen.
    $sString = _StringReplace($sString, "a", "z")
    $sString = _StringReplace($sString, "b", "y")
    $sString = _StringReplace($sString, "c", "x")
    $sString = _StringReplace($sString, "d", "w")
    $sString = _StringReplace($sString, "e", "v")
    $sString = _StringReplace($sString, "f", "u")
    $sString = _StringReplace($sString, "g", "t")
    $sString = _StringReplace($sString, "h", "s")
    $sString = _StringReplace($sString, "i", "r")
    $sString = _StringReplace($sString, "j", "q")
    $sString = _StringReplace($sString, "k", "p")
    $sString = _StringReplace($sString, "l", "o")
    $sString = _StringReplace($sString, "m", "n")
    $sString = _StringReplace($sString, "n", "m")
    $sString = _StringReplace($sString, "o", "l")
    $sString = _StringReplace($sString, "p", "k")
    $sString = _StringReplace($sString, "q", "j")
    $sString = _StringReplace($sString, "r", "i")
    $sString = _StringReplace($sString, "s", "h")
    $sString = _StringReplace($sString, "t", "g")
    $sString = _StringReplace($sString, "u", "f")
    $sString = _StringReplace($sString, "v", "e")
    $sString = _StringReplace($sString, "w", "d")
    $sString = _StringReplace($sString, "x", "c")
    $sString = _StringReplace($sString, "y", "b")
    $sString = _StringReplace($sString, "z", "a")
    Return _CleanUp($sString)
    EndFunc ;==>_Crypt

    [/autoit] [autoit][/autoit] [autoit]

    Func _StringReplace($sString, $sSearch, $sReplace)
    Return StringRegExpReplace($sString, "(?<!\[)" & $sSearch & "(?!\])", "[" & $sReplace & "]")
    EndFunc ;==>_StringReplace

    [/autoit] [autoit][/autoit] [autoit]

    Func _CleanUp($sString)
    Return StringRegExpReplace($sString, "\[(\w+?)\]", "\1")
    EndFunc ;==>_CleanUp

    [/autoit]

    Aufgabe 2:
    Wir haben eine Liste mit eMail-Adressen. Schreibe einen RegExp, dass mithilfe von Backreference doppelte Namen findet. (Der Hostname wie zB. gmx.de kann außer Acht gelassen werden.)

    Email-Liste
    Code
    abc123@gmx.de
    testacc@web.de
    MrEmail@gmx.de
    MrsEmail@googlemail.com
    testacc@hotmail.de
    abc_123@gmx.de
    test_acc@web.de
    Mr_Email@gmx.de
    Mrs_Email@googlemail.com
    Lösung Aufgabe 2
    Code
    ([^@]+)@(?:.+\s)*\1

    =======================================

    LG SEuBo

  • dann werd ich mich jetz auch mal ranmachen etwas mehr mit RegEx zu arbeiten.

    vielen dank schonmal für das tutorial, ich schreib dir dannach dann wie ichs fand ;)

    • Offizieller Beitrag

    Dein Tut finde ich gelungen. Von mir bekommt du eine 1 + :thumbup: SEuBo


    Edit: Ich sehe gerade, du bist ja auch Erleuchtet. :thumbup: Spart viel Strom. :rofl:

  • Super Tutorial :thumbup: :thumbup: :thumbup:

    Ich hab mich mal durchgeackert und kann sagen:
    Es hat sich gelohnt :!: ^^

    mfg Ubuntu

  • Sehr gut geschrieben. Werde es mir mal speichern und ich sage ganz klar: DANKE SCHÖN !!!

    Lieben Gruß,
    Alina

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Geheime Information: ;)
    OuBVU5ebLhHu5QvlnAyQB4A7SzBrvWulwL7RLl2BdH5tI6sIYspeMKeXMSXl

  • Sehr schön und verständlich geschrieben! :thumbup:

  • Ich find das Tut toll und es hat mir auch schon geholfen aber ich hab noch ne Frage zu den Zeichenklassen... in der Hilfe gibt es ne liste wo zb [:print:] drin steht...
    aber wenn cih das in mein skript einbaue dann funzt nix ....

    hier mal nen testskript
    http://pastebin.com/pAgeUTMt

    EDIT:

    mir wurde geholfen^^
    vllt kannst das ja auch noch kurz erwähnen ;)
    Man muss [:print:] noch mal in [] machen dann gehts... also [[:print:]]^^

    Einmal editiert, zuletzt von Kijan (5. Juni 2010 um 11:46)

  • Hab ich was übersehen oder wird hier nichts über den And Charakter gesagt?

    Edit:Ahh wie ich das sehe müssen für and einfach in der klammer zusammengeschriben werden:

    [autoit]

    StringRegExp($test,"(\S\D)",3);liefert alle (sonder)zeichen die keine Zahl und kein trennzeichen sind

    [/autoit]

    mfg Ubuntu

  • Edit:Ahh wie ich das sehe müssen für and einfach in der klammer zusammengeschriben werden:


    Fast Richtig.
    Für AND benutzt man eigene Zeichenklassen.
    Um zb alle Zeichen anzuzeigen, die weder Zahlen noch WhiteSpaces sind, nutzt man [^\d\s].
    Falsch wäre dagegen [\D\S] (obwohl das ja auf den ersten Blick richtig aussieht), weil das alle zeichen findet, die Keine Zahlen und keine Whitespaces sind. Das ist ein Wiederspruch, weil "Keine Zahl" Whitespaces findet, und "Keine WhiteSpaces" Zahlen findet. :confused:
    [^\d\s] dagegen findet "Keine Zahlen und WhiteSpaces". Besser kann ich's leider nicht erklären. Aber ich kann dir noch ein Beispiel zeigen.

    [autoit]

    #include <Array.au3>
    $sText = "abcdefghijklmnopqrstuvwxyz234567890ß´!""§$%&/()=?`+*~#'-_.:,;<>|^°"
    $aRet = StringRegExp($sText,'[\D\S]',3)
    _ArrayDisplay($aRet,"Falsch [\D\S]")

    [/autoit][autoit][/autoit][autoit]

    $aRet = StringRegExp($sText,'[^\d\s]',3)
    _ArrayDisplay($aRet,"Richtig [^\d\s]")

    [/autoit]

    (\S\D) findet allerdings 2 aufeinanderfolgende Zeichen, von denen das erste Kein WhiteSpace, und das zweite keine Zahl sein darf.

  • Kurze Frage, wie mache ich folgendes:
    Das gibt es:

    Spoiler anzeigen


    123
    126
    121
    161
    251
    971
    641
    131


    Nun sollen alle gehen, ausser 123 und 131...
    Am besten wäre es, wenn es ein Pattern mit ungleich gibt...

    Greetz

  • Es geht sogar ohne RegEx:

    Spoiler anzeigen
    [autoit]

    $Var = 123
    If $Var = 123 Or $Var = 131 Then
    MsgBox(0, "", "Gut") ;Die Zahl ist anders als 123 oder 131
    Else
    MsgBox(16, "", "Schlecht") ;Die Zahl ist 123 oder 131
    EndIf

    [/autoit]


    Und für Arrays:

    Spoiler anzeigen
    [autoit]

    Dim $Var[2] = [123, 222]
    For $i = 0 To UBound($Var) - 1
    If $Var[$i] = 123 Or $Var[$i] = 131 Then
    MsgBox(0, "", "Gut") ;Die Zahl ist 123 oder 131
    Else
    MsgBox(16, "", "Schlecht") ;Die Zahl ist anders als 123 oder 131
    EndIf
    Next

    [/autoit]

    Ich hoffe ich hab dein Problem richtig aufgenommen ;)

  • Alizame: Dass es anders geht, ist ja klar, aber wie geht es mit RegEx ist hier die Frage oder?

    Apropos Frage: kann man das Resultat umkehren (inventieren) für allgemeine reg. Ausdrücke?

    Z.B:
    14
    124
    AutoIt
    4
    283
    Coding
    macht
    Spaß
    0815

    Suche nach allen Zahlen mit RegEx und inventiere das Ergebnis, so dass man hier nur Wörter erhält!

    Warum suchst du nicht gleich nach Wörtern, wäre vielleicht eine Antwort! Aber wenn ich nicht weiß, dass es Wörter sind, aber ich weiß, dass es Zahlen sind, wäre es einfach nach dem Rest zu suchen!

    Gruß,
    UEZ

    Auch am Arsch geht ein Weg vorbei...

    ¯\_(ツ)_/¯