Wyrażenia regularne
Mechanizm wyrażeń regularnych (ang. regular expressions, czasem skracane do regexp) jest tak naprawdę parserem pewnego języka służącego do precyzyjnego definiowania dozwolonego formatu ciągu. Korzystanie z wyrażeń regularnych polega na stworzeniu za jego pomocą tzw. wzorca, a następnie jego porównania odpowiednimi funkcjami z interesującym nas ciągiem. Na wyjściu otrzymujemy informację, czy ciąg pasuje do wzorca, czy też nie.
Wyrażenia regularne mają jeszcze większe możliwości. Dzięki nim wyciągnięcie dowolnych interesujących nas informacji z ciągu nie stanowi kłopotu. Wystarczy, że znamy wzorzec go opisujący, a system wyrażeń zwróci nam dodatkowo tablicę uzyskanych z jego wnętrza danych, których potrzebujemy. Wyrażenia regularne dają nam także dostęp do znacznie bogatszego w możliwości kuzyna funkcji str_replace() z poprzedniego rozdziału. O ile tamta funkcja bezmyślnie zamieniała wszystkie napotkane wystąpienia jakiegoś fragmentu na inny, dzięki wyrażeniom regularnym możemy zdefiniować naprawdę wymyślne mechanizmy zamiany uwzględniające wiele dodatkowych czynników.
Jak widać, wyrażenia regularne to potężne narzędzie, jednak przez to też skomplikowane. Niemniej każdy szanujący się programista powinien znać przynajmniej jego podstawy, ponieważ praktyka zawodowa pokazuje, iż wykorzystywane są one bardzo często.
Pierwszy przykład
Nasze pierwsze spotkanie praktyczne z wyrażeniami regularnymi rozpoczniemy od prostego sprawdzenia, czy wypełnione pole formularza zawiera dokładnie jedną cyfrę. Do porównywania wzorca z ciągiem służy funkcja preg_match(), która zwraca true, jeżeli zachodzi zgodność.<?php
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
if(preg_match('/^[0-9]$/D', $_POST['cyfra']))
{
echo '<p>Wpisałeś cyfrę '.$_POST['cyfra'].'</p>';
}
else
{
echo '<p>Nieprawidłowe dane! Skrypt wymaga podania cyfry!</p>';
}
}
else
{
echo '<form method="post" action="preg1.php">
Podaj cyfrę: <input type="text" name="cyfra"/><input type="submit" value="OK"/>
</form>';
}
?>
- {długość} - dozwolona długość określona jest dokładnie.
- {długość_min,długość_max} - podany jest przedział dozwolonych długości
- {długość_min,} - określona jest minimalna długość
- {,długość_max} - określona jest maksymalna długość
- * - 0 lub więcej
- + - 1 lub więcej
- ? - 0 lub 1 (uwaga: znak ten jest także wykorzystywany w innym kontekście)
Klasy znaków
Nauczymy się teraz bardziej dokładnego definiowania klas znaków, jakich można używać w danym miejscu ciągu. Zasada podstawowa jest bardzo prosta: jeśli w jakimś miejscu napiszemy "a", to parser będzie się tam spodziewać litery "a" występującej dokładnie jeden raz. Jeżeli zastosujemy klasę znaków, definiujemy w ten sposób listę dozwolonych na danej pozycji znaków dokładnie jeden raz. W obu przypadkach "dokładnie jeden raz" można zmienić na dowolną inną długość za pomocą omówionych wyżej kwantyfikatorów. Zatrzymajmy się jednak dokładniej przy tym zwrocie. Skoro dokładnie jeden raz, czemu w takim razie podany wyżej przykład dla wyrażenia /[0-9]/ akceptuje ciągi liczb o dowolnej długości? Aby lepiej pokazać, co naprawdę wtedy ma miejsce, wpisz w formularzu tekst "9a" - o dziwo także i on zostanie przyjęty, mimo że na drugiej pozycji mamy literę! Co jest nie tak? Nic - wyrażenie działa prawidłowo. Parser po prostu osiągnął jego koniec przy sprawdzeniu pierwszego znaku ciągu i resztę przepuścił bez żadnej kontroli. Dlatego istotne jest powiadomienie o tym, gdzie ma znajdować się koniec ciągu.
Tworząc klasę znaków, możemy stosować się do następujących reguł:- Wypisujemy w nawiasach kwadratowych wszystkie dopuszczalne znaki, np. [abcdefgh]
- Wprowadzamy zakres: [a-h] (dopuszczalne małe litery od a do h)
- Wprowadzamy kilka zakresów: [a-hA-H] (dopuszczalne duże i małe litery od a do h i od A do H).
<?php
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
if(preg_match('/^[a-zA-Z0-9\.\-\_]+\@[a-zA-Z0-9\.\-\_]+\.[a-z]{2,4}$/D', $_POST['email']))
{
echo '<p>Wpisałeś e-mail '.$_POST['email'].'</p>';
}
else
{
echo '<p>Nieprawidłowe dane! Skrypt wymaga podania adresu e-mail!</p>';
}
}
else
{
echo '<form method="post" action="preg2.php">
Podaj adres e-mail: <input type="text" name="email"/><input type="submit" value="OK"/>
</form>';
}
?>
- /^[a-zA-Z\.\-\_]+ - początek adresu składa się z dowolnych znaków alfanumerycznych, pauzy, kropki oraz podkreślenia i jego długość musi wynosić minimum 1 znak.
- \@ - później ma być małpa
- [a-zA-Z0-9\.\-\_]+ - analogicznej klasy używamy do zdefiniowania domeny.
- \.[a-z]{2,4}$/ - domena musi kończyć się kropką, po której spodziewamy się domeny nadrzędnej (np. .pl, .com).
- . - kropka symbolizuje dowolny znak (za wyjątkiem przełamania linii).
- \d - dowolna cyfra dziesiętna
- \D - dowolny znak niebędący cyfrą
- \s - biały znak (np. spacja, tabulator)
Grupy
Poszczególne fragmenty ciągu mogą być ze sobą łączone w większe grupy, ujmowane w okrągłych nawiasach. Są one wykorzystywane w dwóch celach. Po pierwsze, można do nich zbiorczo zastosować kwantyfikator, żądając, aby np. jakiś fragment powtarzał się od 3 do 5 razy. Za pomocą grup eksportujemy także do PHP interesujące nas dane. Przykładowo, do wyrażenia /^(abc)+$/ pasują ciągi "abc", "abcabc", "abcabcabc" itd.
Przedstawimy teraz, jak wykorzystać wyrażenia regularne w innych dziedzinach, niż tylko kontrola formularzy. Załóżmy, że zlecono nam zadanie przeprojektowania bazy danych, ponieważ stara nie spełnia stawianych jej wymagań. Oczywiście musimy napisać jakiś konwerter, który przeniesie automatycznie dane do nowej bazy. Natknęliśmy się jednak na problem: daty utworzenia rekordów zapisywane są w postaci tekstowej, np. "12 Dec 2006, 16:34", zamiast w łatwych do przetwarzania sekundach od 1.1970. Do rozbicia ciągu na poszczególne fragmenty wykorzystamy wyrażenia regularne:
<?php
$date = '12 Dec 2006, 16:34';
if(preg_match('/^(\d{1,2}) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4})\, (\d{1,2})\:(\d{1,2})/', $date, $found))
{
// Co nam zwrocilo...
echo '<h3>Dane: "'.$date.'"</h3>';
echo '<p>Dzien: '.$found[1].'</p>';
echo '<p>Miesiac: '.$found[2].'</p>';
echo '<p>Rok: '.$found[3].'</p>';
echo '<p>Godzina: '.$found[4].'</p>';
echo '<p>Minuta: '.$found[5].'</p>';
$monthConverter = array('Jan' => 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4, 'May' => 5,
'Jun' => 6, 'Jul' => 7, 'Aug' => 8, 'Sep' => 9, 'Oct' => 10, 'Nov' => 11, 'Dec' => 12);
echo '<p>Unix timestamp: '.mktime($found[4], $found[5], 0, $monthConverter[$found[2]], $found[1], $found[3]).'</p>';
}
else
{
echo '<p>Nieprawidłowy format daty!</p>';
}
?>
Na podstawie kursu PHP na Wikibooks, licencja GNU Free Documentation License
Comment article