Wyrażenia regularne
Jedyną rzeczą za którą kocham perla (pomimo że nic o nim nie wiem) to wyrażenia, które są po prostu kapitalne. Oczywiście php jest lepsze i daje nam wybór bo obsługuje aż dwa rodzaje wyrażeń regularnych POSIX oraz PCRE. :) Te drugie są znacznie bardziej rozbudowane i wygodniejsze w obsłudze. PCRE to skrót od Perl Compatible Regular Expressions (to znaczy - wyrażenia regularne w stylu perl).
Funkcje, które korzystają z biblioteki PCRE zaczynają się od przedrostka 'preg_'. Do tej elitarnej grupy należą funkcje:
preg_match
preg_match_all
preg_replace
preg_replace_callback
preg_quote
preg_split
preg_grep
Myślę, że po przeczytaniu tego tekstu będziecie mogli z powodzeniem ich używać.
Funkcja preg_match przeszukuje podmiot do pierwszego dopasowanego wyrażenia. Jeśli coś znajdzie przestaje działać.
int preg_match( string wzór, string podmiot [, array wyniki [, int flagi]] )
Wzór musi być otoczony przy pomocy tzw. ograniczników, co umożliwia ustawianie modyfikatorów (o nich dalej). Przykładowy wzór może mieć taką postać: '/wzór/'. Funkcja preg_match zwróci prawdę jeśli w tekście znajdzie słowo 'wzór'. Jednak do czegoś takiego wystarczy użycie funkcji strstr lub strpos. Ogromną zaletą funkcji jest tworzenie tablic. Gdy funkcja już znajdzie nasze słowo będzie mogła umieścić je w tablicy. Fakt, że przy takim wzorze na nic to się nie zda, ale nie zawsze dokładnie określamy, to co szukamy.
Kod:
<?php
$liczba = 'To jest liczba 194';
preg_match( '/\d+/', $liczba, $wynik );
var_dump( $wynik );
?>
Wynik:
array
0 => '194'
Drugą funkcją jest preg_match_all. Różni się ona od poprzedniczki tym, że zwraca wszystko, co pasuje do wzoru.
int preg_match( string wzór, string podmiot, array wyniki [, int flagi] )
Funkcja szuka wszystkich pasujących do wzoru wyrażeń. Przeszukuje podany ciąg aż do jego skończenia. Przy podobnym wzorze jak wcześniej funkcja wyłapie wszystkie liczby.
Kod:
<?php
$liczba = 'To jest liczba 194, to jest liczba 10, to jest liczba 1025516';
preg_match_all( '/\d+/', $liczba, $wynik );
var_dump( $wynik );
?>
Wynik:
array
0 =>
array
0 => '194'
1 => '10'
2 => '1025516'
Trzecią funkcją z rodziny jest preg_replace
mixed preg_replace( mixed wzor, mixed zmien, string podmiot [, int limit] )
Funkcja zwraca podmiot po zmianie wszystkich miejsc pasujących do wzoru pierwszego zgodnie z wzorem zmień. Jeśli został określony limit to funkcja podmieni dokładnie tyle razy. Parametr limit może również przyjąć wartość -1. Wtedy zostaną zmienione wszystkie miejsca, które pasują do wzoru (jest to wartość domyślna). Gdy preg_replace nie uda się nic zmienić zwraca niezmieniony podmiot.
Kod:
<?php
$liczba = 'To jest liczba 194, to jest liczba 10, to jest liczba 1025516';
$tekst = preg_replace( '/\d+/', '<b>\\0</b>', $liczba );
var_dump( $tekst );
?>
Wynik:
'To jest liczba 194, to jest liczba 10, to jest liczba 1025516'
Funkcja preg_replace obsługuje również inny sposób pisania wzorów - zamiast '\\0' można napisać '${0}'. Jest to przydatne, gdy nie można zastosować pierwszego formatu. Przy pomocy pierwszego sposobu nie da się uzyskać efektu jaki daje wzór '${0}1'.
Jeśli chcesz możesz również korzystać z tablic. Pamiętaj jednak, że tablice muszą mieć ten sam rozmiar.
Kod:
<?php
$wzor[0] = "/194/";
$wzor[1] = "/10/";
$wzor[2] = "/1025516/";
$zmien[2] = "liczba \\0";
$zmien[1] = "cyfra \\0";
$zmien[0] = "mow mi loonger \\0 ;)";
$liczba = '194, 10, 1025516';
$tekst = preg_replace( $wzor, $zmien, $liczba );
var_dump( $tekst );
?>
Wynik:
'liczba 194, cyfra 10, cyfra mow mi loonger 1025516 ;)'
Dodatkowo tablica druga ($zmien) musi mieć klucze w odwrotnej kolejności, tzn. malejąco.
Czwarta funkcja jest podobna do poprzedniej - preg_replace_callback - przeszukuje podmiot i po znalezieniu przekazuje to, co znalazła do funkcji użytkownika.
mixed preg_replace_callback( mixed wzór, string funkcja_użytkownika , mixed podmiot [, int limit] )
Kod:
<?php
$tekst = "Zmień datę! Data 04/01/2002\n";
$tekst.= "druga data 12/24/2001\n";
function pl_date( $zmien )
{
return $zmien[2].'.'.$zmien[1].'.'.$zmien[3];
}
echo preg_replace_callback( "|(\d{2})/(\d{2})/(\d{4})|", "pl_date", $tekst );
?>
Wynik:
Zmień datę! Data 01.04.2002 druga data 24.12.2001
W ten prosty sposób można zmieniać amerykański format daty na nasz, domowy.
Funkcja piąta to preg_quote. Dodaje ona przed znakami szczególnymi (charakterystycznymi) 'ogranicznik'. Domyślnie ogranicznikiem jest slash '\'.
string preg_quote( string ciąg [, string ogranicznik ] )
Kod:
<?php
$tekst = "to jest link http://www.splatch.desk.pl do mojej strony";
$slowo = "http://www.splatch.desk.pl";
$c = preg_quote( $slowo );
preg_match("|$c|", $tekst , $we );
var_dump( $we );
?>
Wynik:
array
0 => 'http://www.splatch.desk.pl'
Na pierwszy rzut oka wydaje się, że nic łatwiejszego znaleźć w ciągu adres strony. Jest wyraźnie zaznaczone gwiazdkami. Trzeba wiedzieć, że adres url jest napakowany znakami specjalnymi w wyrażeniach regularnych i trzeba je poprzedzać slashem. Równoważnikiem tego, co macie wyżej jest preg_match("/http\://www\.splatch\.desk\.pl/", $tekst , $we ). Można powiedzieć, że dzięki tej funkcji można przekształcić ciąg na wyrażenie regularne. Funkcja preg_quote jest udogodnieniem przy korzystaniu z funkcji preg_replace.
Funkcja szósta to preg_split. Działanie tej funkcji jest zbliżone do explode oraz split z tym, że funkcja dzieli wszystko, co pasuje do wzoru. Przy pomocy preg_split da się zrobić to, czego się nie da przy pomocy explode. :) Funkcja zwraca tablicę. Jeżeli chcesz ustawić jakąś flagę, a nie chcesz określać limitu to w jego miejsce wpisz -1.
array preg_split( string wzór, string ciąg [, int limit [, int flagi]]) )
Kod:
<?php
$ciag = 'ciag testowy';
$znaki = preg_split('//', $ciag, -1, PREG_SPLIT_NO_EMPTY );
var_dump( $znaki );
?>
Wynik
array
0 => 'c'
1 => 'i'
2 => 'a'
3 => 'g'
4 => ' '
5 => 't'
6 => 'e'
7 => 's'
8 => 't'
9 => 'o'
10 => 'w'
11 => 'y'
Dopuszczalne flagi to:
PREG_SPLIT_NO_EMPTY - daje wynik jak wyżej
PREG_SPLIT_DELIM_CAPTURE - tablica ma dwa elementy więcej - pusty na początku i końcu (domyślnie)
PREG_SPLIT_OFFSET_CAPTURE - tablica ma format
array
0 => array
0 => 'c'
1 => '0'
1 => array
0 => 'i'
1 => '1'
...
Drugi klucz w tablicy oznacza pozycję w ciągu wejściowym.
Jeśli chcemy wyciągnąć tylko słowa:
Kod:
<?php
$ciag = 'to jest ciag testowy';
$znaki = preg_split( "/\s/", $ciag );
var_dump( $znaki );
?>
Wynik:
array
0 => 'to'
1 => 'jest'
2 => 'ciag'
3 => 'testowy'
Ostatnią (siódmą) funkcją jest preg_grep. Funkcja zwraca tablicę ze wszystkimi elementami pasującymi do wzoru. Pozostałe nie zostaną w niej umieszczone.
array preg_grep( string wzór, array tablica )
Kod:
<?php
$tablica = array( 20.2, 13.213, 10 );
$tablica_g = preg_grep ("/^(\d+)?\.\d+$/", $tablica );
var_dump( $tablica_g );
var_dump( $tablica );
?>
Wynik:
array
0 => '20.2'
1 => '13.213'
array
0 => 20.2
1 => 13.213
2 => 10
Myślę, że teraz będziecie wiedzieć jakie narzędzie dobrać i do czego. Do odkręcania śruby nie weźmiecie młotka (chyba, że zdolnościami manualnymi dorównujecie absolwentom techników mechanicznych). Być może popełniłem błąd nie omawiając wcześniej budowania wzorów, ale chyba mi wybaczycie?
Każdy wzór musi być ograniczony jakimiś ogranicznikami. Może to być dowolny znak byle nie był znak lub cyfra oraz backslash ('\'). Może to być | / . - * # i podobne lub para {} [] <>.
Drugim ważnym elementem są klasy. Przykładowo
[a-z] mała litera
[A-Z] duża litera
[0-9] cyfra
Aby zaprzeczyć wystąpieniu jakiegoś znaku stosuje się znak potęgi ^
[^a-z] nie mała litera
Istnieją gotowe klasy, które zaoszczędzają wklepywania kodu. Są one poprzedzone znakiem backslash
\d == [0-9]
\D == [^0-9]
\w == [a-zA-Z0-9_] - wszystko co jest literą, cyfrą lub '_'
\W == [^a-zA-Z0-9_]
\s == [\r\t\n\f] - dowolny odstęp
\S == [^\r\t\n\f]
Czasami przydaje się też klasa \b, czyli koniec słowa i jej przeciwieństwo \B. Jeśli chcesz aby wynik znalazł się w tablicy to musisz klasę otoczyć nawiasem np. (\w).
Powtórzenia. Można określić ilość powtórzeń danego znaku. Przykładowo
a{0,1} oznacza od zera do jednego powtórzenia
a{1,2} oznacza od jednego do dwóch powtórzeń
a{1,} oznacza co najmniej jedno powtórzenie
a{1} oznacza dokładnie jedno powtórzenie
Najczęściej używane określenia powtórzeń zostały przypisane wzorcom grupującym. Oto one
* == {0,}
+ == {1,}
? == {0,1}
Ważną rolę we wzorach odgrywa również kropka, ponieważ pasuje ona do wszystkiego, oprócz znaku nowego wiersza ('\n').
Często przydaje się możliwość określania warunków. Wzór, który ma postać '/(\w+|\d+)/' dopasuje się do wszystkich słów lub cyfr. Jeśli coś jest opcjonalne możesz zapisać to na dwa sposoby. Pierwszy to (opcja=(.*?)|) a drugi '/(opcja=(.*?))?/'. Drugi wzór zwróci prawdę, nawet jeśli znajdzie w ciągu wyrazu 'opcja'.
Przy pomocy biblioteki PCRE można też dokładnie przewidzieć, co ma się znaleźć w ciągu, od jego początku do końca. Początek ciągu oznacza się znakiem potęgi '^', a koniec znakiem dolara '$'. Przykładowo mamy wzór '/^start(.*?)koniec$/'. Aby coś do niego pasowało to na jego początku musi się znaleźć słowo start a na końcu koniec. Nieistotne, co jest w środku, byle nie był o w tym znaku '\n'.
Jeśli mamy długi wzór to może się przydać komentarz. Sposób jest prosty - (?#komentarz).
Często zdarza się sytuacja, gdy czegoś nie chcemy w tablicy wynikowej, bo jest nam to zbyteczne, ale nie możemy rozdzielać całości. Przykładowo chcemy sprawdzić poprawność zapisu równania (np x=y) i wyciągnąć z niego same argumenty. Nie interesuje nas znak, jaki występuje pośrodku. Odpowiednim wzorem jest '/((\w+)(?:=|\+|-|\*|\/|^)(\w+)){1}/' Jak widzicie pojawiło się nam cudo ?:. Sprawia ono, że nawias przed którym stoi nie znajdzie się w tablicy wynikowej.
Flagi. Aby ustawić jakiś modyfikator dla wzoru po końcowym ograniczniku powinien znaleźć się znak. Wzór wtedy ma postać
'/(.*?)/s'. Oto niektóre z modyfikatorów:
e możliwość używania funkcji php (tylko preg_replace)
s kropka pasuje do białych znaków
i bez względu na wielkość liter
m znaki $ oraz ^ odnoszą się do końca i początku lini
x znaki białe we wzorze są ignorowane ( ciąg 'lala' pasuje do wzoru '#la la#x'), po prostu nie ma ich, parser ich nie widzi. Umożliwia komentowanie we wzorach.
D umożliwia używanie we wzorze tylko znaku $, bez konieczności używania ^. Ignorowany w parze z modyfikatorem 'm'
Jest jeszcze inny sposób używania modyfikatorów
Początek modyfikatora to (?im). Ustawienie oraz usunięcie to (?im-sx). Samo usunięcie realizuje się przy pomocy (?-im). Wzór wtedy ma nieco inną postać np. wzór '/(?:(?i)niedzielny|(?-i)niedziela)/' pozwala, aby pierwszy wyraz był pisany dowolnie, pasuje do niego 'NiEdZiElNy'. Nie pasuje natomiast 'Niedziela', ponieważ został usunięty modyfikator.
Niestety z moją znajomością angielskiego (a właściwie jej brakiem) nie mogłem sobie poradzić i więcej się dowiedzieć na temat regexp'ów. Mam jednak nadzieję, że to starczy. Na otarcie łez pozostawiam wam kilka wzorów do przeanalizowania i być może poprawienia. Poniższe przykłady testowane na preg_match_all.
Dysk i drzewo katalogów. Można oszukać ten wzór i nie wpisać litery dysku
Kod:
'/(\w+)(?:[:\\]+|[\\]+)*(.*?)[\\]?/'
Ścieżka (unix)
Kod:
'/(?:[\/](\w+)+)/'
Zmienne i wartości zj $_SERVER['QUERY_STRING']
Kod:
'/([\w_]+)=(\w*)[&]?/'
Teraz wzory, które działają najlepiej na preg_match
Adres email
Kod:
'/^([\w\.+-]+)@([a-zA-z\.-]+)\.(\w{2,6})$/'
Adres strony, bez podkatalogów
Kod:
'/(http[s]?):\/\/{1}([\w\.-]+)\.(\w{2,6}){1}/'
Nazwa pliku
Kod:
'/(\w+\.\w{0,10})/'
Komentarz html
Kod:
'//s'
Komentarz php
Kod:
'|\/\*(.*?)\*\/|s'
Komentarz php
Kod:
'/\/\/(.*)/'
No i wiele innych, których ni znam, a które rodzą się w waszych głowach.
Łukasz 'Splatch' Dywicki
splatch@desk.pl
http://splatch.desk.pl