6. Reguläre Ausdrücke

6.1. Wie kann ich mehr über Reguläre Ausdrücke lernen?
6.2. Soll ich ereg() oder preg() verwenden?
6.3. Wie verwende ich die preg()-Funktionen?
6.4. Was sind Reguläre Ausdrücke?
6.5. Welche Bauelemente kommen in Regulären Ausdrücken vor?
6.6. Wie teste ich auf die Existenz mehrerer Suchworte in einem String?
6.7. Wie isoliere ich Suchstrings aus einem größeren Text?
6.8. Wie finde ich alle Links in einer HTML-Datei?
6.9. Wie ersetze ich alle relativen Links in einer HTML-Datei?
6.10. Wie überprüfe ich einen String auf seinen Inhalt?
6.11. Wie ersetze ich in einem Text, jedoch nicht innerhalb von HTML-Tags?
6.12. Wie mache ich aus URIs im Text anklickbare Links?

6.1. Wie kann ich mehr über Reguläre Ausdrücke lernen?

|Tutorial|Buch|

Antwort von Kristian Köhntopp

Bei O'Reilly gibt es das ausgezeichnete Buch Mastering Regular Expressions von Jeffrey E. F. Friedl, welches auch als deutsche Übersetzung unter dem Titel Reguläre Ausrücke vertrieben wird. Es enthält eine ausgezeichnete Übersicht über die verschiedenen Formen von regulären Ausdrücken in Unix mit Beispielen.

Online gibt es z.B. das Tutorial Rx (englisch) über Reguläre Ausdrücke nach POSIX-Standard (ereg()-Funktionen in PHP) und einen Abschnitt über Reguläre Ausdrücke in Perl im HTML-Tutorial SELFHTML (deutsch). Die preg()-Funktionen von PHP sind praktisch 100% kompatibel zu den Regulären Ausdrücken in Perl, nur die ausführenden Funktionen heißen anders.

6.2. Soll ich ereg() oder preg() verwenden?

|POSIX|PCRE|ereg()|

Antwort von Kristian Köhntopp

Wenn die verwendete Version von PHP3 ausreichend neu ist und das Modul PCRE aktiviert ist (dies kann man mit einem Aufruf von phpinfo() leicht feststellen), dann sollte man wo immer es geht die preg-Funktionen verwenden. Sie sind nicht nur schneller, sondern auch flexibler und leistungsfähiger als die alten ereg-Funktionen.

Es gibt keinen Grund mehr, die ereg-Funktionen noch zu verwenden außer Rücksicht auf veraltete Installationen.

6.3. Wie verwende ich die preg()-Funktionen?

|PCRE|

Antwort von Kristian Köhntopp

Die preg()-Funktionen sind im Abschnitt Perl-compatible regular expressions des Online-Handbuches beschrieben. Es handelt sich um die Funktionen

Für alle Funktionen gilt das in den Abschnitten Pattern Modifiers und Pattern Syntax gesagte.

6.4. Was sind Reguläre Ausdrücke?

|Definition|Grundlagen|

Antwort von Kristian Köhntopp

Reguläre Ausdrücke sind Suchmuster, die sich auf Strings anwenden lassen und für die entscheidbar ist, ob sie auf den String passen (match) oder nicht passen. So paßt das Suchmuster ei auf den String Weichei, weil darin die Zeichenfolge ei enthalten ist, aber nicht auf den String Warmduscher. Wendet man ein Suchmuster auf eine Menge von Strings an, dann bekommt man zwei Teilmengen, nämlich die Menge aller Strings, auf die das Muster paßt und die Menge aller Strings, auf die das Muster nicht paßt. Meistens interessiert man sich für eine der beiden Teilmengen ("Finde alle Namen, die mit einem einem A beginnen.", "Finde alle Zeichen, die nicht rechts von einem Kommentarzeichen stehen.")

