Christian: Glossarbegriffe automatisch per RegExp verlinken

Hallo,

ich bastele gerade an einem Script, welches ein Array von Begriffen in einem beliebigen Text automatisch verlinken soll. Allerdings gibt es noch ein kniffeliges Problem zu lösen und ich hoffe auf Eure Hilfe, da ich überhaupt nicht mehr alleine weiterkomme, ich sitze da schon seit Wochen dran :-(

Demo: http://www.christianhart.de/test/glossar.php
kommentierter Source dazu: http://www.christianhart.de/test/glossar.phps
(erscheint dort aber nicht als Source, kann aber heruntergeladen werden)

Das Script soll letztlich folgende Problemstellung bewältigen können:

1. nur Begriffe in Absätzen <p></p> und Listen <li></li> in Betracht ziehen
2. einen dort gefundenen Begriff insgesamt nur 1x verlinken
3. bestimmte Begriffe sollen mittels Flag nur als eigenständiges Wort verlinkt werden, nicht aber mitten in anderen Wörtern
4. kürzere Begriffe, die in längeren Begriffen vorkommen, sollen nicht nochmals verlinkt werden (z.B. SETI in Paläo-SETI o.ä.)

Punkt 1,2,3 funktionieren bereits, allerdings gibt es bei Punkt 4 das Problem, dass eben genau dies passiert. Ich habe dazu im Beispieltext eine Bemerkung hinzugefügt.

Frage: wie muss ich mein Script bzw. die Regular Expression umbauen, damit Punkt 3 funktioniert? Ich habe bereits mit Expressions experimentiert, die versuchen, einen Treffer innerhalb von <a></a> auszuschliessen, aber das war leider nicht von Erfolg gekrönt. Vielleicht kann man sogar das komplexe Aufsplitten der Absätze da mit einbasteln, ich habe im Moment leider keine bessere Lösung als diese gefunden.

Ich wäre echt dankbar, wenn mir da jemand raushelfen könnte :-)

