preg_match_all() erklärt

Der beliebte PHP-Befehl preg_match_all() mit konkreten Beispielen genauer betrachtet.

Inhalt

Was macht preg_match_all()

Der PHP Befehl preg_match_all() untersucht einen übergebenen String auf die Existenz eines festgelegten Suchpatterns (Regular Expression). Im Gegensatz zum bekannten preg_match() liefert er alle Treffer zurück statt nur des ersten Treffers. So weit so gut, denn das kann man auch in PHP.net nachlesen. Doch zeichnet den Befehl etwas aus, was in der Dokumentation nicht so ganz deutlich wird, daher möchte ich diese Besonderheit noch einmal klarer mit einem konkreten Beispiel aus der Praxis hervorheben.

Parsing eines Textes nach numerischen Tokens

Als Demonstration, habe ich eine Aufgabenstellung aus einer CMS Programmierung, prototypisch als Codebeispiel ausgesucht. Der folgende Beispiel-Text ist angefüllt mit so genannten Tokens ala

{999}

Beispiel für einen numerischen Token

(Ziffern von 0-99999 die einen Bausteintext-ID aus einer Datenbank symbolisieren). Zur Laufzeit soll der Parser die Tokens durch die Textphrasen aus einer Datenbank ersetzen.

$data = <<<EOD
Dieser Beispieltext ist angefüllt mit so genannten Tokens, die das Programm mit einer {2468} 
RegularExpression finden und {55} einsammeln soll. Alle Tokens in Form von {0} werden in 
einem Array {3} zusammengetragen. Im Anschluss daran, soll das System diese Tokens {349} 
durch Textbausteine aus einer Datenbank ersetzen.
EOD;

Dummy Text der mit Tokens gefüllt ist die ersetzt werden sollen

Auf den oben gezeigten Heredoc-Text wende ich unterschiedliche Patterns an und zeige wie diese wirken. Besonderes Augenmerk ist dabei auf die Setzung des runden Klammerpaars () zu achten, da die Position maßgeblich für das entstehende Ergebnis-Array $matches ist.

'/{[0-9]*}/' - der Klassiker

Beginnen wir mit dem Klassiker, den man sicherlich als erstes verwenden würde, wenn man mit dieser Aufgabenstellung beginnt:

preg_match_all('/{[0-9]*}/', $data, $matches)

Typischer RegEx zum finden von {399}

Dieser Befehl erzeugt ein recht erwartbares Array wie folgt:

array(1) 
{ 
    [0]=> array(8) 
    { 
        [0]=> string(6) "{2468}" 
        [1]=> string(4) "{55}" 
        [2]=> string(3) "{0}" 
        [3]=> string(3) "{3}" 
        [4]=> string(5) "{349}"  
    } 
}

RegEx: Ergebnis Array

Die Delimiter / leiten das Pattern ein und beenden es mit /. Die RegEx findet folglich alles Stellen die numerisch {0} bis {99999...} reichen.

Tipp: Das Pattern [0-9] würde lediglich einstellige Muster finden, während [0-9]* auch mehrstellige numerische Suchmuster ermitteln kann und genau das soll ja Ziel sein.

'/({)[0-9]*(})/' - schon besser

