Tag: Regular Expression

PHP, RegExp: Parser um Keywords durch Links zu ersetzten

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.


PHP, RegExp: Match exact string with preg_replace

Kurz um, um einen exakten String zu ersetzten, muss man preg_replace mit dem “\b” – Parameter (word boundary) benutzen:

$new_string = preg_replace('/\b'.$exact_string.'\b/', $replace_with);

Ich kannte es sonst nur so:

$new_string = preg_replace('^'.$exact_string.'$', $replace_with);

Das warf bei mir aber immer den Fehler:

Warning: preg_replace() [function.preg-replace]: No ending delimiter '^' found in /home/....php on line ...

Einige werden sagen, warum macht er das denn nicht mit dem viel performanteren str_replace?
Ich nehme preg_replace, weil ich in dem übergebenen String den zu ersetzenden Teilstring nur einmal ersetzen will.
Als dritten Parameter kann man angeben wie oft der Teilstring ersetzt werden soll.

$new_string = preg_replace('/\b'.$exact_string.'\b/', $replace_with,1);

Wenn es case insensitive sein soll einfach ein “i” anhängen:

$new_string = preg_replace('/(\b'.$exact_string.'\b)/i', $replace_with);

Edit:

Plötzlich tauchte diese Warnung bei der Übergabe eines ganz normalen Strings auf:

preg_replace() [function.preg-replace]: Unknown modifier 't'

Abhilfe schaffte die Änderung der Delimiter von Slash (/) auf Pipe (|):

$new_string = preg_replace('|(\b'.$exact_string.'\b)|i', $replace_with);


Copyright © 2007-2012 iTopia. All rights reserved.
Jarrah theme by Templates Next | Powered by WordPress