Regulärer Ausdruck, der Umlaute und nicht Umlaute erfasst?
Blöde Fragestellung aber ich weiß nicht wie ich es genau benennen soll.
Für eine Suchfunktion auf einer Webseite möchte ich die gefundenen Begriffe in der Vorschau der Suchergebnisse farblich markieren. Da die Webseite mehrsprachig ist, kommen auch viele Sonderzeichen wie Umlaute oder Akzentzeichen vor.
Nun möchte ich, dass der Nutzer sowohl Wörter mit Sonderzeichen, als auch ohne eingeben kann und immer das selbe findet. Er kann kann also bspw. nach „passe compose“ oder „passé composé“ suchen und findet immer die gleichen Seiten, auf denen „passé composé“ vorkommt.
In MySQL ist das bei der Suche kein Problem, da kann man mit COLLATE utf8_general_ci genau das erreichen. Schwierig wird es für mich, dass im Ergebnis zu markieren. Hier mal ein Beispielcode in PHP:
if (preg_match_all('/foo/ui', 'foo föö bar bär', $matches)) {
print_r($matches);
}
Das findet nur „foo“ und nicht „föö“. Umgedreht wird nur „föö“ gefunden. Die einzige Lösung die mir einfällt wäre so ein Ausdruck, der aber nicht sonderlich elegant ist und auch noch länger werden müsste:
'/f(?:o|ö)(?:o|ö)/ui'
Also zur Frage: Kennt jemand einen Modifikator oder irgendeine elegante Lösung, wie ich das besser und einfacher erreichen kann?
2 Antworten
Ein Vorschlag:
if (preg_match_all('/f([oö])t\1/ui', 'foto fötö bar bär', $matches)) {
print_r($matches);
}
Ich habe Nadel und Heuhaufen einmal etwas verändert, da es für dich hilfreich sein könnte. Du kannst beim ersten Vorkommen deiner erwarteten Vokale eine Bereich festlegen:
[oöó]
diesen gruppieren:
([oöó])
und somit bei einem erneuten Vorkommen via back reference darauf verweisen:
\1
Dies verkürzt zumindest deinen regulären Ausdruck.
Den Ausdruck würde ich noch mit einem Quantifier abkürzen (falls du es noch nicht selbst bemerkt hast):
/\b(f[oöóòô]{2})\b/ui
Bei so einem manuellem Suchstring natürlich aber ich muss mir den ja vorher erst mal basteln. Der Nutzer kann bei der Suche bspw. entweder richtig „effectuée“ oder auch „effectuee“ eingeben. Mein Suchstring nach der Ersetzung mit einer Normalisierungs-Tabelle würde dann in etwa so aussehen:
'/\b([eéèêë]ff[eéèêë][cĉ]t[uüúùû][eéèêë][eéèêë])\b/ui'
Da macht der Quantifier den Kohl auch nicht fett und ist auch nicht so einfach zu implementieren.
in MySQL ist das bei der Suche kein Problem, da kann man mit COLLATE utf8_general_ci genau das erreichen.
In PHP gibt's dafür
iconv("utf-8","ascii//TRANSLIT",$suchbegriff);
https://www.php.net/manual/en/function.iconv.php
Alex
Ok, aber wie hilft mir das bei meinem Problem weiter? Ich bekomme hier bspw. folgende Ausgabe:
$str = 'Passé composé französisch';
echo iconv("utf-8", "ascii//TRANSLIT", $str);
// Pass'e compos'e franz"osisch
Ich sehe jetzt auf Anhieb nicht, wie ich damit in dem Text „Passé composé französisch“ das Wort „composé“ markieren kann, wenn nach „compose“ gesucht wurde.
Ich sehe jetzt auf Anhieb nicht, wie ich damit in dem Text „Passé composé französisch“ das Wort „composé“ markieren kann, wenn nach „compose“ gesucht wurde.
Mit iconv bringst Du needle (also den Suchbegriff) und haystack (der Text, der durchsucht wird) ins gleiche Format. Das kannst Du dann vergleichen. Aus
Passé composé französisch
wird damit
Pass'e compos'e franz"osisch
Und durch das Ignorieren sämtlicher ' sowie '' bleibt als haystack übrig:
Passe compose franzosisch
Und damit kannst Du die Stelle finden, an der das Wort compose vorkommt.
Das funktioniert in meinen Fall aber leider nicht, da ich den gefundenen Text ja auch wieder anzeigen will und dort dann auch der Originaltext stehen soll. Auf der Webseite steht also bspw.:
Das Passé composé entspricht dem deutschen Perfekt.
Der Nutzer sucht nach „passe compose“. Danach soll der Text so markiert sein:
Das Passé composé entspricht dem deutschen Perfekt.
Das geht halt nicht, wenn ich auch den Originaltext vor dem Suchen schon ändere.
Das funktioniert in meinen Fall aber leider nicht, da ich den gefundenen Text ja auch wieder anzeigen will und dort dann auch der Originaltext stehen soll.
Du sollst nicht die Anzeige des Textes konvertieren sondern nur den haystack, der durchsucht wird. Der gesamte Text steht in der Variablen $original. Daraus machst Du
$haystack = iconv("utf-8", "ascii//TRANSLIT", $original);
und entfernst die einfachen und doppelten Anführungszeichen. Der Suchbegriff steht in der Variablen $suchbegriff. Daraus machst Du
$needle = iconv("utf-8", "ascii//TRANSLIT", $suchbegriff);
und entfernst auch hier die einfachen und doppelten Anführungszeichen.
Jetzt kannst Du $haystack nach den Vorkommen von $needle durchsuchen. Den zurückgegebenen (zweites und drittes Wort) kannst Du dann dazu verwenden, die entsprechende Stelle (=zweites und drittes Wort) in $original mit <strong> zu markieren.
Prinzip klar?
Den zurückgegebenen (zweites und drittes Wort) kannst Du dann dazu verwenden, die entsprechende Stelle (=zweites und drittes Wort) in $original mit <strong> zu markieren.
Da bin ich doch genau wieder am Anfang meines Problems angelangt.
Nach der Variante habe ich dann:
$haystack → 'Das Passe compose entspricht dem deutschen Perfekt.'
$needle → 'passe compose'
In $haystack finde ich dann natürlich $needle, soweit klar. Nur wie soll ich dieses Ergebnis dann nutzen, um in $original das zu bekommen:
Das <b>Passé composé</b> entspricht dem deutschen Perfekt.
Die Umwandlung mit iconv hilft mir ja nur um überhaupt zu testen, ob der Begriff darin vorkommt. Damit habe ich aber noch nicht den Originaltext geändert.
Prinzip klar?
Leider noch nicht.
In $haystack finde ich dann natürlich $needle, soweit klar.
Und damit hast Du die Positionen innerhalb des Strings $original, an denen der Suchbegriff beginnt und endet, und Du kannst die entsprechenden Leerzeichen des Strings $origninal durch das öffnende <b>-Tag bzw das schließende </b>-Tag ersetzen.
In Deinem Beispiel wäre das
- ersetze das vierte character durch '[SPACE]<b>'
- ersetze das achtzehnte character durch '</b>[SPACE]'
Leider funktioniert das eben nicht, da zum Beispiel ß zu ss ersetzt wird und dann zwei statt einem Buchstaben drin sind und $haystack und $original eine andere Länge haben. Auch andere Sonderzeichen wie œ oder æ werden mit zwei Zeichen ersetzt.
Aus „Lerne fleißig Passé composé…“ wird dann eben „Lerne fleissig Passe compose…“ und dann klappt die Ersetzung nicht an diesen Positionen.
und dann klappt die Ersetzung nicht an diesen Positionen.
dann nimm halt die Position sondern zähle die Leerzeichen.
Aus „Lerne fleißig Passé composé…“ wird dann eben „Lerne fleissig Passe compose…“
Dann ersetzt man das zweite Leerzeichen im String durch [SPACE]<b> und das dritte Leerzeichen im String durch </b>[SPACE]
Leerzeichen zählen ist zu unsicher, da es viele Varianten mit Klammern, Satzzeichen oder Zeilenumbrüchen geben kann, die dann alle mit gezählt werden müssen:
Lerne fleißig: Présente (aber auch Passé récent/composé)
unregelmäßigen Verben im:
* Passé composé
* Passé simple
Ich haben keinen Einfluss auf den Originaltext und da kann alles drin stehen.
Ich denke nicht, dass ich so direkt zum Ziel komme. Allerdings hilft mir iconv mal beim Normalisieren des Suchstrings. Danke! Mit dem normalisierten String werden ich dann mittels Regulären Ausdruck wie mit regex9 besprochen die Ersetzung vornehmen.
Zeichenklassen hatte ich erst nicht verwendet, weil ich auch so was im Hinterkopf hatte:
Allerdings macht das genau betrachtet keinen Sinn, da ich nach oe nur suchen müsste, wenn das auch im Suchtext vorkommt und dort sind die Zeichen ja immer richtig. Und wenn jemand mit oe sucht, wird es so oder so schwierig. Da hast du Recht, dass es mit Zeichenklassen etwas kürzer und übersichtlicher ist.
Eine Back-Referenz werde ich nicht brauchen, da ich beim Ersetzen ja nur was darum setzen muss. Ich werde es jetzt mal in dieser Art implementieren:
Mal sehen, was da rauskommt.
Irgendwie hatte ich nur gehofft, dass es einen genauso einfachen Weg wie bei MySQL gibt.