Wstęp

Wyrażenie regularne (ang. skrót regexp lub regex) jest ciągiem znaków za pomocą którego można opisać inne ciągi znaków lub grupy ciągów wg określonych zasad. Wyrażenia te są używane przez różnego rodzaju edytory tekstu, aby przeszukiwać interesujący nas fragment tekstu w celu znalezienia różnych wzorców - pasujące wzorce mogą zostać zastąpione innymi ciągami znaków lub usunięte z tekstu.

Wyrażenie regularne wywodzą się z teorii automatów i języka formalnego (obie są częścią teoretycznej nauki o komputerze). Te dziedziny zajmują się modelami (of computation) i sposobami na klasyfikowanie języków formalnych. Język formalny jest niczym innym jak zestawem ciągów znaków. W latach 40 XX w. Warren McCulloch i Walter Pitts opisali system nerwowy poprzez przedstawienie neuronów jako (automata). Później matematyk Stephen Kleene opisał te modele używając matematycznego zapisu zwanego zbiorami regularnymi. Ken Thompson przeniósł ten zapis do edytora qed a następnie do edytora Unixowego ed i na końcu do grep'a. Od tego czasu wyrażenia regularne były szeroko stosowane w Unixie i jego narzędziach takich jak: expr, awk, Emacs, vin, lex i Perl. Większość narzędzi do wprowadzenia biblioteki regex zostało napisanych przez Henrego Spencera.

PHP4 dostarcza 2 typy funkcji które obsługują wyrażenia regularne:

- ereg* - obsługujące rozszerzone wyrażenia regularne ( Extended Regular Expressions )

- preg_* - obsługujące wyrażenia regularne znane z języka PERL, określane też mianem Basic Regular Expressions lub PCRE czyli Perl Compatible Regular Expressions

W tym artykule zajmiemy się drugim typem wyrażeń, ze względu na to że są one dużo powszechniej stosowane (poza php'em jeszcze np w JavaScriptcie) a i czas ich wykonywania jest krótszy.

Spośród funkcji obsługujących interesujące nas wyrażenia regularne mamy do wyboru:

- preg_match - która zwraca true w przypadku znalezienia odpowiadającego ciągu znaków i false jeżeli dany wzorzec nie został odnaleziony w przeszukiwanym ciągu znaków

- preg_replace - która to zajmuje sie wyszukiwaniem i zastępowaniem danego wyszukiwanego wzorca, w zadanym ciągu znaków

- preg_match_all - ta funkcja przepisuje wszystkie pasujące do wzorca ciągi do tablicy z której to później możemy to odczytać

- preg_split - która umożliwia rozbicie ciągu znaków na tablice wg zadanego wzorca

Są jeszcze preg_quote i preg_replace_callback jednak nimi zajmiemy się dopiero w późniejszej części tego artykułu.

Prosty wzorzec

W przypadku tego rodzaju wyrażeń regularnych wzorzec musi być zawarty pomiędzy znakami które wskażą początek i koniec wyrażenia. Znaki te mogą być dowolne byleby tylko było to samo na początku i na końcu.

Najprostszym wzorcem wg którego można przeszukiwać ciąg znaków jest poszukiwanie jednej literki.

preg_match('/a/', $tekst);

zwróci nam true jeżeli w tekście znajduje się przynajmniej jedno a np.

$tekst = 'Marek Bedkowski'; //true $tekst = 'a'; //true $tekst = 'ala ma kota'; //true //brakuje chocby jednego a wiec zwraca false $tekst = 'Nic z tego';

Możemy także sobie rozszerzyć to wyrażenie i napisać wyrazy oraz ich części, ale mam nadzieje, że użytkownicy już rozumieją jak to można pisać

Jak widać w powyższym kodzie do poprawnego zapisu tego wzorca wybrałem sobie ukośnik (jakoś słowo slash nie przechodzi mi przez usta) jako znak rozpoczęcia i zakończenia wzorca, lecz może to być równie dobrze coś innego, np. !. Wtedy powyższy kod wyglądałby tak:

preg_match('!a!' $tekst);

Trzeba jednak pamiętać, żeby zastosować "uniki" dla naszego znaczka otwarcia i zamknięcia, bo inaczej nasze wyrażenie się wysypie, a php wywali terror. Jeżeli chcielibyśmy poszukać wykrzyknika to trzebaby to zrobić w ten sposób.

preg_match('!\!!' $tekst);

Uwaga:

W przypadku przeszukiwania tylko zadanych ciągów tak jak w powyższym przykładzie zalecane jest korzystanie z funkcji operujących na "czystych" ciągach znaków czyli: str_replace, strstr, strpos. W tym wypadku żeby sprawdzić czy jest w tekście a, można użyć takiego zapisu:

if( strpos($tekst,'a') !== false ) { echo 'Znaleziono a w stringu'; }

Poza tym, że można szukać danego ciągu znaków można jeszcze zdecydować czy wzorzec ma wystąpić na początku czy na końcu przeszukiwanego ciągu znaków.

Wzorzec na początku ciągu znaków

Aby zaznaczyć że szukany wzorzec ma wystąpić na początku przeszukiwanego ciągu należy go poprzedzić znakiem ^


czyli:
preg_match('/^Marek/', $tekst);

zwróci nam true jeżeli na przeszukiwany ciąg znaków będzie się zaczynał od wyrazu Marek (wielkość liter ma znaczenie, jeżeli chcesz wiedzieć co zrobić aby nie miała kliknij tutaj))

