Zur Navigation

RegEx (PHP) verhält sich seltsam

URI identifizieren?

1 Ranma (Gast)

Hallo!

Ich habe das Forum gerade erst entdeckt. Seit ganz kurzem beschäftige ich mich mit Regulären Ausdrücken, mit PHP auch noch nicht besonders lange. Dadurch war ich mir nicht sicher, welche Funktion für welchen Zweck paßt. Darum habe ich sie mal ausprobiert und festgestellt, daß ein und der selbe Reguläre Ausdruck wohl von preg_match_all() ganz anders als von preg_replace() interpretiert wird. Kann das sein?

Mein Regulärer Ausdruck soll URI innerhalb eines beliebigen, längeren Textes erkennen können. Schließlich möchte ich dann noch einen klickbaren Link daraus machen, ohne BB-Code im Text haben zu müssen. Bei einem meiner Versuche funktionierte das zwar und darum dachte ich das komische Verhalten müsse an meinem RegEx liegen, aber es funktionierte nur bis zur Topleveldomain, also machte ich einen Versuch, um mögliche Unterverzeichnisse anzuhängen. Also ich verändere den RegEx immer wieder. Aber jedes Mal wird das Verhalten noch seltsamer.

Seltsam heißt beispielsweise, daß eine Ersetzung doppelt oder sogar vierfach vorgenommen wird (preg_match_all) oder der Link nur einen Teil der URL enthält (preg_replace).

Also habe ich die Frage, ob solches Verhalten für PHP normal ist und außerdem ob jemand einen brauchbaren und sämtliche möglichen URI einschließenden Regulären Ausdruck kennt?
Ranma

06.04.2015 04:30

2 Jörg Kruse

Um das Problem besser nachvollziehen zu können: kannst du einen Beispiel-Code posten, mit dem man das Fehlverhalten reproduzieren kann?

Dieses Forum verwendet diesen Regex:

$text = preg_replace('/(\]|\n| |>|\A)(https?|ftp):\/\/(\S*)(\[| |<|\Z)/isU', '\\1<a href="\\2://\\3">\\2://\\3</a>\\4', $text);

(edit: die automatische Umwandlung in Links ist hier allerdings deaktiviert)

Eine Übersicht über verschiedene URL-Regexe und deren Effektivität findet sich auf dieser Seite:

https://mathiasbynens.be/demo/url-regex

06.04.2015 09:51 | geändert: 06.04.2015 10:04

3 Ranma (Gast)

So ganz verstehe ich den im Forum verwendeten RegEx nicht. Weil erstens, wenn ich keinen BB-Code verwende, dann kann eine URI nur am Protokoll erkannt werden, oder? Also muß der RegEx mit http oder ftp beginnen. Zweitens sind da jede Menge vertikale Striche drin. Als logisches Oder ist das klar, aber was machen die alle noch vor dem http und dann mit > oder < dazwischen? Drittens, wenn der RegEx sicher wäre, dann bräuchte die automatische Umwandlung doch nicht abgeschaltet zu sein. Daraus folgt, daß der entweder nicht sicher ist oder nicht funktioniert. Viertens, das U hintendran hatte ich auch, aber nur weil ich es mit u verwechselt hatte, weil die meisten Tutorials zu alt sind, um u zu erklären, also vor allem daß das zwei verschiedene Modifizierer sind.

Wie das oft so ist, kaum hat man den Mut gefunden, in einem Forum nach Hilfe zu fragen, schon kann man das Problem lösen. Vielleicht sollte ich den Effekt viel öfter nutzen. Also U am Ende gegen u auszutauschen hat schonmal das halbe Problem gelöst und die Verlinkung brach nicht mehr nach der Hälfte ab.

Erst hatte ich preg_match_all() und str_replace() in einer Schleife verwendet, aber mit preg_replace() geht das viel besser, dann gibt es keine Probleme durch überflüssige, zusätzliche Ersetzungen.

Die restlichen Probleme hatte ich, weil ich beim ständigen Nachbessern des RegExes mit den Untergruppen durcheinandergekommen war. Ich mußte schließlich nur noch eine Klammer versetzen, um den RegEx voll funktionsfähig zu machen.

