Harry: "Intelligente" RegExps

Moinmoin !

Nun brüte ich schon seit einigen Tagen über einem Problem, auf dessen ("schöne") Lösung ich einfach nicht komme. Das Problem besteht genauer gesagt in der Formulierung eines regulären Ausdrucks.
Es geht darum, in einem von einem Benutzer eingegebenen Text bestimmte markierte Teile durch HTML zu ersetzen (ähnlich wie hier im Forum <blabla>.

Beispieltext:

Dies ist ein Text mit [fett:fetter Schrift], der hier weitergeht.

Nun kann ich ja problemlos die entsprechende Stelle durch HTML-Code ersetzen (ich verwende hier PHP, aber das ist ja eigentlich egal, es geht nur um den regulären Ausdruck):

preg_replace("/[fett\s?:\s?(.*)\s?]/i", "<b>\1</b>", $text);

Das funktioniert soweit ganz gut, kritisch wird es nur, wenn solch eine Konstruktion vorkommt:

Dies ist ein [kursiv:kursiver Text, der hier [fett:fett geschrieben ist] und hier kursiv] bzw. normal weitergeht.

Wenn ich nun zuerst den fetten und dann den kursiven Teil ersetze, ist auch alles in Ordnung. Blöd wird die Sache aber, wenn zuerst versucht wird, den kursiven Teil zu ersetzen und dann den fetten.

Dann schaut das Ergebnis nämlich so aus:

Die ist ein <i>kursiver Text, der hier <b>fett geschrieben ist</i> und hier kursiv</b> bzw. normal weitergeht.

Obenstehendes Ergebnis ist aus Sicht des von mir verwendeten Ausdruckes ja durchaus korrekt. Schließlich heißt es ja, daß an der nächsten schliessenden eckigen Klammer der kursive Text zu Ende sei. Das ist aber nicht der Fall, da bei der ersten schliessenden eckigen Klammer ja eigentlich erst der der fette Text zu Ende ist.

Jetzt ist die Frage: Wie bringe ich es dem Ausdruck bei, daß er - wenn sich in seinem "Einzugsbereich" weitere X öffnende eckige Klammern befinden er gefälligst auch erst bei der X. schliessenden Klammer Schluß machen soll ?

Von mir aus kann man die ganze Sache auch gerne "anders" lösen, also nicht unbedingt mit einem RegExp. Ich würde jedoch gerne bei der aufgezeigten Struktur bleiben, d.h. ich möchte eigentlich nicht auf Lösungen wie [fett]Text[/fett] umsteigen.

Mein bisherige Lösung schaut so aus, daß ich den Text mit Hilfe mehrerer Explodes bzw. preg_split in seine Einzelteile zerlege und diese in ein n-dimensionales Array (je nach Verschachtelung) einlese, und dann alles entsprechend ersetze (ok, ich hab die Lösung noch nicht ausprobiert, bin mir aber ziemlich sicher, daß sie funktionieren würde) und je nach bedarf den Original-String aus dem Array wieder zusammensetze. Das wär halt nur ein ziemlicher Aufwand und ist für meine Begriffe auch nicht besonders elegant gelöst.

Wenn also jemand Vorschläge hat, wie man die Sache "sauber" lösen könnte oder - noch besser - gar einen passenden RegExp auf Lager hat, immer her damit :)

