Für eine Internetseite war es notwendig, einen Glossar in die CMS-Texte einzubinden. Taucht also im Text ein Glossarbegriff auf, soll der Begriff zur Erklärung im Glossar verlinkt werden.

Folgendes Problem trat dabei auf:

Ich habe ein Glossar mit den Begriffen z.B.:

- intel pentium 4
- intel pentium
- intel

In einem Text in dem die Begriffe auftauchen, sollen diese nun ersetzt werden.

Aus

In meinen Rechner ist ein Intel Pentium 4 verbaut worden.

wird

In meinen Rechner ist ein <a href="/intel pentium 4">Intel Pentium 4</a>
verbaut worden.

Nun passiert es aber, wenn die Parser logik nicht richtig funktioniert, dass aus dem Text folgendes wird:

In meinen Rechner ist ein <a href="/intel pentium 4"><a href="/intel pentium">
<a href="/intel">Intel</a> Pentium</a> 4</a> verbaut worden.

Damit das nicht passiert, habe ich mir folgende Regular Expression in der Anwendung mit preg_replace einfallen lassen:

$text = preg_replace('|\b('.$begriff.')\b(?!\"*\>)
(?!(\<\/a\>))|i',
'<a href="/${1}">${1}</a>'), $text, 1);

Somit werden die Begriffe durch Links ersetzt, auch wenn sie so im Text auftauchen:

- intel.
- (intel)
- intel/amd
- “intel

Edit:
Es ist das Problem aufgetaucht, dass der Parser Probleme damit hat, wenn der Link mehrere Wörter enthält und diese zu dem noch in der Keyword-Liste auftauchen.

Folgender Code bereinigt das Problem und ist außerdem eine kürzere verbesserte Variante der alten Anweisung:

$text = preg_replace('~(<a[^>]*>(.*?)</a>|\b('.$needle.')\b)~i',
'<a href="/${1}">${1}</a>'), $text, 1);

PS: Danke an Mike, der in einem Blog-Artikel die wichtigsten Regular Expressions beschreibt, die ein Webprogrammierer immer zur Hand haben sollte.

Edit2:
Der obige Code war leider immer noch nicht perfekt, er hat nicht nur bei jedem Keyword sondern auch bei jedem Link gemached und ersetzt, was auch logisch ist, wegen dem ODER im RegEx.

Der neue Code löst das Problem mit Hilfe einer Callback-Funktion, somit kann man besser steuern, wann der gefundene String ersetzt werden soll und wann nicht:

$GLOBALS["key_count"] = false;
$text = preg_replace_callback("%(|<.*?>)|\b(".$needle.")\b%i",
create_function('$hit','
if ($hit[1] || $GLOBALS["key_count"]) return $hit[0];
else {
$GLOBALS["key_count"]=true;
return "".$hit[0]."";
}
'), $text);

Ich benutze die Funktion create_function, da es innerhalb einer Klasse irgendwie nicht möglich war eine Methode als Callback anzugeben, ich habe wirklich alles andere versucht. Einen Vorteil hat es allerdings, und zwar kann man mit create_function mehrere Parameter übergeben.

So was passiert nun? Wenn ein Link gefunden wird, gib ihn unverändert zurück. Wird das Keyword (außerhalb eines Links) gefunden, ersetzte es durch einen Link. Soweit funktionierte alles Perfekt, aber ich wollte, dass in einem Text jedes Keyword nur einmal ersetzt wird. Dafür ist der vierte Parameter der Funktion preg_replace_callback geeignet. Wenn ich dort eine 1 setzte, wird nur das erste Vorkommen ersetzt.

Problem: Durch das ODER im RegEx, matched er auch bei jedem Link, obwohl in diesem Fall ja nichts ersetzt wurde.

Lösung: Mit dem Setzten von globalen Variablen (s. $GLOBALS["key_count"]), kann man das Problem umgehen. Ist nicht die schönste Lösung, aber es funktiobniert.

Für Verbesserungsvorschläge bin ich jederzeit offen.