Perl-compatible regulární výrazy v PHP – speciální typy uzávorkování

6. dubna 2005

Kulaté závorky jsme doposud používali pro ohraničení subvýrazů, a to buď proto, že jsme chtěli subvýraz doplnit kvantifikátorem, nebo proto, že jsme se na řetězec odpovídající subvýrazu chtěli odkazovat pomocí zpětné reference. Kulaté závorky se ale v Perl-compatible regulárních výrazech používají také k sestavení speciálních konstrukcí, které ovlivňují chování regulárního výrazu.

Mezi tyto konstrukce patří:

  • uzávorkování netvořící zpětné reference (non-capturing parentheses)
  • pojmenované subvýrazy (named subpatterns), respektive pojmenované zpětné reference
  • komentáře (další možnost jejich zápisu)
  • modifikátory (další možnost jejich zápisu)
  • „look ahead assetions“ a „look behind assertions“ (což můžeme v tomto kontextu přeložit asi jako „tvrzení o následujícím“ a „tvrzení o předcházejícím“)
  • podmíněné subvýrazy

Uzávorkování netvořící zpětné reference

Pokud použijeme například regulární výraz ^a(bc)+d$, budeme moci využít zpětnou referenci na první (jediný) subvýraz. Jestliže však víme, že zpětnou referenci nebudeme potřebovat, ale přesto potřebujeme určit opakování určité sekvence znaků (v našem případě bc následované kvantifikátorem +), pak můžeme použít takzvané uzávorkování netvořící zpětné reference (non-capturing parentheses).

Pokud použijeme regulární výraz ^a(?:bc)+d$, sekvence ?: za levou kulatou závorkou zajistí, že na řetězec (bc) odpovídající výrazu v těchto závorkách se nebude možno odkazovat pomocí zpětné reference. Použití „uzávorkování netvořícího zpětné reference“ má dvě výhody. Jednak si počítač při zpracování regulárního výrazu ušetří práci (respektive místo v paměti), když si nebude muset pamatovat zpětné reference, jednak se tímto způsobem může zpřehlednit práce se zpětnými referencemi v složitém regulárním výrazu.

Pojmenované subvýrazy

Ve složitějším regulárním výrazu můžeme mít mnoho subvýrazů, které tvoří zpětnou referenci. Pak nemusí být snadné se v očíslování jednotlivých subvýrazů vyznat. Krom toho se může stát, že na začátku regulárního doplníte později další subvýraz a všechny subvýrazy (vpravo od něj) se tím přečíslují. Vhodné proto je jednotlivé subvýrazy pojmenovat vlastním označením (můžeme použít alfanumerické znaky a podtržítko).

Takzvané pojmenované subvýrazy (named subpatterns) můžeme využít při použití funkce preg_match() ve tvaru preg_match($re,$str,$matched), kde $matched je pole řetězců, které odpovídají jednotlivým subvýrazům. Toto pole je (jak jsme si řekli již dříve) standardně indexováno čísly jednotlivých subvýrazů. Pokud však některé subvýrazy pojmenujeme, bude pole obsahovat i prvky s indexy odpovídajícími těmto pojmenováním. Pro pojmenování subvýrazu se používá konstrukce (?P<jmeno>výraz).

Podívejme se na modelový příklad, v němž máme za úkol otestovat, zda zadaný řetězec (datum) odpovídá formátu RRRR-MM-DD (přičemž první dvojčíslí roku smí být pouze 19 nebo 20), a pokud odpovídá, zobrazit jednotlivé části data (den, měsíc, rok).

$re=“/^(?P<rok>(?:19|20)[0-9]{2})-(?P<mesic>[0-9]{2})-(?P<den>[0-9]{2})$/“;
$str=“2005-07-12″;
$result=preg_match($re,$str,$matched);
if($result) //zadaný řetězec regulárnímu výrazu odpovídá
{
  echo „Den: „.$matched[„den“].“\n“;
  echo „Měsíc: „.$matched[„mesic“].“\n“;
  echo „Rok: „.$matched[„rok“].“\n“;
}
else //zadaný řetězec regulárnímu výrazu neodpovídá
{
  echo „Špatný formát data.“;
};

Rozeberme si nejdříve samotný regulární výraz. Tento výraz je složen ze tří subvýrazů (pro rok, měsíc a den) tvořících zpětné reference, které jsou odděleny obyčejným znakem pomlčky. První subvýraz (?P<rok>(?:19|20)[0-9]{2}) je složen z pojmenování subvýrazu ?P<rok>, dále z uzávorkování netvořícího zpětnou referenci (?:19|20) a nakonec ze skupiny znaků [0-9] následované kvantifikátorem {2} (poslední dvojčíslí roku). Druhý subvýraz (?P<mesic>[0-9]{2}) se skládá opět z pojmenování subvýrazu ?P<mesic>, za nímž následuje samotný výraz [0-9]{2} popisující dvojciferné číslo. Třetí subvýraz je s druhým až na pojmenování shodný.

Výše uvedený regulární výraz není zcela dokonalý, záměrně jsem jej pro účely demonstrace probíraných principů zjednodušil. Regulárnímu výrazu [0-9]{2} totiž například odpovídá i sekvence znaků 00. Krom toho regulární výraz má popisovat měsíc (respektive den v měsíci), takže například číslo 56 by také nemělo být považováno za správný vstup. Dokonalejším řešením jistě bude výraz ^(?P<rok>(?:19|20)[0-9]{2})-(?P<mesic>0[1-9]|1[0-2])-(?P<den>0[1-9]|[12][0-9]|3[01])$. Zde je pro popis měsíce použito 0[1-9]|1[0-2] (místo původního [0-9]{2}) a pro popis dne v měsíci 0[1-9]|[12][0-9]|3[01] (místo původního [0-9]{2}).

Vraťme se nyní ještě na skok k poli $matched, které obsahuje řetězce odpovídající jednotlivým subvýrazům, a podívejme se, co ve skutečnosti v našem příkladu obsahuje. K zobrazení obsahu pole jsme použili příkaz var_dump($matched).

array(7) {
  [0]=>
  string(10) „2004-07-12“
  [„rok“]=>
  string(4) „2004“
  [1]=>
  string(4) „2004“
  [„mesic“]=>
  string(2) „07“
  [2]=>
  string(2) „07“
  [„den“]=>
  string(2) „12“
  [3]=>
  string(2) „12“
}

Jak vidíme, pole nemá čtyři prvky, jak bychom mohli očekávat, ale sedm prvků. Pokud totiž použijeme pojmenované subvýrazy, hodnota odpovídající subvýrazu se do pole uloží dvakrát. Jednou s indexem odpovídajícím číslu subvýrazu, podruhé s indexem (klíčem) odpovídajícím pojmenování subvýrazu.

Komentáře

Pokud máme složitější regulární výraz, můžeme do něj (i na několik míst) vložit vysvětlující komentáře. Jedna možnost, jak přidat komentáře, byla popsána v předchozím článku (použití modifikátoru „extended“). Druhou možností je vložení speciální konstrukce ve tvaru (?#komentář) do regulárního výrazu. Regulárnímu výrazu ^a(?#můj komentář)b$ tak bude vyhovovat právě a pouze řetězec ab, protože za komentář se považují všechny znaky mezi (?# a ).

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

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

Š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 *