$tekst = 'Marek Bedkowski'; //true $tekst = 'Marek'; //true //false - wyraz Marek jest na koncu $tekst = 'to jest Marek'; //false - mala litera $tekst = 'marek to moj kolega';

Wzorzec na końcu ciągu znaków

Aby zaznaczyć natomiast, że wzorzec ma wystąpić na końcu przeszukiwanego ciągu znaków, musimy go zakończyć znakiem $


>czyli
preg_match('/ki$/', $tekst);

zawróci nam true jeżeli przeszukiwany ciąg znaków będzie zakończony na ki

$tekst = 'Marek Bedkowski'; //true $tekst = 'laski'; //true //false (duza litera) $tekst = 'super lasKi';

Może Wam się to teraz wydać trochę niepotrzebne, ale czasem żeby znaleźć wyraz który nas dokładnie interesuje należy go objąć oboma znakami, czyli

preg_match('/^kot$/', $tekst);

zwróci true tylko i wyłącznie wtedy jeżeli przeszukiwanym ciągiem znaków będzie wyraz kot .

Operatory + * (szukanie powtórzeń)

Wyrażenia regularne oferują też wyszukiwanie powtarzających się znaków służą do tego operatory + oraz * różnią się tym, że plus (+) szuka powtarzającego się wzorca, ale musi on wystąpić przynajmniej raz w przeszukiwanym ciągu znaków, natomiast jeżeli zastosujemy gwiazdkę (*), wystąpienie wzorca nie jest wygmagane, np.

preg_match('/ma*la skala/', $tekst);

W tym momencie funkcja zwróci true nawet jeżeli pomiędzy literami m i l litera a nie wystąpi ani razu

$tekst = 'mla skala'; //true $tekst = 'mala skala'; //true $tekst = 'maaaaaaaaala skala'; //true

Natomiast jeżeli będzie

preg_match('/ma+la skala/', $tekst);

to otrzymamy true tylko i wyłącznie wtedy jeżeli litera a pojawi się przynajmniej jeden raz pomiędzy literami m i l

$tekst = 'mla skala'; //false $tekst = 'maaaaala skala/'; //true

Warto w tym momencie wspomnieć, w jaki sposób wyszukać w ciągu znaków wzorzec składający się z m.in. z plusa, albo gwiazdki. Otóż jak się zapewne część z Was domyśla trzeba zastosować znak unikowy czyli \ (podobnie jak z cudzysłowami w php :))

