Treść udostępniona za zgodą autora. Kurs bezpłatny. Dostosowano układ i odnośniki do tej platformy.
Wyrażenia regularne stanowią doskonały sposób na badanie i modyfikowanie tekstu. Dzięki swej olbrzymiej elastyczności pozwalają w łatwy sposób pobierać pasujące fragmenty tekstu. To tyle książkowej teorii.
A czym są wzorce w praktyce?
Powiedzmy, że mamy fragment tekstu, w którym znajdują się jakieś kody pocztowe. Kod taki składa się z dwóch cyfr, myślnika, po którym występują trzy cyfry. Jeżeli chcielibyśmy znaleźć w takim tekście wszystkie kody, nie moglibyśmy użyć znanych nam już indexOf() czy includes(), ponieważ nie znamy konkretnej wartości. Znamy wzorzec.
Wyrażenia regularne nie sa domeną Javascriptu. Gdy nauczymy się ich - nawet w podstawowej formie - bardzo ułatwią nam codzienną prace z kodem. Za ich pomocą możemy dla przykładu w każdym porządnym edytorze zamieniać podobne wystąpienia tekstu (np. klasy, daty itp), masowo zmieniać nazwy plików itp. Poniżej przykład takiej zamiany:
Osadzony przykład interaktywny nie jest uruchamiany w tej kopii lekcji.
Pisanie podstawowych wzorów wyrażeń nie jest jakieś bardzo skomplikowane. Problem pojawia się przy tych bardziej rozbudowanych. Tutaj częstokroć trzeba korzystać z Google szukając frazy "fraza test regexp" i posiłkować się poradami ze Stack Overflow.
Bardzo też przydają się tutaj narzędzia takie jak: https://regex101.com/, czy http://regexr.com/.
Na koniec warto też zerknąć na stronkę https://regexlearn.com, która interaktywnie uczy pisać takie wyrażenia.
Wyrażenia w Javascript
Aby w JavaScript korzystać z wyrażeń regularnych, możemy posłużyć się skróconym zapisem /wzor/ lub użyć konstruktora RegExp(wzór, flagi*), który przyjmuje
2 argumenty: wzór, którym będziemy testować, oraz dodatkowe flagi, które poznamy poniżej.
const reg = /pani?/gi
//lub za pomocą konstruktora
const reg = new RegExp("pani?" , "gi")
Gdy już stworzymy wzór, musimy go użyć wraz z jedną z dostępnych metod. Je omówimy sobie w kolejnym rozdziale. W poniższych przykładach będe korzystał głównie z metody test(), która zwraca prawdę lub fałsz.
const reg = /[0-9]{3}/
console.log(reg.test("Ala")) //false
console.log(reg.test("102")) //true "*102*"
console.log(reg.test("Ala 007")) //true "Ala *007*"
console.log(reg.test("Numer czołgu miał numer 102 bo tak")) //true "Numer czołgu miał numer *102* bo tak"
Flagi czyli dodatkowe opcje dla wyrażeń
Dla każdego wyrażenia regularnego możemy ustawić dodatkowe flagi (opcje), które zmieniają jego działanie:
Przy skróconej składni flagi umieszczamy za wyrażeniem regularnym. Dla obiektu RegExp umieszczamy je jako drugi parametr:
console.log( /Kot/gi );
//lub
const reg = new RegExp("Kot", "gi");
console.log( reg.test(txt) );
| znak Flagi | znaczenie |
|---|---|
| i | powoduje niebranie pod uwagę wielkości liter |
const txt = "kot i pies, pies i kot";
console.log( /Kot/.test(txt) ) //false
console.log( /Kot/i.test(txt) ) //true "*kot* i pies, pies i kot"
| znak Flagi | znaczenie |
|---|---|
| g | powoduje zwracanie wszystkich pasujących fragmentów, a nie tylko pierwszego |
const txt = "kot i pies, pies i kot";
console.log( /kot/.test(txt) ) //true "*kot* i pies, pies i kot"
console.log( /kot/g.test(txt) ) //true "*kot* i pies, pies i *kot*"
console.log( /Pies/gi.test(txt) ) //true "kot i *pies*, *pies* i kot"
| znak Flagi | znaczenie |
|---|---|
| m | powoduje wyszukiwanie w tekście kilku liniowym. W trybie tym znak początku i końca wzorca (^$) jest wstawiany dla każdej linii z osobna. |
const txt = `
To jest Ania
To jest Marcin
`;
//sprawdzam czy Ania jest na końcu pojedynczej linii
console.log( /Ania$/.test(txt) ) //false
console.log( /Ania$/m.test(txt) ) //true
| znak Flagi | znaczenie |
|---|---|
| s | Sprawia, że znak . pasuje także do znaku nowe linii (\n) |
const txt = `
To jest Ania
To jest Marcin
`;
//sprawdzam czy za Anią coś jeszcze jest w tej samej linii
console.log( /Ania./.test(txt) ) //false
console.log( /Ania./s.test(txt) ) //true
| znak Flagi | znaczenie |
|---|---|
| u | Włącza możliwość używania kodów dla znaków unicode |
| znak Flagi | znaczenie |
|---|---|
| y | Włącza tryb sticky. Kolejne wyszukiwania będą rozpoczynać się od pozycji ostatniego szukania, którą definiuje lastIndex |
Pozycja w tekście
Domyślnie szukany fragment może znajdować się w dowolnym miejscu.
console.log( /kot/.test("to jest fajny kot i pies") ) //true "to jest fajny *kot* i pies"
console.log( /kot/.test("kot jest spoko") ) //true "*kot* jest spoko"
console.log( /kot/.test("pies i kot") ) //true "pies i *kot*"
^
Znak ^ oznacza, że szukany fragment musi znajdować się na początku badanego tekstu
console.log( /^kot/.test("kot lubi ryby") ) //true "*kot* lubi ryby"
console.log( /^kot/.test("ten kot jest gruby") ) //false
console.log( /^kot/.test("kot i pies") ) //true "*kot* i pies"
console.log( /^kot/.test("pies i kot") ) //false
$
Znak $ oznacza, że szukany fragment musi znajdować się na końcu badanego tekstu
console.log( /kot$/.test("pies i kot") ) //true "pies i *kot*"
console.log( /kot$/.test("kot i pies") ) //false
console.log( /^kot$/.test("kot") ) //true "*kot*"
console.log( /^kot$/.test("kot i pies") ) //false
Jeżeli chcemy sprawdzić, czy dany fragment występuje na końcu linii wiele liniowego tekstu, powinniśmy dodać do wyrażenia flagę m
const txt = `
przykladowy-plik.jpg
inny-plik.jpg
jakis-plik-w-formacie-jpg-z-wakacji.jpg
inny-plik.png
`;
//chcę sprawdzić czy jakiś plik kończy się na .jpg
//ten test jest błędny, bo "jpg" może być wewnątrz nazwy
console.log( /jpg/.test(txt) ) //true
/*
przykladowy-plik.*jpg*
inny-plik.*jpg*
jakis-plik-w-formacie-*jpg*-z-wakacji.*jpg*
inny-plik.png
*/
//ten też jest błędny, bo sprawdzi czy cały tekst kończy się na jpg
console.log( /jpg$/.test(txt) ) //false
//ten jest ok, bo sprawdzi czy pojedyncza linia kończy się na jpg
console.log( /jpg$/m.test(txt) ) //true
/*
przykladowy-plik.*jpg*
inny-plik.*jpg*
jakis-plik-w-formacie-jpg-z-wakacji.*jpg*
inny-plik.png
*/
Ilości znaków
*
Znak * oznacza 0 lub więcej wystąpień poprzedzającej grupy lub znaku.
console.log( /pan.*/.test("pan") ) //true
console.log( /pan.*/.test("pani") ) //true
console.log( /.*/.test("dowolny-tekst") ) //true
console.log( /ab*cd/.test("abcd") ) //true
console.log( /ab*cd/.test("acd") ) //true
console.log( /ab*cd/.test("acc") ) //false
console.log( /[a-z]*007/.test("abc007") ) //true
console.log( /[a-z]*007/.test("007") ) //true
+
Znak + oznacza 1 lub więcej wystąpień poprzedzającego znaku lub grupy.
console.log( /ba+/.test("bb") ) //false
console.log( /ba+/.test("ba") ) //true
console.log( /ba+/.test("baaaa") ) //true
console.log( /[a-z]+007/.test("abc007") ) //true
console.log( /[a-z]+007/.test("007") ) //false
{}
Wewnątrz klamer podajemy liczbę znaków. Możemy tutaj podać konkretną wartość {4}, minimalną {4,}, maksymalną {,4} lub zakres znaków {2,4}:
console.log( /^pani{1}/.test("pan") ) //false
console.log( /^pani{2,4}/.test("pani") ) //false
console.log( /^pani{2,4}/.test("paniii") ) //true
console.log( /^a{1,5}/.test("aaa") ) //true
console.log( /^a{5,}$/.test("aaaa") ) //false
console.log( /[0-9]{4}/.test("007") ) //false
console.log( /[0-9]{2}-[0-9]{3}/.test("02-300") ) //true
?
Znak ? oznacza 0 lub 1 wystąpienie poprzedzającego znaku lub grupy.
const req = /pani?/
console.log( req.test("pan") ) //true
console.log( req.test("pani") ) //true
console.log( req.test("pakuś") ) //false
const reg2 = /<div( class=".*")?><\/div>/
console.log( reg2.test(`<div class="element"></div>`) ) //true
console.log( reg2.test(`<div></div>`) ) //true
console.log( reg2.test(`<h2></h2>`) ) //false
Użycie znaku ? za znakami ., + lub {} sprawia, że wyszukiwanie przechodzi w tryb wyszukiwania niezachłannego. Oznacza to, że pasujący ciąg skończy się na najmniejszym pasującym fragmencie, a nie największym
const txt = `<div class="example">Tytuł książki to "Harry Potter"</div>`;
//do ostatniego "
/div class=".*"/.test(txt) //true <*div class="example">Tytuł książki to "Harry Potter"*</div>
//do najbliższego "
/div class=".*?"/.test(txt) //true <*div class="example"*>Tytuł książki to "Harry Potter"</div>
Zbiory znaków
[...]
Między nawiasy kwadratowe możemy wstawić znaki, które będą stanowić zbiór, który weźmie udział w teście
console.log( /[abc]+/.test("ala") ) //true
console.log( /[abc]+/.test("zuzy") ) //false
console.log( /[abc]+/.test("aaaaabbbbbbccccc") ) //true
console.log( /[abc]+/.test("baca") ) //true
console.log( /[abc]+/.test("kury") ) //false
console.log( /[abc]+/.test("kura") ) //true
console.log( /[abc]+/.test("kac") ) //true
console.log( /[123]{3}/.test("111") ) //true
console.log( /[abc]+-[abc]+/.test("aaa-bbb") ) //true
console.log( /[abc]+-[abc]+/.test("25-aaa") ) //false
Podając zbiory znaków możemy określać ich zakresy:
/[a-z]/ //a, b, c, ... , x, y, z
/[A-Z]/ //A, B, C, ... , X, Y, Z
/[a-c]/ //a, b, c
/[0-9]/ //0, 1, 2, 3, ... , 8, 9
/[a-zA-Z]/ //a, b, c, A, B, C, ..., z, Z
/[a-zA-Z0-9]/ //a, b, c, A, B, C, ..., z, Z, 0, ... 9
console.log( /kot[0-9]{3}/.test("kot007") ) //true
console.log( /[a-zA-Z]+[0-9]{3}/.test("ania007") ) //true
console.log( /[0-9]{2}-[0-9]{3}/.test("kot02-370kot") ) //true
console.log( /^[0-9]{2}-[0-9]{3}$/.test("02-370") ) //true
console.log( /^[0-9]{2}-[0-9]{3}$/.test("02-3700") ) //false
Zbiory znaków możemy też negować za pomocą znaku ^:
console.log( /[a-zA-Z]/.test("Ala ma kota") ) //true
console.log( /^[^0-9]+$/.test("Ala ma kota") ) //true
console.log( /^[^0-9]+[0-9]+$/.test("Ala ma kota 007") ) //true
Możemy też skorzystać z przygotowanych dla nas grup znaków:
| Klasa znaków | znaczenie |
|---|---|
| \d | każdy znak będący cyfrą. Równoznaczne z [0-9] |
| \D | każdy znak nie będący cyfrą. Równoznaczne z [^0-9] |
| \w | każdy znak będący literą, cyfrą i znakiem _. Równoznaczne z [a-zA-Z0-9_] |
| \W | każdy znak nie będący literą, cyfrą i znakiem _. Równoznaczne z [^a-zA-Z0-9_] |
| \s | znak spacji, tabulacji lub nowego wiersza |
| \S | każdy znak nie będący spacją, tabulacją lub znakiem nowego wiersza |
| \n | znak nowego wiersza |
| \t | znak tabulacji |
| \uXXXX | oznacza znak o danym kodzie Unicode. Do użycia wymaga odpowiedniej flagi |
| . | oznacza dowolny znak nie będący znakiem nowej linii. Żeby oznaczał też nową linię musimy użyć odpowiedniej flagi. |
console.log( /\w/.test("ania") ) //true
console.log( /^\w$/.test("Jan Nowak") ) //false
console.log( /\w \w/.test("Ania Nowak") ) //true
console.log( /[\w]{3}.test("kot") ) //true
console.log( /^[\w]{3}/.test("kot007") ) //true
console.log( /\w{3}$/.test("kot") ) //true
console.log( /\w{3}$/.test("---") ) //false
console.log( /^[\w]$/.test("Ania.007") ) //false
console.log( /^[\w]+.[\d]+/.test("Ania.007") ) //true
console.log( /^.arka$/.test("ta arka") ) //true
console.log( /^.arka/.test("barka pływa") ) //true
console.log( /^.arka$/.test("barka pływa") ) //false
Dodatkowo mamy tutaj specjalne oznaczenie \b, które oznacza granicę słowa.
Możemy go użyć w trzech kluczowych miejscach:
- na początku słowa, od którego zaczynają się znaki
\w - na końcu słowa przed którym są znaki
\w - w środku słowa gdzie po obu stronach są znaki
\w
console.log( "Hello, Java!".match(/\bJava\b/) ); //Hello, *Java*!
console.log( "Hello, JavaScript!".match(/\bJava\b/) ); //false
Wybór lub
|
Znak | oznacza lub
console.log( /hey|ho/.test("hey") ) //true
console.log( /hey|ho/.test("ho") ) //true
console.log( /p|kara/.test("para") ) //true
console.log( /p|kara/.test("kara") ) //true
console.log( /p|kara/.test("sara") ) //false
console.log( /trzynasty|13-ty|13/.test("trzynasty") ) //true
console.log( /trzynasty|13-ty|13/.test("13-ty") ) //true
console.log( /trzynasty|13-ty|13/.test("13") ) //true
console.log( /trzynasty|13-ty|13/.test("czternasty") ) //false
Grupy
Okrywając kawałek wzoru nawiasami robimy z niego grupę, do której możemy się później odwoływać.
W powyższych przykładach używaliśmy metody .test(), która zwraca wartość boolean.
const reg = /([0-9]{4})-([0-9]{2})-([0-9]{2})/
console.log( reg.test("2020-10-05") ) //true
Przy pracy z grupami warto skorzystać z dodatkowych metod takich jak .match() lub .exec() czy replace(), które zwracają tablicę pasujących fragmentów:
const reg = /([0-9]{4})-([0-9]{2})-([0-9]{2})/
console.log( reg.exec("2020-10-05") ) //["2020-10-05", "2020", "10", "05", ...]
//tutaj uwaga - to metody dla stringów, nie regexp
console.log( "2020-10-05".match(reg) ) //["2020-10-05", "2020", "10", "05", ...]
console.log( "2020-10-05".matchAll(reg) ) //["2020-10-05", "2020", "10", "05", ...]
Do kolejnych znalezionych członów (grup) możemy potem odwoływać się poprzez zapis $1, $2 itd.
Możemy to wykorzystać w metodzie replace(), ale też podczas pracy w edytorze tekstu.
const reg = /([0-9]{4})-([0-9]{2})-([0-9]{2})/
console.log( "1980-10-05".replace(reg, "Mamy rok $1 miesiąc $2 dzień $3") ); //"Mamy rok 1980 miesiąc 10 dzień 05"
Dopasowywane w zależności
Zapis ?= pozwala nam dopasowywać dany fragment tekstu, gdy tuż za nim występuje inny fragment tekstu:
console.log( /Pies(?= Szamson)/.test("Pies Szamson jest super szybki")) //true
console.log( /Pies(?= Szamson)/.test("Pies Azor jest super szybki") ) //false
W ES2018 mamy też bardzo podobny zapis ?<=, który pozwala nam sprawdzać czy dany ciąg poprzedza jakiś tekst:
console.log( /(?<=Pies) Szamson/.test("Pies Szamson jest super szybki")) //true
console.log( /(?<=Pies) Szamson/.test("Kot Szamson jest super szybki") ) //false
Unicode w wyrażeniach regularnych
Domyślnie wyrażenia regularne - tak samo jak reszta tekstów w javascript operuje na pierwszych 1600 znakach z tablicy Unicode. Jeżeli chcielibyśmy działać na niestandardowych znakach (np. Chińskich literach czy ikonach Emoji), powinniśmy do naszego wzoru dodać flagę u:
console.log( /^.$/.test("a") ) //true
console.log( /^.$/.test("😎") ) //false
console.log( /^.$/u.test("😎") ) //true
Strzałka w lewo
Tworzymy baner z obietnicami
Funkcje dla wyrażeń regularnych Strzałka w prawo