Ich hatte mir auch schon Gedanken gemacht, die Reihenfolge der vorzunehmenden Ersetzungen von innen nach außen festzustellen und dann die Ersetzungen entsprechend vorzunehmen, bin aber auf keine vernünftige Idee gekommen, wie man das lösen könnte, da die Struktur im Endeffekt beliebig komplex werden kann (es soll die Möglichkeit geben, außer [fett:], [kursiv:] und [link(http://irgendwas.com):] noch beliebig viele andere Formatierungen vornehmen zu können, die (prinzipiell) beliebig verschachtelt werden können) ...

Vielen Dank schonmal.

In gespannter Erwartung,

Harry

  1. Hi,

    Nun brüte ich schon seit einigen Tagen über einem Problem, auf dessen ("schöne") Lösung ich einfach nicht komme.

    das liegt daran, dass sie nicht existiert. Regular Expressions sind dafür gedacht, Muster zu erkennen und darauf zu reagieren; die Grenze ihrer Leistungsfähigkeit ist überschritten, wenn Du versuchst, Strukturen zu handhaben. Weder HTML, noch XML, noch Pseudo-Tags lassen sich mit Regular Expressions handhaben - weil Schachtelungen möglich sind.

    Das Problem besteht genauer gesagt in der Formulierung eines regulären Ausdrucks.

    Du brauchst zunächst einen Algorithmus, um dem Text die Schachtelung zu entnehmen. Ob Du dies zunächst in eine Objektstruktur umwandelst, Event-getrieben auf Start- und Ende-Markierungen reagierst oder völlig anders vorgehst, bleibt Dir überlassen. Regular Expressions kannst Du aber lediglich als Hilfe verwenden (_falls_ Du sie dazu brauchst), jedoch nicht als alleiniges Mittel.

    Cheatah

    1. Hi

      Du brauchst zunächst einen Algorithmus, um dem Text die Schachtelung zu entnehmen. Ob Du dies zunächst in eine Objektstruktur umwandelst, Event-getrieben auf Start- und Ende-Markierungen reagierst oder völlig anders vorgehst, bleibt Dir überlassen. Regular Expressions kannst Du aber lediglich als Hilfe verwenden (_falls_ Du sie dazu brauchst), jedoch nicht als alleiniges Mittel.

      Sowas ähnliches hatte ich schon befürchtet ...
      Wär ja auch zu schön gewesen :(

      Danke,

      Harry

  2. Hi,

    Von mir aus kann man die ganze Sache auch gerne "anders" lösen, also nicht unbedingt mit einem RegExp. Ich würde jedoch gerne bei der aufgezeigten Struktur bleiben, d.h. ich möchte eigentlich nicht auf Lösungen wie [fett]Text[/fett] umsteigen.

    Ist aber IMHO das einfachste. Erlaube doch den Benutzern, bestimmte HTML-Tags einzugeben und filtere dann die nicht-erlaubten raus.
    Ist für dich einfacher zu programmieren und für die User auch nicht schwerer zu merken, als irgendwelche [:fett]-Konstukte, und wer schon HTML kann braucht sich gar nix zu merken.

    wunderwarzenschwein

  3. Moinmoin !

    Mahlzeit!

    Nun brüte ich schon seit einigen Tagen über einem Problem, auf dessen ("schöne") Lösung ich einfach nicht komme. [...]

    Wie Cheatah schon sagte, bei verschachtelten Strukturen können reguläre Ausdrücke zwar helfen, sie reichen aber nicht aus. Wenn Du ein paar Anregungen brauchst, gehe mal zu http://www.phpbb.com und lade dir die Forumskripte herunter. Interessant für dich dürften vor allem die Module sein, die sich mit bbCode beschäftigen (dem HTML Ersatz für Textauszeichnungen in Forumsbeiträgen).

    Sicher keine optimale Lösung, aber IMHO ist es immer interessant und lehrreich zu sehen, wie andere an ein ähnliches/gleiches Problem herangehen.

    HTH,
    -=tmc=-

  4. Soso ...

    nach ein paar weiteren Stunden angestrengten Nachdenkens freue ich mich die absolut simple Lösung zu präsentieren ... *grrrrrrrrr*

    $text = "[x:xxx]hiernix[fett:fettfett[kursiv:kursivvvv][link(http://www.ilo.de):einlink]fett]wiedernix";
    #----------------------------------------------------------------
    while(preg_match("/([[^[^]]*])/", $text))
    { $t = preg_split("/([[^[^]]*])/", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
     for($i=0; $i<count($t); $i++)
     {
      $t[$i] = preg_replace("/[fett:(.*)]/i", "<b>\1</b>", $t[$i]);
      $t[$i] = preg_replace("/[kursiv:(.*)]/i", "<i>\1</i>", $t[$i]);
      $t[$i] = preg_replace("/[link\s?((.*))\s?:(.*)]/i", "<a href="\1">\2</a>", $t[$i]);
      $t[$i] = preg_replace("/[x:(.*)]/i", "X\1X", $t[$i]);
     }
     $text = implode("", $t);
    }
    #----------------------------------------------------------------
    echo $text;

    Ergibt:
    XxxxXhiernix<b>fettfett<i>kursivvvv</i><a href="http://www.ilo.de">einlink</a>fett</b>wiedernix

    So wie es sein soll ...

    Ciao,

    Harry
     (sauer, daß er dafür solange gebraucht hat)

    1. Jo mei ...

      nach ein paar weiteren Stunden angestrengten Nachdenkens freue ich mich die absolut simple Lösung zu präsentieren ... *grrrrrrrrr*

      Und dieses Ergebnis hat einen dicken Bug: Das Script verfängt sich in einer Endlosschleife, wenn nicht alle eckigen Klammern ersetzt werden können ...
      Für's Archiv die korrigierte Version:

      if(substr_count($text, "]")<substr_count($text, "["))
       $wieoft = substr_count($text, "]")
      else
       $wieoft = substr_count($text, "[")

      $count = 0;
      while(preg_match("/([[^[^]]*])/", $text) && $count<$wieoft)
      { $t = preg_split("/([[^[^]]*])/", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
        for($i=0; $i<count($t); $i++)
        {
          # Hier Ersetzungen vornehmen
        }
        $text = implode("", $t);
        $count++;
      }

      Ciao,

      Harry
       (der echte)
       (überall Plagiate, tsts ...)