Ein bißchen merkwürdig war anfangs, daß jeder Link immer nur zu einem Ziel namens http führen sollte. Irgendwie war PHP mit dem Einsammeln der Treffer überfordert bis ich das Problem dadurch beseitigen konnte, daß ich einige Untergruppen als nichteinfangend kennzeichnete. Vielleicht waren es zuvor einfach zu viele einfangende.

Jedenfalls habe ich jetzt den perfekten RegEx:

#((?:(?:https?)|(?:ftp))://(?:(?:.+:.+@)?(?:[\p{L}\d-]+\.)+\w{2,6})|(?:(?:\d{1,3}\.){3}\d{1,3}))(/[\p{L}\d-/\?\#=\.&]+)*#umis

Mein Versuch fing mit einem abgeschriebenem Lehrbuchbeispiel an, darum glaube ich, daß der Ausdruck jetzt noch besser ist. Jedenfalls sollte er sämtliche GET-Variablen oder Sprungmarken genau wie Unterverzeichnisse gleich miterfassen. Ich brauche für die Ersetzung dann auch keine fünf Variablen, sondern nur \0

Unklar ist mir jetzt nur noch, ob Modifier irgendwie auch extra nur für Untergruppen eingesetzt werden können (ich meine so etwas gelesen zu haben, aber nicht wie das funktionieren soll) oder ob ich das Ziel des Links noch hinter normalem Text verbergen lassen kann.

Das eigentliche Problem ist gelöst.
Ranma

07.04.2015 05:16

4 Jörg Kruse

So ganz verstehe ich den im Forum verwendeten RegEx nicht. Weil erstens, wenn ich keinen BB-Code verwende, dann kann eine URI nur am Protokoll erkannt werden, oder? Also muß der RegEx mit http oder ftp beginnen. Zweitens sind da jede Menge vertikale Striche drin. Als logisches Oder ist das klar, aber was machen die alle noch vor dem http und dann mit > oder < dazwischen?

Das Forum bezieht den Kontext der URLs mit ein, d.h. was vor und nach der URL steht (\\1 und \\4).

Drittens, wenn der RegEx sicher wäre, dann bräuchte die automatische Umwandlung doch nicht abgeschaltet zu sein. Daraus folgt, daß der entweder nicht sicher ist oder nicht funktioniert.

Dieses Forum hatte insbesondere in seiner Anfangszeit einen starken Fokus auf SEO-Themen. Dabei wurden auch Webseiten thematisiert, die aus Sicht von Google nicht ganz "koscher" sind. Damit das Forum wegen "Bad Links" nicht in Mitleidenschaft gezogen wird, hatte ich die automatische Verlinkung deaktiviert.

In diesem Forum ist sie z.B. aktiviert.

Viertens, das U hintendran hatte ich auch, aber nur weil ich es mit u verwechselt hatte, weil die meisten Tutorials zu alt sind, um u zu erklären, also vor allem daß das zwei verschiedene Modifizierer sind.

Ja, u ist wohl ein neuerer Modifier. Im Foren-Regex ist ein Ungreedy-Modifier notwendig, weil nur bis zum nächsten Begrenzungszeichen (\\4) gesucht werden soll

Dein Regex hat keine solchen Begrenzungen, deswegen bricht PHP mit dem U-Modifier mitunter frühzeitig ab

Unklar ist mir jetzt nur noch, ob Modifier irgendwie auch extra nur für Untergruppen eingesetzt werden können (ich meine so etwas gelesen zu haben, aber nicht wie das funktionieren soll)

Da bin ich überfragt. Du kannst mittels preg_replace_callback() die Untergruppen noch weiterverarbeiten, falls das weiterhilft

oder ob ich das Ziel des Links noch hinter normalem Text verbergen lassen kann.

das habe ich jetzt nicht verstanden?

07.04.2015 08:47 | geändert: 07.04.2015 08:48

5 Ranma (Gast)

Mit normalem Text meinte ich das, was zwischen <a href="..."> und </a> stehen würde. Der eigentliche Link ist ja nur zwischen den Doublequotes notwendig. Eigentlich wollte ich das nächste Wort noch für die Benennung des Links verwenden und darum gleich noch im RegEx miteinfangen. Aber das war mir dann zu schwierig, weil da auch etwas anderes als ein normales Wort stehen könnte. Eigentlich ginge da so etwas wie [/w|.]{2} aber steht der Link ganz am Schluß vom Text und einige Leute haben die Angewohnheit genau da ihren Link hinzustellen, dann würde sicher alles, was hinter den Link schaut, versagen?