Chcemy sobie znaleźć w ciągu znaków działanie matematyczne 2+3, wyrażenie będzie wyglądało następująco:

preg_match('/2\+3/', $tekst);

Zapis

preg_match('/2+3/', $tekst)

oznacza: szukaj powtarzającej się przynajmniej raz dwójki po której (których) nastąpi 3 (Zauważcie, że MUSI wystąpić przynajmniej jedna dwójka aby funkcja zwróciła true).

Pobobnie z mnożeniem (jeżeli znakiem mnożenia jest gwiazdka):

preg_match('/2\*3/', $tekst);

Natomiast w tym przypadku zapis:

preg_match('/2*3/', $tekst);

oznacza: szukaj trójki która może być poprzedzona jakimiś dwójkami (jej wystąpienie nie jest wymagane).

Zastąpienie dowolnego znaku (. - kropka)

Kolejnym elementem wyrażeń regularnych z którym chciałbym Was zapoznać jest . (tak to jest kropka :)). Otóż ten znaczek może nam zastąpić dowolny znak w tekście, więc można zrobić coś takiego:

preg_match('/Marek B.dkowski/', $tekst);

Jako, że moje nazwisko zawiera literkę ę, która nie wszędzie jest akceptowana i czasem zamiast niej pojawiają się różne inne dziwne znaki, to mogę sobie wstawić kropkę i wtedy będzie akceptowany dowolny znak na tym miejscu (włączając cyfry).

Grupowanie w nawiasy okrągłe (odwoływanie sie do zmiennej)

Wszystkie użyte wzorce, w wyrażeniu regularnym można zgrupować razem używając nawiasów okrągłych dzięki czemu będziemy mogli się odwołać do każdego nawiasu jak do zmiennej. Nawiasy są numerowane w kolejności ich wystąpienia we wzorcu, gdzie 0 oznacza cały wzorzec a kolejne cyfry kolejne nawiasy.

echo preg_replace('/ma(rek) b/', '$1', 'marek bed');

Po odpaleniu tego kodu zobaczycie w przeglądarce napis: reked . Czyli całe wyrażenie zostało zastąpione tym co się znajduje w nawiasię.

Aby zapobiec zapisywaniu zawartości nawiasu do zmiennej, trzeba poprzedzić wzorzec wstawiając ?:

echo preg_replace('/ma(?:rek) b/', '$1', 'marek bed');

W tym momencie naszym oczom ukaże się na ekranie napis ed bo nawias został pominięty, więc funkcja zastąpiła szukany wzorzec pustym ciągiem.

Teraz drugi przykład na grupowanie: w tekście mamy błędne znaczniki <A>. Otóż błędy polegają na tym, że atrybut href pozostaje pusty, a wartość znacznika, czyli to co widzą użytkownicy jako klikalne łącze, nadal jest widoczna w wyniku czego wszystkie adresy prowadzą do naszej strony!. Przykładowy błędny znacznik może wyglądać tak:

<A href="">www.semestr.pl</A>

Możnaby zrobić tak, że pozbywamy się wszystkich znaczników przez strip_tags, później szukamy części tekstu podejrzanych o adresy www (takich reguł jest mnóstwo w necie) i je zastępujemy. No tak wszystko pięknie i cacy, ale problem się pojawia jeżeli mamy w tekście wstawione swoje linki, które na dodatek maja zdefiniowany inny styl (bo np. chcemy żeby znacznik do strony sponsora się wyróżniał), no to wtedy zostanie on też usunięty.

Wtedy trzeba kombinować :>

robimy tak

$tekst ='costam bla bla <a href="">www.semestr.pl</a> costam blalbl '; echo preg_replace( '/<a href="">(.*)><\/a>/', '<a href="http://$1">$1</a>', $tekst );

Jak widać teraz nasz link pięknie wskazuje tam gdzie trzeba, pamiętajcie tylko żeby użyć uniku na / gdyż jest to znak otwierający i zamykający wzorzec i jako taki nie może być używany bezpośrednio w samych wyrażeniach, jeżeli chcecie użyć samego uniku czyli \ to trzeba go wpisać aż 4 razy :))

