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:
- jedná se o řetězec o délce 10 znaků složený výhradně
z číslic - 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:
- 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) - 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]
) - 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:
- 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. - 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“.
Interval.cz, kde naleznete originální verzi článku.
Nenech si to pro sebe...
Pokud tě článek zaujal, sdílej ho s ostatními. Díky!