Michi: RegEx schreiben lernen

Hallo,

ich versuche RegEx zu lernen, bzw. meine doch bescheidenen RegEx-Kenntnisse etwas zu verbessern. Nun habe ich das Tutorial von regenechsen.de durchgearbeitet und kann kann im großen und ganzen alle Beispiele und Übungen auch nachvollziehen. Als Übung versuche ich jetzt eine RegEx zu schreiben, die Attributwerte ohne Anführungszeichen innerhalb von HTML-Tags treffen soll.
Nun habe ich eine RegEx, die (einigermaßen korrekt) alle Attributwerte trifft und komme ab hier jedoch einfach nicht weiter.
Hier mal was ich bisher habe:
RegEx: (?:((?<==)[a-zA-Z0-9\_-[]]+))
Teststring: <div class=foo id="bar"><span class=foo id=bar>nix=bar und nix=bar</span>foobar</div>

Das trifft jetzt mal (mit global-flag) alle Attributwerte, leider aber auch diejenigen, die zwischen den Tags vorkommen.

Wie muss ich jetzt weitermachen, dass zwischen den Tags nichts mehr getroffen wird?

Mein Ansatz wäre jetzt gewesen, das mit einem Ausdruck zu kombinieren, der nur Treffer innerhalb von Tags liefert also z.B.:
((</?)(\w+)([^>]*?>))

Nur bekomme ich die Kombination nicht hin. Probiert habe ich Subpatternmatching nach der Art:
((</?)(\w+)([^>]*?>))\1(?:((?<==)[a-zA-Z0-9\-[]]+))
oder konditionales Matching nach der Art:
(?(</?)(\w+)([^>]*?>)(?:((?<==)[a-zA-Z0-9\
-[]]+)))

Aber das zeigt mir im RegEx Coach, in dem ich die Exen probiere keine Treffer.

Danke vielmals für Eure Hilfe und viele Grüße

