Computer Keyboard - Głównie JavaScript

Głównie JavaScript

by Jakub T. Jankiewicz

Co Nowego w Wyrażeniach Regularnych w JavaScript

Standard ES2018 czyli ES9 został zatwierdzony. W ramach tej wersji weszło kilka nowy funkcjonalności do wyrażeń regularnych czyli regexów (od angielskiego Regular Expressions). W tym wpisie przedstawie co nowego weszło do wyrażeń regularnych w JavaScript.

  • Flaga s

Aby dopasować dowolny znak używa się operatora kropki. Nie zadziała ona jednak gdy chcemy dopasować znak do znaku nowej linii. Aby obejść to ograniczenie najczęściej stosowano takie wyrażenie [\s\S], które oznacza dowolną spacje lub nie spacje czyli dowolny znak. Nie jest to jednak bardzo czytelne i może dodatkowo komplikować wyrażenia. Z pomocą przychodzi nowa flaga s, która określa że znak kropki dopasowuje się do dowolnego znaku. Czyli jest to „Dot All” znany z innych języków.

  • Flaga u

Jest to flaga oznaczająca Unicode, która jeśli użyta udostępnia, wewnątrz wyrażenia:

  • wyrażenie \u{kod unicode} np.:
'☃'.match(/\u{2603}/u);
  • właściwości tekstu \p{ } (ang. Unicode property escape):

To nowy sposób dopasowywania do zakresu znaków. Działa podobnie jak wyrażenie \u{}, z tym że możemy przekazywać nazwy określające jakie znaki chcemy dopasować, np:

/^\p{ASCII}+$/u.test('AABB');
// true
/^\p{Script=Hebrew}+\s\p{Script=Hebrew}+$/u.test('העלא וועלט');
// true
/^[\p{Letter}\p{White_Space}]+$/u.test('Γειά σου Κόσμε');
// true

Wielkość znaków właściwości ma znaczenie.

  • flaga u wpływa także na operator kropki czyli dowolnego znaku:
var string = 'a💩b';

console.log(/a.b/.test(string));
// false
console.log(/a.b/u.test(string));
// true

Znak 💩 (Pile of Poo) znajduje się w tzw. przestrzeni Symboli Astralnych (ang. astral symbols), tzn. że w języku JavaScript ich kod zawiera dwa znaki, są to tzw. pary surogatów (ang. surrogate pairs).

'💩'.length;
// 2

Dlatego bez flagi u trzeba by użyć dwóch kropek, albo użyć wyrażenia /a(.{1,2})b/. Może to jednak być problem, gdy chcemy dopasować, w typ samym miejscu, znaki składają się z jednego lub z dwóch znaków.

Więcej o Unicode, w języku JavaScript, możesz przeczytać w artykule Mathiasa Bynensa (JavaScript has a Unicode problem)[https://mathiasbynens.be/notes/javascript-unicode].

  • Nazwane grupy

Do tej pory można było pobierać grupy tylko za pomocą indeksów czyli:

var input = 'var foo = bar;';
var re = /var ([A-Za-z]+)\s*=\s*([^;]+);/;
var m = input.match(re);
console.log(`przypisanie ${m[1]} do zmiennej ${m[2]}`)

Jest to mało czytelne. Dodatkowo jeśli musimy dodać nową grupę na początku np. gdy musimy pobrać const, let lub var, to musimy w każdym miejscu, gdzie była użyta grupa, dodać do indeksu jeden. Rozwiązaniem tego problemu są grupy nazwane. Ich składnia wygląda tak (?<nazwa>wyrażenie), oto poprzedni przykład z nazwanymi grupami.

var input = 'var foo = bar;';
var re = /var (?<name>[A-Za-z]+)\s*=\s*(?<value>[^;]+);/;
var m = input.match(re);
console.log(`przypisanie ${m.groups.value} do zmiennej ${m.groups.name}`)
  • Asercje wsteczne (ang. Look Behind)

Asercje do przodu (ang. Look Ahead) są w Wyrażeniach Regularnych od dawna (może nawet od samego początku ich istnienia).

Ich składnia wygląda tak (?=wyrażenie) oraz (?!wyrażenie) np:

var re = /var ([A-Za-z]+)(?=\s*=\s*[0-9]+(?:.[0-9]+)?)/;
var input = 'var foo = 10;';
input.match(re);
// ["var foo", "foo", index: 0, input: "var foo = 10;", groups: undefined]

to wyrażenie będzie dopasowane do nazwy zmiennej ale tylko jeśli wartością będzie liczba, która nie jest zawarta w wyniku. Podobnie działa asercja negatywna (ang. Negative Look Ahead).

Natomiast nowe są asercje wsteczne (ang. Look Behind), znane z wyrażeń regularnych w innych językach takich jak java, PHP czy Python. Ich składnia też jest taka sama jak w innych językach czyli (?<=wyrażenie) pozytywne wsteczne oraz (?<!wyrażenie) negatywne wsteczne.

Przykład:

var re = /(?<!var[^=]\s*=\s*)([0-9]+(?:.[0-9]+)?);/;
'var foo = 10;'.match(re);
// null
'const foo = 20;'.match(re);
// ["20;", "20", index: 12, input: "const foo = 20;", groups: undefined]

Wparcie, dla poszczególnych dodatków, możecie zobaczyć w tabeli standardu ECMAScript.

  • String::matchAll

To nowa funkcja, niestety nie zaimplementowana jeszcze w żadnej przeglądarce, oprócz Chrome (z chwilą pisanie tego artykułu), ale trzeba ją włączyć ręczenie. Funkcja ta upraszcza wielokrotne dopasowanie, przykład:

const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
const string = 'Ten tekst zawiera znaki DEADBEEF CAFE AAFFBB';
let match;
while (match = regex.exec(string)) {
   console.log(match);
}

ten kod zadziała ponieważ funkcja exec zachowuje indeks poprzedniego wywołania, ale tylko gdy wyrażenie ma flagę g, inaczej będzie to nieskończona pętla. Ten kod można jednak zastąpić funkcją matchAll:

const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
const string = 'Ten tekst zawiera znaki DEADBEEF CAFE AAFFBB';
for (const match of string.matchAll(regex)) {
   console.log(match);
}
źródło strony (aby zobaczyć kod na GitHubie, musisz kliknąć przycisk raw)
Komentarze

Hasło, które podasz umożliwi ponowne zalogowanie się i np. usunięcie komentarza, jest dobrowolne. Email jest szyfrowany i używany do wysyłania powiadomień o odpowiedziach do komentarzy oraz do pobierania awatara dzięki usłudze Gravatar.com.