Übrigens, das nächste Problem, das ich mit einem RegEx angehen will, ist ein Plagiat oder Zitat zu finden. Ich habe mir überlegt, daß man normalerweise erst einen Zeilenumbruch macht, bevor man so etwas einfügt. Das eigentliche Problem dabei ist, daß ich in zwei Texten sozusagen zugleich suchen muß.

Und noch eines: Ich habe in einem anderem Forum gesehen, daß ein Textabschnitt, der durch einen Moderator gelöscht wurde, dort mit XXXX überschrieben wird. Das finde ich gut. Wenn ich das nachahmen wollte und dabei die Länge des überschriebenen Abschnittes beibehalten will, wie läßt sich dessen Länge herausfinden? Also praktisch genau das Umgekehrte tun, das ein Quantifier macht, nur wie? Oder wäre es in jedem Fall (mir fällt nämlich keine andere Anwendung dafür ein) besser die Länge dann schon beim Löschen in einer extra Variablen abzuspeichern?
Ranma

09.04.2015 01:43

6 Jörg Kruse

Eigentlich wollte ich das nächste Wort noch für die Benennung des Links verwenden und darum gleich noch im RegEx miteinfangen. Aber das war mir dann zu schwierig, weil da auch etwas anderes als ein normales Wort stehen könnte. Eigentlich ginge da so etwas wie [/w|.]{2} aber steht der Link ganz am Schluß vom Text und einige Leute haben die Angewohnheit genau da ihren Link hinzustellen, dann würde sicher alles, was hinter den Link schaut, versagen?

Was sind das für Texte - von Usern eingestellte? dann muss es ja eine Konvention geben, dass ein Wort hinter einen Link der Linktext sein soll? es könnten ansonsten ja auch Wörter folgen, die mit der Beschreibung der URL nichts zu tun haben.

Übrigens, das nächste Problem, das ich mit einem RegEx angehen will, ist ein Plagiat oder Zitat zu finden. Ich habe mir überlegt, daß man normalerweise erst einen Zeilenumbruch macht, bevor man so etwas einfügt.

Auch hier kommt es auf Konventionen an. Ein Zitat kann z.B. durch Anführungszeichen oder auch durch Einrücken eines Textes gekennzeichnet sei.

Das eigentliche Problem dabei ist, daß ich in zwei Texten sozusagen zugleich suchen muß.

Willst du überprüfen, ob zwei Texte gleiche Textabschnitte enthalten?

Und noch eines: Ich habe in einem anderem Forum gesehen, daß ein Textabschnitt, der durch einen Moderator gelöscht wurde, dort mit XXXX überschrieben wird. Das finde ich gut. Wenn ich das nachahmen wollte und dabei die Länge des überschriebenen Abschnittes beibehalten will, wie läßt sich dessen Länge herausfinden? Also praktisch genau das Umgekehrte tun, das ein Quantifier macht, nur wie? Oder wäre es in jedem Fall (mir fällt nämlich keine andere Anwendung dafür ein) besser die Länge dann schon beim Löschen in einer extra Variablen abzuspeichern?

Willst du gleich beim Löschen durch Xe ersetzen, oder soll dies jeweils bei der Ausgabe passieren? Im zweiten Fall könnte man zwei Marker setzen, die Anfang und Ende markieren (z.B. [del] und [/del]). Den zu löschenden Abschnitt könntest du dann mit preg_replace_callback() identifizieren und in der Callback-Funktion alle Zeichen durch X ersetzen.

09.04.2015 16:35 | geändert: 09.04.2015 16:41

7 Ranma (Gast)

Also der Hintergrund ist, daß ich mal ein Forum betreiben wollte, aber innerhalb von zwei Wochen hatte ich es irgendwie geschafft, mein Admin-Konto zu löschen. Das erschien mir so riskant, daß ich alles gelöscht habe und nichts mehr online stellen will, von dem ich nicht völlig verstanden habe, wie es funktioniert. Hauptsächlich überlege ich zur Zeit, wie Forumsfunktionen idealerweise aussehen sollten.

