bell: Im String alles mit (xxx) ersetzten

Hallo,

ich habe eine Frage, und zwar habe ich diverse Strings mit Klammern zB.

[...]Wie ich(oder du) das finden ist doch egal[...]

ich habe es bisher mit einem "str_replace" ab zu fangen, doch die Varianten sind schon sehr zahlreich.

Was kann ich machen?

  1. @@bell

    ich habe eine Frage, und zwar habe ich diverse Strings mit Klammern zB.

    [...]Wie ich(oder du) das finden ist doch egal[...]

    Vor ( gehört ein Leerzeichen. (Vor [ und nach ] auch.)

    ich habe es bisher mit einem "str_replace" ab zu fangen

    ??

    doch die Varianten sind schon sehr zahlreich.

    Was kann ich machen?

    Dein Problem zu beschreiben wäre ein Anfang.

    LLAP 🖖

    -- „Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“ —Kurt Weidemann
    1. Hallo Gunnar,

      Vor ( gehört ein Leerzeichen. (Vor [ und nach ] auch.)

      Es besteht eine relevante Wahrscheinlichkeit dafür, dass die Ersteller der zu parsenden Texte das nicht wissen oder drauf gepfiffen haben.

      Heißt: Eine Funktion, die eingeklammerte Textteile sucht und irgendwas damit macht, sollte NICHT voraussetzen, dass die Regeln zur korrekten Leerzeichenverwendung eingehalten wurden.

      Spannend wird es vor dann, wenn im zu verarbeitenden Text die Klammerschachtelung falsch ist. Also zum Beispiel: Der Mann (bekleidet mit einer [nicht löchrigen) Jeans] steht hinter dem Haus.

      Mein Ansatz wäre, nach geschlossenen Klammern zu suchen (z.B. mit p = strcspn($sting, ")}]") und von dort aus rückwärts nach der passenden öffnenden Klammer (z.B. mit strrpos). Hat man auf diese Weise ein geklammertes Fragment gefunden, muss man noch prüfen ob es einer der öffnenden Klammern enthält; wenn ja, liegt ein Schachtelungsfehler vor. Wenn nicht, kann man mit dem Fragment tun, was immer nötig ist, und zwar so, dass das gefundene Klammerpaar nachher nicht mehr im Sting steht. Und danach fängt man von vorn an, solange, wie etwas gefunden wird.

      Wenn geschachtelte Klammern nicht relevant sind und man das Problem falscher Schachtelung ignorieren kann (oder will), reicht auch

      $matches = ARRAY(); $success = preg_match("\[.*?]|\(.*?\)|\{.*?\}", $sting, $matches, PREG_OFFSET_CAPTURE) foreach ($matches[0] as $match) { echo "Finde $match[0] an Position $match[1]\n"; }

      Das Pattern sucht Paare aus [], () und {}. Die Zeichen zwischen den Paaren werden non-greedy übersprungen, weil andernfalls für "[...] Hallo [...]" der ganze Sting gematcht würde. Die Treffer stehen im Index 0 des $matches Array, mit Inhalt und Startposition. Der genaue Aufbau von $matches ist relativ kompliziert und kann im Handbuch nachgelesen werden, für die vorliegende Regex gibt es aber nur den Eintrag $matches[0].

      Rolf

      -- sumpsi - posui - clusi
      1. @@Rolf B

        Vor ( gehört ein Leerzeichen. (Vor [ und nach ] auch.)

        Es besteht eine relevante Wahrscheinlichkeit dafür, dass die Ersteller der zu parsenden Texte das nicht wissen oder drauf gepfiffen haben.

        Unzweifelhaft. Ich wollte bell nur mitgeben, wie es richtig ist.

        Mein Ansatz wäre, nach geschlossenen Klammern zu suchen …

        Meiner nicht. Mein Ansatz ist, bell erstmal die Anforderung beschreiben zu lassen und erst danach zu überlegen, wie diese denn umzusetzen wären. Hier mit einer technischen Lösung vorzupreschen ohne überhaupt die Anforderung zu kennen, ist wie immer kaum zielführend.

        LLAP 🖖

        -- „Wer durch Wissen und Erfahrung der Klügere ist, der sollte nicht nachgeben. Und nicht aufgeben.“ —Kurt Weidemann
      2. Spannend wird es vor dann, wenn im zu verarbeitenden Text die Klammerschachtelung falsch ist. Also zum Beispiel: Der Mann (bekleidet mit einer [nicht löchrigen) Jeans] steht hinter dem Haus.

        Mein Ansatz wäre, nach geschlossenen Klammern zu suchen (z.B. mit p = strcspn($sting, ")}]") und von dort aus rückwärts nach der passenden öffnenden Klammer (z.B. mit strrpos). Hat man auf diese Weise ein geklammertes Fragment gefunden, muss man noch prüfen ob es einer der öffnenden Klammern enthält; wenn ja, liegt ein Schachtelungsfehler vor. Wenn nicht, kann man mit dem Fragment tun, was immer nötig ist, und zwar so, dass das gefundene Klammerpaar nachher nicht mehr im Sting steht. Und danach fängt man von vorn an, solange, wie etwas gefunden wird.

        Ich würde das anders lösen, hier mein (ungetester) Entwurf eines fehlertoleranten Parsers. Wie üblich, habe ich den Parser aufgeteilt in eine lexikalische und eine syntaktische Phase. Der Lexer zerlegt den Eingabestring in einen Token-Stream: Ein Token ist entweder eine runde oder eckige Klammer, oder ein Text ohne Vorkommen von Klammern:

        <?php function tokenize(string $input) : Generator { $buffer = ''; for ($i = 0; $i < mb_strlen($input); $i++) { $char = mb_substr($input, $i, 1); switch ($char) { case '(': case ')': case '[': case ']': yield $buffer; $buffer = ''; yield $char; break; default: $buffer .= $char; } } yield $buffer; }

        Der Parser bekommt einen Token-Stream und baut einen Stack auf: Immer wenn eine öffnende Klammer gelesen wird, wird sie auf den Stack gelegt. Wenn eine schließende Klammer gelesen wird, wird überprüft ob oben auf dem Stack eine passende öffnende Klammer liegt. Falls ja, wird das Element vom Stack genommen. Falls der Stack leer ist oder die schließende Klammer nicht zur öffnenden Klammer passt, wird ein Parser-Fehler generiert. Wenn ein Textfetzen ohne Klammern gelesen wird, wird er zusammen mit seiner Verschachtelungstiefe ausgegeben. Wenn alle Token durchlaufen wurden und der Stack noch ungeschlossende Klammern enthält, wird für jede der Klammern ein Parser-Fehler produziert.

        function parse(Generator $tokens) : Generator { $stack = new SplStack(); for ($tokens as $token) { switch ($token) { case '(': case '[': $stack->push($token); break; case ')': if ($stack->count() === 0) { yield new Expcetion("Unmatched closing round bracket.") } elseif ($stack->top() !== '(') { yield new Exception("Expected closing square bracket, but found closing round bracket.") } else { $stack->pop(); } break; case ']': if ($stack->count() === 0) { yield new Expcetion("Unmatched closing square bracket.") } elseif ($stack->top() !== '[') { yield new Exception("Expected closing round bracket, but found closing square bracket.") } else { $stack->pop(); } break; default: yield [$token, $stack->count()] } } while ($item = $stack->pop()) { switch ($item) { case '(': yield new Expection("Unmatched opening round bracket."); break; case '[': yield new Expection("Unmatched opening quare bracket."); break; } } }

        Mit fehlertolerant meine ich, dass der Parser auch nach einem Parser-Fehler weitermacht so gut er kann.

        1. Hallo 1unitedpower,

          schöner Einsatz für Generatoren 😀

          Rolf

          -- sumpsi - posui - clusi
        2. Hello,

          schönes Beispiel.

          Wärst Du bereit dazu, es noch etwas ausführlicher zu erklären? Dann könnten wir es ins Wiki für PHP unter dem Abschnitt "Generatoren" aufnehmen.

          So könnten sich dann nach und nach interessante Anwendungsbeispiele ansammeln ;-)

          Glück Auf
          Tom vom Berg

          -- Es gibt nichts Gutes, außer man tut es!
          Das Leben selbst ist der Sinn.
          1. Wärst Du bereit dazu, es noch etwas ausführlicher zu erklären? Dann könnten wir es ins Wiki für PHP unter dem Abschnitt "Generatoren" aufnehmen.

            Ein Parser ist zu umfangreich um als Einführungsbeispiel zu dienen. Ich würde kleiner anfangen und zeigen, wie man ein paar der herkömmlichen Array-Funktionen mit Generatoren nachbauen kann. Ein wenig Inspiration:

            Vorab eine kleine Helferfunktion:

            function log(iterable $source) : void { foreach ($source as $value) { echo "$value "; } } log([1,2,3]); // 1 2 3

            Zwei iterables aneinander ketten:

            function concat(iterable $first, iterable $second) : Generator { foreach ($first as $key => $value) { yield $key => $value; } foreach ($second as $key => $value) { yield $key => $value; } } log(concat([1,2,3], [4,5,6])); // 1 2 3 4 5 6

            Ein iterable filtern:

            function filter(callable $f, iterable $source) : Generator { foreach ($source as $key => $value) { if ($f($value)) { yield $key => $value; } } } log(filter(function ($n) { return $n mod 2 === 0 }, [1,2,3,4,5,6]); // 2 4 6

            Jedes Element aus einem iterable auf ein neues Element abbilden:

            function map(callable $f, iterable $source) : Generator { foreach ($source as $key => $value) { yield $key => $f($value); } } log(map(function ($n) { return $n mod 2; }, [1,2,3,4,5,6])); // 1 0 1 0 1 0

            Werte akkumulieren:

            function fold(mixed $start, callable $f, iterable $source) : mixed { $accumulator = $start; foreach ($source as $key => $value) { $accumulator = $f($accumulator, $value); } return $accumulator; } log(fold(0, function ($a, $b) { return $a + $b; }, [1,2,3,4,5,6])); // 21

            Werte akkumulieren und Zwischenschritte ausgeben:

            function scan(mixed $start, callable $f, iterable $source) : Generator { $accumulator = $start; foreach ($source as $key => $value) { $accumulator = $f($accumulator, $value); yield $key => $accumulator; } } log(fold(0, function ($a, $b) { return $a + $b; }, [1,2,3,4,5,6])); // 1 3 6 10 15 21
  2. Tach!

    ich habe eine Frage, und zwar habe ich diverse Strings mit Klammern zB.

    [...]Wie ich(oder du) das finden ist doch egal[...]

    ich habe es bisher mit einem "str_replace" ab zu fangen, doch die Varianten sind schon sehr zahlreich.

    Finde die Position der öffnende Klammer. Finde die Position der nächsten schließenden Klammer. Erstell einen neuen String ohne den Klammern-Teil. Mach das solange in einer Schleife, bis keine Klammern mehr zu finden sind.

    Außerdem gibt es reguläre Ausdrücke, mit denen Muster gesucht werden können.

    In beiden Fällen ist zu beachten, dass das Vorgehen bei verschachtelten Klammern versagt oder besondere Beachtung benötigt.

    dedlfix.

    1. Hello,

      Finde die Position der öffnende Klammer. Finde die Position der nächsten schließenden Klammer. Erstell einen neuen String ohne den Klammern-Teil. Mach das solange in einer Schleife, bis keine Klammern mehr zu finden sind.

      Passt noch nicht ganz.

      Merke Dir auf der Suche nach der schließenden Klammer die Position der letzten öffnenden Klammer links davon. D. h., wenn auf der Suche nach der nächsten schließenden Klammer eine weitere öffnende gefunden wird, gilt deren Position als gemerkte.

      Glück Auf
      Tom vom Berg

      -- Es gibt nichts Gutes, außer man tut es!
      Das Leben selbst ist der Sinn.
      1. Passt noch nicht ganz.

        dedlfix erwähnte ja schon, dass verschachtelte Klammern ein Problemfall sind.

        Merke Dir auf der Suche nach der schließenden Klammer die Position der letzten öffnenden Klammer links davon. D. h., wenn auf der Suche nach der nächsten schließenden Klammer eine weitere öffnende gefunden wird, gilt deren Position als gemerkte.

        Es reicht dann sich die jeweils zuletzt gelesene öffnende Klammer zu merken, du brauchst einen Stack, um dir alle zuvor gelesenen öffnenden Klammern zu merken, die noch nicht geschlossen wurden.

        1. Hello,

          Passt noch nicht ganz.

          dedlfix erwähnte ja schon, dass verschachtelte Klammern ein Problemfall sind.

          Merke Dir auf der Suche nach der schließenden Klammer die Position der letzten öffnenden Klammer links davon. D. h., wenn auf der Suche nach der nächsten schließenden Klammer eine weitere öffnende gefunden wird, gilt deren Position als gemerkte.

          Es reicht dann sich die jeweils zuletzt gelesene öffnende Klammer zu merken, du brauchst einen Stack, um dir alle zuvor gelesenen öffnenden Klammern zu merken, die noch nicht geschlossen wurden.

          Ja klar. Ich wusste eben nicht, wie ich das einfach beschreiben könnte. Genauso meinte ich es aber. :-)

          Glück Auf
          Tom vom Berg

          -- Es gibt nichts Gutes, außer man tut es!
          Das Leben selbst ist der Sinn.