preg_regex_to_pattern

(Auszug aus "Reguläre Ausdrücke" von Jeffrey E. F. Friedl)

Wenn ein regulärer Ausdruck in einer String-Variablen vorliegt (z.B. aus einer Konfigurationsdatei oder aus einem Webformular) und in einer preg-Funktion verwendet werden soll, muss er zuerst in Begrenzungszeichen »eingepackt« werden, nur so ist er als Pattern-Argument tauglich.

Das Problem

Im einfachsten Fall geht es bei der Verwandlung eines regulären Ausdrucks in ein Pattern-Argument nur darum, vorn und hinten einen Schrägstrich anzufügen. Beispielsweise wird so der Regex-String ›[a-z]+‹ zu ›/[a-z]+/‹, ein String, der sich als Pattern-Argument verwenden lässt.

Die Umsetzung wird komplizierter, wenn der reguläre Ausdruck das Zeichen enthält, das als Begrenzungszeichen dienen soll. Wenn der Regex-String ›^http://([^/:]+)‹ ist, ergibt ein bloßes Verpacken in Schrägstriche den String ›/^http://([^/:]+)/‹; wenn dieser als Pattern-Argument verwendet wird, wird die Fehlermeldung »Unknown modifier /« ausgegeben.

Wie im Kasten Der Fehler »Unknown Modifier« genauer beschrieben, erscheint diese Meldung, weil die ersten zwei Schrägstriche als Begrenzungszeichen interpretiert werden und alles danach (in diesem Fall: der dritte Schrägstrich) als Anfang der Modifikator-Liste.

Die Lösung

Dieser Begrenzer-Modifikator-Konflikt lässt sich auf zwei Arten vermeiden. Zum einen kann man ein Begrenzungszeichen wählen, das nicht in der Regex vorkommt, und das ist sicher der vernünftigste Weg, wenn man eine Regex in einem Programm aufbaut. Aus diesem Grund habe ich bei den Programmbeispielen unter Die ›preg‹-Programmierschnittstelle, preg_match - Erläuterungen und Trefferdaten auswerten geschweifte Klammern als Begrenzer verwendet.

Diese Methode kann aber schwierig oder unmöglich werden, wenn die Regex und die darin vorkommenden Zeichen nicht im Voraus bekannt sind. Wenn die Regex aus einer externen Quelle stammt, ist die zweite Möglichkeit vorzuziehen: Man wählt ein bestimmtes Begrenzungszeichen, und wenn dieses Zeichen in der Regex selbst vorkommt, wird es mit einem Backslash maskiert.

Aber auch das ist etwas vertrackter, als es den Anschein macht. Man muss nämlich ein paar wichtige Grenzfälle berücksichtigen. Zum Beispiel muss ein Backslash am Ende der Regex besonders behandelt werden, damit nicht das angehängte Begrenzungszeichen maskiert wird.

Die folgende Funktion erzeugt aus einem Regex-String und einem optionalen String mit Modifikatoren einen Pattern-String, der direkt als Pattern-Argument zu einer der preg-Funktionen eingesetzt werden kann. Der Code enthält eine haarsträubende Menge von Backslashes – zum Maskieren von Regex- und PHP-String-Metazeichen – und ist eines sicher nicht: leicht lesbar. (Mehr zu den Strings in Hochkommas in PHP finden Sie unter Der »Pattern«-Parameter.)

/*
 * Verpackt einen ›nackten‹ Regex-String (und einen optionalen String mit Modifikatoren) so, dass
 * er als Pattern-Argument eingesetzt werden kann. Die Regex bekommt Begrenzungszeichen und
 * die Modifikatoren werden angehängt.
 */
function preg_regex_to_pattern($nackte_regex, $modifikatoren = "")
{
  /*
   * Die Regex muss in Begrenzungszeichen (wir verwenden einfache Schrägstriche) eingefasst
   * und die Modifikatoren angehängt werden. Wir müssen sicherstellen, dass alle nicht bereits 
   * maskierten Begrenzungszeichen im Regex-String maskiert werden, außerdem muss auch ein 
   * Backslash am Ende des Regex-Strings geschützt werden (er würde sonst das Begrenzungs-
   * zeichen am Ende maskieren).
   *
   * Wir können nicht blindlings alle Begrenzungszeichen maskieren, weil damit die bereits
   * maskierten Zeichen die Regex ungültig machen würden. Bei einer Regex wie '\/' würde
   * blindes Maskieren '\\/' ergeben, was mit den Begrenzungszeichen zu einem ungültigen
   * Pattern-Argument führt: '/\\//'.
   *
   * Wir unterteilen die Regex deshalb in Abschnitte: bereits maskierte Zeichen, nicht maskierte
   * Schrägstriche (die wir maskieren müssen) und alles andere. Ein Backslash am Ende muss 
   * als Spezialfall gesondert behandelt und ebenfalls maskiert werden.
   */
  if (! preg_match('{\\\\(?:/|$)}', $nackte_regex)) /* '/', gefolgt von '\' oder EOS */
  {
     /* Weder bereits maskierte Zeichen noch ein Backslash am Ende -- wir können die
      * Schrägstriche blindlings maskieren. * /
     $maskiert = preg_replace('!/!', '\/', $nackte_regex);
  }
  else
  {
     /* Mit diesem Pattern unterteilen wir den Regex-String $nackte_regex.
      * In den einfangenden Klammern die zwei Teile, deren Treffer wir maskieren werden. */
     $pattern = '{  [^\\\\/]+  |  \\\\.  |  (  /  |  \\\\$  )  }sx';
     /* Unsere Callback-Funktion wird bei jedem Treffer von $pattern im String $nackte_regex
      * aufgerufen. Wenn $treffer[1] nicht leer ist, geben wir die maskierte Version davon zurück,
      * sonst einfach den unbehandelten Treffer. */
     $f = create_function('$treffer', '           // Dieser lange
          if (empty($treffer[1]))                 // String in Hochkommas
              return $treffer[0];                 // wird zu
          else                                    // unserer
              return "\\\\" . $treffer[1];        // Callback-Funktion.
     ');
     /* Nun endlich $pattern auf $nackte_regex ansetzen. */
     $maskiert = preg_replace_callback($pattern, $f, $nackte_regex);
  }
  /* $maskiert kann jetzt gefahrlos in Begrenzungszeichen eingefasst werden. */
  return "/$maskiert/$modifikatoren";
}

Dieser Algorithmus ist schon so komplex, dass ich ihn keinesfalls jedes Mal neu ausprogrammieren möchte, wenn ich ihn brauche; deshalb habe ich ihn in eine Funktion verkapselt (und es wäre schön, wenn diese Funktion Teil des preg-Pakets würde).

Es ist ganz lehrreich, die Regex im unteren Teil der Funktion genauer zu analysieren, wie sie mit preg_replace_callback und der Callback-Funktion zusammenspielt und dabei nur die Schrägstriche maskiert, die nicht bereits schon maskiert sind.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Reguläre Ausdrücke bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:
   

Copyright der deutschen Ausgabe © 2008 by O’Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Reguläre Ausdrücke" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstr. 81, 50670 Köln