Hm ja, man könnte wahrscheinlich auch das eine tun ohne das andere zu lassen. Also sowohl einen unmarkierten Link klickbar machen als auch einen durch BB-Code markierten der Konvention nach umsetzen. Dann sollte ich negative Lookaraounds brauchen, damit die beiden Funktionen sich nicht gegenseitig behindern?

Zu überprüfen, ob zwei Texte gleiche Textabschnitte enthalten, ist tatsächlich die bessere Formulierung für das, was ich noch versuche.

Beim Löschen bin ich noch am meisten am überlegen, wie man das am besten macht. Heutzutage bieten die meisten Foren noch nachträgliches Editieren eines Beitrages an. Damit könnte ein Benutzer doch die Löschung durch den Moderator wieder überschreiben? Löschmarken wären vielleicht auch weitere Aufgaben gut wie zum Beispiel nachzuzählen, wie oft Löschungen bei einem bestimmtem Benutzer notwendig waren. Aber wie vermeide ich, daß die Löschmarken auch mal nur zufällig in einem Text enthalten sind? Vielleicht wäre eine Schwarze Liste, die dann auch weniger Handarbeit durch den Moderator bedeuten würde, doch besser?
Ranma

10.04.2015 02:08

8 Jörg Kruse

Hm ja, man könnte wahrscheinlich auch das eine tun ohne das andere zu lassen. Also sowohl einen unmarkierten Link klickbar machen als auch einen durch BB-Code markierten der Konvention nach umsetzen. Dann sollte ich negative Lookaraounds brauchen, damit die beiden Funktionen sich nicht gegenseitig behindern?

Ja, die nicht markierten URLs müssen als solche erkannt werden, sonst kommt es zu doppelten Ersetzungen :)

Zu überprüfen, ob zwei Texte gleiche Textabschnitte enthalten, ist tatsächlich die bessere Formulierung für das, was ich noch versuche.

Unter Linux gibt es hierzu das Programm comm, das ließe sich vielleicht auch in PHP mittels system() nutzen. Ansonsten braucht es eine selbstgestrickte Funktion, die die zwei Dateien Zeile für Zeile vergleicht. Es gibt noch similar_text() - aber da wird anscheinend nur die Prozentzahl der Übereinstimmugen zurückgegeben

Beim Löschen bin ich noch am meisten am überlegen, wie man das am besten macht. Heutzutage bieten die meisten Foren noch nachträgliches Editieren eines Beitrages an. Damit könnte ein Benutzer doch die Löschung durch den Moderator wieder überschreiben?

Ja, aber das könnte er auch, wenn der Textabschnitt direkt ge-ixt wurde

Aber wie vermeide ich, daß die Löschmarken auch mal nur zufällig in einem Text enthalten sind? Vielleicht wäre eine Schwarze Liste, die dann auch weniger Handarbeit durch den Moderator bedeuten würde, doch besser?

Du meinst eine Liste von Wörtern, die automatisch ersetzt werden? das ist sicher einfacher umzusetzen, dazu reicht auch schon ein einfaches str_replace()

10.04.2015 17:07 | geändert: 10.04.2015 17:10

9 Ranma (Gast)

Jetzt habe ich versucht Lookarounds ((?<!(<a href="))(?!(</a>))) einzubauen. Als Folge davon wird nur noch ein leerer String als Link formatiert. Die weiteren Klammern machen das Ganze komplett unübersichtlich. Man kann den gefundenen Untergruppen angeblich Namen geben, aber dafür habe ich noch keine Anleitung gefunden. Ich weiß nichtmal, ob die Lookarounds an einer bestimmten Stelle stehen müssen. Andererseits erscheint mir beides als Ursache für den leeren String unwahrscheinlich, weil nicht nur die URI, sondern mein kompletter Beispieltext, unsichtbar geworden ist. Im durch den Brauser sichtbaren Quelltext steht nur noch <a href=""></a>.
Ranma

11.04.2015 01:04

10 Jörg Kruse

Der negative Lookahead sollte doch eher so ausschauen (?):

(?!(">))

Das "</a>" folgt ja erst später, nach dem Linktext

Und zwischen Lookbehind und Lookahead, steht dann noch dein Regex für die URL, nehme ich an?

11.04.2015 16:14 | geändert: 11.04.2015 16:19