Klasy (nawiasy [])

Teraz przejdziemy do nawiasów kwadratowych w wyrażeniach regularnych, otóż zawarte w nich znaki nazywane są klasami. Mamy następujące klasy:

- [0-9] dla cyfr

- [a-z] dla małych liter

- [A-Z] dla dużych liter

Jednakże wcale nie musimy definiować tych klas ręcznie, można skorzystać już z gotowych klas dostarczanych przez mechanizm wyrażeń regularnych:

- \s spacja, znak nowej linii (powrotu karetki), znak tabulacji

- \d cyfry [0-9]

- \D wszystko oprócz cyfr

- \w wszystkie litery, cyfry i podkreślenie [0-9A-Za-z_]

- \W dowolne znaki niebędące cyframi, literami, bądź podkreśleniem

I teraz możemy lekko przekształcić naszą regułę na poszukiwanie błędnych linków, tak żeby sprawdzała za nas czy w adresie jest www. na początku i ewentualne dodawanie w przypadku braku. Będzie nam do tego potrzebny jeszcze znak zapytania ? który dodany na końcu danego wzorca określa że jest on opcjonalny, ale do rzeczy:

$tekst ='costam bla bla <a href="">www.semestr.pl</a> costam '; echo preg_replace( '/<a href="">(http:\/\/)?(www\.)?([\w.]+)<\/a>/', '<a href="http://$2$3">$2$3</a>', $tekst );

W tym przykładzie warto zwrócić uwagę na to że raz "unikamy" kropki a innym razem nie. Otóż jeżeli używamy kropki wewnątrz klasy (czyli nawiasów kwadratowych) to nie jest ona w tym momencie zastąpieniem dowolnego znaku.

Pewnie część z Was powie, że to niepotrzebne, ale chciałem jeszcze wspomnieć o znakach nowej linii, tabulacji, które w wyrażeniach regularnych przydają się nader często:

- \n - nowa linia

- \r - tabulacja

Ograniczenie ilości powtórzeń (nawiasy klamrowe {})

Jeżeli chcemy żeby dany znak powtarzał się odpowiednia ilość razy możemy użyć nawiasów klamrowych. Są dwa sposoby, aby to zrobić.

[znak]{liczba_powtorzeń}

np. możemy to zastosować do powyższego wyrażenia

echo preg_replace( '/<a href="">(http:\/\/)?(w{3}\.)?([\w.]+)<\/a>/', '<a href="http://$2$3">www.$3<\/a>', $tekst );

Można też w klamrach podać 2 wartości

[znak]{minimalna_liczba_powtórzeń, maksymalna_liczba_powtórzeń}

np. z tego można skorzystać kiedy chcemy napisać wzorzec dla adresu e-mail, gdzie na końcu może wystąpić końcówka domeny, czyli litera 2 lub 3 razy:

[\w]{2,3}

Alternatywa we wzorcu (znak |)

Ostatnim już chyba elementem który nam będzie potrzebny jest znak | (pionowa kreska), który oznacza alternatywę.

preg_match('/abc|def/', $tekst); $tekst = 'abc costam def'; //true $tekst = 'abc'; //true $tekst = 'ab c def'; //true

Jak widać wystarczy nam jeden z tych ciągów liter w przeszukiwanym ciągu aby było ok.

Modyfikatory

Zostały nam jeszcze modyfikatory wyrażeń, czyli to co następuje po zamykającym znaku wyrażenia

/[\w]/ i - powoduje że w przeszukiwanym ciągu znaków wielkość liter nie ma znaczenia (case i nsensitive)

/(.*?)/ s - zastosowanie tego modyfikatora powoduje, że przeszukiwany ciąg znaków jest traktowany tak jakby był pojedynczą linią (bez tego po napotkaniu pierwszego znaku nawet linii wyrażenie zakończy prace).