RegExp `v` Flag mit Mengennotation und Eigenschaften von Zeichenketten
JavaScript unterstützt reguläre Ausdrücke seit ECMAScript 3 (1999). Sechzehn Jahre später führte ES2015 Unicode-Modus (das u
-Flag), Sticky-Modus (das y
-Flag) und die RegExp.prototype.flags
Getter-Funktion ein. Weitere drei Jahre später führte ES2018 dotAll
-Modus (das s
-Flag), Lookbehind-Assertions, Named Capture Groups und Unicode-Zeicheneigenschafts-Auswege ein. Und in ES2020 erleichterte String.prototype.matchAll
die Arbeit mit regulären Ausdrücken. JavaScript-Regular Expressions haben sich enorm weiterentwickelt und verbessern sich weiterhin.
Das jüngste Beispiel hierfür ist der neue unicodeSets
-Modus, der mithilfe des v
-Flags aktiviert wird. Dieser neue Modus ermöglicht die Unterstützung von erweiterten Zeichenklassen, einschließlich der folgenden Funktionen:
- Unicode-Eigenschaften von Zeichenketten
- Mengennotation + Zeichenketten-Literal-Syntax
- verbesserte Abrundung der Groß-/Kleinschreibung
Dieser Artikel behandelt all diese Themen. Aber zuerst — hier ist, wie Sie das neue Flag nutzen können:
const re = /…/v;
Das v
-Flag kann mit bestehenden regulären Ausdrücken-Flags kombiniert werden, mit einer bemerkenswerten Ausnahme. Das v
-Flag aktiviert alle positiven Eigenschaften des u
-Flags, jedoch mit zusätzlichen Funktionen und Verbesserungen — einige davon sind nicht rückwärtskompatibel mit dem u
-Flag. Entscheidend ist, dass v
ein vollständig separater Modus von u
ist und nicht ein ergänzender Modus. Aus diesem Grund können die Flags v
und u
nicht kombiniert werden — der Versuch, beide Flags für denselben regulären Ausdruck zu verwenden, führt zu einem Fehler. Die einzigen gültigen Optionen sind: entweder u
nutzen, oder v
, oder weder u
noch v
. Aber da v
die funktionsreichste Option darstellt, ist die Wahl klar…
Tauchen wir in die neue Funktionalität ein!
Unicode-Eigenschaften von Zeichenketten
Der Unicode-Standard weist jedem Symbol verschiedene Eigenschaften und Eigenschaftswerte zu. Um beispielsweise die Symbole zu erhalten, die im griechischen Schriftsystem verwendet werden, durchsuchen Sie die Unicode-Datenbank nach Symbolen, deren Eigenschaftswert Script_Extensions
Greek
enthält.
Mit den Unicode-Zeicheneigenschaftsauswegen von ES2018 ist es möglich, diese Unicode-Zeichen-Eigenschaften direkt in ECMAScript-Regular-Expressions zu verwenden. Beispielsweise stimmt das Muster \p{Script_Extensions=Greek}
mit jedem Symbol überein, das im griechischen Schriftsystem verwendet wird:
const regexGreekSymbol = /\p{Script_Extensions=Greek}/u;
regexGreekSymbol.test('π');
// → true
Definitionsgemäß erweitern sich Unicode-Zeicheneigenschaften zu einer Menge von Codepunkten und können somit als Zeichenklasse transpiliert werden, die die Codepunkte enthält, mit denen sie übereinstimmen. Beispielsweise ist \p{ASCII_Hex_Digit}
gleichbedeutend mit [0-9A-Fa-f]
: es stimmt immer nur mit einem einzelnen Unicode-Zeichen/Codepunkt gleichzeitig überein. In manchen Situationen reicht das nicht aus:
// Unicode definiert eine Zeicheneigenschaft namens “Emoji”.
const re = /^\p{Emoji}$/u;
// Übereinstimmung mit einem Emoji, das aus nur 1 Codepunkt besteht:
re.test('⚽'); // '\u26BD'
// → true ✅
// Übereinstimmung mit einem Emoji, das aus mehreren Codepunkten besteht:
re.test('👨🏾⚕️'); // '\u{1F468}\u{1F3FE}\u200D\u2695\uFE0F'
// → false ❌
Im obigen Beispiel stimmt der reguläre Ausdruck nicht mit dem 👨🏾⚕️ Emoji überein, da es aus mehreren Codepunkten besteht und Emoji
eine Unicode-Zeichen-Eigenschaft ist.
Glücklicherweise definiert der Unicode-Standard auch mehrere Eigenschaften von Zeichenketten. Solche Eigenschaften umfassen eine Menge von Zeichenketten, von denen jede einen oder mehrere Codepunkte enthält. In regulären Ausdrücken werden Eigenschaften von Zeichenketten in eine Menge von Alternativen übersetzt. Um dies zu veranschaulichen, stellen Sie sich eine Unicode-Eigenschaft vor, die auf die Zeichenketten 'a'
, 'b'
, 'c'
, 'W'
, 'xy'
und 'xyz'
zutrifft. Diese Eigenschaft wird in eines der folgenden regulären Ausdrucksmuster übersetzt (unter Verwendung von Alternativen): xyz|xy|a|b|c|W
oder xyz|xy|[a-cW]
. (Die längsten Zeichenketten zuerst, damit ein Präfix wie 'xy'
eine längere Zeichenkette wie 'xyz'
nicht verdeckt.) Im Gegensatz zu bestehenden Unicode-Escape-Eigenschaften kann dieses Muster mehrstellige Zeichenketten erfassen. Hier ist ein Beispiel für die Verwendung einer Eigenschaft von Zeichenketten:
const re = /^\p{RGI_Emoji}$/v;
// Ein Emoji erfassen, das nur aus 1 Codepunkt besteht:
re.test('⚽'); // '\u26BD'
// → true ✅
// Ein Emoji erfassen, das aus mehreren Codepunkten besteht:
re.test('👨🏾⚕️'); // '\u{1F468}\u{1F3FE}\u200D\u2695\uFE0F'
// → true ✅
Dieser Codeausschnitt bezieht sich auf die Eigenschaft von Zeichenketten RGI_Emoji
, die von Unicode als „die Teilmenge aller gültigen Emojis (Zeichen und Sequenzen), die für den allgemeinen Austausch empfohlen werden“ definiert ist. Damit können wir jetzt Emojis erfassen, unabhängig davon, wie viele Codepunkte sie im Hintergrund enthalten!
Das v
-Flag ermöglicht von Anfang an die Unterstützung der folgenden Unicode-Eigenschaften von Zeichenketten:
Basic_Emoji
Emoji_Keycap_Sequence
RGI_Emoji_Modifier_Sequence
RGI_Emoji_Flag_Sequence
RGI_Emoji_Tag_Sequence
RGI_Emoji_ZWJ_Sequence
RGI_Emoji
Diese Liste der unterstützten Eigenschaften könnte in Zukunft wachsen, da der Unicode-Standard zusätzliche Eigenschaften von Zeichenketten definiert. Obwohl alle aktuellen Eigenschaften von Zeichenketten emoji-bezogen sind, könnten zukünftige Eigenschaften von Zeichenketten völlig andere Anwendungsfälle bedienen.
Hinweis: Obwohl Eigenschaften von Zeichenketten derzeit über das neue v
-Flag aktiviert werden, planen wir, sie schließlich auch im u
-Modus verfügbar zu machen.
Mengen-Notation + Zeichenkettenliteral-Syntax
Beim Arbeiten mit \p{…}
-Escapes (sei es bei Zeichen-Eigenschaften oder den neuen Eigenschaften von Zeichenketten) kann es nützlich sein, Differenzen/Subtraktion oder Schnittmengen durchzuführen. Mit dem v
-Flag können Zeichenklassen jetzt geschachtelt werden, und diese Mengenoperationen können darin durchgeführt werden, anstatt mit angrenzenden Lookahead-, Lookbehind-Aussagen oder langen Zeichenklassen, die die berechneten Bereiche ausdrücken.
Differenz/Subtraktion mit --
Die Syntax A--B
kann verwendet werden, um Zeichenketten zu erfassen, die in A
, aber nicht in B
enthalten sind, also Differenz/Subtraktion.
Zum Beispiel, wenn Sie alle griechischen Symbole außer dem Buchstaben π
erfassen möchten. Mit Mengen-Notation ist dies trivial zu lösen:
/[\p{Script_Extensions=Greek}--π]/v.test('π'); // → false
Durch die Verwendung von --
für Differenz/Subtraktion erledigt die Regex-Engine die harte Arbeit für Sie, während Ihr Code lesbar und wartbar bleibt.
Was ist, wenn wir anstelle eines einzelnen Zeichens die Menge der Zeichen α
, β
und γ
subtrahieren möchten? Kein Problem - wir können eine geschachtelte Zeichenklasse verwenden und deren Inhalt subtrahieren:
/[\p{Script_Extensions=Greek}--[αβγ]]/v.test('α'); // → false
/[\p{Script_Extensions=Greek}--[α-γ]]/v.test('β'); // → false
Ein anderes Beispiel ist das Erfassen von nicht-ASCII-Ziffern, zum Beispiel um sie später in ASCII-Ziffern umzuwandeln:
/[\p{Decimal_Number}--[0-9]]/v.test('𑜹'); // → true
/[\p{Decimal_Number}--[0-9]]/v.test('4'); // → false
Die Mengen-Notation kann auch mit den neuen Eigenschaften von Zeichenketten verwendet werden:
// Hinweis: 🏴 besteht aus 7 Codepunkten.
/^\p{RGI_Emoji_Tag_Sequence}$/v.test('🏴'); // → true
/^[\p{RGI_Emoji_Tag_Sequence}--\q{🏴}]$/v.test('🏴'); // → false
Dieses Beispiel erfasst jede RGI-E-Emoji-Tag-Sequenz außer der Flagge Schottlands. Beachten Sie die Verwendung von \q{…}
, einer neuen Syntax für Zeichenkettenliterale innerhalb von Zeichenklassen. Zum Beispiel erfasst \q{a|bc|def}
die Zeichenketten a
, bc
und def
. Ohne \q{…}
wäre es nicht möglich, fest kodierte mehrstellige Zeichenketten zu subtrahieren.
Schnittmenge mit &&
Die A&&B
-Syntax erfasst Zeichenketten, die in sowohl A
als auch B
enthalten sind, also Schnittmenge. Dies ermöglicht es, Dinge wie griechische Buchstaben zu erfassen:
const re = /[\p{Script_Extensions=Greek}&&\p{Letter}]/v;
// U+03C0 GRIECHISCHER BUCHSTABE PI
re.test('π'); // → true
// U+1018A GRIECHISCHES NULLZEICHEN
re.test('𐆊'); // → false
Erfassen aller ASCII-Leerzeichen:
const re = /[\p{White_Space}&&\p{ASCII}]/v;
re.test('\n'); // → true
re.test('\u2028'); // → false
Oder Erfassen aller mongolischen Zahlen:
const re = /[\p{Script_Extensions=Mongolian}&&\p{Number}]/v;
// U+1817 MONGOLISCHE ZIFFER SIEBEN
re.test('᠗'); // → true
// U+1834 MONGOLISCHER BUCHSTABE CHA
re.test('ᠴ'); // → false
Vereinigung
Zeichenketten zu erfassen, die in A oder in B enthalten sind, war zuvor bereits für einstellige Zeichenketten möglich, indem eine Zeichenklasse wie [\p{Letter}\p{Number}]
verwendet wurde. Mit dem v
-Flag wird diese Funktionalität leistungsfähiger, da sie nun auch mit Eigenschaften von Zeichenketten oder Zeichenkettenliteralen kombiniert werden kann:
const re = /^[\p{Emoji_Keycap_Sequence}\p{ASCII}\q{🇧🇪|abc}xyz0-9]$/v;
re.test('4️⃣'); // → true
re.test('_'); // → true
re.test('🇧🇪'); // → true
re.test('abc'); // → true
re.test('x'); // → true
re.test('4'); // → true
Die Zeichenklasse in diesem Muster kombiniert:
- eine Eigenschaft von Zeichenketten (
\p{Emoji_Keycap_Sequence}
) - eine Zeichen-Eigenschaft (
\p{ASCII}
) - die Syntax für Zeichenketten-Literale für die mehrstelligen Zeichenketten
🇧🇪
undabc
- klassische Zeichenklassen-Syntax für einzelne Zeichen
x
,y
undz
- klassische Zeichensatz-Syntax für den Zeichenbereich von
0
bis9
Ein weiteres Beispiel ist das Matching aller häufig verwendeten Flaggen-Emojis, unabhängig davon, ob sie als zweibuchstabiger ISO-Code (RGI_Emoji_Flag_Sequence
) oder als spezieller Tag-Sequenz (RGI_Emoji_Tag_Sequence
) codiert sind:
const reFlag = /[\p{RGI_Emoji_Flag_Sequence}\p{RGI_Emoji_Tag_Sequence}]/v;
// Eine Flaggen-Sequenz, bestehend aus 2 Codepunkten (Flagge von Belgien):
reFlag.test('🇧🇪'); // → true
// Eine Tag-Sequenz, bestehend aus 7 Codepunkten (Flagge von England):
reFlag.test('🏴'); // → true
// Eine Flaggen-Sequenz, bestehend aus 2 Codepunkten (Flagge der Schweiz):
reFlag.test('🇨🇭'); // → true
// Eine Tag-Sequenz, bestehend aus 7 Codepunkten (Flagge von Wales):
reFlag.test('🏴'); // → true
Verbesserte Groß-/Kleinschreibungs-unabhängige Übereinstimmung
Das ES2015 u
-Flag leidet unter verwirrendem Verhalten bei der Groß-/Kleinschreibungs-unabhängigen Übereinstimmung. Betrachten Sie die folgenden zwei regulären Ausdrücke:
const re1 = /\p{Lowercase_Letter}/giu;
const re2 = /[^\P{Lowercase_Letter}]/giu;
Der erste Ausdruck stimmt mit allen Kleinbuchstaben überein. Der zweite Ausdruck verwendet \P
anstelle von \p
, um mit allen Zeichen außer Kleinbuchstaben übereinzustimmen, wird dann jedoch in einer negierten Zeichengruppe ([^…]
) verpackt. Beide regulären Ausdrücke werden durch das Setzen des i
-Flags (ignoreCase
) groß-/kleinschreibungs-unabhängig gemacht.
Intuitiv könnte man erwarten, dass beide regulären Ausdrücke sich gleich verhalten. Tatsächlich verhalten sie sich jedoch sehr unterschiedlich:
const re1 = /\p{Lowercase_Letter}/giu;
const re2 = /[^\P{Lowercase_Letter}]/giu;
const string = 'aAbBcC4#';
string.replaceAll(re1, 'X');
// → 'XXXXXX4#'
string.replaceAll(re2, 'X');
// → 'aAbBcC4#''
Das neue v
-Flag verhält sich weniger überraschend. Mit dem v
-Flag anstelle des u
-Flags verhalten sich beide Ausdrücke gleich:
const re1 = /\p{Lowercase_Letter}/giv;
const re2 = /[^\P{Lowercase_Letter}]/giv;
const string = 'aAbBcC4#';
string.replaceAll(re1, 'X');
// → 'XXXXXX4#'
string.replaceAll(re2, 'X');
// → 'XXXXXX4#'
Allgemeiner macht das v
-Flag [^\p{X}]
≍ [\P{X}]
≍ \P{X}
und [^\P{X}]
≍ [\p{X}]
≍ \p{X}
, unabhängig davon, ob das i
-Flag gesetzt ist oder nicht.
Weiterführende Literatur
Das Proposals-Repository enthält weitere Details und Hintergrundinformationen zu diesen Funktionen und ihren Designentscheidungen.
Im Rahmen unserer Arbeit an diesen JavaScript-Funktionen gingen wir über „nur“ Vorschläge für Spezifikationsänderungen in ECMAScript hinaus. Wir haben die Definition von „Eigenschaften von Zeichenfolgen“ an Unicode UTS#18 übermittelt, sodass andere Programmiersprachen ähnliche Funktionen auf einheitliche Weise implementieren können. Wir schlagen auch eine Änderung am HTML-Standard vor, um diese neuen Funktionen auch im pattern
-Attribut zu ermöglichen.
RegExp-v
-Flag-Unterstützung
V8 v11.0 (Chrome 110) bietet experimentelle Unterstützung für diese neue Funktionalität über das --harmony-regexp-unicode-sets
-Flag. V8 v12.0 (Chrome 112) hat die neuen Funktionen standardmäßig aktiviert. Babel unterstützt auch das Transpilieren des v
-Flags — probieren Sie die Beispiele aus diesem Artikel im Babel REPL aus! Die untenstehende Unterstützungs-Tabelle enthält Links zu Tracking-Issues, bei denen Sie sich für Updates anmelden können.