Dieter: Grosses preg_replace - Problem...

Hallo,

leider komme ich mit meinem bescheidenen Regexp-Kenntnissen seit Tagen nicht weiter.
Ich möchte einen Stringbereich zwischen zwei Begrenzungswerten (in diesem Fall doppelte eckige Klammern) austauschen, sowie den ursprünglichen Inhalt in einem Array speichern.
Da sich der Inhalt (der auszutauschende) noch durch eine Regel (Trennzeichen "|") unterscheiden kann, wollte ich zwei nachfolgend arbeitende Pattern verwenden, scheitere aber
leider schon an der Regel:

$pfad = "PFAD";
$pattern = '/[[(.*)|(.*)]]/eisU';
$ersatz = '<a href="'.$pfad.'/\1">\2</a>';
$string = preg_replace($pattern, $ersatz, $string);

Folgendes Ergebnis soll rauskommen:

Originalstring: Das ist ein Testsatz. Dieser Testsatz ist in einem [[String]] gespeichert und soll via preg_replace [[Arbeit|bearbeitet]] werden - leider sind aber meine bisherigen [[Bemühung|Bemühungen]] recht erfolglos.
Ergebnis: Das ist ein Testsatz. Dieser Testsatz ist in einem <a href="PFAD/String">String</a> gespeichert und soll via preg_replace <a href="PFAD/Arbeit">bearbeitet</a> werden - leider sind aber meine bisherigen <a href="PFAD/Bemühung">Bemühungen</a> recht erfolglos.

Nur leider bekomme ich das nicht hin. Mit dem Suchmuster bekomme ich folgendes Ergebnis:
Das ist ein Testsatz. Dieser Testsatz ist in einem <a href="PFAD/String]] gespeichert und soll via preg_replace [[Arbeit">bearbeitet</a> werden - leider sind aber meine bisherigen <a href="PFAD/Bemühung">Bemühungen</a> recht erfolglos.

Ich komme einfach nicht weiter und möchte daher um eure Mithilfe bitten:
a) wo ist mein Fehler bzw. was muß ich an der regexp verändern (bitte wenn möglich auch mit Erklährung, damit ich auch verstehe was falsch gelaufen ist)
b) wie kann ich zudem den Wert "\1" in ein Array bekommen ?

