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 kod pocztowy. Kod taki można opisać w następujący sposób: na początku są 2 dowolne cyfry, potem znak myślnika, po czym występują trzy dowolne cyfry (np. 02-380). Powyższy opis kodu pocztowego to właśnie wzorzec. Powiedzmy, że w jakimś tekście chcielibyśmy znaleźć takie kody pocztowe. Problem w tym, że nie wiemy jakie one mają numery... I tutaj właśnie pomogą nam wzorce.
Przykładowy wzorzec może mieć np postać /^[a-zA-Z]{2,}\s[a-zA-Z]{2,}$/. Może się on wydawać bardzo skomplikowanym zapisem, jednak w praktyce tak nie jest!
Aby w Javascript korzystać z wyrażeń regularnych, musimy utworzyć obiekt RegExp(wyrażenie,flaga), który przyjmuje 2 argumenty: wyrażenie, którym będziemy testować, oraz dodatkowe flagi, które poznamy w tym rozdziale.
var Wyrazenie = new RegExp("prawdzamy//?*" , "g")
lub
var Wyrazenie = /sprawdzamy/?*/g
Którą metodę wybrać?
Każda z nich ma swoje wady i zalety.
Pierwsza wymaga poprzedzania specjalnych znaków np ? (które zaraz poznamy) podwójnym ukośnikiem //. W drugiej metodzie specjalne znaki poprzedzamy jednym ukośnikiem, ale całe wyrażenie musimy objąć parą ukośników.
Większość deweloperów wybiera drugą metodę, jest to jednak kwestia gustu.
Każdy wzorzec składa się z meta znaków, czyli specjalnych znaków, które opisują jak mają wyglądać wyszukiwane fragmenty tekstu. Przykładowo wzorzec składający się z meta znaków p.p. będzie pasował do słowa "popo", ale równie dobrze będzie pasował do słowa "papi". Poniżej zamieściłem tabelę zawierającą opis meta znaków.
| Metaznak | Znaczenie | Przykład wyrażenia | Zgodne ciągi z wyrażeniem | Niezgodne ciągi z wyrażeniem |
|---|---|---|---|---|
| ^ | początek wzorca | ^za | zapałka, zadra, zapłon, zarazek | kazanie, poza, bazar |
| $ | koniec wzorca | az$ ^.arka$ | uraz, pokaz barka, warka | azymut, pokazy parkan |
| . | dowolny pojedyńczy znak | .an.a | panda, Wanda, panna, kania | rana, konia |
| [...] | dowolny z wymienionych znaków; możemy podawać kolejne znaki lub wpisywać zakre - na przykład [a-z] oznacza wszystkie małe litery. Wymieniając specjale znaki z końca tej tabeli nie musimy poprzedzać znakiem \ | [a-z]an[nd]a [a-z][a-zA-Z0-9.-][pus] | pana, panda, wanna pas, mAs, p2p, m3u, b-s, z.u | Wanda, kania Bas, bal, balu, mp3 |
| [^...] | dowolny z niewymienionych znaków | kre[^st] | krew, krem | kres, kret |
| | | dowlony z rozdzielonych znakiem ciągów | [nz]a|pod|przed trzynasty|13-ty|13 | na, za, pod, przed trzynasty, 13-ty, 13 | |
| (...) | zawężenie zasięgu | g(ż|rz)eg(ż|rz)(u|ó)łka (ósmy|8-my|8)(maj|maja) | gżegżółka, gżegrzółka, gżegrzułka, grzegrzułka ósmy maja, 8-my maj, 8 maja | |
| ? | zero lub jeden poprzedzający znak lub element; elementem może być na przykład wyrażenie umieszczone wewnątrz nawiasów (...) | ro?uter (ósmy|8(-my)?)maja? | router, ruter ósmy maja, ósmy maj, 8-mymaja, 8-my maj, 8 maja, 8 maj | |
| + | jeden lub więcej poprzedzającch znaków lub elementóe; elementem może być na przykład wyrażenie umieszczone wewnątrz nawiasów (...) | [0-9]+[abc] pan+a (tam)+ | 10a, 1b, 003c, 42334b pana, panna, pannnna tam, tamtam, tamtamtam | a, b, c, z, 14, 03, 12d, 1231z paa, panda, ta, tamta, mat |
| * | zero lub więcej poprzedzającch znaków lub elementóe; elementem może być na przykład wyrażenie umieszczone wewnątrz nawiasów (...) | [0-9]*[abc] pora*n*a* | 10a, 1b, 003c, 42334b, a, b, c por, poa, poranna, poraannnaa, pornnna | k, 2335, porada, panna |
| {4} | dokładnie 4 porzedzające znaki lub elementy | [0-9]{4} | 8765, 8273, 2635 | 12345, 234, 2123456 |
| {4,} | 4 lub więcej poprzedzających znaków lub elementów | [ah]{4,} | haha, haaaaahaha, ahaaa | haa, ha, hehe, aha |
| {2,4} | od 2 do 4 poprzedzających znaków lub elementów | p.{2,4}a | piana, pola, polana | psa, poranna |
| \. | znak kropki | [0-9]{,3}\.[0-9]{,3}\.[0-9]{,3} | 128.0.0.2 | 128-0-0-2 |
| \* | znak * | \*.+ | *nic | nic*, nic |
| \/ | znak / | ^\/\/$ | // | |
| \? | znak ? | ^.+\?$ | Czy to jest kot? | Czy to jest kot |
| \: | znak : | ^.+\:$ | Oto one: | :nic |
| \. | znak . | \.+ | ...... | |
| \^ | znak ^ | .*\^ | To jest ^ | To jest & |
| \+ | znak + | [0-9]+\+[0-9]+ | 928374+29832 | 23873-32787 238738278 |
| \\ | znak \ | c\:\\ | c:\ | |
| \= | znak = | [0-9]+\+[0-9]+\=[0-9]+ | 11+12=23 | 11+12+23 |
| \| | znak | | x \|\| y | x || y |
Poza wymienionymi metaznakami istnieją specjalne parametry (flagi), które oddziałują na wyszukiwanie wzorców.:
var Wyrazenie = /[a-z]*/mg
var Wyrazenie = new RegExp("[a-z]*","g")
| znak Flagi | znaczenie |
|---|---|
| i | powoduje niebranie pod uwagę wielkości liter |
| g | powoduje zwracanie wszystkich psujących fragmentów, a nie tylko pierwszego |
| m | powoduje wyszukiwanie w tekście kilku linijkowym. W trybie tym znak początku i końca wzorca (^$) jest wstawiany przed i po znaku nowej linni (\n). |
| s | powoduje włączenie przeszukiwania jedno linijkowego. W trybie tym znak . oznacza nową linijkę |
Dodatkowo Javascript udostępnia specjalne klasy znaków. Zamiast wyszukiwać wszystkich liter za pomocą [a-zA-Z_] możemy skorzystać z klasy znaków \w.
| Klasa znaków | znaczenie |
|---|---|
| \s | znak spacji, tabulacji lub nowego wiersza |
| \S | znak nie będący spacją, tabulacją lub znakiem nowego wiersza |
| \w | każdy znak będący literą, cyfrą i znakiem _ |
| \W | każdy znak nie będący literą, cyfrą i znakiem _ |
| \d | każdy znak będący cyfrą |
| \D | każdy znak nie będący cyfrą |
Zacznijmy od najprostszych rzeczy czyli od sprawdzenia, czy w danym tekście występuje nasz wzorzec:
Metoda test() służy do sprawdzania, czy dane wyrażenie znajduje się w tekście:
var tekst = "cat dog";
var wzor = /cat/;
wzor.test(tekst) === true //bo cat znajduje się w tekście
var wzor2 = /^cat$/;
alert(wzor.test(tekst)); //false - bo wzorzec zaczyna się z początkiem i kończy z końcem tekstu (znaki ^ i $) - jedyny pasujący tekst to "cat"
var tekst = "Turlal goryl po Urlach kolorowe korale...";
var wyrazenie = new RegExp("[A-Z]{1}[^\s]+");
wyrazenie.test(tekst) === true //bo w tekście jest wyraz zaczynający się z dużej litery, po którym następują małe litery (np. Turlal)
[A-Z]{1} - jedna duża litera
[^\s]+ - plus oznacza jeden lub więcej znaków, a [^\s] oznacza znak nie będący spacją - czyli razem: jeden lub więcej znaków nie będących spacją
var tekst = "Turlal goryl po Urlach kolorowe korale...";
var wyrazenie = new RegExp("[0-9]+");
wyrazenie.test(tekst) === false //bo w tekście nie ma żadnej cyfry
var wyrazenie = new RegExp("[0-9]*");
wyrazenie.test(tekst) === true //w tekście nie ma żadnej cyfry, ale * oznacza 0 lub więcej
W powyższych przykładach sprawdzaliśmy tylko czy dany ciąg w ogóle występuje w danym tekście. Aby uzyskać dostęp do pasujących fragmentów, skorzystamy z metody exec() wraz z flagą "g":
var tekst = "Fantomas robi mase - marchewkowo-marcepanowa";
var wyrazenie = new RegExp("mas", "g"); //stosujemy flagę g, by sprawdzić wszystkie wystąpienia
var tablicaWynikow = wyrazenie.exec(tekst);
if (tablicaWynikow.length) {
console.log(tablicaWynikow.join('-'));
}
Obiekt String posiada metodę match(), która spełnia tę samą funkcję co metoda exec() obiektu RexExp. Możemy więc napisać powyższy skrypt nieco inaczej:
var tekst = "Fantomas robi mase - marchewkowo-marcepanowa";
var wzor = /mas/g;
var tablicaWynikow = tekst.match(wzor);
if (tablicaWynikow.length) {
console.log(tablicaWynikow.join('-'));
}
W kolejnym przykładzie korzystamy z klasy znaków czyli [...], w których podajemy grupę znaków, z których będzie wybierany pasujący znak:
var tekst = "kot, pot, Wot Lot";
var wzor = /[kpw]ot/gi;
var znalezione = tekst.match(wzor); //kot, pot, Wot
var tekst = "Numer1, Numer2, Numer3, NumerB, Numer5, NumerD";
var wzor = /Numer[1-4A-C]/g;
var znalezione = tekst.match(wzor); //Numer1, Numer2, Numer3, NumerB
Metoda search() obiektu RexExp działa tak samo jak metoda indexOf() obiektu string, czyli zwraca indeks pierwszego wystąpienia podciągu w ciągu:
var tekst = "Fantomas robi mase - marchewkowo-marcepanowa";
var wzor = /at/gi";
console.log("Search: " + tekst.search(wzor));
console.log("Index of: " + tekst.indexOf("at"));
Obiekt string posiada metodę replace(), która służy do zamiany jednego ciągu na drugi. Przy jej stosowaniu możemy używać wyrażeń regularnych:
var tekst = "Kolorowy kolor nie jest kolorowy?..."; document.write(tekst + "<br />"); var wzor = /lor/g //Nasze wyrazenie document.write(tekst.replace(wzor,"<strong>ral</strong>")); //Wyszukujemy w tekście wszystkie wystapienia "lor" i zamieniamy je na pogrubione "ral"
Jako drugi argument tej metody możemy podać funkcję. Pobiera ona jeden argument - znaleziony fragment tekstu, oraz zwraca tekst, który zastąpi znaleziony fragment:
var tekst = "Super Samson jest fajny.";
var wzor = /fajny/;
var zamieniony = tekst.replace(wzor, function(match) {
return "super" + match;
});
console.log(zamieniony); //Super Samson jest super fajny
Chcemy sprawdzić, czy użytkownik wpisał numer. Jak widać w tabeli zamieszczonej powyżej, Javascript udostępnia nam klasę \d, która oznacza dowolą cyfrę:
var zmienna = "909384758699";
var wzor = /^\d+$/
if (wzor.test(zmienna)) {
document.write("To jest liczba")
} else {
document.write("To nie jest liczba...")
}
Aby wyszukać w tekście kod pocztowy uzyjemy wyrazenia:
var WzorKoduP = /[0-9]{2}-[0-9]{3}/g;
//lub
var WzorKoduP = /[\d]{2}-[\d]{3}/g;
[0-9]{2} - powinny znaleźć się 2 cyfry
- - po 2 cyfrach powinien znaleźć się znak -
[0-9]{3} - po kturym powinny znaleźć się 3 cyfry
g - mają być zwrócone wszystkie wyszukane pasujące ciągi
Nasz wzór możemy wykorzystać w skrypcie za pomocą metody match (zwracającej pasujące ciągi):
var tekst = "Moj kod pocztowy to nie 12-323 ani tez 03-400 ... jest po prostu inny.";
var WzorKoduP = /[0-9]{2}-[0-9]{3}/g; //parametr g nakazuje zwrócenie wszystkich znalezionych ciągów (normalnie zwracany jest tylko pierwszy)
var a = tekst.match(WzorKoduP);
if (a) {//jeżeli w a znajdują się pasujące ciągi
for (x=0; x<a.length; x++) {
document.write(a[x]+"<br />");
}
}
Wzorzec opisujący poprawność tych danych ma postać:
var WzorNazwiska = /^[a-zA-Z]{2,}\s+[a-zA-Z]{2,}$/;
//lub
var WzorNazwiska = /^[\D]{2,}\s+[\D]{2,}$/;
/ - od tego znaku muszą się zaczynać i kończyć wszystkie wzorce w JavaScripcie
^ - Wzorzec ma się zaczynać z początkiem tekstu
[a-zA-Z]{2,} - Ciąg musi zawierać przynajmniej 2 litery (imie)
\s+ - Po których znajdą się spacje lub tabulatory (min jeden)
[a-zA-Z]{2,} - Po których znajdą się znowu przynajmnej 2 litery (nazwisko)
$ - Wzorzec ma się kończyć z końcem tekstu
Teraz możemy nasz wzór wykorzystać w skrypcie za pomocą metody test:
var imieINazw = "Marcin Domanski";
WzorNazwiska = /^[a-zA-Z]{2,}\s[a-zA-Z]{2,}$/;
if (WzorNazwiska.test(imieINazw)) document.write('Imie i Nazwisko jest OK!')
Jak wiemy, aby adres email był prawidłowy musi spełniać kilka zasad:
Wzór który by opisywał adres mail będzie miał postać:
var wzorMaila = /^[0-9a-zA-Z_.-]+@[0-9a-zA-Z.-]+\.[a-zA-Z]{2,3}$/
/ - od tego znaku muszą się zaczynać i kończyć wszystkie wzorce w JavaScripcie
^ - Wzorzec ma się zaczynać z początkiem tekstu
[0-9a-zA-Z_.-]+ - Następnie badamy nazwę konta, która może składać się z dowolnych znaków (cyfry, litery, .-_ )
@ - Potem sprawdzamy wystąpienia znaku @
[0-9a-zA-Z_.-]+ - Po znaku @ sprawdzamy domenę, która może składać się z takich samych znaków co nazwa konta oprócz znaku _
\. - Po dokumencie musi wystąpić kropka
[a-zA-Z]{2,3} - Po kropce musi wystąpić końcówka domeny, która może się składać wyłącznie z liter i jej długość musi być od 2 do 3 znaków
$ - Wzorzec ma się kończyć z końcem tekstu
Zamiast za każdym razem wpisywać a-zA-Z, możemy uprościć formularz. Na końcu za znakiem / należy wpisać i, co sprawi, że wielkość liter nie będzie brana pod uwagę. Dzięki temu nasze wyrażenie nieco się uprości:
var wzorMaila = /^[0-9a-z_.-]+@[0-9a-z.-]+\.[a-z]{2,3}$/i
Sprawdźmy nasz wzór w przykładowym polu tekstowym (za pomocą metody test):
var mail = "mar-dom@wp.pl";
var wzorMaila = /^[0-9a-z_.-]+@[0-9a-z.-]+\.[a-z]{2,3}$/i
if (wzorMaila.test(mail)) document.write('Mail OK')
Gdy dokonujemy sprawdzenia według wzorca, możemy nasze części wyrażenia objąć w nawiasy tworząc tak zwane Atomy. Wówczas kolejne Atomy będą zawierać kolejne części znalezionego tekstu, do których będziemy mogli się indywidualnie odwołać:
var str = "http://www.webreference.com/js/index.php#piosenka?l=2"; var wzor = str.match(/(\w+:\/\/)([^/]+)([^#?]*)([^?]*)\?*(.+)*/);
Wzorzec wyszuka:
(\w+:\/\/) - dowolne litery po których znajduje się ciąg ://
([^/]+) - po których znajduje się ciąg do znaku /
([^#?]*) - po którym znajduje się kolejny ciąg do ? lub #
([^?]*) - po których znajduje się ciąg do ? (czyli jeżeli adres wywołał kotwicę)
\?* - po którym może znajdować się znak ?
(.+)* - po którym może wystąpić ciąg znaków (czyli query string
Po zastosowaniu atomów, możemy się odwoływać do ich treści. Jest on podzielona według naszych atomów, i nazywa się wsteczną referencją. Pierwszym ze sposobów odwoływania do tych treści jest wykorzystanie obiektu RegExp:
var str = "http://www.webreference.com/js/index.php#piosenka?l=2";
var wzor = str.match(/(\w+:\/\/)([^/]+)([^#?]*)([^?]*)\?*(.+)*/);
document.write('Badany ciąg: ' + str + '
');
document.write("Wyrazenie z 1 nawiasu: "+ RegExp.$1 +"
");
document.write("Wyrazenie z 2 nawiasu: "+ RegExp.$2 +"
");
document.write("Wyrazenie z 3 nawiasu: "+ RegExp.$3 +"
");
document.write("Wyrazenie z 4 nawiasu: "+ RegExp.$4 +"
");
document.write("Wyrazenie z 5 nawiasu: "+ RegExp.$5 +"
");
Drugi sposób odwoływania się do poszczególnych Atomów polega na podaniu numeru atomu poprzedzonym ukośnikiem:
var tekst = "FantomasFantomas"
var wzor = /(Fantomas)\1/;
tekst.test(wzor);
Powyższy wzór korzysta z tej metody.
Pierwszy Atom pasuje do słowa Fantomas, więc wyrażenie (Fantomas)\1 jest równe "FantomasFantomas".
Ostatnią metodą wykorzystania wstecznej referencji jest skorzystanie z metody replace(), która pozwala na korzystanie z numerów poprzedzonych znakiem dolara, oznaczających numer Atomu:
var tekst = "2010-10-20";
var wzor = /(\d{4})-(\d{2})-(\d{2})/;
var zamieniony = tekst.replace(wzor, "$3 $2 $1"); //20-10-2010
Jeżeli chcemy by nasz Atom nie tworzył wstecznej referencji, wtedy musimy rozpocząć jego treść od "?:"
var wzor = /(\d{4})-(?:\d{2})-(\d{2})/;
var zamieniony = tekst.replace(wzor, "$2 $1"); //20-10-2010
Jak widzieliśmy w poprzednim przykładzie, kolejne atomy są kolejno numerowane. Ich treść jest przechowywana do późniejszej obróbi i zwie się referencją powrotną.
To podziału tekstu wykorzystamy metodę split().
Pierwszym parametrem jest wyrażenie regularne, a drugim dodatkowym parametrem tej metody jest maksymalna ilość elementów zwracanych w formie tablicy (IE pomija ten parametr).
Dodatkową uwagę przy stosowaniu tej metody trzeba zwracać na stosowany wzorzec, gdyż np zastosowany poniżej /\W+/ niby zwraca wszystkie litery, ale widać typowo Polskie litery nie są literami... uhh...
var s = "To jest zdanie doś podzielenia na pojedyńącze slowa"
var a = s.split(/\W+/); //dzielimy zdanie na słowa (wedle sekwencji nie literowych)
if (a) {
for (x=0; x<a.length; x++) {
document.write(a[x]+"<BR>");
}
}
Aby usunąć tagi z jakiegoś tekstu, wystarczy skorzystać z poniższej funkcji, która używa do tego celu wyrażeń regularnych:
function StripTags(str) {
var wzor=new RegExp('<[a-zA-Z/]{1,15}.*?>','g');
return str.replace(wzor,'');
}
Powyższą funkcję możemy napisać nieco prościej:
function StripTags(str) {
var wzor = /<(?:.|\s)*?>/g;
return str.replace(wzor,'');
}