Setzen Sie nun die geschweiften Klammern { je zwischen zwei runde Klammern, also so:

({)...(})

RegEx mit runden Klammern

erzeugt der Befehl:

preg_match_all('/({)[0-9]*(})/', $data, $matches)

RegEx Version 2

eine ganz andere Array-Struktur. Der Befehl erzeugt für jedes Paar () ein eigenes Array und ein Array mit dem Ergebnis das sich zwischen den beiden Klammerpaaren ermittelt:

array(3) 
{ 
    [0]=> array(8) 
    { 
        [0]=> string(6) "{2468}" 
        [1]=> string(4) "{55}" 
        [2]=> string(3) "{0}" 
        [3]=> string(3) "{3}" 
        [4]=> string(5) "{349}"
    } 
    [1]=> array(8) 
    { 
        [0]=> string(1) "{" 
        [1]=> string(1) "{" 
        [2]=> string(1) "{" 
        [3]=> string(1) "{" 
        [4]=> string(1) "{" 
    } 
    [2]=> array(8) 
    { 
        [0]=> string(1) "}" 
        [1]=> string(1) "}" 
        [2]=> string(1) "}" 
        [3]=> string(1) "}" 
        [4]=> string(1) "}"
    } 
}

Ergebnis-Array der RegEx

'/{([0-9]*)}/' die beste Wahl

Das gewünschte Ergebnis stellt sich für die oben gezeigte Aufgabenstellung jedoch mit diesem Pattern ein:

/{([0-9]*)}/

RegEx: numerisch extrahieren

dabei ist der numerische Anteil des Pattern geklammert und erzeugt damit ein dazu passendes Ergebnis-Array in $matches. Der folgende Befehl:

preg_match_all('/{([0-9]*)}/', $data, $matches)

RegEx: extrahieren des numerischen Anteil

führt daher zu dem volgenden Ergebnis:

array(2) 
{ 
    [0]=> array(8) 
    { 
        [0]=> string(6) "{2468}" 
        [1]=> string(4) "{55}" 
        [2]=> string(3) "{0}" 
        [3]=> string(3) "{3}" 
        [4]=> string(5) "{349}" 
    } 
    [1]=> array(8) 
    { 
        [0]=> string(4) "2468" 
        [1]=> string(2) "55" 
        [2]=> string(1) "0" 
        [3]=> string(1) "3" 
        [4]=> string(3) "349" 
    } 
}

Der numerische Teil ist geklammert und wird als eigenes Array erzeugt

Sofern also der numerische Wert im Token {399} die Textbaustein-ID eines Eintrages in einer Datenbank wiederspiegeln soll, kann mit dem Array $matches[1] darüber iteriert werden und daraus ein SELECT erzeugt werden, etwa so:

foreach($matches as $value) 
{
    $sql    = 'SELECT * FROM docbin_baustein WHERE id = '.$value;
    $result = ...
}

Verwendung der Trefferliste als ID-Geber für SELECT

Beispiel: öffnende und schließende HTML-Tags

Für die folgenden RegEx-Beispiele, soll der folgende Demotext als Quelle dienen:

<h1>Lorem Ipsum</h1>
<p class="fw-bold">Das ist der Inhalt des Beitrags für das RegEx Pattern.</p>
<h2>Dolor sit amet</h2>
<p class="fw-bold">Das ist der Inhalt des Beitrags für das RegEx Pattern.</p>

Demo-Text

Zunächst ein interessantes Pattern aus der PHP-Dokumentation:

preg_match_all("|<[^>]+>(.*)</[^>]+>|U", $data, $matches);

RegEx Pattern aus der Doku

Das oben gezeigte Pattern extrahiert alle <.> - </.> Tags aus dem Content und zerlegt die Treffer in zwei Arrays:

array(2) 
{
    [0]=> array(4) 
    {
        [0]=> string(20) "<h1>Lorem Ipsum</h1>"
        [1]=> string(78) "<p class="fw-bold">Das ist der Inhalt des Beitrags für das RegEx Pattern.</p>"
        [2]=> string(23) "<h2>Dolor sit amet</h2>"
        [3]=> string(78) "<p class="fw-bold">Das ist der Inhalt des Beitrags für das RegEx Pattern.</p>"
    }
    [1]=> array(4) 
    {
        [0]=> string(11) "Lorem Ipsum"
        [1]=> string(55) "Das ist der Inhalt des Beitrags für das RegEx Pattern."
        [2]=> string(14) "Dolor sit amet"
        [3]=> string(55) "Das ist der Inhalt des Beitrags für das RegEx Pattern."
    }
}

Ergebnis-Array der RegEx

Im Array $matches[1] kann über die reinen ASCII-Inhalte iteriert werden, während das Array $matches[0] zusätzlich die HTML-Tokens enthält. Je nach Geschmack kann eines der beiden Arrays genutzt werden.

Beispiel: H-Tags extrahieren

Im zweiten Beispiel, sollen die H-Tags extrahiert werden, dazu reicht es aus, das eingans gezeigte Pattern um h zu erweitern, also so:

preg_match_all("|<h[^>]+>(.*)</h[^>]+>|U", $data, $matches);

RegEx: H-tags extrahieren

um das folgende Ergebnis zu erhalten:

array(2) 
{
    [0]=> array(2) 
    {
        [0]=> string(20) "<h1>Lorem Ipsum</h1>"
        [1]=> string(23) "<h2>Dolor sit amet</h2>"
    }
    [1]=> array(2) 
    {
        [0]=> string(11) "Lorem Ipsum"
        [1]=> string(14) "Dolor sit amet"
    }
}

Ergebnis-Array der RegEx

Beispiel: P-Tags und class extrahieren

Im dritten Beispiel können Sie mit dem folgenden Pattern alle P-Tags und den Inhalt zwischen Start und Ende der Tags extrahieren:

preg_match_all('/<[p].*>(.*)<\/[p]>/', $data, $matches);

RegEx: P-Tags extrahieren

Mit diesem RegEx-Pattern erhalten Sie das folgende Array:

array(2) 
{
    [0]=> array(2) 
    {
        [0]=> string(78) "<p class="fw-bold">Das ist der Inhalt des Beitrags für das RegEx Pattern.</p>"
        [1]=> string(62) "<p>Das ist der Inhalt des Beitrags für das RegEx Pattern.</p>"
    }
    [1]=> array(2) 
    {
        [0]=> string(55) "Das ist der Inhalt des Beitrags für das RegEx Pattern."
        [1]=> string(55) "Das ist der Inhalt des Beitrags für das RegEx Pattern."
    }
}

Ergebnis-Array der RegEx

Sie extrahieren alle Inhalte zwischen den P-Tags in $matches[1] bzw. die vollständigen Treffermuster mit HTML-Tags in $matches[0]. Möchten Sie zudem auch noch die class="..." Spezifizierung ermitteln, dann müssen Sie ein Klammerpar in /<[p].* einfügen, folglich /<[p](.*). Mit dem Befehl:

preg_match_all('/<[p](.*)>(.*)<\/[p]>/', $data, $matches);

RegEx: zum extrahieren der P-Tags und Class-Attribute

entsteht das folgende Array:

array(3) 
{
    [0]=> array(2) 
    {
        [0]=> string(78) "<p class="fw-bold">Das ist der Inhalt des Beitrags für das RegEx Pattern.</p>"
        [1]=> string(62) "<p>Das ist der Inhalt des Beitrags für das RegEx Pattern.</p>"
    }
    [1]=> array(2) 
    {
        [0]=> string(16) " class="fw-bold""
        [1]=> string(0) ""
    }
    [2]=> array(2) 
    {
        [0]=> string(55) "Das ist der Inhalt des Beitrags für das RegEx Pattern."
        [1]=> string(55) "Das ist der Inhalt des Beitrags für das RegEx Pattern."
    }
}

Ergebnis-Array der RegEx

Folglich enthält $matches[1] alle Treffer zu den Parametern hinter dem P im öffnenden P-Tag, meist sind das Class-Parameter oder ID-Angaben.


FlightCMS
2024-01-28
preg_match RegEx Regular Expression
post
0

Ein eigenes CMS programmieren I.

In diesem mehrteiligen Workshop entwickeln Sie ein eigenes kleines Content Management System mit einer Flatfile Datenbank und Markdown Parser - inkl. Download.

Ein eigenes CMS programmieren II.

In der zweiten Hälfte des Workshops, geht es darum, das kleine CMS noch weiter auszubauen und vollwertig zu machen. Sie binden die Template-Engine Smarty ein.