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

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.

Ř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.

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][ASULKHEP­CJBMTZ].$. 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][ASULKHEP­CJBMTZ], 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][ASUL­KHEPCJBMTZ]) 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][ASUL­KHEPCJBMTZ])(?=­.[0–9])[A-FH-NPR-VX-Z0–9]{5,7}$.

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.

Ř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).

Ř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.

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“.

Tento článek byl původně publikován na serveru
Interval.cz, kde naleznete originální verzi článku.

Miroslav Pecka

Share
Published by
Miroslav Pecka
Tags: phpregexp

Recent Posts

GA4 (not set) problém & jeho řešení

Jak se zbavit (not set) v Session Source a Session Medium?

2 roky ago

Měření QR kódů a offline zdrojů do Google Analytics

Chcete doměřit efekt vaší offline reklamy, ze které vedete lidi na váš web? Jde to…

2 roky ago

5+1 věcí, které se online markeťák může naučit od ajťáka

Tenhle článek jsem měl rozepsaný fakt dlouho, ale je stále aktuální… Trápí mě, že opakovaně…

4 roky ago

Profesionál v onlinu: řemeslo + kontext + přesahy

Poslední dobou jsem se setkal s pár majiteli malých firem, kteří mají web a snaží…

4 roky ago

Nástroje pro tvorbu screencast videí

Občas se mě někdo ptá, co používám pro tvorbu screencastů a online videí. Které nástroje…

4 roky ago

7 Google Analytics video návodů pro efektivnější práci

V rámci 5 videí najdete 7 krátkých video tipů pro zefektivnění práce s Google Analytics…

5 roky ago