frank
Goto Top

PHP Glossar-Parser Herausforderung für Profis

Euer Webmaster braucht mal ein wenig Hilfe face-smile

Hi, heute habe ich mal eine kleine Aufgabe für PHP Profis.
Für unser Glossar auf administrator.de brauche ich einen performanten Glossar-Parser.

Folgendes Szenario bzw. Variablen bestehen (vereinfacht):

$content = "Das ist jetzt der Text der Mitglieder mit Glossar-Begriffen wie Server oder Internet. Umlaute wie &ouml; oder &szlig; sind mit htmlentities umgewandelt. Das TCP/IP Protokoll oder den Begriff Firewall sollen mit einem Glossar-Link markiert werden. Der Text hat aber auch <a href=\"/index.php?content=Serverprobleme_im_Internet.html\" titel=\"Server und Internet Probleme\">Links</a> oder Images <img src=\"/images/articles/server.jpg\" alt=\"\" /> die nicht interpretiert werden dürfen.";  

$glossar_arr = array(array("Internet","2"), array("Server","3"), array("Firewall","4"), array("TCP/IP","5"), array("etc.","id"));  

$content
Die Inhalte von "$content" liegt im "htmlentities($content, ENT_QUOTES);" Format vor (aus Sicherheitsgründen, damit kein HTML interpretiert wird). Die HTML-Tags wie <img.. > oder <a href..> kommen zunächst von unserem Format-Parser. Danach soll der Glossar-Parser starten. Er darf HTML-Tags also nicht berücksichtigen. Die Variable "$content" kann bis zu 10.000 Wörter fassen.

$glossar_arr
Das Array "$glossar_arr" kann aus mehr als 10.000 Begriffen bestehen. Der erste Wert ist der Glossar-Begriff, der zweite die ID. Der Glossar-Link zum Aufruf lautet dann z.B.: /index.php?mod=glossar&idx=2

Ich suche jetzt eine Abfrage, eine RegExp oder sonstige PHP Lösung, die sehr performant den Inhalt in "$content" durchsucht und die zutreffenden Glossar-Begriffe aus dem Array (wie im Beispiel "Internet", "Server", etc.) mit dem Link markiert bzw. den Begriff durch den Link plus Begriff ersetzt.

Natürlich dürfen die HTML-Tags wie <img ..> oder <a href..> nicht berücksichtigt werden (z.B. darf im Link "title="-Tag der Glossar-Parser nicht greifen, am besten alle HTML-Tags excluden). Auch sollen nur ganze Wörter und keine Teilbegriffe gefunden werden.

Für eine Lösung wäre ich sehr dankbar face-smile

P.S. Ich poste meine bisherige Lösung extra nicht, um neue Ideen und einen neuen Ansatz zu finden. Meine Lösung ist bisher zu langsam.

Gruß
Frank
Webmaster

Content-Key: 89698

Url: https://administrator.de/contentid/89698

Ausgedruckt am: 28.03.2024 um 22:03 Uhr

Mitglied: masterG
masterG 12.06.2008, aktualisiert am 05.12.2013 um 00:41:45 Uhr
Goto Top
Hallo Frank!
Hast du schon mit preg_replace bzw. str_replace ausprobiert?
Also dass es z.B. so aussehen könnte:
<?php
$content = str_replace("Internet","<a href="glossar.php">Internet</a>", $content);  
?>
Auch eine Lösung wär auch das du das mit einer while funktion machst:
<?php
mysql_connect("localhost","root","");  
mysql_select_db("db");  
$sql = "SELECT ID, titel FROM glossar;";  
$result = mysql_query($sql);
while($row = mysql_fetch_array($result) or die(mysql_error()))
{
$content = str_replace($row['titel'],"<a href=glossar.php?idx=".$row['ID'].">".$row['titel']."</a>", $content);  
}
?>
mfg
masterG
Mitglied: Frank
Frank 12.06.2008 um 15:32:15 Uhr
Goto Top
Hi masterG,

nicht ganz face-wink

a) Bei den HTML-Tags wie z.B.
.. text text <a href="/Internet.html" title="Das ist der Internet Testbeitrag">Internet Link</a> text text ...  
in "$content" hättest Du mit Deiner Lösung ein großes Problem. Das gilt auch für Bilder mit "alt=" oder "title=" Tag. Die HTML-Tags dürfen nicht mit durchsucht werden.