Michi

  1. Hi,

    Das trifft jetzt mal (mit global-flag) alle Attributwerte, leider aber auch diejenigen, die zwischen den Tags vorkommen.

    und auch den Inhalt von foo="bar=qaz".

    Wie muss ich jetzt weitermachen, dass zwischen den Tags nichts mehr getroffen wird?

    Dich von dem Gedanken trennen, das Problem sei allein mit Regular Expressions lösbar. Wenn in der Aufgabenbeschreibung irgendwo ein "innerhalb von" vorkommt, dann kannst Du schon mal sicher sein, das RegExp alleine nicht ausreichen.

    Cheatah

    --
    X-Self-Code: sh:( fo:} ch:~ rl:° br:> n4:& ie:% mo:) va:) de:] zu:) fl:{ ss:) ls:~ js:|
    X-Self-Code-Url: http://emmanuel.dammerer.at/selfcode.html
    X-Will-Answer-Email: No
    X-Please-Search-Archive-First: Absolutely Yes
    1. Hallo,

      Das trifft jetzt mal (mit global-flag) alle Attributwerte, leider aber auch diejenigen, die zwischen den Tags vorkommen.

      und auch den Inhalt von foo="bar=qaz".

      Ich weiß. Und es würde auch z.B. bei foo=b+ar als Treffer b liefern. Aber da würde ich 5 mal gerade sein lassen. (Ich wills ja nicht verwenden sondern nur probieren/üben.)

      Wie muss ich jetzt weitermachen, dass zwischen den Tags nichts mehr getroffen wird?

      Dich von dem Gedanken trennen, das Problem sei allein mit Regular Expressions lösbar.

      Das muss doch gehen?
      (Auch wenns mit einem kleinen Automaten "einfacher" und wahrscheinlich auch um einiges performanter ist. Habs mal in PHP schnell implementiert. "Einfacher" fand ich das allemal und um wie viel es perfomanter ist würde ich dann gerne mal probieren, wenn ich es mit RegEx schaffe zu lösen.)

      Wenn in der Aufgabenbeschreibung irgendwo ein "innerhalb von" vorkommt, dann kannst Du schon mal sicher sein, das RegExp alleine nicht ausreichen.

      Meinst Du wirklich, dass sie nicht ausreichen oder meinst Du, dass sie nicht gerade die beste Wahl wären? Z.B. ist doch Text zwischen zwei Tags nicht allzu schwer zu treffen?

      Danke und viele Grüße

      Michi

      1. echo $begrüßung;

        Dich von dem Gedanken trennen, das Problem sei allein mit Regular Expressions lösbar.

        Das muss doch gehen?
        Meinst Du wirklich, dass sie nicht ausreichen oder meinst Du, dass sie nicht gerade die beste Wahl wären? Z.B. ist doch Text zwischen zwei Tags nicht allzu schwer zu treffen?

        Es wird sicherlich möglich sein, mit allerhand Assertions und anderem "Reg-Exp-Schweinkram" einen passenden regulären Ausdruck hinzubekommen. Um zu wissen, ob du innerhalb eines Tags, eines Attributes oder eines Element-Inhalts steckst, musst du ja "nur" auf gerade oder ungerade Anzahl von vorangegangenen signifikanten Zeichen testen, das allerdings in Abhängigkeit von der Stelle an der sie gerade stehen ... Da kommt dann irgendwann ein Monstrum von RegExp heraus, das kein Mensch mehr zu lesen oder zu pflegen bereit ist.

        echo "$verabschiedung $name";

    2. gudn tach!

      Wenn in der Aufgabenbeschreibung irgendwo ein "innerhalb von" vorkommt, dann kannst Du schon mal sicher sein, das RegExp alleine nicht ausreichen.

      noe:
      aufgabenstellung: ersetze alle "innerhalb von" durch "bollabolla".
      loesung: s/innerhalb von/bollabolla/g;

      ;-p

      aber auch ohne spass stimmt deine aussage nicht.
      problem: es sollen strings innerhalb von spitzen klammern eruiert werden.
      loesung: print $1,"\n" while /<([^>]+)>/g;

      prost
      seth

  2. gudn tach!

    Als Übung versuche ich jetzt eine RegEx zu schreiben, die Attributwerte ohne Anführungszeichen innerhalb von HTML-Tags treffen soll.

    ok, das ist aber i.a.r. wirklich nur als uebung geeignet. in der praxis sollte sichergestellt sein, dass einige code-bestandteile wie javascript und sowas gar nicht erst durchsucht werden. gehen wir also von einem "gutartigen" html-dokument aus, dass wirklich nur html enthaelt und bei dem spitze klammern nur fuer tags verwendet werden. (in wirklichkeit waere es vermutlich eher so, dass bei fehlenden anfuehrungszeichen auch die wahrscheinlichkeit fuer nicht-maskierte spitze klammern innerhalb eines textes erhoeht waere.)

    RegEx: (?:((?<==)[a-zA-Z0-9\_-[]]+))

    wozu die aeussere klammer?

    /(?:((?<==)[a-zA-Z0-9\_-[]]+))/

    sollte das gleiche sein wie

    /((?<==)[a-zA-Z0-9\_-[]]+)/

    aber auch hier sollte noch die aeussere klammer ueberfluessig sein, weil in $& (perl) bzw. $0 (php, iirc) eh der komplette gematchte string drinsteht.

    underscore (_) muss uebrigens nicht maskiert werden.
    und "[" innerhalb einer zeichenklasse auch nicht.
    und wenn das minus am beginn oder ende der zeichnklasse steht, muss es auch nicht maskiert werden.
    also:

    /(?<==)[a-zA-Z0-9_[]-]+/

    sollte das gleiche sein, ist aber kuerzer und es ist besser lesbar.

    Wie muss ich jetzt weitermachen, dass zwischen den Tags nichts mehr getroffen wird?

    ich wuerde es nicht mittels eines einziges ausdrucks angehen. uebersichtlicher ist es, das problem zweizuteilen. da du allgemein ueber die "programmiertechnik" sprichst, nehme ich jetzt mal perl zur hand. in php kann man das vermutlich so aehnlich (bloss nicht so huebsch) bewerkstelligen.
    schaue dir mal an, das \G fuer ein tolles dings ist: perldoc perlre und perldoc perlop.
    bei "perlop" ist auch ein beispiel eines kleinen scanners, wie du ihn auch bauen koenntest.

    eine erste loesung (nicht gucken, falls du selbst erst ueberlegen moechtest) koennte

    #!/usr/bin/perl -w  
    use strict;  
      
    $_='<div class=foo1y id="barnn"><span class=foo1y id=bar1y>nix=barn und nix=barn</span>foonbarn</div>';  
    while(/</g){  
      while(/\G[^>=]+=(?:([^" >]+)|"[^"]*")/gc){  
        print $1,"\n" if defined $1;  
      }  
    }
    

    sein.

    prost
    seth

    1. Hi seth,

      RegEx: (?:((?<==)[a-zA-Z0-9\_-[]]+))

      wozu die aeussere klammer?

      /(?:((?<==)[a-zA-Z0-9\_-[]]+))/

      Die äußerste "Klammerung: (?:...)" habe ich mal "vorsichtshalber" gesetzt und sie macht den Rest nur "nicht gierig". Ich hab zwar kein Bsp. gefunden, wo die Gier ein Problem gewesen wäre (und glaube, dass es in dem Fall auch keines gibt), aber dachte mir, dass es auch nicht schaden kann (in etwa analog zu defensiver Programmierung, wenn ich z.B. etwas in einen try-catch-Block schreibe, wo mir auf gedei und verderb nichts einfällt, wie das eine Exception werfen könnte).
      Die zweite Klammer ist eigentlich auch überflüssig. Die habe ich nur gesetzt, um auszuprobieren, ob sich das gierige Innere (\2) und das nicht gierige äußere (\1) irgendo unterscheiden.

      underscore (_) muss uebrigens nicht maskiert werden.
      und "[" innerhalb einer zeichenklasse auch nicht.
      und wenn das minus am beginn oder ende der zeichnklasse steht, muss es auch nicht maskiert werden.
      also:

      /(?<==)[a-zA-Z0-9_[]-]+/

      sollte das gleiche sein, ist aber kuerzer und es ist besser lesbar.

      Super, habe ich noch nicht gewusst, dass man in diesen Fällen nicht maskieren muss.

      Wie muss ich jetzt weitermachen, dass zwischen den Tags nichts mehr getroffen wird?

      ich wuerde es nicht mittels eines einziges ausdrucks angehen. uebersichtlicher ist es, das problem zweizuteilen. da du allgemein ueber die "programmiertechnik" sprichst, nehme ich jetzt mal perl zur hand. in php kann man das vermutlich so aehnlich (bloss nicht so huebsch) bewerkstelligen.

      Hier hab eich meine Frage offensichtlich etwas blöd forumliert und bin viel zu stark auf mein Beispiel verhaftet geblieben. Was ich eigentlich wissen /lernen will ist, wie ich es am besten angehe, wenn ich was komliziertes mit RegEx lösen will. Hier bin ich es so angegangen, dass ich zunächst versucht habe, alle Attribute zu treffen, dann habe ich probiert, wie ich "hinzu kombinieren kann", dass diese Treffer auch innerhalb von Tags liegen (also quasi inside-out od. bottom-up oder so ähnlich - wäre das prinzipiell ein brauchbarer Anfang?). Wenn dann ein Punkt kommt, an dem es nicht weitergeht, mit welcher Strategie fährt man fort? - Versucht man einen Lösungsweg für den nächsten Problemteil separat zu finden und ihn mit dem anderen Teil zu kombinieren? - Fängt man besser gleich mit einem anderen Ansatz von Vorne an?

      schaue dir mal an, das \G fuer ein tolles dings ist: perldoc perlre und perldoc perlop.
      bei "perlop" ist auch ein beispiel eines kleinen scanners, wie du ihn auch bauen koenntest.

      eine erste loesung (nicht gucken, falls du selbst erst ueberlegen moechtest) koennte

      #!/usr/bin/perl -w

      use strict;

      $_='<div class=foo1y id="barnn"><span class=foo1y id=bar1y>nix=barn und nix=barn</span>foonbarn</div>';
      while(/</g){
        while(/\G[^>=]+=(?:([^" >]+)|"[^"]*")/gc){
          print $1,"\n" if defined $1;
        }
      }

        
        
      Das ist wirklich ganz nett. Auf die Art und Weise kann man es sicher in diversen Sprachen angehen. Ich hab mir preg\_match in PHP zwar noch nie genauer angesehen, denke aber, dass es auch da ziemlich einfach wäre, mit einem ersten Durchlauf alle Tags in ein Array zu holen um auf diese Treffer dann das obige Pattern loszulassen.  
      Nichtsdesto trotz möchte ich es dennoch "in einer RegEx" lösen können.  
        
      Danke und viele Grüße  
        
      Michi
      
      1. gudn tach!

        RegEx: (?:((?<==)[a-zA-Z0-9\_-[]]+))

        wozu die aeussere klammer?

        Die äußerste "Klammerung: (?:...)" habe ich mal "vorsichtshalber" gesetzt und sie macht den Rest nur "nicht gierig".

        huch, seit wann das?

        (?:)
        heisst eigentlich nur, dass der kram darin nicht gespeichert wird.

        /(hulla)/;   # $1 wird erstellt
        /(?:hulla)/; # $1 wird nicht erstellt

        (mehr siehe perldoc perlre)

        Was ich eigentlich wissen /lernen will ist, wie ich es am besten angehe, wenn ich was komliziertes mit RegEx lösen will.

        ich denke, am besten ist es, wenn man einfach viel damit macht und somit erfahrung gewinnt. ein bissl wissen ueber chomsky kann aber auch nicht schaden.

        Hier bin ich es so angegangen, dass ich zunächst versucht habe, alle Attribute zu treffen, dann habe ich probiert, wie ich "hinzu kombinieren kann", dass diese Treffer auch innerhalb von Tags liegen (also quasi inside-out od. bottom-up oder so ähnlich - wäre das prinzipiell ein brauchbarer Anfang?).

        oft schon. oft ist es hilfreicher, drei mal "om!" zu sagen und sich vorzustellen, man waere ein regexp-parser.

        schaue dir mal an, das \G fuer ein tolles dings ist: perldoc perlre und perldoc perlop.
        bei "perlop" ist auch ein beispiel eines kleinen scanners, wie du ihn auch bauen koenntest. [...]

        Das ist wirklich ganz nett. Auf die Art und Weise kann man es sicher in diversen Sprachen angehen.

        ja, und vor allem in perl. ;-)
        und in perl waer's auch recht schnell, weil man nur einmal durch den string marschieren muss.

        Nichtsdesto trotz möchte ich es dennoch "in einer RegEx" lösen können.

        hmm, ok, macht zwar eigentlich keinen praktischen sinn, aber fuer reine uebungszwecke kann man ja mal ueberlegen. hmm, aber ueberlegen macht hungrig. ich geh jetzt erst mal essen...

        prost
        seth