Wolfgang Wiese: Perl-Nuss: Verbesserung eines regulaeren Ausdrucks

Hi,

folgendes brauch ich eigentlich nicht, aber ich denke es waere fuer alle die an Perl und Regular Expressions ebenfalls Spass haben, eine interessante Aufgabe.

Ich habe folgenden Code erstellt:
$parselink_text ist dabei ein String welcher einen beliebig langen HTML-Text enthält.
Alle anderen Variablen sollten klar sein...

while ($parselink_text =~ /<a\s*(.*?)mailto:([\w.-]+@[\w.-]+)(?:["'\s]?)\s*
(.*?)>(.*?)</a>/si) {
        $parselink_text = $';
        $parselink_host = $4;
        $parselink_host =~ s/<([^>]\s+)*>//g;
        $parselink_host =~ s/\s+/ /g;
        $parselink_host =~ s/^\s+//g;
        push(@parselink_liste, "$2$parselink_host");
    }

Konkret würde ich gern die while-Schleife weghaben...Also das die Email-Adressen und deren Bezeichner gleich in einem Feld geschrieben werden...

Na, wer knackt die Nuss?

Ciao,
  Wolfgang

  1. Hi nochmal,

    Kurzer Add-On: Die Verwendung von Modulen für die Verbesserung des Ausdrucks
    zählt natürlich nicht :)

    Ciao,
    Wolfgang

  2. Hallo Wolfgang!

    Mmh... hast Du diesen Code getestet? Irgendwie finde ich ihn recht seltsam, und die Ergebnisse, die er liefert, sind vermutlich nicht das, was Du Dir erhofft hast.

    Also von vorn: Ich nehme an, Du willst eine Liste aller Mails eines Dokuments erstellen, wobei jeder Eintrag das Format "AddresseLinktext" hat. Ich habe es mal mit http://www.teamone.de/selfaktuell/e-woerter.htm (interessanter Artikel uebrigens) ausprobiert, und das Ergebnis ist:

    Kess@topmail.de
    muenz@compuserve.com
    Stefan Münz, muenz@csi.com

    while ($parselink_text =~ /<a\s*(.*?)mailto:([\w.-]+@[\w.-]+)(?:["'\s]?)\s*(.*?)>(.*?)</a>/si) {
            $parselink_text = $';
            $parselink_host = $4;
            $parselink_host =~ s/<([^>]\s+)*>//g;
            $parselink_host =~ s/\s+/ /g;
            $parselink_host =~ s/^\s+//g;
            push(@parselink_liste, "$2$parselink_host");
        }

    Einige Dinge, die mir aufgefallen sind:
    Warum machst Du soviele Klammern, wenn Du hinterher die Haelfte der erzeugten Backreferences nicht benutzt? Warum schreibst Du (?:["'\s]?) und nicht einfach ["'\s]?  ? Ist Dir klar, dass das $2 zum Zeipunkt des push nicht mehr die Mailadresse enthaelt, weil es in der dritten Zeile der Schleife undef wird? Uebrigens, die perlvar manpage sagt, dass die Benutzung der Variablen $`, $' und $& nicht zu vernachlaessigende Performanceeinbussen nach sich zieht.

    Ich wuerde erstmal alle relavanten Teile mit
    @all = ($parselink_text =~ /<a\s.*?mailto:([\w.-]+@[\w.-]+).*?>(.*?)</a>/gsi);
    extrahieren. In @all steht dann abwechselnd die Email und der Linktext, letzterer jedoch noch mit voller Markup-Ausstattung. Um jetzt die Form "AddresseLinktext" zu bekommen, kommt man imho um eine Schleife nicht herum. Auch um die Ersetzung s/\s+/ /g; vorzunehmen, braucht man meines Erachtens eine Schleife (naja, man koennte den Ausdruck vielleicht auch vorher auf den gesamten Text anwenden). Die Ersetzungen s/<([^>]\s+)*>//g; (die ich einfach mal auf s/<.*?>//g; reduziert habe) und s/^\s//g; dagegen koennte man in den oben genannten RegExp einbauen, wenn man annimmt, dass der Linktext zwar durch Markup eingeschlossen ist, jedoch nicht davon unterbrochen wird (also sowas wie Link<BR>text nicht vorkommt).

    Also zwei Vorschlaege, um die Pipe-Darstellung zu erreichen:

    $x = '';
    for (@all) {
        $x or ($x = $_, next);
        s/<.*?>//g;
        s/\s+/ /g;
        s/^\s//g;
        push(@pairs, $x.''.$_);
        $x = '';
    }

    Oder wenn Du es etwas more hardcore magst:

    push(@pairs, shift(@all) . '' . do {for ($all[0]) {s/<.*?>//g; s/\s+/ /g; s/^\s//g}; shift(@all)}) while (@all);

    Konkret würde ich gern die while-Schleife weghaben...Also das die Email-Adressen und deren Bezeichner gleich in einem Feld geschrieben werden...

    »»

    Na, wer knackt die Nuss?

    Leider nicht geknackt, aber trotzdem ganz amuesant. :-)

    Calocybe

    P.S. Fuer's Archiv: Leider entfernt der Schwanzabschneider die Pipe-Zeichen (der vertikale Strich), die hier drin vorkommen. Koennte also stellenweise etwas sinnlos wirken.

    1. Hi,

      Mmh... hast Du diesen Code getestet? Irgendwie finde ich ihn recht seltsam, und die Ergebnisse, die er liefert, sind vermutlich nicht das, was Du Dir erhofft hast.

      »
      In der Tat hab ich den Code etwas gekuerzt als ich ihn aufs Forum packte und dabei etwas falsch gemacht :(
      Das Orginal hat die RegExp in eine if()-Schleife, die in der While-Schleife ist. Ausserdem hatte ich einer Variable den Wert von $2 vor dem Push gegeben und dann diese Variable gepusht..

      Schande auf mein Haupt, das hatte ich voellig verpennt, das das etwas kritisch ist...

      Dein Loesung ist aber cool!

      Aber bei der Erstetzung
        s/<.*?>//g;  (anstelle von s/<([^>]\s+)*>//g; )
      hab ich bedenken, da im String ja noch immer Zeilenumbruehe vorkommen koennten, die dort nicht einen Strich durch die Rechnung machen?
      Jedenfalls sagt mir das die Perldoc (link:http://www.rrze.uni-erlangen.de/webadm/perl/pod/perlre.html) ?

      Ciao,
        Wolfgang

      1. Hi again!

        Aber bei der Erstetzung
          s/<.*?>//g;  (anstelle von s/<([^>]\s+)*>//g; )
        hab ich bedenken, da im String ja noch immer Zeilenumbruehe vorkommen koennten, die dort nicht einen Strich durch die Rechnung machen?

        Ah ja, das habe ich uebersehen. Aber Du hast die Loesung in Deinem ersten RegExp ja schon selber gezeigt, einfach den s Modifier anhaengen, also
          s/<.*?>//gs;
        Oder hab ich das falsch verstanden? (Hatte mir erst gestern durchgelesen, was der bedeutet.)

        So long