the-FoX: Ersetzen von Platzhaltern durch Variableninhalte

Hallo alle zusammen,

ich versuche grade Design und Inhalt verstärkt voneinander zu trennen. Ich würde gerne Platzhalter in Templates benutzen, an derne Stelle später die Inhalte von den gleichnamigen Variablen erscheinen sollen. Also ich habe zum Beispiel folgendes Template:

Hallo %Name%!

Dies sollte in folgendes umgewandelt werden:

Hallo Tester!

Wenn $Name den Inhalt "Tester" besitzt. Ich versuche mich ein wneig an dem ereg_replace(), daber so ganz der Freak bin ich dann doch nicht ;-) Das wirkt alles so kryptisch. Kann man das überhaupt so machen ?

Die Engine: Smarty wollte ich nicht verwenden, da sie einfach viel zu umfangreich für mein Problem ist.

Danke schon mal!

the-FoX

  1. Hallo %Name%!

    Wenn du <NAME> schreibst, brauchst du in der RegEx nicht zu escapen. Außerdem wird der Platzhalter dann nicht angezeigt, wenn er mal nicht ersetzt werden sollte.

    $content="Tester";
    $template=preg_replace("/<NAME>/",$content,$template);

    gruß
    csx

    1. Hallo %Name%!

      Wenn du <NAME> schreibst, brauchst du in der RegEx nicht zu escapen. Außerdem wird der Platzhalter dann nicht angezeigt, wenn er mal nicht ersetzt werden sollte.

      $content="Tester";
      $template=preg_replace("/<NAME>/",$content,$template);

      gruß
      csx

      mmh..vieleicht hab ich mcih ncih so ganz klar ausgedrückt. also dein beispiel wäre jetzt für einen platzhalter mit dem namen "NAME". Wenn ich aber 100 Platzhalter davon hab, müsste ich das preg_replace 100mal schreiben. richtig ? ich möchte aber, dass einfach das, was zwischen % steht als variablename angesehen wird und versucht wird, die gleichnamige variable an dessen stelle zu setzen.

      zum beispiel:
      %name%, %vorname%, %test%,..
      soll automatisch ersetzt werden durch entsprechende variablen, die zum beispiel so gesetzt sind:
      $name="test";
      $vorname="hans";

      $test existiert nicht, also sollte dies auch nciht ersetzt werden:
      test, Hans, %test% ...

      sollte das ergebnis sein. oder so ca. :-)
      hoffe es war verständlciher.
      mmh..ich schau mir grade smarty doch mal genauer an. ist es sehr langsam ? also sind performance einbussen zu bemerken bei hohem seitenaufkommen ?

      the-FoX

  2. Moin!

    ich versuche grade Design und Inhalt verstärkt voneinander zu trennen. Ich würde gerne Platzhalter in Templates benutzen, an derne Stelle später die Inhalte von den gleichnamigen Variablen erscheinen sollen. Also ich habe zum Beispiel folgendes Template:

    Hallo %Name%!

    Dies sollte in folgendes umgewandelt werden:

    Hallo Tester!

    Wenn $Name den Inhalt "Tester" besitzt. Ich versuche mich ein wneig an dem ereg_replace(), daber so ganz der Freak bin ich dann doch nicht ;-) Das wirkt alles so kryptisch. Kann man das überhaupt so machen ?

    Es gibt einen komplizierteren Weg und einen einfachen Weg. Beide haben Vor- und Nachteile.

    Der einfache Weg zuerst:
    Dir ist sicherlich bekannt, dass man in PHP in String-Ausgaben Variablen-Ersetzungen innerhalb von doppelten Anführungszeichen machen kann:

    echo "Hallo $name!";
    gibt genau das aus, was du willst. Es ist nur das Problem, dass du den String nicht dynamisch zur Laufzeit belegen kannst. Aber gegen sowas gibt es eval(). Das ist zwar im Prinzip eine irgendwie böse Funktion, aber sie läßt sich relativ effektiv einsetzen, um dynamischen Code auszuführen. Wenn du also einen Befehlsstring zusammenbastelst, der dir die ersetzten Variablen im Text zurückgibt, dann hast du im Prinzip schon eine Menge gewonnen.

    $gruss = 'Hallo $name!'; // Einfache Anführungszeichen - keine Variablenersetzung
    eval ("echo "".$gruss."""); // Ausgabe der Ersetzung.

    Wenn du die Variablenersetzung nicht schon ausgeben, sondern weiterverwenden willst:

    $ausgabe = eval("return "".$gruss."""); // Ersetzung in $ausgabe speichern.

    Im Prinzip mußt du also $gruss nur mit deinem kompletten String belegen, in dem alle Variablen in der klassischen Form $variable enthalten sind, und kriegst die Ersetzung in $ausgabe.

    Das Problem bei dieser einfachen Variante ist nur, dass Sonderwünsche eben nicht gehen. Was ist beispielsweise, wenn eine Variable leer ist? Dann wird sie durch einen Leerstring ersetzt. Das kann gut oder schlecht sein. Was ist, wenn eine Variable gar nicht auftaucht? Dann wird sie als leer behandelt. Was ist, wenn jemand es schafft, systeminterne Variablen in den zu ersetzenden String einzuschmuggeln? Dann werden die Variablen natürlich ersetzt und gehen möglicherweise an die Öffentlichkeit. Und was ist mit Array-Variablen? Die muß man besonders behandeln, indem man sie in geschweifte Klammern einschließt: {$arrayvar[index]}.

    Ich habe mir deshalb mal eine Funktion geschrieben, um Templates für EMails mit Formulardaten füllen zu können. Da kann es ja immer vorkommen, dass Felder nicht ausgefüllt sind - außerdem sollen keine Formularfelder verloren gehen, auch wenn sie im Template nicht verwendet wurden.

    function parse_string(
      // Diese Funktion parst einen String, welcher die Platzhalter {platzhalter} enthält,
      // und ersetzt Sie, falls vorhanden, durch den Hasheintrag von $parse_vars
      $to_parse,
        // String, der geparst werden soll
      $parse_vars,
        // Array mit Elementen, die einzufügen sind
      $hide_field_in_summary = true,
        // Flag, welches beeinflusst, ob ersetzte Variablen als "gefunden" gekennzeichnet werden sollen
      $not_found = "(Feld '!FELD!' nicht vorhanden)",
        // Text, der eingefügt wird, wenn $parse_vars ein Element des Namens nicht enthält
      $not_filled = "(Feld '!FELD!' leer)",
        // Text, der eingefügt wird, wenn das Element in $parse_vars existiert, aber leer ist.
      $delim = array('{','}')
        // Array mit den Begrenzungszeichen im Template (links, rechts)
      )

    {

    global $key_exclude; // Gefundene Keys hier eintragen als true

    // Vorbereitung der Fehlermeldung:
      $not_found = str_replace("!FELD!","$value",$not_found);
      $not_filled = str_replace("!FELD!","$value",$not_filled);
      // In den Strings steht danach
      // (Feld '$value' nicht vorhanden)
      // Das wird durch eval() mit dem aktuellen Feldnamen ersetzt und eingebaut

    // parsefähige Elemente suchen
      $regex = '/'.$delim[0].'(.*?)'.$delim[1].'/';
      preg_match_all($regex,$to_parse,$parts);
      $offset = 0; // Position initialisieren
      foreach ($parts[1] as $value)
      { // String umbauen, um Wert einzusetzen
        $offset = strpos($to_parse,$delim[0].$value.$delim[1],$offset);
        $to_parse = substr($to_parse,0,$offset)
                 . ( isset($parse_vars[$value])
                    ? ( empty($parse_vars[$value])
                       ? eval("return "".$not_filled."";")
                       : $parse_vars[$value]
                      )
                    : eval("return "".$not_found."";")
                   )
                 . substr($to_parse,$offset+strlen($delim[0].$value.$delim[1]));
        $offset += strlen(
                           ( isset($parse_vars[$value])
                            ? ( empty($parse_vars[$value])
                               ? eval("return "".$not_filled."";")
                               : $parse_vars[$value]
                              )
                            : eval("return "".$not_found."";")
                           )
                   );
        if ($hide_field_in_summary && isset($parse_vars[$value]))
        {
          $key_exclude[$value]=true; // Schlüssel als "benutzt" merken
        }
      }
      return $to_parse;
    }

    Ok, hier steckt auch noch eval() drin, und das relativ häufig für einen relativ kleinen Zweck, mir ist aber irgendwie nichts gutes eingefallen, um in einem String eine Variable bzw. einen Platzhalter durch den Wert ebendieser Variablen zu ersetzen.

    Noch ein paar Worte zur Funktionsweise:
    Die Funktion benötigt mindestens zwei Parameter, den zu bearbeitenden String sowie einen Hash, in dem die Schlüssel gleich den zu ersetzenden Platzhaltern sind. Damit wird garantiert, dass nur die dort enthaltenen Informationen verwendet werden. Sinnvollerweise übergibt man bei Formularen direkt $_POST, aber um die Funktion variabel zu halten (sie wird von mir z.B. noch zum Generieren des Mail-Subjects verwendet), wollte ich nicht zwingend auf diese superglobale Variable zurückgreifen.

    Die weiteren Parameter sind optional. Man kann angeben, ob ersetzte Werte (in einer anderen Funktion wird das geprüft) als verwendet gekennzeichnet werden sollen (andernfalls kommt unter die Mail eine Liste aller nicht verwendeten Werte - ziemlich gut für mehr oder weniger dynamische Formulare, die sich mal ändern können). Man kann die Strings bestimmen, die für leere und nicht gesendete Formularfelder eingefügt werden (der Platzhalter !FELD! wird hierbei dann durch den Variablennamen ersetzt), und man kann die Begrenzer der Platzhalter im String bestimmen (default sind {}).

    Der entscheidende Punkt, der die Funktion etwas komplizierter macht: Ersetzungen gehen nur Stück für Stück. Wenn ich nun einfach str_replace auf den String anwende, um z.B. {name} durch den Namen zu ersetzen, und dann erneut, um {vorname} durch den Vornamen zu ersetzen - was passiert, wenn der ersetzte Name "{vorname}" lautet? Dann habe ich im ersten Schritt im Platzhalter-String "{name} {vorname}" stehen, ersetze dann zu "{vorname} {vorname}", und dann schließlich den (vermeintlich) doppelten Vornamen zum echten Vornamen "Peter Peter".

    Deshalb muß der String von vorne nach hinten durchgegangen werden und die jeweils gefundenen Platzhalter werden einmal ersetzt, und danach nie wieder angefaßt.

    Wenn du die Funktion verwenden willst, solltest du ggf. die Funktion "verwendete Elemente kennzeichen" rausnehmen - das ist im Prinzip alles, was die globale variable $key_exclude betrifft.

    Ach ja: Sollte jemand in PHP eine vorhandene Funktion kennen, die das macht, was ich hier mühsam zusammengebastelt habe, wäre ein kleiner Hinweis nicht schlecht. :) Ich hab jedenfalls keine gefunden.

    - Sven Rautenberg

    --
    "Bei einer Geschichte gibt es immer vier Seiten: Deine Seite, ihre Seite, die Wahrheit und das, was wirklich passiert ist." (Rousseau)
    1. Oha ;-)
      Danke erstmal für die Ausführliche Antwort. Ich werde es mal in aller Ruhe ausprobieren.