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.

13 Příspěvků v diskuzi

  1. „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?

  2. To 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.

  3. „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?

  4. Dě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.

  5. V PHP 5.3 je ereg:
    This function has been DEPRECATED as of PHP 5.3.0. Relying on this feature is highly discouraged.

Odpovědět