Reguläre Ausdrücke werden meistens durch einen endlichen Automaten realisiert. Die Informatik kennt Verfahren, mit denen man automatisch einen Automaten generieren kann, der für ein bestimmtes Suchmuster entscheidet, ob es auf einen String paßt oder nicht. Auch die regulären Ausdrücke in PHP funktionieren so: Bei der ersten Benutzung eines regulären Ausdruckes wird ein solcher Automat intern generiert (das Muster wird "compiliert") und dann angewendet. Bei späteren Benutzungen desselben Suchmusters kann dieser Automat dann unter Umständen wieder verwendet werden, was deutlich schneller ist.

Einige Suchmuster und Bedingungen sind zu komplex, als daß man sie mit Hilfe von regulären Ausdrücken und Automaten formulieren kann. Typische Beispiele dafür sind Dinge, die Abzählungen erforderlich machen ("Finde alle Worte, die aus genausovielen b's bestehen, wie sie a's enthalten") und Dinge, die Vorbedingungen notwendig machen ("Finde alle Worte print, aber nur, wenn sie nicht in Anführungszeichen stehen oder Bestandteil eines Kommentares sind."). In diesem Fällen braucht man leistungsfähigere Konzepte und Werkzeuge, kontextfreie oder kontextsensitive Grammatiken und dazu passende Parser.

In der Praxis verwendet man reguläre Ausdrücke, um zu entscheiden, ob ein String bestimmten formalen Kritierien genügt ("Akzeptiere den Formularwert nur dann, wenn er ausschließlichlich Ziffern enthält.") oder um bestimmte Teilstücke aus Strings herauszuschneiden ("Liefere mit den Text zwischen dem begin und end aus dem gegebenen String.").

6.5. Welche Bauelemente kommen in Regulären Ausdrücken vor?

|Pattern|Bedeutung|Sonderzeichen|

Antwort von Kristian Köhntopp

Suchmuster in regulären Ausdrücken bestehen aus gewöhnlichen Zeichen und Zeichen mit einer Sonderbedeutung. Gewöhnliche Zeichen in Suchausdrücken stehen für die entsprechenden Zeichen in einem String. Der Suchausdruck hallo paßt also auf alle Strings, die irgendwo genau diese Zeichenfolge enthalten. Zeichen mit Sonderbedeutung stehen als Platzhalter für ein oder mehrere andere Zeichen, für Zeilenanfänge oder -enden oder für andere Sonderfunktionen. Sie sind es, die reguläre Ausdrücke eigentlich mächtig und sinnvoll machen.

In regulären Ausdrücken gibt es Zeichenmengen (wird weiter unten erklärt), die durch eckige Klammern [ und ] eingeschlossen werden. Bei Zeichen mit Sonderbedeutung gelten leicht unterschiedliche Regeln, je nachdem ob man gerade eine außerhalb einer Zeichenmenge arbeitet oder innerhalb.

Außerhalb von Zeichenmengen gibt es die folgenden besonderen Regeln:

Eckige Klammern leiten eine Zeichenmenge ein. Eine Zeichenmenge steht immer für genau ein Zeichen und zwar für ein Zeichen, das in der Menge enthalten ist. Also paßt der reguläre Ausdruck [0123456789] auf genau eine beliebige Ziffer. Innerhalb der eckigen Klammern einer Zeichenmenge haben die folgenden Zeichen eine besondere Bedeutung:

Verwendet man einen Suchausdruck mit den preg*-Funktionen, dann ist der Suchausdruck in sogenannte Begrenzerzeichen (Delimiter) einzuschließen, hinter denen noch Optionen mit angegeben werden können. Meistens verwendet man entweder die Schrägstriche / oder Gleichheitszeichen =.

Aus diesen Komponenten kann man sich mit einiger Übung Suchausdrücke zusammenbauen, die nicht nur weitgehend unlesbar sind, sondern die außerdem schnell und schmerzlos die gewünschten Suchfunktionen oder Such- und Ersetzefunktionen durchführen.

6.6. Wie teste ich auf die Existenz mehrerer Suchworte in einem String?

|Anwendung|Beispiel|

Antwort von Johannes Frömter

Der einfachste Anwendungsfall von preg_match() ist zu testen, ob ein Suchmuster auf einen gegebenen String paßt. Beispiele:


