Regulární výrazy v praxi – subvýrazy
Regulární výrazy jsou mocným nástrojem, který může výrazně zjednodušit a zpřehlednit kód programu. Důležitou součástí práce s regulárními výrazy jsou takzvané subvýrazy. Subvýrazy jsou části regulárního výrazu, které umožňují zjednodušení zápisu běžného regulárního výrazu i tvorbu komplikovanějších výrazů, schopných inteligentně postihnout a manipulovat například i s řetězci, jejichž přesnou podobu nelze předem určit.
Pokud vám pojem regulární výrazy mnoho neříká, doporučuji vám přečíst si nejdříve předchozí články této série. Regulární výrazy je možno používat v mnoha (nejen) programovacích jazycích. My se v tomto článku omezíme na použití v PHP a na regulární výrazy dle normy POSIX 1003.2 (extended). Pro práci s takovými regulárními výrazy disponuje PHP třemi funkcemi (respektive šesti funkcemi, pokud počítáme i „case insensitive“ verze těchto funkcí). Tyto funkce byly také stručně popsány v jednom z předchozích článků.
Regulární výraz může být velmi účinný, i když nemá žádné subvýrazy. Použitím subvýrazů se však možnosti použití regulárních výrazů značně zvětší. Nejdříve si ukažme, jak vypadá regulární výraz obsahující subvýrazy. Výraz ^a(b(c))d(e)f$
obsahuje tři subvýrazy, které jsou dány uzávorkováním kulatými závorkami. Subvýrazy se označují (číslují) v pořadí podle pozice otevírací (levé) závorky a tak prvnímu subvýrazu bude odpovídat bc
, druhému subvýrazu c
a třetímu e
.
Proč jsou subvýrazy tak užitečné a jak je můžeme využít? Napadají mě rovnou tři situace, kdy se budou subvýrazy hodit:
- Chceme definovat, že se určitý textový řetězec (sekvence znaků) má v rámci regulárního výrazu opakovat.
- Chceme zaměnit řetězec za jiný řetězec, který vznikne „přeskládáním“ původního řetězce.
- Chceme rozdělit (parsovat) řetězec na určité části, se kterými můžeme následně pracovat samostatně.
Na jednotlivé situace se podíváme blíže, a to vždy s použitím dvou příkladů. První (modelový) příklad je vždy jednodušší a má především demonstrovat princip. Druhý příklad je z běžného života (tedy spíše běžného života programátora).
První situace
Chceme definovat, že se určitý textový řetězec (sekvence znaků) má v rámci regulárního výrazu opakovat.
Modelový příklad
Chceme, aby regulárnímu výrazu odpovídaly řetězce, které začínají písmenem „a“, končí písmenem „d“ a mezi těmito písmeny se musí minimálně jedenkrát vyskytovat sekvence znaků „bc“. Takový regulární pak bude vypadat ^a(bc)+d$
a budou mu odpovídat řetězce jako abcd
, abcbcd
, abcbcbcd
a podobně – abcbd
však už vyhovovat nebude.
Příklad z praxe
Chceme vytvořit regulární, kterému by odpovídal dvouciferný i čtyřciferný zápis letopočtu 20. a 21. století. Použijeme regulární výraz ^(19|20)?[0-9]{2}$
. První (a jediný) subvýraz (19|20)
odpovídá prvním dvěma cifrám letopočtu 20. a 21. století. Kvantifikátor ?
, který následuje bezprostředně za tímto subvýrazem, signalizuje, že řetězec odpovídající subvýrazu (tedy 19
či 20
) nemusí být vůbec přítomen.
Pro úplnost doplním, že to, zda určitý řetězec odpovídá regulárnímu výrazu, zajistíme pomocí funkce ereg($pattern, $string)
, kde $pattern
je regulární výraz a $string
je testovaný textový řetězec. Funkce vrací TRUE
, pokud $string
odpovídá regulárnímu výrazu $pattern
, jinak vrací FALSE
.
Druhá situace
Chceme zaměnit řetězec za jiný řetězec, který vznikne „přeskládáním“ původního řetězce.
Modelový příklad
Máme k dispozici jméno osoby ve tvaru Jméno Příjmení a chceme ho zobrazit jako Příjmení Jméno. K tomuto převodu použijeme funkci ereg_replace($pattern, $replacement, $string)
, která zjistí, zda $string
odpovídá regulárnímu výrazu $pattern
a pokud ano, vrátí tato funkce řetězec $replacement
. V opačném případě vrátí funkce $string
(tedy původní řetězec).
Síla této funkce spočívá v tom, že $replacement
může obsahovat řetězce odpovídající subvýrazům regulárního výrazu. Pomocí speciální konstrukce \\číslo
je možno se odkazovat na subvýrazy regulárního výrazu $pattern
, kde číslo odpovídá číslu subvýrazu, jak jsem jej popsal výše. Pokud použijeme funkci s parametry $pattern="^([^ ]+) ([^ ]+)$"
, $replacement="\\2 \\1"
a jako $string
„předhodíme“ funkci Jan Novák
, potom nám funkce vrátí Novák Jan
. Regulární výraz $pattern
je tvořen dvěma subvýrazy oddělenými mezerou. Každému z těchto subvýrazů odpovídá sekvence znaků (o délce minimálně jeden znak) neobsahující mezeru (protože mezera nám slouží pro oddělení prvního a druhého subvýrazu – tedy jména a příjmení).
Příklad z praxe
Máme k dispozici (například v databázi) datum ve formátu MM/DD/RRRR a chceme ho převést na české datum ve formátu DD. MM. RRRR. Pokud použijeme funkci ereg_replace()
s parametry $pattern="^([0-9]{2})/([0-9]{2})/((19|20)[0-9]{2})$"
, $replacement="\\2. \\1. \\3"
a jako datum ($string
) „předhodíme“ funkci 12/05/2002
, potom nám funkce vrátí 05. 12. 2002
. Výraz $pattern
má čtyři subvýrazy. Podstatné první tři jsou subvýrazy, které postihují měsíc, den a rok. V řetězci $replacement
pak už jen stačí prohodit první a druhý subvýraz a doplnit za čísly tečky místo původních lomítek.
Jak vás asi napadlo, použití $pattern="^([0-9]{2})/([0-9]{2})/((19|20)[0-9]{2})$"
není příliš kvalitní řešení, protože považuje za validní datum například i řetězec 76/54/2002
. Proto upravíme subvýraz pro měsíc na (0[1-9]|1[0-2])
a subvýraz pro den v měsíci na (0[1-9]|[12][0-9]|3[01])
a dostaneme tak regulární výraz ^(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])/((19|20)[0-9]{2})$
. Pokud bychom chtěli akceptovat i datum, kde jednociferným číslům dnů či měsíců může (ale nemusí) předcházet nula, pak stačí za 0
v regulárním výrazu doplnit kvantifikátor ?
. Celý regulární výraz pak bude ^(0?[1-9]|1[0-2])/(0?[1-9]|[12][0-9]|3[01])/((19|20)[0-9]{2})$
.
Třetí situace
Chceme rozdělit (parsovat) řetězec na určité části, se kterými můžeme následně pracovat samostatně.
Modelový příklad
Máme k dispozici jméno osoby ve tvaru Jméno Příjmení. My však potřebujeme pracovat zvlášť se jménem a zvlášť s příjmením. K tomu opět použijeme funkci ereg($pattern, $string, $regs)
. Nepovinný parametr $regs
udává pole, do nějž budou uloženy části řetězce $string
odpovídající jednotlivým subvýrazům regulárního výrazu $pattern
. Pokud použijeme funkci s parametry $pattern="^([^ ]+) ([^ ]+)$"
a $string="Jan Novák"
, jednotlivé části jména pak budeme mít dostupné v poli $regs
.
Pokud funkci „předhodíme“ jako $string
například Jan Novák
, výpis obsahu pole $regs
pomocí funkce var_dump()
bude vypadat následovně:
array(3) {
[0]=>
string(9) „Jan Novák“
[1]=>
string(3) „Jan“
[2]=>
string(5) „Novák“
}
Příklad z praxe
Máme k dispozici poštovní adresu, respektive část adresy udávající název ulice, číslo orientační, případně číslo popisné. A naším cílem je získat tyto jednotlivé části. Takový požadavek může typicky nastat při změně struktury databáze s adresami. Budeme předpokládat, že číslo orientační je uvedeno vždy a číslo popisné být uvedeno může, ale nemusí. Pokud však je číslo popisné uvedeno, pak jsou čísla uvedena ve tvaru číslo_popisné/číslo_orientační (jako na občanském průkazu). Regulární výraz se tak bude skládat ze tří hlavních částí – názvu ulice, čísla popisného (nepovinně) a čísla orientačního.
Popsaný problém řeší regulární výraz ^(.*[^0-9]+) (([1-9][0-9]*)/)?([1-9][0-9]*[a-cA-C]?)$
. První subvýraz popisuje název ulice – tedy textový řetězec o minimální délce jeden znak, který nesmí končit číslicí (zajistíme tak, že se do názvu ulice nezahrne i číslo popisné, respektive orientační). Druhý subvýraz popisuje číslo popisné následované lomítkem (přičemž třetí subvýraz popisuje číslo popisné samotné). Celý druhý subvýraz je následován kvantifikátorem ?
a tak může být odpovídající řetězec přítomný maximálně jednou (to znamená, že nemusí být přítomný vůbec). Čtvrtý subvýraz popisuje číslo orientační, které může být vyjádřeno číslem, případně číslem následovaným písmeny a-c (respektive A-C).
Pokud funkci „předhodíme“ jako $string
například Nám. 5. května 568/28
, výpis obsahu pole $regs
pomocí funkce var_dump()
bude vypadat následovně:
array(5) {
[0]=>
string(21) „Nám. 5. května 568/28“
[1]=>
string(14) „Nám. 5. května“
[2]=>
string(4) „568/“
[3]=>
string(3) „568“
[4]=>
string(2) „28“
}
Starší komentáře ke článku
Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.
Mohlo by vás také zajímat
-
9 nejzajímavějších doménových koncovek
19. srpna 2024 -
Aktualizujete svoji .NET webovou aplikaci? Může se hodit app_offline.htm
10. července 2024 -
Nepodceňte UX na vašem webu: Proč na něm záleží?
10. dubna 2024
Nejnovější
-
Jak rozšířit úložiště Macu za pětinovou cenu?
16. prosince 2024 -
Nové trendy v doménách pro osobní projekty – DIY, LIVING a LIFESTYLE
9. prosince 2024 -
Jak chránit webové stránky před Web/AI Scrapingem
27. listopadu 2024 -
Jaký monitor je nejlepší k novému Macu Mini?
25. listopadu 2024