b) Bei einem Glossar von 10.000 Begriffen und einem Text von 5000 Wörtern in "$content" hättest Du mir Deiner DB-Lösung ein sehr großes Performance Problem.

Es muss eine Lösung OHNE Datenbank sein (bitte beachtet nur das Array $glossar_arr und die Variable $content). Es sieht leicht aus, ist es aber nicht.

Bitte validiert bzw. testet Euren Quellcode mit den Variablen $content und dem Array $glossar_arr vom Beitrag oben. Es sollte schon etwas konkreter sein.

Gruß
Frank
Mitglied: Dani
Dani 12.06.2008 um 16:07:52 Uhr
Goto Top
Hi Frank,
wie hast du es mit den Formatierungsmöglichkeiten gelöst?! Da wirst du doch auch sicher REGEX verwendet haben, oder?

Ansonsten gehen mir 2 Ideen gerade durch den Kopf (erfährst du später mehr)....mit was ich mir ein bisschen schwer tu, ist der Faktor "Leistungsaufwand der MySQL". Wie hälst du die 10.000 Begriffe immer im PHP? Hast du ne $_SESSION Array gebildet oder wie darf ich mir das vorstellen. Denn wir reden hier ja grad nicht über ne 0815 Datenbank. face-smile


Gruss,
Dani
Mitglied: Frank
Frank 12.06.2008 um 16:31:11 Uhr
Goto Top
Hi Dani,

Die Datenbank für den Glossar ist komplett im Arbeitsspeicher (ich sag mal RamDisk, MySQL hat einen Tabellentyp: MEMORY). Also habe ich alle 10.000 Begriffe in dem Array wie oben beschrieben. Darüber braucht man sich also keinen Kopf zu zerbrechen.

Es geht hier nur um eine schnelle Funktion dieses gesamte Array ($glossar_arr) jetzt gegen einen Text ($content) abzugleichen, dabei aber die vorhandenen HTML-Tags nicht zu berücksichtigen (und das sehr performant).

wie hast du es mit den Formatierungsmöglichkeiten gelöst?! Da wirst du doch auch sicher REGEX verwendet
haben, oder?

Ja, im Format-Parser benutze ich RegExp. Aber auch da habe ich Probleme bei manchen HTML-Tags Kombinationen die ich dann (ich sag mal "dirty") abfange. Daher brauche ich einen neuen Ansatz.

Gruß
Frank
Mitglied: 16568
16568 12.06.2008, aktualisiert am 05.12.2013 um 00:42:30 Uhr
Goto Top
Ich verstehe den aktuellen Zustand sehr wohl, jedoch würde ich zu einer anderen Lösung ansetzen.

Anstelle die Glossar-Links jedesmal on-the-fly zu generieren, würde ich diese bereits beim in-die-Datenbank-schreiben setzen lassen.
Hat einzig und alleine den Nachteil, daß wenn ein Begriff wegfällt, die ganze DB aktualisiert werden müßte.
(ob dies jemals vorkommt, weiß ich allerdings nicht...)

Der Regex ist sicherlich ein wenig komplexer, aber ich denke, Du wirst mit Sicherheit schon den performantesten haben.
Regex bedeutet immer rechenintensiv, weil er ausgewertet werden muß.
Die o.g. Lösung wird einfach nur ausgespuckt, und auch die Aktualisierung der DB liese sich in 1-2 Tagen komplett realisieren face-wink


Lonesome Walker
Mitglied: filippg
filippg 12.06.2008 um 21:57:03 Uhr
Goto Top
Hallo,

ich möchte ja jetzt nicht als Miesepeter dastehen. Aber ich würde empfehlen, das Problem durch Verzicht auf das Glossar zu lösen.
Brauchen wir hier wirklich einen Link, der uns erklärt, was ein "Server" oder eine "Firewall" ist? Und wenn es um "schwierigere" Begriffe geht ist ein Glossar m.E. nach sowieso schnell am Ende. Solche Begriffe bedürfen dann auch meist keiner allgemeinen Erklärung sondern sind oft Kontextabhängig (oder halt sehr umständlich zu erklären).
Vorschlag für eine Alternative: eine Funktion, mit der man ein Wort markieren und dann mit einem Mausklick in einer gängigen Suchmaschine suchen kann.