# Kommt "Wort" in $eingabe vor?
preg_match("/Wort/", $eingabe);

# Kommt "Wort", "wort" oder "wOrT" etc. in $eingabe vor?
preg_match("/wort/i", $eingabe);

# Kommt "Wort" am Anfang oder am Ende von $eingabe vor?
preg_match("/^Wort|Wort$/", $eingabe);

# Kommt "Wort", "Wart", "Wirt" oder "Wert" in $eingabe vor?
preg_match("/W[aeio]rt/", $eingabe);

# Kommt "Wort" oder "Word" in $eingabe vor?
preg_match("/Wor(t|d)/", $eingabe);

# Kommt "DM " oder "TDM " mit einer zwei- bis
# dreistelligen Zahl in $eingabe vor?
preg_match("/T?DM \d{2,3}/", $eingabe);

# Kommt "Word " mit einer Versionsnummer (z.B. 7.0 oder 97)
# in $eingabe vor? (\d+ paßt auf "eine oder mehr" Ziffern,
# \.? KANN ein Punkt sein, \d* sind "Null oder mehr" Ziffern)
preg_match("/Word \d+\.?\d*/", $eingabe);

6.7. Wie isoliere ich Suchstrings aus einem größeren Text?

|Tag|Grabbing|mehrzeilig|Modifier|

Antwort von Kristian Köhntopp

Das folgende vollständige Beispiel zeigt, wie man den Inhalt des Body-Tags aus einer HTML-Datei isolieren kann.


  $str = "lalalala
<body bgcolor=#cccccc>lang und weilig
noch eine zeile
<h1>Bla</h1>
</body>
tralalal";
  preg_match_all("=<body[^>]*>(.*)</body>=siU", $str, $a);
  print $a[1][0];

Das Beispiel macht von den Optionen i, s und U der Perl Regular Expressions Gebrauch: Die Option i sorgt dafür, daß Groß- und Kleinschreibung keine Rolle spielen, die Option U sorgt dafür, daß Ungreedy gematched wird, d.h. der kürzest mögliche Match verwendet wird. Die Option s bewirkt, daß der Punktoperator auch Newlines mit matched. Dadurch ist es möglich, den regulären Ausdruck auf auf einen mehrzeiligen String anzuwenden.

6.8. Wie finde ich alle Links in einer HTML-Datei?

|Hyperlink|

Antwort von Björn Schotte

$zeile sei der Inhalt einer zuvor eingelesenen HTML-Datei. Diese Variable muß innerhalb der While-Schleife neu zusammengebaut werden, sonst läuft man hier in eine Endlosschleife.


$pattern = '=^(.*)<a(.*)href\="?(\S+)"([^>]*)>(.*)</a>(.*)$=msi';
while (preg_match($pattern, $zeile, $txt))
{
  /* $txt[3] enthält die gewünschte URL. */
  echo $txt[3]."\n";

  /* $zeile neu bauen */
  $zeile = $txt[1]." hier war mal ein Link ".$txt[6];
}

/* $zeile zur Kontrolle ausgeben */
print "<br>".nl2br($zeile);

$txt enthält als Array alle Tokens, die in der Regexp in Klammern angegeben sind. $txt[0]als Sonderstellung enthält den ganzen Text.

6.9. Wie ersetze ich alle relativen Links in einer HTML-Datei?

|Hyperlink|relativ|absolut|

Antwort von Björn Schotte

$zeile sei der Inhalt einer zuvor eingelesenen HTML-Datei. Im folgenden Beispiel werden alle relativen Links durch das Konstrukt <?php $sess->purl("relativerlink"); ?> ersetzt. relativerlink sei hierbei der relative Link, der gefunden wurde.

Bei längeren Texten ist darauf zu achten, dass dieses Konstrukt teilweise recht lange dauert, bis es den kompletten Text durchsucht hat.


print "Konvertiere";
flush();