MfG
Dieter

  1. n'abend,

    $pfad = "PFAD";
    $pattern = '/[[(.*)|(.*)]]/eisU';

    du erwartest »[[etwas|anderes]]«, den Fall »[[etwas]]« hast du hier gar nicht berücksichtigt. Das ist auch der Grund, warum dein Test misslingt. Siehe unten

    Schau dir bitte die Pattern Modifiers noch mal genauer an.
    »e« bedeutet, dass der replace-string als PHP interpretiert werden soll - willst du das hier wirklich?
    »s« bedeutet, dass auch Zeilenumbrüche durch . (DOT_ALL) erkannt werden sollen - willst du das hier wirklich? Kann / darf es Zeilenumbrüche in deinen [[blubb]] Tags geben?
    »U« macht an dieser Stelle mehr oder weniger Sinn. UNGREEDY bewirkt, dass DOT_ALL nur soviel liest, wie es minimal lesen kann.

    $ersatz = '<a href="'.$pfad.'/\1">\2</a>';

    hast du dir durchgelesen, wie sich das mit dem Replace-String in preg_replace() so verhält?

    $string = preg_replace($pattern, $ersatz, $string);

    Originalstring: Das ist ein Testsatz. Dieser Testsatz ist in einem [[String]] gespeichert und soll via preg_replace [[Arbeit|bearbeitet]] werden - leider sind aber meine bisherigen [[Bemühung|Bemühungen]] recht erfolglos.

    Dein RegeExp matcht hier (zufällig). Aber nicht ganz so, wie du dir das vorstellst:

    /[[(.*)|(.*)]]/
    \0 ist dabei »[[String]] gespeichert und soll via preg_replace [[Arbeit|bearbeitet]]«
    \1 ist dabei »String]] gespeichert und soll via preg_replace [[Arbeit«
    \2 ist dabei »bearbeitet«

    Ich komme einfach nicht weiter und möchte daher um eure Mithilfe bitten:
    a) wo ist mein Fehler bzw. was muß ich an der regexp verändern (bitte wenn möglich auch mit Erklährung, damit ich auch verstehe was falsch gelaufen ist)

    Du kannst Bereiche mit einem ? optional machen:
    /[[(.*)(|(.*))?]]/ -- achtung, die Indexe in deinem Replace-String werden sich hierbei ändern.

    Der Punkt-Operator (DOT_ALL) matcht jedes Zeichen. Willst du nicht vielleicht nur Buchstaben (und ggf. Zahlen) matchen? Etwa so:
    /[[([a-zA-Z0-9]+)(|([a-zA-Z0-9]+))?]]/

    Du kannst auch, wenn du nicht mit einer Callback-Funktion arbeiten willst (was du aber wegen unterschiedlicher Indexe vermutlich machen willst) zwei separate RegExps benutzen, einer der [[string]] matcht und einer der [[string|string2]] matcht.

    b) wie kann ich zudem den Wert "\1" in ein Array bekommen ?

    du könntest dir mal preg_replace_callback() anschauen.

    weiterhin schönen abend...

    --
    #selfhtml hat ein Forum?
    sh:( fo:# ch:# rl:| br:> n4:& ie:{ mo:} va:) de:] zu:} fl:( ss:? ls:[ js:|
  2. (Hallo|Hi(ho)|Tag) Dieter,

    Ich möchte einen Stringbereich zwischen zwei Begrenzungswerten (in diesem Fall doppelte eckige Klammern) austauschen, sowie den ursprünglichen Inhalt in einem Array speichern.

    Das sieht auf den ersten Blick wie Links in MediaWiki-Quellcodes aus. Also
    könnte es sich lohnen mal im MediaWiki-Quellcode nachzuschauen. Irgendwo
    dort sollte sowohl der passende RegEx, als auch die dazugehörige
    Ersetzungsstrategie zu finden sein. ;-)

    Naja, ich mal schnell was zusammengestrickt, was zumindest mit
    deinem Testsatz zurechtkommt:

      
    $haystack = 'Das ist ein Testsatz. Dieser Testsatz ist in einem [[String]]  
    gespeichert und soll via preg_replace [[Arbeit|bearbeitet]] werden -- leider  
    sind aber meine bisherigen [[Bemühung|Bemühungen]] recht erfolglos.';  
      
    $pfad = 'PFAD/';  
    $pcre = '/\[\[([^\x7c\]]+)(\x7c(.+))?\]\]/e';  
    $rpl = 'sprintf(  
        \'{a href="' . $pfad . '%s"}%s{/a}\',  
        "$1",  
        ( "$3" ? "$3" : "$1" )  
    )';  
    $out = preg_replace($pcre, $rpl, $haystack);  
    
    

    a) wo ist mein Fehler bzw. was muß ich an der regexp verändern (bitte wenn möglich auch mit Erklährung, damit ich auch verstehe was falsch gelaufen ist)

    Das Suchmuster $pcre besteht aus den umgebenden doppelten eckigen Klammern.
    Darin befinden sich zwei Subpattern in runden Klammern. Das erste
    "([^\x7c]]+)" passt auf mindestens ein oder eine beliebige Zahl von
    Zeichen, die nicht der vertikale Trennstrich "\x7c" und auch nicht eine
    schließende eckige Klammer "]" darstellen.

    Das zweite "(\x7c(.+))?" passt auf die Zeichenfolge "|" plus ein oder
    mehrere beliebige Zeichen. Das Muster "(.+)" ist als drittes
    Subpattern nötig, weil wir ja beim Ersetzen nur den Text nach dem
    senkrechten Strich brauchen.

    Der Modifikator "/e" sorgt dafür, dass der Ersetzen-String $rpl als
    PHP-Code ausgeführt wird. Das ist nötig, da wir ja eine Fallunterscheidung
    treffen müssen: Ist das Subpattern Nummer 3 gefunden worden, wird
    es im Ausgabe-String eingesetzt, wenn nicht, übernimmt Subpattern 1
    diese Aufgabe.

    Man könnte den Ersetzen-String auch anders basteln. Ich habe für die
    Fallunterscheidung den Konditionaloperator
    ("is_wahr ? dann_nimm_meinen_wert : nö,_dann_nimm_meinen_wert")
    und sprintf() missbraucht. Glücklicherweise wertet PHP leere Zeichenketten als
    "nicht TRUE", deshalb gibt
    ( "$3" ? "$3" : "$1" )
    den Wert von "$1" zurück, wenn das Subpattern 3 nicht gepasst hat.

    Das Ganze ist eher als Prototyp zu verstehen. Vor allem solltest du das
    Verhalten beim Auftreten der Zeichen "|", "[", "]" und eventuell noch
    anderer für dein Script bedeutsamer Steuerzeichen testen, damit nicht
    "kaputte Pfadangaben" nach der Ersetzung herauskommen. Im Moment frisst
    das Suchmuster auch Zeichenketten, die über mehrere Zeilen gehen. Am
    besten machst du dir erstmal klar, welche Zeichen im Pfadbestandteil
    überhaupt erlaubt sind. Eventuell müssen sie auch noch URL-enkodiert
    werden.

    Auch kann man mit preg_replace_callback und einer anonymen Funktion
    übersichtlicheren Quellcode und ein schnelleres Script produzieren.

    b) wie kann ich zudem den Wert "\1" in ein Array bekommen ?

    Das ist schwierig. Mit preg_replace_callback() kommst du zwar an den
    Wert, aber du kannst ihn nur über eine globale Hilfs-Variable an das Script
    weitergeben. Sauberer wäre aus meiner Sicht eine Lösung mit preg-match(),
    dessen Offset-Parameter und PREG_OFFSET_CAPTURE in einer Schleife.

    ... oder du teilst mit [preg_split() den Text in ein Array aus gewöhnlichen
    Textstücken und Links, ersetzt dann alle Links durch den passenden
    HTML-Quelltext und schraubst das Array am Schluss wieder zu einer
    Zeichenkette zusammen.

    MffG
    EisFuX

    1. (Hallo|Hi(ho)|Tag) EisFuX,

      b) wie kann ich zudem den Wert "\1" in ein Array bekommen ?

      Das ist schwierig. Mit preg_replace_callback() kommst du zwar an den
      Wert, aber du kannst ihn nur über eine globale Hilfs-Variable an das Script
      weitergeben.

      Was nicht ganz stimmt, das gilt nur für anonyme Funktionen, also die, die mit
      create_function() erzeugt wurden.

      Du kannst aber preg_replace_callback() auch eine beliebige selbstdefinierte
      Funktion oder eine Methode eines Objekts übergeben.

      MffG
      EisFuX