Perl-compatible regulární výrazy v PHP – tvrzení v praxi, podmíněné subvýrazy

3. května 2005

V předchozím článku jsme si vysvětlili tvrzení o následujícím (a tvrzení o předcházejícím) a ukázali si jednoduché příklady. Tentokrát se podíváme na příklad z praxe, který ovšem již tak jednoduchý nebude. Budeme testovat platnost registrační značky (bývalé SPZ). V druhé části článku se pak podíváme na zoubek takzvaným podmíněným subvýrazům.

Vícenásobné testování řetězce

Pro náš příklad je nutné si uvědomit, že tvrzení o následujícím přináší de facto možnost porovnávat (testovat) jeden řetězec s více regulárními výrazy, přičemž řetězec je považován za vyhovující, pokud odpovídá všem regulárním výrazům. Než se pustíme do testování registrační značky, předveďme si výše popsané na jednoduchém příkladu.

Modelový příklad

Řekněme, že chceme zjistit, zda řetězec odpovídá dvěma podmínkám:

  1. jedná se o řetězec o délce 10 znaků složený výhradně z číslic
  2. jedná se o řetězec obsahující (alespoň jednu) číslici 0

Pro první podmínku snadno zkonstruujeme regulární výraz ^[0-9]{10}$, který nepotřebuje komentář. Pro druhou podmínku zkonstruujeme regulární výraz ^.*0.*$. Znak 0 se může vyskytovat, kdekoli v řetězci (od první do poslední pozice). Proto znaku 0 předchází (i za ním následuje) .*, čemuž odpovídá sekvence libovolných znaků o neurčené (i nulové) délce.

Nyní je třeba obě podmínky nějak spojit. Toho dosáhneme právě pomocí kladného tvrzení o následujícím. Jako kladné tvrzení o následujícím použijeme regulární výraz odpovídající druhé podmínce. Celý regulární výraz pak bude (?=^.*0.*$)^[0-9]{10}$. Tvrzení o následujícím zajistí, že se řetězec vpravo od (?=^.*0.*$) zápisu podrobí testu pomocí regulárního výrazu ^.*0.*$.

Vzhledem k tomu, že ukotvení na začátek (^) a konec ($) je obsaženo v hlavní části regulárního výrazu ^[0-9]{10}$, není třeba, aby stejné ukotvení bylo i v tvrzení o následujícím. Když toto ukotvení z tvrzení o následujícím odstraníme, stačí místo .*0.* ponechat jen .*0. Pak sice výraz v tvrzení o následujícím nepokryje celý desetiznakový řetězec, nám ale stačí, aby tvrzení o následujícím potvrdilo, že se vpravo od tvrzení (které je na začátku) nachází znak 0. Pak bude celý regulární výraz vypadat jako ^(?=.*0)[0-9]{10}$ (tvrzení o následujícím jsme přesunuli za ukotvení ^ – stále se však nachází na faktickém začátku, tedy před výrazem, kterému odpovídají znaky řetězce). Pro otestování, zda řetězec odpovídá formátu registrační značky, využijeme analogický postup.

Příklad z praxe – registrační značka

Ačkoli se běžně setkáváme s registračními značkami ve tvaru „1A01234“ (kde „A“ je písmeno označující kraj – v tomto případě konkrétně Prahu), zákon definuje platnou registrační značku mnohem volněji. Ve vyhlášce Ministerstva dopravy a spojů o registraci vozidel (243/2001 Sb.) se v paragrafu 16, odstavec 2, uvádí:

Jednotlivé znaky registrační značky jsou provedeny nejméně pěti a nejvíce sedmi velkými písmeny latinské abecedy a arabskými číslicemi. V registrační tabulce musí být vždy umístěno nejméně jedno písmeno a nejméně jedna číslice. První písmeno ve standardní registrační značce vyjadřuje kód kraje, do jehož obvodu náleží registrační místo, které registrační značku přiděluje, vyjma registrační značky diplomatické, registrační značky cizinecké a registrační značky pro historická vozidla.

Kromě toho paragraf 18, odstavec 1 uvádí:

Pro znaky vyjádřené velkým písmenem latinské abecedy, které se přidávají za kód kraje nebo za první povinný znak, se použijí písmena A, B, C, D, E, F, H, I, J, K, L, M, N, P, R, S, T, U, V, X, Y, Z.

Převeďme si tato ustanovení do jednotlivých podmínek, kterým musí řetězec představující registrační značku odpovídat. Musí se jednat o řetězec:

  1. složený z číslic 0-9 a povolených písmen (A, B, C, D, E, F, H, I, J, K, L, M, N, P, R, S, T, U, V, X, Y, Z)
  2. obsahující (alespoň jedno) povolené písmeno, přičemž první písmeno musí odpovídat některému z písmen označujících kraj (skupina znaků [ASULKHEPCJBMTZ])
  3. obsahující (alespoň jednu) číslici (numerický znak)

První podmínku můžeme zapsat pomocí regulárního výrazu ^[A-FH-NPR-VX-Z0-9]{5,7}$. Skupina znaků je v tomto případě tvořena pěti intervaly znaků (A-F, H-N, R-V, X-Z, 0-9) a jedním samostatným znakem (P). Další dvě podmínky vyjádříme pomocí kladného tvrzení o následujícím.

Druhou podmínku bychom mohli zapsat pomocí výrazu ^[0-9]*[ASULKHEPCJBMTZ].*$. Protože však ukotvení řetězce na začátek a konec zajišťuje již výraz ^[A-Z0-9]{5,7}$, v tvrzení o následujícím stačí zachovat regulární výraz [0-9]*[ASULKHEPCJBMTZ], který otestuje přítomnost písmena označujícího kraj na pozici, které smí předcházet pouze pozice obsazené číslicemi. Třetí podmínku bychom mohli zapsat pomocí výrazu ^.*[0-9].*$, který můžeme opět zjednodušit (analogie k druhé podmínce) na .*[0-9].

