mixmastertobsi: preg_replace Problem

Hallo,

ich scheiter gerade an einer regular expression Anweisung.

Der Text ist wie folgt.

TEXTTEXT {{ ZITAT 1 {{ ZITAT 2 }} }} ENDE TEXT

Nun soll das zwischen {{ und }} in einen DIV-Container gepackt werden.

Meine Anweisung war wie folgt

preg_replace("/{{([^\}\{]*)}}/i","<div class=\"quote\">$1</div>",$text);

aber ich bekomme nur einmal die "Klammer" ersetzt

TEXTTEXT {{ ZITAT 1 <div class="quote"> ZITAT 2 </div> }} ENDE TEXT

eiegntlich sollte es so ausehen.

TEXTTEXT <div class="quote"> ZITAT 1 <div class="quote"> ZITAT 2 </div> </div> ENDE TEXT

Wenn ich die Ausgabe zweimal hintereinander mache, klappt es, aber das kann ja nicht die Lösung sein, denn theoretisch könnte der Text 10 solcher Klammern haben - also brauche ich 10 mal diese replace Anweisung

$text = preg_replace("/{{([^\}\{]*)}}/i","<div class=\"quote\">$1</div>",$text);
$text = preg_replace("/{{([^\}\{]*)}}/i","<div class=\"quote\">$1</div>",$text);
  1. Tach!

    TEXTTEXT {{ ZITAT 1 {{ ZITAT 2 }} }} ENDE TEXT
    

    Das sieht nach einem rekursiven Muster aus.

    preg_replace("/{{([^\}\{]*)}}/i","<div class=\"quote\">$1</div>",$text);
    

    Die geschweiften Klammern sind übrigens keine Sonderzeichen und müssen nicht maskiert werden.

    dedlfix.

    1. Danke DIR!

      Kannst Du mal bitte ein Beispiel machen, wie es in meinem Beispiel aussehen müsste?

      1. Tach!

        Kannst Du mal bitte ein Beispiel machen, wie es in meinem Beispiel aussehen müsste?

        Nee, kann ich nicht. Ich weiß nur, dass das dafür gedacht ist und hab es dir als Stichwort zur weiteren konkreten Recherche geliefert.

        dedlfix.

      2. Hallo mixmastertobsi,

        Kannst Du mal bitte ein Beispiel machen, wie es in meinem Beispiel aussehen müsste?

        Da du ja ersetzen möchtest (nicht nur die äußerste Ebene finden), wird preg_replace mit einem rekursiven regex nicht ausreichen. Das rekursive Suchmuster ermöglicht es, die äusserste Ebene zu finden, es ist aber afaik nicht möglich, in jedem Selbstaufruf mit dem captured match zu ersetzen.

        1.) Nun könntest du per preg_replace_callback (siehe Beispiel) von aussen nach innen auflösen.

        function parse_recursive($inp)
        {
          if (is_array($inp))
            $inp = '<div class="quote">'.$inp[1].'</div>';
        
          return preg_replace_callback('/{{((?>[^}{]+|(?R))*)}}/', 'parse_recursive', $inp);
        }
        
        

        Hier ein Beispiel auf eval.in und das Suchmuster auf regex101 zum probieren.

        2.) Oder wie schon angesprochen per preg_replace und while von innen nach aussen.

        $regx = '/{{([^}{]+)}}/'; $repl = '<div class="quote">$1</div>';
        
        while(($tmp = preg_replace($regx, $repl, $str)) !== $str) $str = $tmp;
        

        Noch ein Beispiel auf eval.in

        Würde eher zu Variante 2 tendieren. Viel Erfolg, Robert

    2. @mixmastertobsi @dedlfix

      Ein Problem mit solchen Fragestellungen ist auch immer, dass man erst mal definieren müsste, was bei {{ foo }} bar }} oder {{ foo {{ bar }} passieren soll.

      1. Tach!

        Ein Problem mit solchen Fragestellungen ist auch immer, dass man erst mal definieren müsste, was bei {{ foo }} bar }} oder {{ foo {{ bar }} passieren soll.

        Garbage in, garbage out - wäre eine Strategie, den Verwender in die Pflicht zu nehmen, Ordentliches zu liefern, wenn er Ordentliches zurückhaben möchte. Wenn man solche Fehler finden möchte, wird man mit Regex nicht weiterkommen. Da muss dann ein Parser ran, der in der Lage ist, ungültige Syntax zu erkennen. Das ist letzlich eine Frage von Aufwand und Nutzen unter Berücksichtigung des Ziels, ob der einfache Regex reicht oder ob es ein komplexer Parser sein muss.

        dedlfix.

        1. @@dedlfix

          Garbage in, garbage out - wäre eine Strategie

          Oft ist Garbage in, error message out die bessere.

          LLAP 🖖

          --
          „Wenn du eine weise Antwort verlangst, musst du vernünftig fragen.“ —Johann Wolfgang von Goethe
          1. Tach!

            Garbage in, garbage out - wäre eine Strategie

            Oft ist Garbage in, error message out die bessere.

            Oft ist es besser, nicht ohne Anwendungsfall die Strategien zu bewerten (=> anwendungsfallorientiert statt strategieorientiert).

            dedlfix.

  2. Du suchst den Schnauzer? Oder das hier, damit gehts auch ;)

    1. Du suchst den Schnauzer? Oder das hier, damit gehts auch ;)

      Hast du die richtige Seite verlinkt? Ich sehe da spontan keinen wirklichen Zusammenhang zur Fragestellung. :)

  3. @@mixmastertobsi

    TEXTTEXT {{ ZITAT 1 {{ ZITAT 2 }} }} ENDE TEXT
    

    Nun soll das zwischen {{ und }} in einen DIV-Container gepackt werden.

    Ich sehe da zwei Fehler:

    1. div? Für Zitate ist blockquote (Block) bzw. q (Inline) da.

    2. Ein regulärer Ausdruck ist für solch einen Parser das falsche Werkzeug.

    LLAP 🖖

    --
    „Wenn du eine weise Antwort verlangst, musst du vernünftig fragen.“ —Johann Wolfgang von Goethe
    1. Hallo,

      das DIV war ja nur ein Beispiel. Kann gerne auch blockquote sein.

      Aber warum ist hier ein regulärer Ausdruck der falsche ANsatz?

      1. Würde mich auch interessieren, welche fertige Library dafür geeignet ist. Wenn man es ohne Regex mit der Hand programmiert, wird es sicherlich fixer, aber auf jeden Fall ist der Code dann länger.

        Deine Regex funktioniert aber schlecht. Sollte

        TEXTTEXT {{ ZITAT 1 { ZITAT FORTSETZUNG }} ENDE TEXT
        

        nicht

        TEXTTEXT <q> ZITAT 1 { ZITAT FORTSETZUNG </q> ENDE TEXT
        

        ergeben? Tut es aber nicht.

        Ich habe es mal mit einer lookahead-Assertion versucht. Das Pattern matcht zuerst ein {{ und macht die Assertion, dass darauf kein weiteres {{ folgt. Dann matcht es beliebige Zeichen, aber non-greedy, und ein }}. Der non-greedy Match sorgt dafür, dass .* bei einem geschachtelten Zitat das innere }} nicht einschließt.

        Das ganze legst Du in eine Schleife und machst es so lange, wie etwas ersetzt wird. Die Spielerei mit $i soll nur anzeigen, in welcher Reihenfolge er die Zitate ersetzt. Das musst Du natürlich für den realen Einsatz entfernen.

        $text = "TEXTTEXT {{ ZITAT 1 {{ ZITAT 2 }} Z1 }} {{ ENDE }} TEXT";
        
        $i=1;
        while (true) {
           $textNeu = preg_replace("//{{(?!.*{{)(.*?)}}/i","<q_$i>$1</q_$i>",$text);
           if ($text === $textNeu) break;
           $text = $textNeu;
           $i++;
        }
        echo $text;
        

        Eine Lösung ohne Regex wäre wohl ein handgemachter Parser, der von links nach rechts durchgeht und einen Quote-Stack bildet, oder die Nutzung einer PHP Library, die ich nicht kenne.

        Rolf