Gruß

Filipp
Mitglied: Frank
Frank 13.06.2008 um 10:04:34 Uhr
Goto Top
Hi Lonesome Walker,

klar, ich kann das auch schon vorher (z.B. per Scriptlauf) in die Datenbank schreiben anstatt es "onthefly" zu machen.
Trotzdem brauche ich auch dafür die Funktion das gesamte Array ($glossar_arr) gegen den Text ($content) abzugleichen und dabei die vorhandenen HTML-Tags nicht zu berücksichtigen. Gut, dass muss zwar dann nicht mehr so peformant sein, aber schnell muss es trotzdem gehen. Sonst würde das Script bei der Masse an Beiträge und Kommentare zu lange laufen.

Also bitte nur auf diese Funktion konzentrieren und nicht dem drumherum!

Vielleicht jetzt noch mal etwas einfacher: Ich suche eine schnelle RegExp-Lösung die eine Array als Suchfeld benutzt und bei dem durchsuchenden Text die HTML-Tags nicht berücksichtigt. Es muss auch nicht zwingend reines RexExp sein, es kann z.B. auch eine Array- oder "sonstwas" Lösung sein. Das Problem liegt ganz klar bei den HTML-Tags. Die nicht zu berücksichtigen ist mein Hauptproblem.

Also vergesst bitte die DB, vergesst die Art wie es angezeigt wird (vorberechnet oder "onthefly") und stellt bitte keine Aussagen über den Sinn- oder Unsinn eines Glossar hier rein. Nur die Funktion ist wichtig.

Hier ein möglicher Befehl: "preg_replace" oder "preg_replace_callback". Kennt sich damit einer sehr gut aus?
Gibt es denn keine PHP oder RegExp Profis mehr?

Gruß
Frank
Webmaster
Mitglied: Frank
Frank 13.06.2008 um 10:09:10 Uhr
Goto Top
Hallo Filipp,

"Server" oder "Firewall" sind hier nur einfache Beispiele.

Und wenn es um "schwierigere" Begriffe geht ist ein Glossar m.E. nach sowieso schnell am Ende.
Da wäre es doch schön wenn wir es später besser machen würden.

Vorschlag für eine Alternative: eine Funktion, mit der man ein Wort markieren und dann mit einem Mausklick in einer gängigen
Suchmaschine suchen kann.
Mein Safari-Browser kann das jetzt schon und mit dem Firefox wird es auch bald gehen. Das ist keine Funktion die von uns kommen kann, immer nur vom Browser. Über den Sinn- oder Unsinn des Glossars will ich hier aber jetzt nicht diskutieren. Nur weil wir bisher keine gute Lösung gefunden haben, heiß es nicht das es sie nicht gibt.
Also bitte keine Kommentare mehr in diese Richtung, die lösche ich dann sofort.

Gruß
Frank
Mitglied: Frank
Frank 13.06.2008, aktualisiert am 06.01.2015 um 15:06:17 Uhr
Goto Top
Hi,

nach dem da ja nix wirklich sinnvolles kam, hier mal mein neuer Ansatz:

<?
// ---------------------------
// GLOSSAR-PARSER Version: 2.0
// ---------------------------

// CONTENT

$content = "Das ist jetzt der Text der Mitglieder mit Glossar-Begriffen wie Server oder Internet. Umlaute wie &ouml; oder &szlig; sind mit htmlentities umgewandelt. Das TCP/IP Protokoll oder den Begriff Firewall sollen mit einem Glossar-Link markiert werden. Der Text hat aber auch <a href=\"/index.php?content=Serverprobleme_im_Internet.html\" title=\"Server und Internet Probleme\">Links</a> oder Images <img src=\"/images/icons/16x16/filesystems/folder_home.gif\" alt=\"Das Internet und die Server\" title=\"Server und Internet Probleme\"/> die nicht interpretiert werden dürfen. ACL aber schon.";  

// INIT GLOSSAR ARRAY
// Beispiel-Daten

