Warum kann ich neben einer Wortgrenze keine Zeichen mit Akzent verwenden?
Lesezeit: 21 Minuten
Rexxars
Ich versuche, eine dynamische Regex zu erstellen, die mit dem Namen einer Person übereinstimmt. Es funktioniert bei den meisten Namen ohne Probleme, bis ich am Ende des Namens auf akzentuierte Zeichen stieß.
Beispiel: Einige ausgefallene Namen
Die Regex, die ich bisher verwendet habe, ist:
/\b(Fancy Namé|Namé)\b/i
So verwendet:
"Goal: Some Fancy Namé. Awesome.".replace(/\b(Fancy Namé|Namé)\b/i, '<a href="#">$1</a>');
Das passt einfach nicht zusammen. Wenn ich das é durch ae ersetze, passt es gut. Wenn ich versuche, einen Namen wie “Some Fancy Naméa” abzugleichen, funktioniert es einwandfrei. Wenn ich den letzten Wortgrenzanker des Wortes entferne, funktioniert es einwandfrei.
Warum funktioniert das Wort Grenzfahne hier nicht? Irgendwelche Vorschläge, wie ich dieses Problem umgehen könnte?
Ich habe überlegt, so etwas zu verwenden, bin mir aber nicht sicher, wie die Leistungseinbußen aussehen würden:
"Some fancy namé. Allow me to ellaborate.".replace(/([\s.,!?])(fancy namé|namé)([\s.,!?]|$)/g, '$1<a href="#">$2</a>$3')
Vorschläge? Ideen?
Ich vermute, dass é vielleicht nicht als Wortzeichen (\w) betrachtet wird …
– Rauben
15. März 2010 um 19:17 Uhr
Bobine
Die Regex-Implementierung von JavaScript ist nicht Unicode-fähig. Es kennt nur die “Wortzeichen” in Standard-Low-Byte-ASCII, die nicht enthalten sind é oder andere akzentuierte oder nicht-englische Buchstaben.
Weil é ist kein Wortzeichen für JS, é gefolgt von einem Leerzeichen kann niemals als Wortgrenze betrachtet werden. (Das würde passen \b wenn es in der Mitte eines Wortes verwendet wird, wie Namés.)
/([\s.,!?])(fancy namé|namé)([\s.,!?]|$)/
Ja, das wäre die übliche Problemumgehung für JS (allerdings wahrscheinlich mit mehr Satzzeichen). Für andere Sprachen würden Sie im Allgemeinen Lookahead/Lookbehind verwenden, um zu vermeiden, dass die Zeichen vor und nach der Grenze übereinstimmen, aber diese werden in JS schlecht unterstützt/fehlerhaft, also am besten vermieden.
Das erklärt es. Ich endete mit folgendem: /(\W|^)(fancy namé|name)(\W|$)/ig Was meinen Bedürfnissen zu entsprechen scheint 🙂
– Rexxars
16. März 2010 um 9:30 Uhr
Rob hat Recht. Zitiert aus der ECMAScript 3rd Edition:
15.10.2.6 Behauptung:
Die Produktion Behauptung\b bewertet nach …
2. Anruf IsWordChar(e−1) und lass ein sei das boolesche Ergebnis 3. Anruf IsWordChar(e) und lass B sei das boolesche Ergebnis
und
Die interne Hilfsfunktion IsWordChar … führt Folgendes aus:
3. Wenn C eines der dreiundsechzig Zeichen in der folgenden Tabelle ist, return wahr.
a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
0 1 2 3 4 5 6 7 8 9 _
Seit é ist keines dieser 63 Zeichen die Stelle dazwischen é und a wird als Wortgrenze betrachtet.
Wenn Sie die Zeichenklasse kennen, können Sie eine negative Look-Forward-Assertion verwenden, z
Unglücklicherweise, auch wenn Javascript eines Tages volle und angemessene Unterstützung für Unicode haben sollte, werden Sie es tun still müssen mit Wortgrenzen äußerst vorsichtig sein. Es ist leicht zu missverstehen, was a \b wirklich tut.
Hier ist Perl-Code, der erklärt, was \b wirklich tut, was wahr ist, unabhängig davon, ob Ihre Pattern-Engine bereits auf BNM aktualisiert wurde:
# if next is word char:
# then last isn't word
# else last isn't nonword
$word_boundary_before = qr{ (?(?= \w ) (?<! \w ) | (?<! \W ) ) }x;
# if last is word:
# then next isn't word
# else next isn't nonword
$word_boundary_after = qr{ (?(?<= \w ) (?! \w ) | (?! \W ) ) }x;
Das erste ist wie ein \b vor etwas, und das zweite ist wie a \b Danach. Das verwendete Konstrukt ist die Regex-Bedingung „IF-THEN=ELSE“, die von der allgemeinen Form ist (?(COND)THEN|ELSE). Hier verwende ich a BED test, das ist im ersten Fall ein Lookahead, im zweiten Fall ein Lookahead. Die DANN und ANDERS Klauseln sind in beiden Fällen negierte Lookarounds, sodass sie den Rand der Zeichenfolge berücksichtigen.
Ich erkläre hier mehr über den Umgang mit Grenzen und Unicode in regulären Ausdrücken.
Unterstützung von Unicode-Eigenschaften
Der aktuelle Stand der Dinge im Umgang von Javascript mit Unicode scheint um das wie Java zu sein, die Definitionen von Javascript \w und solche sind noch verkrüppelt, weil man in den 1960er Jahren feststeckte Welt des ASCII. Das ist einfach eine miserable Situation, das gebe ich zu. Sogar Python, das in diesen Dingen ziemlich konservativ ist (es unterstützt zum Beispiel nicht einmal rekursive reguläre Ausdrücke), tut erlauben seine Definitionen von \w und \s korrekt mit Unicode arbeiten. Das ist wirklich das Nötigste an Funktionalität.
Es ist sowohl besser als auch schlechter in Javascript. Das ist wegen dir kann Verwenden Sie einige der grundlegendsten Unicode-Eigenschaften in Javascript (oder Java). Es sieht so aus, als ob Sie in der Lage sein sollten, die ein- und zweistelligen Unicode-Eigenschaften „Allgemeine Kategorie“ zu verwenden. Das bedeutet, dass Sie in der Lage sein sollten, die Kurznamenversionen aus der ersten Spalte unten zu verwenden:
Short Name Long Name
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
\pL \p{Letter}
\p{Lu} \p{Uppercase_Letter}
\p{Ll} \p{Lowercase_Letter}
\p{Lt} \p{Titlecase_Letter}
\p{Lm} \p{Modifier_Letter}
\p{Lo} \p{Other_Letter}
Short Name Long Name
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
\pM \p{Mark}
\p{Mn} \p{Nonspacing_Mark}
\p{Mc} \p{Spacing_Mark}
\p{Me} \p{Enclosing_Mark}
Short Name Long Name
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
\pN \p{Number}
\p{Nd} \p{Decimal_Number},\p{Digit}
\p{Nl} \p{Letter_Number}
\p{No} \p{Other_Number}
Short Name Long Name
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
\pP \p{Punctuation}, \p{Punct})
\p{Pc} \p{Connector_Punctuation}
\p{Pd} \p{Dash_Punctuation}
\p{Ps} \p{Open_Punctuation}
\p{Pe} \p{Close_Punctuation}
\p{Pi} \p{Initial_Punctuation}
\p{Pf} \p{Final_Punctuation}
\p{Po} \p{Other_Punctuation}
Short Name Long Name
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
\pS \p{Symbol}
\p{Sm} \p{Math_Symbol}
\p{Sc} \p{Currency_Symbol}
\p{Sk} \p{Modifier_Symbol}
\p{So} \p{Other_Symbol}
Short Name Long Name
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
\pZ \p{Separator}
\p{Zs} \p{Space_Separator}
\p{Zl} \p{Line_Separator}
\p{Zp} \p{Paragraph_Separator}
Short Name Long Name
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
\pC \p{Other}
\p{Cc} \p{Control}, \p{Cntrl}
\p{Cf} \p{Format}
\p{Cs} \p{Surrogate}
\p{Co} \p{Private_Use}
\p{Cn} \p{Unassigned}
Sie müssen die kurzen Namen nur in Java und Javascript verwenden, aber Perl lässt Sie auch die langen Namen verwenden, was der Lesbarkeit dient. Die Version 5.12 von Perl unterstützt etwa 3.000 Unicode-Eigenschaften. Python still hat keine nennenswerte Unterstützung für Unicode-Eigenschaften, und Ruby fängt gerade an, sie in der Version 1.9 zu bekommen. PCRE hat eine begrenzte Unterstützung, hauptsächlich wie die von Java 1.7.
Java6 unterstützt Unicode-Blockeigenschaften wie \p{InGeneralPunctuation} oder \p{Block=GeneralPunctuation}und Java7 unterstützt Unicode-Skripteigenschaften wie \p{IsHiragana} oder \p{Script=Hiragana}.
Es unterstützt jedoch immer noch nichts, was auch nur annähernd dem entspricht vollständiger Satz von Unicode-Eigenschafteneinschließlich nahezu kritischer wie \p{WhiteSpace}, \p{Dash}und \p{Quotation_Mark}geschweige denn die anderen Zweiteiler mögen \p{Line_Break=Alphabetic}, \p{East_Asian_Width:Narrow}, \p{Numeric_Value=1000}oder \p{Age:5.2}.
Die ersteren Sets sind ziemlich unverzichtbar – insbesondere angesichts der fehlenden Unterstützung für \s funktioniert richtig – und das letztere Set ist manchmal verdammt nützlich.
Etwas anderes, das Java und Javascript noch nicht unterstützen, ist benutzerdefinierte Zeicheneigenschaften. Ich benutze die ziemlich viel. Auf diese Weise können Sie Dinge definieren wie \p{English::Vowel} oder \p{English::Consonant}was ziemlich praktisch ist.
Wenn Sie an Unicode-Eigenschaften für Regex-Arbeiten interessiert sind, sollten Sie sich vielleicht die Einheit Suite von Programmen: Uniprops, uncharsund Uninamen. Hier ist eine Demo von jedem dieser drei:
$ uninames face
፦ 4966 1366 ETHIOPIC PREFACE COLON
⁙ 8281 2059 FIVE DOT PUNCTUATION
= Greek pentonkion
= quincunx
x (die face-5 - 2684)
∯ 8751 222F SURFACE INTEGRAL
# 222E 222E
☹ 9785 2639 WHITE FROWNING FACE
☺ 9786 263A WHITE SMILING FACE
= have a nice day!
☻ 9787 263B BLACK SMILING FACE
⚀ 9856 2680 DIE FACE-1
⚁ 9857 2681 DIE FACE-2
⚂ 9858 2682 DIE FACE-3
⚃ 9859 2683 DIE FACE-4
⚄ 9860 2684 DIE FACE-5
⚅ 9861 2685 DIE FACE-6
⾯ 12207 2FAF KANGXI RADICAL FACE
# 9762
〠 12320 3020 POSTAL MARK FACE
龜 64206 FACE CJK COMPATIBILITY IDEOGRAPH-FACE
: 9F9C
$ unichars '\pN' '\D' '\p{Latin}'
Ⅰ 8544 02160 ROMAN NUMERAL ONE
Ⅱ 8545 02161 ROMAN NUMERAL TWO
Ⅲ 8546 02162 ROMAN NUMERAL THREE
Ⅳ 8547 02163 ROMAN NUMERAL FOUR
Ⅴ 8548 02164 ROMAN NUMERAL FIVE
Ⅵ 8549 02165 ROMAN NUMERAL SIX
Ⅶ 8550 02166 ROMAN NUMERAL SEVEN
Ⅷ 8551 02167 ROMAN NUMERAL EIGHT
(etc)
$ unichars -a '\pL' '\p{Greek}' 'NFD ne NFKD' 'NAME =~ /SYMBOL/'
ϐ 976 3D0 GREEK BETA SYMBOL
ϑ 977 3D1 GREEK THETA SYMBOL
ϒ 978 3D2 GREEK UPSILON WITH HOOK SYMBOL
ϓ 979 3D3 GREEK UPSILON WITH ACUTE AND HOOK SYMBOL
ϔ 980 3D4 GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL
ϕ 981 3D5 GREEK PHI SYMBOL
ϖ 982 3D6 GREEK PI SYMBOL
ϰ 1008 3F0 GREEK KAPPA SYMBOL
ϱ 1009 3F1 GREEK RHO SYMBOL
ϲ 1010 3F2 GREEK LUNATE SIGMA SYMBOL
ϴ 1012 3F4 GREEK CAPITAL THETA SYMBOL
ϵ 1013 3F5 GREEK LUNATE EPSILON SYMBOL
Ϲ 1017 3F9 GREEK CAPITAL LUNATE SIGMA SYMBOL
Oh und BNM bedeutet „Schönes neues Jahrtausend“, was sich auf unsere moderne Post-ASCII-Welt bezieht, in der Zeichen mehr als nur sieben winzige Bits breit sind. ☺
Josef Swobod
String.replace() akzeptiert die Callback-Funktion als zweiten Parameter. (Ich weiß nicht, warum so viele JS-Tutorials diese nützliche Funktion weglassen.) So können wir unseren eigenen Test für Wortgrenzen schreiben.
Die an anderer Stelle vorgeschlagene Lösung mit regexp /(\W|^)(fancy namé|namé)(\W|$)/iggibt bei Texten wie „nameé“ falsch positive Ergebnisse.
String.prototype.isWordCharAt = function(i) {
// should work for European languages and Unicode
return (this.charAt(i) >= 'A' && this.charAt(i) <= 'Z')
|| (this.charAt(i) >= 'a' && this.charAt(i) <= 'z')
|| (this.charCodeAt(i) >= 0xC0 && this.charCodeAt(i) < 0x2000)
;
};
"Namé. Goal: Some Fancy Namé. Namé. Nénamé. Namée. Nénamée. Namé"
.replace(/(Namé|Fancy Namé)/ig, function(
match, part1, /* part2, part3, ... */ offset, fullText) {
// Keep in mind that the number of arguments changes
// if the number of capturing parenthesis in regexp changes.
// We could use 'arguments' pseudo-array instead.
var len1 = part1.length;
var leftWordBoundary;
var rightWordBoundary;
if (offset === 0) {
leftWordBoundary = fullText.isWordCharAt(offset);
}
else {
leftWordBoundary = (fullText.isWordCharAt(offset - 1)
!= fullText.isWordCharAt(offset));
}
if (offset + len1 == fullText.length) {
rightWordBoundary = fullText.isWordCharAt(offset + len1 - 1);
}
else {
rightWordBoundary = (fullText.isWordCharAt(offset + len1 - 1)
!= fullText.isWordCharAt(offset + len1));
}
if (leftWordBoundary && rightWordBoundary) {
return '<a href="#">' + part1 + '</a>';
}
else {
return part1;
}
});
Wenn Sie mit “my_word” übereinstimmen möchten, können Sie negatives Hinten verwenden ?<! und negativer Blick nach vorn ?!
Dadurch wird überprüft, dass dem Wort kein Nicht-Wort-Zeichen vorangeht und kein Nicht-Wort-Zeichen folgt new RegExp(`(?<![A-Za-zÀ-ÖØ-öø-ÿ])my_word(?![A-Za-zÀ-ÖØ-öø-ÿ])`, "gi");
Wie andere Antworten bereits betont haben, betrachtet die JS-Regex-Engine “é” nicht als Wortzeichen. Da dies der Fall ist und Sie abgleichen möchten, ob auf diesen Buchstaben ein anderes Nichtwortzeichen folgt, können Sie die verwenden \B Behauptung dort:
> "Goal: Some Fancy Namé. Awesome.".replace(/\b(Fancy Namé|Namé)\B/i, '<a href="#">$1</a>');
'Goal: Some <a href="#">Fancy Namé</a>. Awesome.'
Wahrscheinlich nicht der beste Code, wenn Sie möchten, dass seine Absicht offensichtlich ist, aber in diesem Fall funktioniert er, hehe.
Zwiebel-Ritter
Versuchen Sie es vielleicht mit der \o oder \x Flags, wenn Sie Ihre Regex verwenden.
Ich vermute, dass é vielleicht nicht als Wortzeichen (\w) betrachtet wird …
– Rauben
15. März 2010 um 19:17 Uhr