[Dresden-pm] Reguläre Ausdrücke - Suchen und Ersetzen von Worten

Aristoteles Pagaltzis pagaltzis at gmx.de
Fr Aug 15 23:43:04 PDT 2008


* Thomas Rittsche <thomas.rittsche at gmail.com> [2008-08-12 16:55]:
> Ich muss wirklich erst mal schauen, ob ich das nutzen kann,
> ohne dass die Performance leidet.

Das ist generell die falsche Frage. Wenn dein grundsätzlicher
Ansatz schnell genug ist, wird keine der Lösungen redenswert
langsam sein.

> Wenn Langeweile in Kopenhagen aufkommt, würde ich mich noch
> über ein paar Erläuterungen freuen.

Da ist es mir kein bisschen langweilig geworden. Aber ich bin
jetzt wieder zurück… also:

>     my %insertion;
>     for my $kw ( @keywords ) {
>         my ( $rx ) = map qr/$_/i, quotemeta $kw;
>         while ( $text =~ /$rx/g ) {

Bis hierhin sollte das alles ziemlich klar sein; das ist alles
08/15-Perl. Eine Schleife geht über die Schlüsselwörter, die
jeweils mit `qr//` kompiliert werden, und dann geht eine innere
Schleife über die einzelnen Treffer des Schlüsselwortes im Text.

>             push    @{ $insertion{ $-[0] } }, '<a href="#">';
>             unshift @{ $insertion{ $+[0] } }, '</a>';
>         }
>     }

Hier kommt der interessante Teil. Der Kern sind das `$-[0]` und
das `$+[0]`. Das sind Zugriffe auf zwei komisch benannte Arrays
mit spezieller Funktion. Auszugweise aus `perldoc perlvar`:

    @LAST_MATCH_END
    @+
        This array holds the offsets of the ends of the last
        successful submatches in the currently active dynamic
        scope. $+[0] is the offset into the string of the end of
        the entire match. This is the same value as what the
        "pos" function returns when called on the variable that
        was matched against.

    @LAST_MATCH_START
    @-
        $-[0] is the offset of the start of the last successful
        match.

Dh. wenn der Regex einen Treffer findet, steht im nullten Element
von `@-` der Index in den String für den Anfang des Gesamttreffers
und in `@+` der Index des ersten Zeichens nach dem Gesamttreffer.

Und vor genau diesen Stellen willst du öffnende und schliessende
`<a>`-Tags einfügen!

Also notiert der Code in einem Hash diese Positionen. Die
Position dient als Schlüssel, der Inhalt ist ein Array von
Strings, wo die Tags draufgeschoben werden. Da das öffnende Tag
am Schluss möglichst weit nach rechts kommen soll, kommt es mit
`push` aufs Array. Das schliessende Tag soll möglichst weit nach
links, also kommt es mit `unshift` rein.

Nach dem Ende der äußeren Schleife steht also in `%insertion`,
an welcher Position im String jeweils welche Liste von Tags
eingefügt werden muß. Und das tut der Code dann auch:

>     my $result    = '';
>     my $last_offs = 0;
>     for my $offset ( sort { $a <=> $b } keys %insertion ) {

Die Schlüssel von `%insertion` sind ja wie gesagt Indizes von
Einfügepunkten. Diese Indizes werden hier numerisch sortiert,
damit sie nacheinander abgearbeitet werden können.

>         $result
>             .= substr( $text, $last_offs, $offset - $last_offs )

An jedem Einfügepunkt entnimmt der Code dann den Textabschnitt
zwischen dem vorigen und dem aktuellen Punkt, und hängt ihn ans
Ergebnis, …

>             .  join( '', @{ $insertion{ $offset } } );

… gefolgt von all den Tags, die zu diesem Einfügepunkt gehören.

>         $last_offs = $offset;
>     }

Nach jedem Durchgang muß natürlich noch festgehalten werden, wo
der jeweils vorige Einfügepunkt war.

>     $result .= substr( $text, $last_offs );

Und zum Schluß wird der Abschnitt hinter dem letzten Einfügepunkt
auch noch mitgenommen.

Ganz einfach.

Gruß,
-- 
Aristoteles Pagaltzis // <http://plasmasturm.org/>


Mehr Informationen über die Mailingliste Dresden-pm