Gruß Christian

  1. Hi,

    1. kürzere Begriffe, die in längeren Begriffen vorkommen, sollen nicht nochmals verlinkt werden (z.B. SETI in Paläo-SETI o.ä.)

    Du musst doch nur prüfen, ob vor und nach dem Wort ein Leerzeichen oder Satzzeichen steht.
    Oder umgekehrt, du prüfst, ob vor und nach dem Wort KEIN Buchstabe ist. Dann hast du die gewissheit, das dieses Wort alleine steht.

    MfG
    Manuel

    1. Du musst doch nur prüfen, ob vor und nach dem Wort ein Leerzeichen oder Satzzeichen steht.
      Oder umgekehrt, du prüfst, ob vor und nach dem Wort KEIN Buchstabe ist. Dann hast du die gewissheit, das dieses Wort alleine steht.

      Hi Manuel,

      ehm das funktioniert bereits, ist Punkt 3 meiner Liste (hab mich da vorher leicht vertippt). Es geht um Punkt 4. Das Problem ist, da die Begriffe in einer Schleife abgearbeitet werden (müssen), und zwar vom längsten zum kürzesten Begriff (wegen der Logik, da der kürzere Begriff im längeren vorkommen kann, nicht aber umgekehrt) kommt es zu Doppelverlinkungen.

      Nehmen wir an, es wurde bereits z.B. der Begriff "Paläo-SETI" verlinkt. Es steht in der Variable $text nun also an dessen Stelle <a href="?id=x">Paläo-SETI</a>. Beim späteren Durchgang mit dem Begriff "SETI" findet die Expression diesen Begriff ("ahh, SETI wurde noch nicht verlinkt, können wir ändern") und verlinkt ihn, ob wohl "SETI" bereits innerhalb von <a></a> im Rahmen von "Paläo-SETI" verlinkt wurde, nur mit anderem Ziel halt. Wir haben dann also <a href="?id=x">Paläo-<a href="?id=y">SETI</a></a>. Nicht gerade korrektes HTML, von der Übersichtlichkeit ganz zu schweigen :-(

      Und genau diese Doppelverlinkungen möchte ich vermeiden. Die Expression müsste irgendwie so schlau werden, dies alleine zu erkennen, und daran scheitere ich.

      Gruß Christian

      1. echo $begrueszung;

        Nehmen wir an, es wurde bereits z.B. der Begriff "Paläo-SETI" verlinkt. Es steht in der Variable $text nun also an dessen Stelle <a href="?id=x">Paläo-SETI</a>. Beim späteren Durchgang mit dem Begriff "SETI" findet die Expression diesen Begriff

        Nutzen dir da vielleicht Assertions etwas? Siehe gleichlautenden Abschnitt im Kapitel PCRE Pattern Syntax im PHP-Manual.

        Damit kann nach Zeichen vor oder nach dem Suchbegriff schauen, ohne dass diese Zeichen "verbraucht" werden.

        echo "$verabschiedung $name";

      2. Huhu Christian

        ehm das funktioniert bereits, ist Punkt 3 meiner Liste (hab mich da vorher leicht vertippt). Es geht um Punkt 4. Das Problem ist, da die Begriffe in einer Schleife abgearbeitet werden (müssen), und zwar vom längsten zum kürzesten Begriff (wegen der Logik, da der kürzere Begriff im längeren vorkommen kann, nicht aber umgekehrt) kommt es zu Doppelverlinkungen.

        Mmmmh, mit ohne Abarbeitung in einer Schleife wäre es ggf. einfacher.
        Hier mal ein Beispiel
        http://simplecontent.net/snippets/view/project/PHP_Snippets/03_Beispiele/01_text/link_words_5.html

        Dort werden alle zu ersetzenden Begriffe zu einem RegExp "verodert".

        Wenn Du die Schleife verwenden musst könnte es evtl. helfen wenn

        a) Du Deinen Regulären Ausdruck so erweiterst, dass er nie innerhalb von a-Tags matcht.

        b) Dein Skript alle bereits ersetzten Begriffe zwischenspeichert, z.B. in einem Array.
        Vor einem erneuten Durchgang muss das Skript nun den neuen Begriff gegen das Array prüfen ob er dort irgendwo "matchen" würde.
        Falls ja wird dieser Durchgang ausgelassen und der nächste Begriff geholt.

        c) oder etwas völlig anderes ;-)

        Viele Grüße

        lulu

        --
        bythewaythewebsuxgoofflineandenjoytheday
        1. Hi Lulu,

          danke für den Tip, das Beispiel

          http://simplecontent.net/snippets/view/project/PHP_Snippets/03_Beispiele/01_text/link_words_5.html

          kenne ich, hatte es früher aber nur überflogen. Ich habe es jetzt in meinem Script verwurstet und es funktioniert auch sehr gut (ist auch ca. 10x schneller!), und ich kann dabei auf den Inline-Flag offenbar ganz verzichten (auch, wenn ich es nicht so ganz verstehe, wieso ;-). Das Problem 4 wäre damit gelöst, allerdings ist Problem 2 nun wieder aufgetaucht, das Script verlinkt einen Begriff nun mehrmals anstatt nur 1x im gesamten Text. Ein Limit von 1 beim preg_replace macht hier keinen Sinn, da dann nur insgesamt 1 der 15 Begriffen aus dem Array verlinkt wird.

          Demo: http://www.christianhart.de/test/glossar2.php
          Source: http://www.christianhart.de/test/glossar2.phps

          Frage: Kann man das mit einer einfachen Änderung in meinem neuen Script irgendwie so ändern, dass z.B. "Ancient Skies" nur 1x verlinkt wird anstatt wie jetzt 4x? Wird sonst im Text etwas unübersichtlich mit den Wiederholungen... Ich habe schon überlegt, ob ich den fertig ersetzten Text nach '<a href="###">Begriff</a>' durchsuche und dann die Anzahl der Matches minus 1 durch den Begriff ohne Link ersetze, was letztlich nur einen Link ergeben würde. Klingt aber so wie von hinten durch die Brust ins Auge :-p

          Das muss doch irgendwie möglich sein *seufz*

          Gruß Christian

          1. Huhu

            Frage: Kann man das mit einer einfachen Änderung in meinem neuen Script irgendwie so ändern, dass z.B. "Ancient Skies" nur 1x verlinkt wird anstatt wie jetzt 4x?

            Das muss doch irgendwie möglich sein *seufz*

            Danke für die interessante Aufgabenstellung, die Lösung ist eigentlich ganz einfach.
            Du musst dafür sorgen, dass nach einer erfolgten Ersetzung das entsprechende Array-Element "verschwindet".

            Wenn Du selbst probieren möchtest schau Dir mal das hier an
            http://simplecontent.net/snippets/view/project/PHP_Snippets/03_Beispiele/01_text/extract_links_1.html

            dort werden die Treffer in einem per Referenz übergebenen Array gesammelt.
            Für Deine Anwendung braucht man das genau umgekehrt, also die gefundenen Treffer sollen aus dem Array entfernt werden.

            Viele Grüße

            lulu

            --
            bythewaythewebsuxgoofflineandenjoytheday
            1. Hi Lulu,

              ich glaube, der Link hier ist besser, ist nämlich die Erweiterung zu dem ersten Link von Dir:

              http://simplecontent.net/snippets/view/project/PHP_Snippets/03_Beispiele/01_text/link_words_6.html

              Habs eingebaut und nun wird alles nur 1x verlinkt, Punkt 1-4 erledigt, juchhu! Vielen Dank, endlich geht es, nach Wochen!!!

              Gruß Christian

              Hier das funktionierende Script für alle anderen:

              <?

              Funktionen zur Geschwindigkeitsmessung des Scripts

              FUNCTION S_TIMER(){list($low,$high)=SPLIT(" ",MICROTIME());$t=$high+$low;RETURN $t;}
              FUNCTION E_TIMER($s){LIST($low,$high)=SPLIT(" ",MICROTIME());$t=$high+$low;$u=$t-$s;PRINTF("<hr><p>Script in %s%0.6f Sekunden berechnet</p>","",$u);RETURN $t;}

              function cmp($a,$b)
              {
                  $bool = strlen($a) < strlen($b);
                  return $bool;
              }

              function gimmeTheLink($k, &$map)
              {
                  $r = '';
                  if (isset($map[$k])){
                      $r = $map[$k];
                      unset($map[$k]);
                  }else{
                      $r = $k;
                  }
                  return $r;
              }

              zu durchsuchender Text

              $text.='<h1>Überschrift mit dem Begriff Paläo-SETI, nicht verlinkbar</h1><p>Am 14. September 1973 gründete Dr. Gene M. Phillips aus Chicago (Illinois, USA) die Ancient Astronaut Society als gemeinnützige Gesellschaft. Das Certificate of Registration des Staatssekretärs des US-Bundesstaates Illinois vom 4. Dezember 1973 hielt als Gesellschaftszweck fest: "Die AAS ist eine Organisation von Einzelpersonen und Gruppen, die sich ausschliesslich für wissenschaftliche, erzieherische und literarische Zwecke im Rahmen der Aktivitäten der Gesellschaft finden" (Dokument Nr. 22 481 1389)</p><p>Die Gesellschaft gab eine kleine Mitteilungszeitschrift mit Namen "Ancient Skies" heraus, die sich im Laufe der Jahre stark verbesserte. Anfangs war es ein vierseitiger englischsprachiger Schwarz-Weiss-Folder. Mit der Zeit nahm die Seitenzahl sukzessive zu. In den 90er Jahren wurde Ancient Skies schliesslich farbig. 1998 hatte man schliesslich ein hervorragend layoutetes 32 seitiges Farbheftchen.</p><p>Genau 25 Jahre nach Gründung benannte sich die Ancient Astronaut Society in Forschungsgesellschaft für Archäologie, Astronautik und SETI um. <b><<< Leider wird das Wort "SETI" im bereits verlinkten Begriff verlinkt (siehe Linkziel in der Statusleiste oder Quelltext), wie link :-(</b> Im September 1998 erhielten die Mitglieder erstmals "Sagenhafte Zeiten" anstelle von Ancient Skies. Mit dieser Umbenennung pensionierte sich der einstige Präsident, Dr. Gene M. Phillips, selbst. Im "Führungsteam" der A.A.S. sind heute im wesentlichen vier Personen: Erich von Däniken, als geistiger Vater der Gesellschaft sowie Ulrich Dopatka, ein Bibliothekar aus Bern, der sich mit den neuen Medien auskennt und Querverbindungen zu schaffen weiss. Desweiteren Cornelia von Däniken sowie Peter Fiebag, die gemeinsam die Redaktion der Mitgliedszeitschrift "Sagenhafte Zeiten" leiten.</p><p>Die internationale AAS wurde nach der letzten Nummer von "Ancient Skies" ebenfalls neu strukturiert. Giorgio A. Tsoukalos und Ulrich Dopatka gründeten in der Tradition der Ancient Astronaut Society die neue "Archaeology, Astronautics and SETI Research Association" die unter dem Name "legendarytimes.com" im Web zu finden ist und auch die Zeitschrift "Legendary Times" herausgibt. Sitz dieser neuen Forschungsgesellschaft ist Ithaca, New York.</p><h3>Testabsatz für Inline-Flag</h3><p>Kein AASX oder XAAS verlinken. Es gibt einen verlinkten Ort Ica in Peru, nicht aber das Ica im Wort Chicago.</p><small>&copy Christian Hart</small>';

              zu verlinkende Glossarbegriffe (inline|Begriff)

              $suchbegriffe=ARRAY(
                "AAS" => "#1",
                "Ancient Skies" => "#2",
                "Archaeology, Astronautics and SETI Research Association" => "#3",
                "Christian Hart" => "#4",
                "Erich von Däniken" => "#5",
                "Forschungsgesellschaft für Archäologie, Astronautik und SETI" => "#6",
                "Gene M. Phillips" => "#7",
                "Giorgio A. Tsoukalos" => "#8",
                "Ica" => "#9",
                "Legendary Times" => "#10",
                "Paläo-SETI" => "#11",
                "Peter Fiebag" => "#12",
                "SETI" => "#13",
                "Sagenhafte Zeiten" => "#14",
                "Ulrich Dopatka" => "#15"
                );

              $start=S_TIMER();

              $map = array();

              foreach ($suchbegriffe as $k => $v){
                  $key2use = $k;
                  $map[$key2use] = sprintf('<a href="%s">%s</a>', $v, $k);
              }

              uksort($map, 'cmp');
              $tmp = '(\b'.join ('\b|\b', array_keys($map)).'\b)';

              alle Absätze und Aufzählungen mit {{ }} markieren

              $text=PREG_REPLACE('%<(p|li)>(.*)</(p|li)>%siU',"<$1>{{$2}}</$3>",$text);

              Felder mit {{ }} suchen

              PREG_MATCH_ALL('/{{(.*)}}/U',$text,$felder);

              gefundene {{ }} Felder zu einem zusammenfügen

              $feld="";
              FOREACH($felder[0] AS $treffer)
              {
              $feld.=$treffer;
              }

              $regexp = "/".$tmp."(?![^<]+>)/ie";

              $feld = preg_replace($regexp,'gimmeTheLink("\1", $map)', $feld);

              Feld nach }} aufsplitten

              $neuesfeld=EXPLODE('}}',$feld);

              Alle Felder im Text ersetzen

              FOR($i=0;$i<COUNT($neuesfeld);$i++)
              {

              }} Klammern am Feldende wieder hinzufügen

              $neuesfeld[$i].='}}';

              geändertes Feld im Text ersetzen

              $text=STR_REPLACE($felder[0][$i],$neuesfeld[$i],$text);
              }

              Feldmarkierungen entfernen

              $text=PREG_REPLACE('%{{(.*)}}%siU',"$1",$text);

              echo $text;

              E_TIMER($start);

              ?>