Wyrażenia regularne

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.

Wszelakie zabawy z wyrażeniami regularnymi warto przeprowadzać w jednym z narzędzi testujących jak to, czy to. Zresztą wystarczy w wyszukiwarkę wpisać "reg tester"...

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 reg = new RegExp("pani?" , "gi")

lub 

var reg = /pani?/gi

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.

Metaznaki

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.

MetaznakZnaczeniePrzykład wyrażeniaZgodne ciągi z wyrażeniemNiezgodne ciągi z wyrażeniem
^początek wzorca^zazapałka, zadra, zapłon, zarazekkazanie, poza, bazar
$koniec wzorcaaz$
^.arka$
uraz, pokaz
barka, warka
azymut, pokazy
parkan
.dowolny pojedynczy znak.an.apanda, Wanda, panna, kaniarana, konia
[...]dowolny z wymienionych znaków; możemy podawać kolejne znaki lub wpisywać zakres - 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ówkre[^st]krew, kremkres, kret
|dowolny 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ęgug(ż|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ących znaków lub elementów; 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ących znaków lub elementów; 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 poprzedzające znaki lub elementy[0-9]{4}8765, 8273, 263512345, 234, 2123456
{4,}4 lub więcej poprzedzających znaków lub elementów[ah]{4,}haha, haaaaahaha, ahaaahaa, ha, hehe, aha
{2,4}od 2 do 4 poprzedzających znaków lub elementówp.{2,4}apiana, pola, polanapsa, poranna
\.znak kropki[0-9]{,3}\.[0-9]{,3}\.[0-9]{,3}128.0.0.2128-0-0-2
\*znak *\*.+*nicnic*, 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+2983223873-32787
238738278
\\znak \c\:\\c:\ 
\=znak =[0-9]+\+[0-9]+\=[0-9]+11+12=2311+12+23
\|znak |x \|\| yx || y 

Flagi

Poza wymienionymi meta znakami istnieją specjalne parametry (flagi), które oddziałują na wyszukiwanie wzorców.:


var reg = /[a-z]*/mg
var reg = new RegExp("[a-z]*","g")
znak Flagiznaczenie
ipowoduje niebranie pod uwagę wielkości liter
gpowoduje zwracanie wszystkich psujących fragmentów, a nie tylko pierwszego
mpowoduje wyszukiwanie w tekście kilku liniowym. W trybie tym znak początku i końca wzorca (^$) jest wstawiany przed i po znaku nowej linii (\n).

Klasy znaków

Dodatkowo Javascript udostępnia specjalne klasy znaków. Zamiast wyszukiwać wszystkie litery za pomocą [a-zA-Z_] możemy skorzystać z klasy znaków \w.

Klasa znakówznaczenie
\sznak spacji, tabulacji lub nowego wiersza
\Sznak nie będący spacją, tabulacją lub znakiem nowego wiersza
\wkażdy znak będący literą, cyfrą i znakiem _
\Wkażdy znak nie będący literą, cyfrą i znakiem _
\dkażdy znak będący cyfrą
\Dkażdy znak nie będący cyfrą

Zastosowanie metody test()

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 text = "cat dog";
var reg = /cat/;
reg.test(text) === true //bo cat znajduje się w tekście

var reg2 = /^cat$/;
alert(reg2.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 text = "Turlal goryl po Urlach kolorowe korale...";
var reg = new RegExp("[A-Z]{1}[^\s]+");
reg.test(text) === 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ą


ar text = "Turlal goryl po Urlach kolorowe korale...";
var reg = new RegExp("[0-9]+");
reg.test(text) === false //bo w tekście nie ma żadnej cyfry

var reg = new RegExp("[0-9]*");
reg.test(text) === true //w tekście nie ma żadnej cyfry, ale * oznacza 0 lub więcej

Zastosowanie metody exec()

Metoda excec() przeszukuje dany ciąg znaków, a następnie zwraca tablicę zawierającą składowe pierwszego wyszukanego fragmentu.

Jeżeli metoda nic nie znajdzie, zwróci null.
Jeżeli metoda coś znajdzie, wtedy element 0 zawiera ostatni pasujący ciąg, natomiast elementy o indeksie 1 - n zawierają poszczególne części wyszukiwania (patrz tutaj). Poniższy przykład pokazuje składowe takiego wyniku:


var re = /d(b+)(d)/ig;
var result = re.exec("cdbBdbsbz");
ObiektWłaściwość/IndexOpisPrzykład
result [0] Ostatni znaleziony pasujący ciąg dbBd
[1], ...[n] Kolejne Atomy pasującego ciągu [1] = bB
[2] = d
index początkowy indeks od kąd zaczyna się znaleziony kawałek tekstu 1
input Oryginalny tekst w którym szukamy cdbBdbsbz
re lastIndex Indeks odkąd rozpocznie się następne szukanie 5
ignoreCase Wskazuje flagę "i" - nie branie pod uwagę wielkości liter true
global Wskazuje flagę "g" - wielokrotne wyszukiwanie w tym samym ciągu true
multiline Wskazuje flagę "m" - wyszukiwanie w wielu liniach false
source Wzór wyrażenia regularnego d(b+)(d)

console.log(result[0]) //dbBd
console.log(result.index) //1
console.log(result.input) //cdbBdbsbz

console.log('----')

console.log(re.lastIndex) //5
console.log(re.multiline) //false
console.log(re.ignoreCase) //true
console.log(re.source) //d(b+)(d)
Przykład zaczerpnięty ze strony: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec

Jeżeli użyjesz flagi "g", wtedy będziesz mógł użyć metody exec() do wielokrotnego wyszukiwania w tym samym tekście:


var myRe = /ab*/g;
var str = "abbcdefabh";
var myArray;

while ((myArray = myRe.exec(str)) !== null) {
    var msg = "Znaleziono: " + myArray[0] + "    ";
    msg += "Następne wyszukiwanie rozpocznie się od: " + myRe.lastIndex;
    
    console.log(msg);
    
    //Znaleziono: abb - następne wyszukiwanie rozpocznie się od: 3
    //Znaleziono: ab - następne wyszukiwanie rozpocznie się od: 9
}
Przykład zaczerpnięty ze strony: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec

Match()

Obiekt String posiada metodę match(), która spełnia tę samą funkcję co metoda exec() obiektu RexExp, jednak zwraca od razu wszystkie pasujące fragmenty.


var text = "Numer1, Numer2, Numer3, NumerB, Numer5, NumerD";
var reg = /Numer[1-4A-C]/g;
console.log(text.match(reg)); //Numer1, Numer2, Numer3, NumerB

var reg = /d(b+)(d)/ig;
var result = "cdbBdbsbz".match(reg);

if (result.length) {
    console.log(result.join('-')); //dbBd
}

Zastosowanie metody search()

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 text = "Fantomas robi masę - marchewkowo-marcepanowa";
var reg = /at/gi";
        
console.log("Search: " + text.search(reg));
console.log("Index of: " + text.indexOf("at"));

Zastosowanie metody replace()

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 text = "Kolorowy kolor nie jest kolorowy?...";
console.log(tekst);
var reg = /lor/g //nasze wyrażenie
console.log(text.replace(reg,"ral")); //Wyszukujemy w tekście wszystkie wystąpienia "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 text = "Super Samson jest fajny.";
var reg = /fajny/;
var textEnhanced = text.replace(reg, function(match) {
    return "super" + match;
});

console.log(textEnhanced); //Super Samson jest super fajny

Czy liczba

Chcemy sprawdzić, czy użytkownik wpisał numer. Jak widać w tabeli zamieszczonej powyżej, Javascript udostępnia nam klasę \d, która oznacza dowolną cyfrę:


var text = "909384758699";
var reg = /^\d+$/
if (reg.test(text)) {
    console.log("To jest liczba")
} else {
    console.loge("To nie jest liczba...")
}

Sprawdzanie Kodu Pocztowego

Aby wyszukać w tekście kod pocztowy użyjemy wyrażenia:


var codeReg = /[0-9]{2}-[0-9]{3}/g;
//lub
var codeReg = /[\d]{2}-[\d]{3}/g;

[0-9]{2} - powinny znaleźć się 2 cyfry
- - po 2 cyfrach powinien znaleźć się znak -
[0-9]{3} - po którym 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 text = "Moj kod pocztowy to nie 12-323 ani tez 03-400 ... jest po prostu inny.";
var codeReg = /[0-9]{2}-[0-9]{3}/g; //parametr g nakazuje zwrócenie wszystkich znalezionych ciągów (normalnie zwracany jest
tylko pierwszy)
var matchReg = text.match(codeReg);
if (matchReg) {//jeżeli w a znajdują się pasujące ciągi
    for (var x=0; x<matchReg.length; x++) {
        console.log(matchReg[x]);
    }
}

Weryfikacja Imienia i Nazwiska

Wzorzec opisujący poprawność tych danych ma postać:


var nameReg = /^[a-zA-Z]{3,}\s+[a-zA-Z]{3,}$/;
//lub
var nameReg = /^[\D]{3,}\s+[\D]{3,}$/;

/ - od tego znaku muszą się zaczynać i kończyć wszystkie wzorce w JavaScripcie
^ - Wzorzec ma się zaczynać z początkiem tekstu
[a-zA-Z]{3,} - Ciąg musi zawierać przynajmniej 4 litery (imię)
\s+ - Po których znajdą się spacje lub tabulatory (min jeden)
[a-zA-Z]{3,} - Po których znajdą się znowu przynajmniej 3 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 nameSurname = "Marcin Domański";
var nameReg = /^[a-zA-Z]{2,}\s[a-zA-Z]{2,}$/;
if (nameReg.test(nameSurname)) document.write('Imię i Nazwisko jest OK!')

Weryfikacja E-Maila

Jak wiemy, aby adres email był prawidłowy musi spełniać kilka zasad:

  • musi posiadać nazwę konta (składającą się z kiku znaków - poza znakami ?,:*/ i spacji)
  • po dokumencie musi znaleźć się znak @
  • po małpie powinna znaleźć się przynajmniej jedna . i to pomiędzy domeną a końcówką adresu
  • końcówka adresu powinna składać się z przynajmniej 2 liter (pl, com itp.)

Wzór który by opisywał adres mail będzie miał postać:


var mailReg = /^[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 mailReg = /^[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 mailReg = /^[0-9a-z_.-]+@[0-9a-z.-]+\.[a-z]{2,3}$/i
if (mailReg.test(mail)) console.log('Mail OK')

Atomy i referencja powrotna:

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 reg = 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 text = "http://www.webreference.com/js/index.php#piosenka?l=2";
var reg = text.match(/(\w+:\/\/)([^/]+)([^#?]*)([^?]*)\?*(.+)*/);

console.log('Badany ciąg: ' + text);
console.log("Wyrażenie z 1 nawiasu: "+ RegExp.$1);
console.log("Wyrażenie z 2 nawiasu: "+ RegExp.$2);
console.log("Wyrażenie z 3 nawiasu: "+ RegExp.$3);
console.log("Wyrażenie z 4 nawiasu: "+ RegExp.$4);
console.log("Wyrażenie 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 reg = /(Fantomas)\1/;
text.test(reg);

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 text = "2010-10-20";
var reg = /(\d{4})-(\d{2})-(\d{2})/;
var textReplaced = text.replace(reg, "$3 $2 $1"); //20-10-2010

Jeżeli chcemy by nasz Atom nie tworzył wstecznej referencji, wtedy musimy rozpocząć jego treść od "?:"


var reg = /(\d{4})-(?:\d{2})-(\d{2})/;
var zamieniony = tekst.replace(reg, "$2 $1"); //20-10-2010

Referencja powrotna

Jak widzieliśmy w poprzednim przykładzie, kolejne atomy są kolejno numerowane. Ich treść jest przechowywana do późniejszej obróbki i zwie się referencją powrotną.

Podział tekstu na słowa przy pomocy metody split()

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 text = "To jest zdanie do podzielenia na pojedyncze słowa"
var textSplit = s.split(/\W+/); //dzielimy zdanie na słowa (wedle sekwencji nie literowych)
if (a) {
    for (var x=0; x<textSplit.length; x++) {
        console.log(textSplit[x]+"<BR>");
    }
}

Usuwanie tagów HTML z tekstu

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 reg = new RegExp('<[a-zA-Z/]{1,15}.*?>','g');
    return str.replace(reg, '');
}

Powyższą funkcję możemy napisać nieco prościej:


function stripTags(str) {
    var reg = /<(?:.|\s)*?>/g;
    return str.replace(reg, '');
}