'abc' beginnen'.mjs' nicht enthaltenIn diesem Kapitel untersuchen wir Lookaround-Assertions in regulären Ausdrücken anhand von Beispielen. Eine Lookaround-Assertion ist nicht erfassend und muss das, was vor oder nach der aktuellen Position in der Eingabezeichenkette kommt, abgleichen (oder nicht abgleichen).
| Muster | Name | |
|---|---|---|
(?=«muster») |
Positiver Lookahead | ES3 |
(?!«muster») |
Negativer Lookahead | ES3 |
(?<=«muster») |
Positiver Lookbehind | ES2018 |
(?<!«muster») |
Negativer Lookbehind | ES2018 |
Es gibt vier Lookaround-Assertions (Tab. 4)
(?=«muster») gleicht ab, wenn muster mit dem übereinstimmt, was nach der aktuellen Position in der Eingabezeichenkette kommt.(?!«muster») gleicht ab, wenn muster nicht mit dem übereinstimmt, was nach der aktuellen Position in der Eingabezeichenkette kommt.(?<=«muster») gleicht ab, wenn muster mit dem übereinstimmt, was vor der aktuellen Position in der Eingabezeichenkette kommt.(?<!«muster») gleicht ab, wenn muster nicht mit dem übereinstimmt, was vor der aktuellen Position in der Eingabezeichenkette kommt.Die Beispiele zeigen, was mit Lookaround-Assertions erreicht werden kann. Reguläre Ausdrücke sind jedoch nicht immer die beste Lösung. Eine andere Technik, wie z. B. korrektes Parsen, kann eine bessere Wahl sein.
Lookbehind-Assertions sind ein relativ neues Feature, das möglicherweise nicht von allen JavaScript-Engines unterstützt wird, die Sie anvisieren.
Lookaround-Assertions können die Leistung negativ beeinflussen, insbesondere wenn ihre Muster lange Zeichenketten abgleichen.
In der folgenden Interaktion extrahieren wir Anführungszeichen-umschlossene Wörter
Zwei Lookaround-Assertions helfen uns hier
(?<=") „muss von einem Anführungszeichen vorangestellt sein“(?=") „muss von einem Anführungszeichen gefolgt sein“Lookaround-Assertions sind besonders praktisch für .match() im /g-Modus, der ganze Treffer (Erfassungsgruppe 0) zurückgibt. Was auch immer das Muster einer Lookaround-Assertion abgleicht, wird nicht erfasst. Ohne Lookaround-Assertions erscheinen die Anführungszeichen im Ergebnis
Wie können wir das Gegenteil von dem erreichen, was wir im vorherigen Abschnitt getan haben, und alle nicht in Anführungszeichen gesetzten Wörter aus einer Zeichenkette extrahieren?
'how "are" "you" doing'['how', 'doing']Unser erster Versuch ist, positive Lookaround-Assertions einfach in negative Lookaround-Assertions umzuwandeln. Leider schlägt das fehl
Das Problem ist, dass wir Zeichenfolgen extrahieren, die nicht von Anführungszeichen umschlossen sind. Das bedeutet, dass in der Zeichenkette '"are"' das „r“ in der Mitte als nicht in Anführungszeichen gesetzt gilt, da es von einem „a“ vorangestellt und von einem „e“ gefolgt wird.
Wir können dies beheben, indem wir angeben, dass Präfix und Suffix weder ein Anführungszeichen noch ein Buchstabe sein dürfen
Eine weitere Lösung besteht darin, über \b zu fordern, dass die Zeichenfolge [a-z]+ an Wortgrenzen beginnt und endet
Eine Sache, die bei negativem Lookbehind und negativem Lookahead schön ist, ist, dass sie auch am Anfang oder Ende einer Zeichenkette funktionieren – wie im Beispiel gezeigt.
Negative Lookaround-Assertions sind ein mächtiges Werkzeug und normalerweise unmöglich durch andere reguläre Ausdrucksmittel nachzubilden.
Wenn wir sie nicht verwenden wollen, müssen wir normalerweise einen völlig anderen Ansatz wählen. Zum Beispiel könnten wir in diesem Fall die Zeichenkette in (in Anführungszeichen gesetzte und nicht in Anführungszeichen gesetzte) Wörter aufteilen und dann diese filtern.
const str = 'how "are" "you" doing';
const allWords = str.match(/"?[a-z]+"?/g);
const unquotedWords = allWords.filter(
w => !w.startsWith('"') || !w.endsWith('"'));
assert.deepEqual(unquotedWords, ['how', 'doing']);Vorteile dieses Ansatzes
Alle Beispiele, die wir bisher gesehen haben, haben gemeinsam, dass die Lookaround-Assertions vorschreiben, was vor oder nach dem Treffer kommen muss, ohne diese Zeichen jedoch in den Treffer einzubeziehen.
Die im restlichen Teil dieses Kapitels gezeigten regulären Ausdrücke sind anders: Ihre Lookaround-Assertions zeigen nach innen und schränken ein, was innerhalb des Treffers liegt.
'abc' beginnenNehmen wir an, wir möchten alle Zeichenketten abgleichen, die nicht mit 'abc' beginnen. Unser erster Versuch könnte der reguläre Ausdruck /^(?!abc)/ sein.
Das funktioniert gut für .test()
.exec() gibt uns jedoch eine leere Zeichenkette zurück
Das Problem ist, dass Assertions wie Lookaround-Assertions den abgeglichenen Text nicht erweitern. Das heißt, sie erfassen keine Eingabezeichen, sie stellen nur Anforderungen an die aktuelle Position in der Eingabe.
Daher besteht die Lösung darin, ein Muster hinzuzufügen, das Eingabezeichen erfasst.
Wie gewünscht, lehnt dieser neue reguläre Ausdruck Zeichenketten ab, die mit 'abc' vorangestellt sind.
Und er akzeptiert Zeichenketten, die nicht den vollständigen Präfix haben.
'.mjs' nicht enthaltenIm folgenden Beispiel möchten wir finden
import ··· from '«module-specifier»';
wobei module-specifier nicht auf '.mjs' endet.
const code = `
import {transform} from './util';
import {Person} from './person.mjs';
import {zip} from 'lodash';
`.trim();
assert.deepEqual(
code.match(/^import .*? from '[^']+(?<!\.mjs)';$/umg),
[
"import {transform} from './util';",
"import {zip} from 'lodash';",
]);Hier fungiert die Lookbehind-Assertion (?<!\.mjs) als Wächter und verhindert, dass der reguläre Ausdruck Zeichenketten abgleicht, die '.mjs' an dieser Stelle enthalten.
Szenario: Wir möchten Zeilen mit Einstellungen parsen und dabei Kommentare überspringen. Zum Beispiel
const RE_SETTING = /^(?!#)([^:]*):(.*)$/
const lines = [
'indent: 2', // setting
'# Trim trailing whitespace:', // comment
'whitespace: trim', // setting
];
for (const line of lines) {
const match = RE_SETTING.exec(line);
if (match) {
const key = JSON.stringify(match[1]);
const value = JSON.stringify(match[2]);
console.log(`KEY: ${key} VALUE: ${value}`);
}
}
// Output:
// 'KEY: "indent" VALUE: " 2"'
// 'KEY: "whitespace" VALUE: " trim"'Wie sind wir zum regulären Ausdruck RE_SETTING gekommen?
Wir begannen mit dem folgenden regulären Ausdruck für Einstellungen
Intuitiv besteht er aus einer Sequenz der folgenden Teile
Dieser reguläre Ausdruck lehnt einige Kommentare ab
Aber er akzeptiert andere (die Doppelpunkte enthalten).
Wir können das beheben, indem wir (?!#) als Wächter voranstellen. Intuitiv bedeutet dies: „Die aktuelle Position in der Eingabezeichenkette darf nicht vom Zeichen # gefolgt werden.“
Der neue reguläre Ausdruck funktioniert wie gewünscht.
Nehmen wir an, wir möchten Paare von geraden doppelten Anführungszeichen in geschwungene Anführungszeichen umwandeln
`"yes" and "no"``“yes” and “no”`Das ist unser erster Versuch
Nur das erste und das letzte Anführungszeichen sind geschwungen. Das Problem hier ist, dass der *-Quantifizierer gierig (so viel wie möglich) übereinstimmt.
Wenn wir ein Fragezeichen nach dem * setzen, stimmt es widerwillig überein.
Was ist, wenn wir das Escaping von Anführungszeichen mit Backslashes erlauben möchten? Das können wir erreichen, indem wir den Wächter (?<!\\) vor den Anführungszeichen verwenden.
> const regExp = /(?<!\\)"(.*?)(?<!\\)"/g;
> String.raw`\"straight\" and "curly"`.replace(regExp, '“$1”')
'\\"straight\\" and “curly”'Als Nachbearbeitungsschritt müssten wir immer noch Folgendes tun
Dieser reguläre Ausdruck kann jedoch fehlschlagen, wenn ein mit Backslash maskierter Backslash vorhanden ist.
Der zweite Backslash verhinderte, dass die Anführungszeichen geschwungen wurden.
Wir können das beheben, indem wir unseren Wächter anspruchsvoller gestalten (?: macht die Gruppe nicht erfassend).
Der neue Wächter erlaubt Paare von Backslashes vor Anführungszeichen.
> const regExp = /(?<=[^\\](?:\\\\)*)"(.*?)(?<=[^\\](?:\\\\)*)"/g;
> String.raw`Backslash: "\\"`.replace(regExp, '“$1”')
'Backslash: “\\\\”'Ein Problem bleibt. Dieser Wächter verhindert, dass das erste Anführungszeichen abgeglichen wird, wenn es am Anfang einer Zeichenkette steht.
> const regExp = /(?<=[^\\](?:\\\\)*)"(.*?)(?<=[^\\](?:\\\\)*)"/g;
> `"abc"`.replace(regExp, '“$1”')
'"abc"'Wir können das beheben, indem wir den ersten Wächter ändern zu: (?<=[^\\](?:\\\\)*|^)
> const regExp = /(?<=[^\\](?:\\\\)*|^)"(.*?)(?<=[^\\](?:\\\\)*)"/g;
> `"abc"`.replace(regExp, '“$1”')
'“abc”'@jonasraoni auf Twitter vorgeschlagen.RegExp)“ in „JavaScript for impatient programmers“