$pattern  = '=^(.*)<a\\n*(.*)href\="?([^h][^t][^t][^p][^:]\S+)"';
$pattern .= '([^>]*)\\n*>(.*)</a\\n*>(.*)$=msi';
$repl = '\\1<a\\2 href="<?php $sess->purl("\\3"); ?>"\\4>\\5</a>\\6';

while (!$fertig)
{

$zeilenew = preg_replace($pattern, $repl, $zeile);

  if ($zeilenew == $zeile)
  {
     $fertig = true;
     $zeile = $zeilenew;
     flush();
     break;
  } else {
     $zeile = $zeilenew;
     print ".<br>\n";
     flush();
  }
}

print "Ersetzt:<br><br>".nl2br(htmlspecialchars($zeile));

6.10. Wie überprüfe ich einen String auf seinen Inhalt?

|pruefen|testen|

Antwort von Martin Jansen

Häufig ist es nötig, festzustellen, ob ein String nur Ziffern bzw. nur Buchstaben enthält.

$string sei die Zeichenkette, die überprüft werden soll. Die Regular Expression im ersten Beispiel überprüft, ob nur Ziffern in $string enthalten sind. Ist dies der Fall, gibt sie "Zeichenkette OK" aus, ansonsten lautet die Ausgabe "Ungültiges Zeichen in der Zeichenkette".


/* Regex zur Ueberpruefung des Strings */
if (!preg_match("/^\d+$/",$string)) {
  echo "Ungültiges Zeichen in der Zeichenkette";
} else {
  echo "Zeichenkette OK";
}

Um zu überprüfen, ob in der Zeichenkette nur Buchstaben stehen, kann man folgende Regex verwenden, die auf dem gleichen Prinzip beruht:


if (!preg_match("=^[a-zäöüß]+$=i",$string)) {
  echo "Ungültiges Zeichen in der Zeichenkette";
} else {
  echo "Zeichenkette OK";
}

6.11. Wie ersetze ich in einem Text, jedoch nicht innerhalb von HTML-Tags?

|ersetzen|begrenzen|

Antwort von Johannes Frömter

Mit Regulären Ausdrücken kann man zwar wunderbar "positive Treffer" formulieren, aber das Gegenteil davon geht nur sehr schwer (abgesehen von negierten Zeichenklassen und Lookaheads/-behinds geht es nicht). Die Entscheidung, ob ein Treffer innerhalb eines HTML-Tags (also zwischen < und >) liegt oder nicht, muß man von PHP treffen lassen. Hierzu gibt es den Modifier e, der PHP das zweite Argument von preg_replace() als PHP-Code auswerten läßt.

Mit folgender Konstruktion kann man in $t alle Vorkommen von $s außerhalb von < und > durch $r ersetzen; die zweite Version ist besonders mit dem Modifier i interessant, um Wörter unabhängig von ihrer Groß-/Kleinschreibung unter Beibehaltung der Schreibweise hervorzuheben:


// $s in $t durch $r ersetzen:
preg_replace("/((<[^>]*)|$s)/e", '"\2"=="\1"? "\1":"$r"', $t);

// $s case-insensitive in $t hervorheben:
preg_replace("/((<[^>]*)|$s)/ie", '"\2"=="\1"? "\1":"<b>\1</b>"', $t);

6.12. Wie mache ich aus URIs im Text anklickbare Links?

|Hyperlink|HTML|

Antwort von Björn Schotte

Besten Dank an Thomas Weinert, von dem die ursprüngliche RegExp stammt.

Folgender regulärer Ausdruck ersetzt alle normalen URIs, das heißt zum Beispiel http://www.phpcenter.de/, news:de.comp.lang.php, mailto:bjoern@thinkphp.de oder ftp://ftp.suse.com/ durch HTML-Code, damit diese URIs für den Benutzer klickbar werden.


/**
* replace URIs with appropriate HTML code to be clickable.
*/
function replace_uri($str) {
  $pattern = '#(^|[^\"=]{1})(http://|ftp://|mailto:|news:)([^\s<>]+)([\s\n<>]|$)#sm';
  return preg_replace($pattern,"\\1<a href=\"\\2\\3\"><u>\\2\\3</u></a>\\4",$str);
}