Celý regulární výraz se tak bude skládat ze dvou bezprostředně následujících tvrzení o následujícím ((?=[0-9]*[ASULKHEPCJBMTZ]) a (?=.*[0-9])) a jednoho „normálního“ regulárního výrazu. Kotevní metaznak ^ opět (jako v jednodušším výše uvedeném příkladu) pro přehlednost umístíme na úplný začátek regulárního výrazu. Celý výraz pak bude mít podobu ^(?=[0-9]*[ASULKHEPCJBMTZ])(?=.*[0-9])[A-FH-NPR-VX-Z0-9]{5,7}$.

Podmíněné subvýrazy

Podmíněné subvýrazy jsou části regulárního výrazu, které jsou brány v potaz, pouze je-li splněna určitá podmínka. Pokud podmínka splněna není, je subvýraz ignorován (konstrukce „if-then“) nebo je použit alternativní subvýraz (konstrukce „if-then-else“). Pro podmíněný subvýraz tak existují konstrukce:

  • (?(podmínka)splněna) (konstrukce „if-then“)
  • (?(podmínka)splněna|nesplněna) (konstrukce „if-then-else“)

Existují dva typy podmínek:

  1. Pokud se v uzávorkování (podmínka) nachází desítkové číslo, pak toto číslo odpovídá subvýrazu příslušného čísla. Pokud tento subvýraz odpovídá příslušnému řetězci (tedy dojde ke shodě), je podmínka splněna.
  2. Pokud se v uzávorkování (podmínka) nachází kladné či záporné tvrzení o následujícím či předcházejícím, pak je podmínka splněna, pokud je toto tvrzení pravdivé.

Pro oba typy podmínek si předvedeme jednoduché příklady.

První příklad – závislost na subvýrazu, konstrukce „if-then“

Řekněme, že chceme otestovat telefonní číslo a dovolíme národní (123456789) i mezinárodní zápis čísla (+123123456789). V našem příkladě (pro zjednodušení) předpokládáme čísla bez mezer. Národní zápis čísla se skládá ze sekvence devíti číslic a mezinárodní zápis čísla se skládá ze znaku +, tří číslic kódu země a devíti číslic pro číslo v rámci země. Pokud tedy řetězec začíná znakem +, budeme požadovat, aby následovalo dvanáct číslic, v opačném případě jen devět číslic.

Takové zadání vyřešíme pomocí regulárního výrazu ^(\+)?(?(1)\d{3})\d{9}$. Na začátku výrazu (pomineme-li ukotvení) se nachází subvýraz (\+) následovaný znakem ?, což značí, že řetězec odpovídající subvýrazu (v našem případě jím je znak +) nemusí být vůbec přítomný. Dále následuje konstrukce podmíněného subvýrazu (?(1)\d{3}). Pokud se na začátku našeho řetězce vyskytuje znak +, dojde ke shodě s prvním subvýrazem (\+). Podmínka je tak splněna a výraz \d{3} (obsažený v konstrukci podmíněného výrazu) je brán v potaz (to jinými slovy znamená, že je očekávána trojmístná předvolba země). Za konstrukcí podmíněného subvýrazu pak již následuje jen výraz \d{9} popisující vždy přítomné devíticiferné číslo (respektive sekvenci číslic).

Druhý příklad – závislost na kladném výrazu o následujícím, konstrukce „if-then-else“

Řekněme, že máme systém, který přijímá dva typy kódů. První typ je sekvencí pěti alfanumerických znaků (uvažujme jen malá písmena), přičemž alespoň jeden znak musí být písmeno. Druhý typ je sekvencí osmi číslic. Pomocí tvrzení o následujícím zjistíme, zda řetězec (náš kód) obsahuje alespoň jedno písmeno. Pokud ano, použije se regulární výraz pro pětiznakový alfanumerický kód, v opačném případě se použije regulární výraz pro osmiznakový číselný kód.

Celý regulární výraz tedy bude ^(?(?=[^a-z]*[a-z])[a-z0-9]{5}|[0-9]{8})$, kde (?=[^a-z]*[a-z]) je podmínka realizovaná kladným tvrzením o následujícím, [a-z0-9]{5} je výraz, který se použije při splnění podmínky, a [0-9]{8} je výraz, který se použije při nesplnění podmínky. Výrazu v podmínce ([^a-z]*[a-z]) vyhoví řetězec (i nulové délky) libovolných znaků kromě znaků a-z (malými písmeny), který je následován jedním ze znaků a-z.

Pár slov (skoro) závěrem

Tvrzení o následujícím (respektive předcházejícím) a podmíněné subvýrazy rozhodně patří k pokročilým technikám při práci s regulárními výrazy. Mnohá zadání (a to nejen v souvislosti s tvrzeními a podmíněnými subvýrazy) mají několik možných řešení pomocí různých regulárních výrazů. Nelze proto předpokládat, že se z vás pouhým přečtením několika článků stane odborník na práci s regulárními výrazy. Cílem je spíše představit danou vlastnost regulárních výrazů a ukázat její použití na příkladu. Některé regulární výrazy je možno optimalizovat tak, aby pracovaly při porovnávání efektivněji. Takové optimalizace by si však vyžadovaly další (a ne zrovna krátký) komentář a především by jejich vysvětlování předpokládalo rozsáhlejší a teoretičtější úvod do principů fungování regulárních výrazů – tato série článků však rozhodně nemůže a nechce suplovat knihu typu „Jak se stát guru v regulárních výrazech“.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Předchozí článek skalar.cz
Další článek pjripper
Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *