PHP nabízí řadu funkcí používajících regulární výrazy pro práci s textem. Ve článku ukážeme jejich základní použití pro kontrolu formátu textového řetězce.
Na úvod nejprve připomeňme, že PHP nabízí funkce pracující se dvěma typy regulárních výrazů: výrazy standardu POSIX-extended a výrazy kompatibilní s Perlem. Funkce standardu POSIX byly ve verzi PHP 5.3.0 označeny jako zastaralé a vývojářům je doporučováno používat pouze funkce Perlovské. My se však přesto zaměříme na standard POSIX, protože je o něco jednodušší a tedy vhodnější pro výklad. Přechod na Perlovské funkce potom není nijak obtížný a budeme se jím zabývat v příštím článku.
Regulární výraz je formálně zapsaný vzor, se kterým se porovnává testovaný řetězec. Testovaný řetězec může a nemusí vzoru vyhovovat a jednomu vzoru může vyhovovat i více řetězců. Jednoduchým příkladem použití může být testování uživatelského vstupu. Řekněme, že zpracováváme HTML formulář, do kterého měl uživatel zadat datum a případně i čas. Vhodně napsaným regulárním výrazem snadno otestujeme, zda je řetězec vyplněný uživatelem v požadovaném formátu. Pojďme se tedy podívat, jak takový regulární výraz vypadá.
Syntaxe regulárního výrazu
Regulární výraz je řetězec se specifickou syntaxí. Většinu znaků (např. písmena a číslice) chápeme jako obyčejné znaky. Některé znaky mají ale zvláštní význam – tzv. metaznaky. Všechny metaznaky postupně probereme v následujících odstavcích. Pokud bychom potřebovali zapsat nějaký metaznak tak, aby byl brán jako obyčejný, přidáme před něj zpětné lomítko. Například znak tečka '.'
zastupuje v regulárním výrazu libovolný symbol. Pokud bychom chtěli použít tečku jako obyčejný znak, musíme ji zapsat jako '\.'
.
Zde je vhodné ještě připomenout pravidla pro zápis řetězců v PHP. Nezapomeňme, že při zápisu řetězce do uvozovek probíhá nahrazování některých sekvencí. Např. "\n"
je nahrazen za zalomení řádku. Lepší je proto psát regulární výrazy do apostrofů, kde musíme dávat pozor jen na znak apostrofu a zpětného lomítka.
Základy
Nejjednodušší regulární výraz se skládá pouze z obyčejných znaků. Takovému výrazu odpovídají všechny řetězce, které mají daný výraz jako podřetězec. Mějme na příklad výraz 'Pepa'
. Řetězec 'Náš Pepa rozumí regulárním výrazům.'
odpovídá tomuto výrazu, avšak 'Pepo, pojď mi vysvětlit PHP!'
už mu neodpovídá.
Metaznaky ^
a $
zastupují začátek resp. konec řetězce. Jejich použitím upřesňujeme, že se má daný výraz nacházet pouze na začátku, na konci, případně odpovídat celému řetězci.
Reg. výraz | Odpovídající řetězce |
'^Ahoj' |
řetězce, které začínají na Ahoj |
'Nashledanou$' |
řetězce, které končí na Nashledanou |
'^hroch$' |
pouze řetězec obsahující právě slovo hroch bez dalších znaků |
Žolík tečka
V některých případech ovšem nechceme specifikovat daný podřetězec přesně. Některé znaky chceme ponechat libovolné, případně je omezit na znaky z určité množiny. K tomu nám slouží dva nástroje. Prvním z nich je zástupný symbol .
(tečka). Tečka slouží jako žolík, kterému vyhovuje jeden libovolný znak.
Reg. výraz | Odpovídající řetězce |
'.ůl' |
řetězce, obsahující slova jako kůl, půl, sůl, fůl apod. |
'^A...$' |
všechny čtyřpísmenné řetězce začínající na A |
Výčtové množiny
Pokud potřebujeme výběr písmene na daném místě omezit pouze na několik možností, můžeme místo tečky použít množinu znaků. Množina znaků je uzavřena do hranatých závorek []
. Uvnitř těchto závorek je výčet všech znaků, které se na daném místě mohou vyskytovat. Speciálně je také možné definovat rozsahy znaků v ASCII tabulce ve formátu znak-znak, tedy např. 0-9
odpovídá všem číslicím nebo a-z
všem malým písmenům anglické abecedy. Pokud je prvním znakem v množině stříška ^
, bere se tento znak jako metaznak (není součástí množiny) a celá množina bude chápána jako doplněk ke specifikovaným znakům. Jinými slovy takové množině budou vyhovovat všechny znaky, které nejsou ve výčtu.
Reg. výraz | Odpovídající řetězce |
'[hks]ůl' |
řetězce obsahující hůl, kůl nebo sůl, ale už ne např. půl ani stůl |
'^[a-zA-Z]' |
řetězce začínající písmenem anglické abecedy |
'[^0-9]' |
řetězce obsahující alespoň jeden znak, který není číslice |
Nyní vás jistě napadlo, co dělat, když bychom chtěli vložit pomlčku nebo zavírací hranatou závorku do výčtu. Klasická pravidla zde totiž neplatí a metaznaky (jako např. zpětné lomítko) se uvnitř množiny chápou jako obyčejné znaky. Pro tyto případy platí následující pravidla:
- Hranatá závorka
]
je brána jako obyčejný znak, pokud se nachází ve výčtu jako první (případně těsně za stříškou^
, pokud používáme doplněk množiny). - Pomlčka je brána jako obyčejný znak, pokud je první nebo poslední ve výčtu.
- Stříška
^
je brána jako obyčejný znak, pokud není první ve výčtu.
Pro větší pohodlí zápisu často používaných kombinací existují třídy znaků — předdefinované seznamy, které snadno vložíme do výčtu. Zapisují se do hranatých závorek s dvojtečkou [:třída:]
. Identifikátory existujících tříd a jejich význam je uveden níže.
Identifikátor | Význam |
alnum |
alfanumerické znaky (kombinace alpha a digit ) |
alpha |
písmena včetně znaků národních abeced (v závislosti na nastaveném locale) |
blank |
bílé znaky (mezera a tabulátor) |
cntrl |
speciální systémové řídicí znaky |
digit |
číslice |
graph |
všechny tisknutelné znaky kromě mezery |
lower |
malá písmena |
print |
všechny tisknutelné znaky včetně mezery |
punct |
všechny tisknutelné znaky kromě mezery a alfanumerických znaků (tečky, čárky, závorky, …) |
space |
jakékoli prázdné znaky (mezera, tabulátor, zalomení řádku, …) |
upper |
velká písmena |
xdigit |
číslice šestnáctkové soustavy (tedy včetně písmen a-f resp. A-F) |
V jedné množině může být použito více tříd. Např. '[[:alpha:][:blank:]]'
odpovídá libovolnému písmenu nebo bílému znaku.
Větvení
Regulární výrazy by byly celkem k ničemu, kdybychom nemohli v jednom výrazu vyjádřit více možností. Jednotlivé možnosti (obvykle nazývané též větve) se oddělují znakem svislítko '|'
. Testovaný řetězec pak vyhovuje výrazu, pokud vyhovuje některé jeho větvi (větve jsou spojeny logickým nebo). Pomocí kulatých závorek ()
můžeme vytvářet větve pouze z částí výrazů, případně je různě kombinovat.
Reg. výraz | Odpovídající řetězce |
'Ahoj|Nazdar|Čau' |
libovolný řetězec, který obsahuje alespoň jeden ze zmíněných pozdravů |
'(Ná|vý|zá)chod' |
řetězce, obsahující Náchod, východ nebo záchod |
Opakující se podvýrazy
Libovolné písmeno, zástupný symbol '.'
, množinu []
nebo podvýraz v kulatých závorkách můžeme nechat „opakovat“. Přesněji řečeno, povolíme více opakovaných výskytů za sebou. Počty těchto výskytů napíšeme číslem do složených závorek za dané písmeno nebo podvýraz, tedy {3}
značí právě tři opakování. Volitelně můžeme definovat také rozsah možných opakování — např. {2,4}
odpovídá dvěma, třem nebo čtyřem opakováním. Pokud vynecháme druhé číslo (a použijeme čárku), automaticky se za něj dosadí nekonečno, takže rozsah {5,}
vyjadřuje nejméně pět, ale jinak libovolný počet opakování.
Symbol | Popis |
? |
je ekvivalentní s {0,1} , tedy volitelná část, která se buď nevyskytuje vůbec, nebo právě jednou |
* |
je ekvivalentní s {0,} , tedy zcela libovolný počet opakování (včetně možnosti úplného vypuštění) |
+ |
je ekvivalentní s {1,} , tedy libovolný počet opakování, avšak bez možnosti vypuštění |
Pro lepší pochopení ještě uveďme jednoduchý příklad. Následující regulární výraz testuje, zda je daný řetězec datem zapsaným podle českých zvyklostí. Ve výrazu kontrolujeme pochopitelně pouze platnost znaků, nikoli zda je zapsané datum platné (zda odpovídají počty dnů v měsíci apod.).
'^([0-9]{1,2}\.){2}([0-9]{4})?$'
V rámci opakování připomeňme na tomto výrazu ještě některé věci. Kulaté závorky slouží pouze k oddělení částí podvýrazu. V našem případě proto, abychom za ně mohli připojit počet opakování (do složených závorek). Dále si všimněme, že před tečkou máme lomítko, protože potřebujeme skutečný znak tečky, nikoli žolíka, který zastupuje libovolný znak.
Použití
Nyní, když jsme probrali základy syntaxe regulárních výrazů, můžeme se podívat na jejich použití v PHP. Základem jsou funkce ereg()
a eregi()
, které testují, zda daný řetězec odpovídá předepsanému regulárnímu výrazu. Obě funkce se liší pouze tím, že eregi()
není case-sensitive. Ve většině případů je ale lepší používat funkci ereg()
a otázku malých/velkých písmen řešit svépomocí v regulárním výrazu.
int ereg(string $pattern string $str, [array $regs])
Řetězec $pattern
obsahuje regulární výraz, $str
je testovaný řetězec. Pole $regs
je volitelné a jeho použití ukážeme v příštím díle. Funkce vrací délku podřetězce, který odpovídá výrazu, nebo false
, pokud řetězec výrazu nevyhovuje. Na závěr ještě ukažme dva praktické příklady.
V prvním příkladu trochu rozšíříme regulární výraz uvedený dříve pro testování zadaného data. Jednak budeme chtít trochu zpřísnit podmínky (aby nebylo možné zadat datum jako 42.15.2010) a také přidáme volitelně možnost za datum uvést ještě čas. Časový údaj mohou být pouze hodiny (např. 14h), nebo hodiny a minuty oddělené dvojtečkou.
'^[1-3]?[0-9]\.([1-9]|1[0-2])\.([0-9]{4})?( [1-2]?[0-9](h|:[0-5][0-9]))?$'
Pravidla pro jednotlivé dny nebo hodiny by šla samozřejmě ještě zpřísnit, avšak pro tento příklad není nutné situaci dále komplikovat. V druhém příkladu si ukážeme jednoduchý způsob jak ověřit, zda je řetězec e-mailovou adresou.
'^[-a-zA-Z0-9._+%]+@[-a-zA-Z0-9.]+\.[a-zA-Z]{2,4}$'
Pochopitelně i tento příklad je silně zjednodušený. Skutečná pravidla pro formát e-mailové adresy jsou poněkud složitější (definuje je RFC 2822), takže i regulární výraz, který by je správně otestoval je dlouhý.
V příštím díle si ukážeme, jak pomocí regulárních výrazů také vybírat podřetězce, provádět nahrazování nebo rozdělit řetězec na tokeny.
Mohlo by vás také zajímat
-
Netcat a Ncat
8. prosince 2022 -
Souboj na trhu s CPU pro servery: AMD vs. Intel
8. prosince 2023 -
AI v programování: Jak používat GitHub Copilot (část 1)
12. února 2024
Nejnovější
-
Výkonný a kompaktní: ASOME Max Studio s výjimečným poměrem cena/výkon
11. listopadu 2024 -
Šokující data od Microsoftu: Kyberútoky rostou o stovky procent!
8. listopadu 2024 -
Chcete jedinečnou doménu? Objevte koncovky FOOD, MEME a MUSIC!
7. listopadu 2024 -
OpenAI představilo novou funkci ChatGPT Search
6. listopadu 2024
Pavel
Čvc 15, 2010 v 7:05Můžu se zeptat, proč začátečníkům doporučujete funkci ereg, která byla označena jako deprecated?
Ivo Toman
Čvc 15, 2010 v 8:31POSIX je jednodušší na naučení, ale myslím také že měl rovnou začít s PERL
Miroslav Kučera
Čvc 15, 2010 v 9:12Zdravim, tohle je jenom dvoudilny clanek. Perl-compatible vyrazy budou hned v tom druhem dilu.
tiso
Čvc 15, 2010 v 10:56„My se však přesto zaměříme na standard POSIX, protože je o něco jednodušší a tedy vhodnější pro výklad.“
A dvojdielny seriál o tvorbe layoutu začne tabuľkovým layoutom?
Miroslav Pecka
Čvc 15, 2010 v 12:08Dovolím si zmínit, že devitidílný seriál o Perlovských RE v PHP uz na Intervalu je:-)
1. díl http://interval.cz/clanky/perl-compatible-regularni-vyrazy-v-php-zakladni-konstrukce/
Po přechodu Intervalu na WordPress bohužel (jestli se nepletu) nelze zobrazit články, které patří do jednoho seriálu – proto kompletní seznam dílů viz
http://www.regularnivyrazy.info/serial-php-pcre-perl-compatible.html
Miroslav Kučera
Čvc 15, 2010 v 12:18Jsem rad, ze se pane Pecka ozyvate, protoze jsem vam pred cca rokem asi dvakrat psal ohledne opravy odkazu k temto pet let starym clankum na vasem webu http://www.regularnivyrazy.info, bohuzel, absolutne bez jakekoliv odezvy ;-)
Goofy
Čvc 15, 2010 v 13:06funkce ereg/i je deprecated, ten článek jste asi vyhrabali z nějakého archivu že ? :)
Majkl578
Čvc 15, 2010 v 19:37To snad ani nemyslíte vážně, učit někoho jak pracovat s deprecated rozšířením.
Jednodušší na naučení? To jsou lži, vždyť na modifikátorech není nic složitého…
Prosím smazat a neplést začátečníkům hlavu, děkuji.
J.D.
Čvc 16, 2010 v 14:57„POSIX je jednodušší na naučení“ a aj napriek tomu tam máte chybu:
‚[^0-9]‘ řetězce, které neobsahují číslice
Je to skutočne pravda? Nie sú to náhodou reťazce ktoré obsahujú aspoň jeden znak iný ako číslice 0-9?
Martin Kruliš
Čvc 16, 2010 v 21:33Děkuji za připomínku, máte pravdu. Myslel jsem to jako „řetězce, které neobsahují pouze číslice“, což není úplně šťastná formulace. Každopádně už jsem to opravil.
Marek
Čvc 25, 2010 v 21:19co ten příklad na datum, to jsem nepochopil podle mě projde i „39.1.0000 29h“
Martin Kruliš
Čvc 27, 2010 v 10:21To ano, ale jedná se pouze o jednoduchý příklad použití reg. výrazů, který si nekladl za cíl pokrýt všechny detaily zadávání datumu.
urso
Led 6, 2011 v 13:59V PHP 5.3 je ereg:
This function has been DEPRECATED as of PHP 5.3.0. Relying on this feature is highly discouraged.