$glossar_search_arr='/Internet\b/i';  
$glossar_search_arr[1]='/Server\b/i';  
$glossar_search_arr[2]='/Firewall\b/i';  
$glossar_search_arr[3]='/TCP\/IP\b/i';  
$glossar_search_arr[4]='/ACL\b/i';  

$glossar_replace_arr='<a href="/index.php?mod=glossar&idx=1">Internet</a>';  
$glossar_replace_arr[1]='<a href="/index.php?mod=glossar&idx=3">Server</a>';  
$glossar_replace_arr[2]='<a href="/index.php?mod=glossar&idx=4">Firewall</a>';  
$glossar_replace_arr[3]='<a href="/index.php?mod=glossar&idx=5">TCP/IP</a>';  
$glossar_replace_arr[4]='<a href="/index.php?mod=glossar&idx=2">ACL</a>';  

echo $content."<hr>";  

// NACH DEM ERSTELLEN DER ARRAYS ZEITMESSUNG STARTEN
$time_start = getmicrotime(); // Zeitmessung

// OUTPUT GLOSSAR-PARSER 
echo parse_glossar($content);

// END
$time = getmicrotime() - $time_start;
echo "<p>Time: ".round ($time,5)." sec.</p>";  

// ---------
// FUNCTIONS
// ---------

function parse_glossar ($content) {
	// SPLIT INTO ARRAY
	$search = "/(<.*?<\/.*?>|<.*?\/>)/";  
	$parse_arr = preg_split($search, $content,-1,PREG_SPLIT_DELIM_CAPTURE);
	$parse_arr = array_map("parse_glossar_array", $parse_arr);  
	return(join($parse_arr));
}

function parse_glossar_array($parse_arr) {
	global $glossar_search_arr, $glossar_replace_arr; 
	// strpos ist schneller als jedes preg etc.
	if (strpos($parse_arr,"<") === false) {  
		// wenn kein HTML-Tags drin sind dann preg_replace 
		return (preg_replace($glossar_search_arr, $glossar_replace_arr, $parse_arr,1));
	} else {
		return ($parse_arr);
	}
}

// NUR FUER DIE ZEITMESSUNG
function getmicrotime(){ // GIBT IN MILLISECUNDEN ZURUECK
    list($usec, $sec) = explode(" ",microtime());  
    return ((float)$usec + (float)$sec);
}
?>

Im Prinzip erst ein "preg_split" damit $content nach "<.*>" Tags gesplittet wird.
Dann ein "array_map" um jedes Element im Array schnell durch eine Funktion laufen zu lassen.
Die Funktion "parse_glossar_array" schaut dann nach ob im Element ein "<" vorkommt (mit strpos, dass ist sehr schnell). Wenn nicht wird das Array mit den Glossar-Begriffen mit "preg_replace" gefunden ersetzt. Danach wird alles wieder mit "join" zusammengesetzt.

Einzig das Glossar-Array wie oben beschrieben muss anders aufgebaut werden (ein Such- und ein Replace-Array).
Nach meinen Messungen ist das sehr schnell.

Gruß
Frank
Mitglied: Guenni
Guenni 17.06.2008, aktualisiert am 05.12.2013 um 00:41:06 Uhr
Goto Top
@scholl

Hi,

ich habe hier einen Codeschnipsel zu preg_replace_callback, der aus einer
Adresse in einem Text einen Link erstellt.

Analog dazu müsste es ja dann auch möglich sein, aus einem Link wieder eine Adresse zu machen, in dem man die entsprechenden Tags wieder entfernt.

<?
$content = "Hier werden sie geholfen https://administrator.de, auf jeden Fall";  

// Die Callback-Funktion

function repl($erg){
 return $erg[1].'<a href="'.($erg[2]=='www.'?'http://'.$erg[2]:$erg[2]).$erg[3].'"   
target="_blank">'.$erg[2].$erg[3].'</a>';  
}

function mreplace($string){
	$pattern = '#(^|\s)(https*://|ftp://|mailto:|news:|www\.)([^\s<>]+)#sm';  
  return preg_replace_callback($pattern,'repl',$string);  
}

echo $content."<hr>";  
echo mreplace($content);

?>

Den Original-Schnipsel von Slava findest du hier http://www.bituniverse.com/forum/s/viewtopic.php/t,216/postdays,0/posto ...

Gruß
Günni