Subvýrazy a zpětná reference
Jak jsme si ukázali v článku o základních konstrukcích (krok 7.), subvýrazy se mohou hodit při kvalitnější práci s alternativami nebo kvantifikátory. Asi nejsilnější zbraní subvýrazů je možnost vytváření takzvaných „zpětných referencí“ (backreferences).
Řekněme, že chceme rozhodnout, zda řetězec začíná a končí stejným písmenem. Nestačí nám tedy, aby první a poslední písmeno bylo z určité skupiny znaků. V okamžiku, kdy algoritmus porovnává poslední písmeno, musí mít někde v paměti uloženo první písmeno, aby mohl rozhodnout, zda je shodné s posledním písmenem. Pro řešení této úlohy můžeme použít regulární výraz ^([a-zA-Z])[a-zA-Z]*\\1$
. První (a jediný) subvýraz odpovídá prvnímu písmenu řetězce. Před zakončujícím $
pak vidíme speciální sekvenci \\1
, která je právě onou zpětnou referencí na první subvýraz.
Obecně je tedy zpětná reference ve tvaru \\n
, kde n je číslo subvýrazu (v rozsahu 1 až 99). Subvýrazy mohou být vnořeny, a proto je třeba mít na paměti, že se subvýrazy číslují (zleva doprava) podle pozice levé (otevírací) kulaté závorky. Zpětná reference odpovídající celému výrazu je zapisována jako \\0.
(Ta nemůže být použita v rámci výrazu samotného, hodí se však při nahrazování řetězců – viz dále.)
Použití v PHP
Nyní, když už víme, že se na řetězce odpovídající jednotlivým subvýrazům můžeme odkazovat, je na místě podívat se znovu na funkci preg_match()
a na funkci preg_replace()
.
preg_match()
Při prvním setkání s preg_match()
(v předchozím článku) jsme zanedbali nepovinné parametry. Nyní se přece jen na jeden velmi užitečný nepovinný parametr podíváme. Použijeme tak funkci ve tvaru preg_match($re, $str, $matched)
, kde $re
je regulární výraz, $str
je řetězec porovnávaný s regulárním výrazem a $matched
je proměnná (pole), do níž budou uloženy části řetězce, které odpovídají jednotlivým subvýrazům. Indexy pole odpovídají číslu subvýrazu. V prvku pole s indexem nula je k dispozici řetězec odpovídající celému regulárnímu výrazu. Malá ukázka objasní více:
$str=“Jan Novák“;
preg_match($re,$str,$matched);
Příkaz var_dump($matched)
pak zobrazí následující obsah pole:
[0]=>
string(9) „Jan Novák“
[1]=>
string(3) „Jan“
[2]=>
string(5) „Novák“
}
Regulární výraz ^(\S+) (\S+)$
se skládá ze dvou subvýrazů oddělených mezerou. Každému ze subvýrazů odpovídá řetězec o minimální délce jednoho znaku, neobsahující takzvané „bílé znaky“. (A tedy ani mezeru, která zde slouží pro oddělení prvního a druhého subvýrazu.)
preg_replace()
Funkce preg_replace($re, $replacement, $str)
zjistí, zda řetězec v proměnné $str
odpovídá regulárnímu výrazu $re
, a pokud ano, nahradí jej řetězcem $replacement
. Pokud ke shodě s regulárním výrazem došlo, funkce vrací upravený řetězec ($replacement
), v opačném případě vrací původní řetězec ($str
). Velkým přínosem je možnost použít v řetězci $replacement
zpětnou referenci. Řetězec, který má být náhradou, tak může obsahovat části původního řetězce (případně celý původní řetězec). Pokud použijeme příklad uvedený výše, můžeme tak třeba prohodit jméno a příjmení:
$str=“Jan Novák“;
$replacement=“\\2 \\1″;
$result=preg_replace($re,$replacement,$str);
Proměnná $result
bude obsahovat Novák Jan
. Analogicky by bylo možno dosáhnout také například výsledku Novák, J.
. Stačí, když nastavíme obsah proměnných na $re=\"/^(\S)(\S+) (\S+)$/"
a $replacement="\\3, \\1."
. První subvýraz bude odpovídat prvnímu znaku řetězce (prvnímu písmenu křestního jména). Druhý subvýraz bude odpovídat zbytku křestního jména, kromě jeho prvního písmene. Třetí subvýraz bude odpovídat příjmení (tedy obecně řetězci bez bílých znaků o minimální délce jeden znak). V proměnné $replacement
jsou následně využity zpětné reference na první a třetí subvýraz. Všechny ostatní znaky (čárka, mezera a tečka) nemají žádný speciální význam, a proto se ve výsledném textu pouze zobrazí. Mějte na paměti, že ačkoli je možno do proměnné $replacement
vkládat zpětné reference, nejedná se o regulární výraz, a tak není třeba jakékoli znaky (tečku, plus a další) doplňovat o zpětné lomítko.
Od PHP 4.0.4 výše je také možno zpětnou referenci v proměnné $replacement
zapisovat nejen jako \\n
, ale také jako $n
(kde n je číslo subvýrazu). Může se vám také stát, že potřebujete, aby v proměnné $replacement
bezprostředně za zpětnou referencí následovala nějaká číslice. Řekněme, že chcete do proměnné $replacement zapsat referenci na první subvýraz následovanou číslicí 2
. Pokud zadáte $replacement="$12"
, bude to PHP považovat za zpětnou referenci na dvanáctý subvýraz. Řešením je uzavření čísla zpětné reference do složených závorek – $replacement="${1}2"
již bude fungovat dle očekávání.
Praktické příklady
Následující příklady ukazují možné využití regulárních výrazů při často se opakujících mechanických úpravách textu.
Příklad 1. – odstranění zdvojených slov
Při psaní se nám může do textu občas vloudit zdvojení slov jako například: „Právě jsem jsem se vrátil.“ Odstranění takových duplicit z textu můžeme provést pomocí funkce preg_replace($re, $replacement, $str)
, kde $re=\"/(\S+)\s\\1/"
, $replacement="\\1"
a v proměnné $str
je text, z něhož chceme odstranit zdvojená slova. Výrazu (\S+)\s\\1
odpovídají řetězce, které se skládají ze sekvence „nebílých“ znaků o minimální délce jednoho znaku (to odpovídá prvnímu subvýrazu), která je následována bílým znakem. Za tímto bílým znakem následuje řetězec, který je shodný s řetězcem odpovídajícím prvnímu subvýrazu. Jako náhradu v proměnné $replacement
pak stačí uvést zpětnou referenci na první subvýraz.
Příklad 2. – odstranění několika shodných znaků za sebou
Odstranění několika opakujících se znaků (respektive ponechání jen jednoho znaku) se může hodit například při přeposílání e-mailu jako SMS zprávy. E-maily často obsahují horizontální oddělovače (linky) sestavené z mnohokrát se opakujícího znaku =
, -
nebo *
a podobně. Řekněme, že chceme ponechat maximálně tři stejné znaky za sebou. Z toho plyne, že nás zajímají znaky, které se opakují minimálně čtyřikrát. Opět použijeme funkci preg_replace($re, $replacement, $str)
, tentokrát však s parametry $re="/(.)\\1{3,}/"
a $replacement="\\1\\1\\1"
. Prvnímu subvýrazu odpovídá jeden libovolný znak. Tento znak musí být následován minimálně třemi stejnými znaky (zpětná reference). Jako náhrada se v proměnné $replacement
uvede zpětná reference třikrát za sebou.
Příklad 3. – záměna HTML tagů
Zatím jsme mlčky přepokládali, že parametry funkce preg_replace()
jsou řetězce. Jak proměnná $re
, tak proměnná $replacement
však mohou být typu pole. Toho můžeme dobře využít, pokud chceme provést více záměn jedním voláním funkce. Můžeme například chtít zaměnit v HTML kódu určité HTML tagy. V naší ukázce budeme chtít zaměnit všechny tagy
(a
) za
) a všechny tagy
- (a
) za
- (a
). Lze předpokládat, že tyto tagy budou doplněny o atributy (například název CSS třídy). I s tím si ale naše řešení poradí.
"/<(\/?)pre(>| [^>]*>)/",
"/<(\/?)ul(>| [^>]*>)/");
$replacement=array(
"<\\1div\\2",
"<\\1ol\\2"); $str="
text1
- položka
text2
";
$result=preg_replace($re,$replacement,$str);
Výsledkem (obsahem proměnné $result
) pak je následující HTML kód:
- položka
Příklad si rozhodně zaslouží popis. Vytvořili jsme dvě dvouprvková pole $re
a $replacement
. Pole $re
obsahuje regulární výrazy popisující text (řetězec), který má být vyhledán v textu uloženém v proměnné $str
. Část textu z proměnné $str
, která odpovídá regulárnímu výrazu z prvního prvku pole $re
, bude nahrazena řetězcem z prvního prvku pole $replacement
. Analogicky platí, že regulárnímu výrazu z druhého prvku pole $re
odpovídá náhrada z druhého prvku pole $replacement
.
A nyní již k regulárnímu výrazu samému. Výraz <(/?)pre(>| [^>]*>)
popisuje otvírací i zavírací HTML tag párového elementu pro vyznačování předformátovaného textu. Hned za <
následuje (v prvním subvýrazu) nepovinné lomítko (uplatní se u zavíracího tagu). Následná sekvence pre
označuje vlastní název tagu. Hned za touto sekvencí může být buď rovnou uzavírací >
, nebo jednotlivé parametry (například class
, id
či title
), a teprve až za nimi uzavírací >
. Těmto dvěma možnostem odpovídají dvě alternativy v druhém subvýrazu ((>| [^>]*>)
). Jestliže za pre
nebude bezprostředně následovat >
, zkusí se druhá alternativa [^>]*>
. Té vyhoví sekvence znaků, která začíná mezerou, končí znakem >
a mezi těmito znaky je sekvence libovolných znaků kromě >
.
Nahrazovací řetězec <\\1div\\2
se skládá ze čtyř částí - otevíracího znaku <
, zpětné reference na první subvýraz, textu div
(pre
tagy chceme nahradit div
tagy) a zpětné reference na druhý subvýraz (který obsahuje uzavírací znak >
). Náhrada
- (a
) za
- (a
) je prostou analogií výše popsaného, pouze s jinými tagy.
originální verzi článku.
Nenech si to pro sebe...
Pokud tě článek zaujal, sdílej ho s ostatními. Díky!