Regulární výrazy (3.) – podskupiny

Třetí díl seriálu o regulárních výrazech bude věnován obyčejným kulatým závorkám ( a ). Ony vlastně ani nejsou zvlášť složité, celá jejich funkce by se snadno dala shrnout do tvou slov „vymezení skupin“ – jenže jde o to, že tímhle jedním mechanismem jde provádět celou řadu zajímavých operací, drtivé většině uživatelů neznámých. Na ty ale dojde až v příštím pokračování, dnes se seznámíme jen se základním použitím závorek, na které v dalším díle navážeme (a konečně v něm dojde na slibovanou často žádanou funkčnost „jak napsat výraz, který nenajde zadané slovo“).

Jak už jsem napsal, závorky slouží k vymezení skupin – jinými slovy, umožní definovat podvýraz a s ním pak pracovat jako s celkem. K tomu stačí zvolený regulární výraz „obalit“ kulatými závorkami – otvírací vlevo, zavírací vpravo. Z obyčejného regulárního výrazu [a-z_][a-z0-9_]* (který popisuje platné identifikátory ve skoro všech klasických vyšších programovacích jazycích) uděláme skupinu takhle: ([a-z_][a-z0-9_]*). Samozřejmě i tento výraz je regulárním výrazem a lze z něj udělat další skupinu, např. (([a-z_][a-z0-9_]*),?). A tak dále a tak podobně.

K čemu je to dobré? Samo o sobě skoro k ničemu. Kdyby jediným efektem závorek bylo, že se jimý dá obklopit jakýkoliv jiný výraz, tak bychom se s nimi vůbec nemuseli obtěžovat. Jenže ono se s nimi dá dělat mnohem víc:

Podvýraz je „znakem“

Připomeňte si prosím kvantifikátory z minulého dílu: Pomocí znaků +, *, ?, složených závorek a jejich kombinací jste mohli ürčit, kolikrát se má předchozí znak nebo výčet znaků opakovat. Pro účely kvantifikátorů je i obsah závorek takovým „znakem“. Stejně jako jste mohli napsat a+ pro „posloupnost jednoho nebo více po sobě jdoucích znaků a„, můžete napsat (pepak)+ pro „posloupnost jednoho nebo více po sobě jdoucích výrazů pepak“ – tj. vyhoví pepak nebo pepakpepak, ale už ne pepa.k.

Závorky v tomto smyslu využijete všude, kde se může počet výskytů nějakého slova nebo výrazu měnit. V YouTube Downloaderu například potřebuji dekódovat, na jakém serveru se nachází uživatel zadané video. Mohl bych samozřejmě postupovat velmi přímočaře a testovat, jestli adresa videa vyhoví výrazu jako youtube\.com; to bych ovšem dopadl dost špatně, protože takovému výrazu vyhoví také například adresa http://www.killyoutube.com (webová adresa, ale na jiném serveru), admin@youtube.com (e-mailová adresa) nebo dokonce www.ukradneme-vase-penize.cz/youtube.com/hahaha. Když se nad problémem trochu zamyslíte, snadno přijdete na trochu důmyslnější regulární výraz ^https?://www\.youtube\.com/, který bude fungovat podstatně lépe. Ale pořád to nebude ono – co když uživatel nebude zadávat adresu videa „obvyklým způsobem“ s www na začátku, ale jako správný lenochod si ušetří prsty a napíše jenom http://youtube.com (která bude skutečně fungovat, mimochodem)? Nebo co když použije jinou, také platnou subdoménu – třeba někdy v budoucnosti půjde psát http://pepak.users.youtube.com? Je evidentní, že ta část youtube.com bude platit vždy, ale co je před ní, to se může i dost výrazně měnit. A tady právě přicházejí na řadu závorky; adresy ověřuji výrazem jako ^https?://([a-z0-9-]+\.)*youtube\.com/. Pojďme si dekódovat, co tenhle výraz znamená:

  • ^ – Viz část „kotvy“ v druhém díle seriálu: zbytek výrazu se musí nacházet hned na začátku (abych vyloučil nápady jako www.server.cz/www.youtube.com/xyz).

  • https?:// – Zajímají mě pouze protokoly HTTP a HTTPS, které by se v URI notaci zapsaly jako http:// respektive https:// – je zjevné, že jsou skoro stejné, až na to, že v jednom z nich je před dvojtečkou navíc s. Tudíž jsem využil kvantifikátoru „otazník“ pro „jeden nebo nula výskytů“.

  • ([a-z0-9-]+\.) – Tady máme skupinu, podvýraz. Skupina se skládá z posloupnosti jednoho nebo více písmen, číslovek a mínusů (tj. např. www nebo mirror nebo pepakova-videa) následovaných tečkou (kterou je třeba uvést zpětným lomítkem, aby vystupovala skutečně ve významu „tečka“ a ne „libovolný znak“).

  • * – Za skupinou je hvězdička, kvantifikátor znamenající „nula nebo více výskytů předchozího znaku“. „Předchozím znakem“ je v tomto případě skupina, která se tím pádem v adrese může, ale také nemusí vyskytovat, a to v libovolném počtu. Jinými slovy, vyhovuje mi jak www. (právě jeden výskyt), tak pepak.users. (dva výskyty) nebo beta.pepak.users. (tři výskyty), ale také (nula výskytů).

  • youtube\.com – Zbytek názvu serveru už by měl být zřejmý, potřebuji, aby doména končila textem youtube.com.

  • / – potřebuji se vypořádat ještě s tradičním „phishingovým“ přístupem, kdy sice adresa obsahuje požadovaný text, ale na špatném místě (http://www.youtube.com.phishingovy-web.cz/). Lomítko je v URI specifikaci symbol, který říká „tady končí jméno serveru a začíná cesta na tomto serveru“. Tím mám zajištěno, že hledaný text youtube.com bude to poslední, co ve jménu serveru je. (Zrovna tak dobře by v tomto demonstračním případě šlo místo lomítka použít symbol $, „konec řetězce“. Lomítko tam mám proto, že při reálném použití v YTD potřebuji, aby adresa ještě něčím pokračovala.)

(Pro šťouraly: Jsem si vědom toho, že ani tento regulární výraz nepokrývá všechno. Kdybych měl řešit nějaké zabezpečení, které vyžaduje platnou adresu na YouTube a zároveň vyžaduje, aby to fungovalo se všemi možnými platnými adresami, musel bych použít podstatně a podstatně složitější výraz. Pro moje potřeby „zjistit, na kterém serveru se požadované video nachází“, je ale tento výraz dostatečně účinný. Viz také rozdíl mezi běžné používanými jednoduchými regulárními výrazy pro ověření e-mailových adres a úplným regulárním výrazem.)

Podvýraz vymezuje hranice; svislítko (roura)

Závorky, ve spolupráci s tím, co je před a za nimi, stanovují začátek a konec platnosti nějakého podvýrazu. Nejjednodušší použití je ve spolupráci se symbolem | (roura, svislítko), který vlastně správně patří mezi základní operátory (a tím pádem do druhého dílu seriálu), ale reálný smysl má až ve spolupráci se závorkami.

Svislítko | rozděluje regulární výraz na dvě nebo více alternativ, ze kterých se použije první vyhovující. Regulární výraz pepa|karel|franta vyhledá buď Pepu, nebo Karla, nebo Frantu, ale ne Tomáše. [0-9]+|0x[0-9a-f]+|[0-9][0-9a-f]*h najde libovolné celé číslo v desítkové soustavě (část [0-9]+ – např. 40960), v šestnáctkové soustavě v Cčkové notaci (část 0x[0-9a-f]+, např. 0xa000) a v šestnáctkové soustavě v assemblerové notaci (část [0-9][0-9a-f]*h – např. 0a000h), ale zastaví se před nesmysly typu 0xa000h (kombinace Cčkového a assemblerového zápisu).

První alternativa (v mém příkladu [0-9]+) normálně začíná hned na začátku regulárního výrazu, další alternativy leží vždy mezi dvěma svislítky, a poslední alternativa začíná za posledním svislítkem a končí až koncem výrazu. Což je fajn, ale co když potřebuji, aby to číslo výše bylo v uvozovkách? To bych je musel napsat do každé alternativy ("[0-9]+"|"0x[0-9a-f]+"|"[0-9][0-9a-f]*h"), a to už není zrovna ideální – zvlášť když to zesložitím a místo „v uvozovkách“ budu chtít, aby to „bylo obklopené nějakým složitým výrazem“. A tady právě přijde ke cti druhá funkce závorek, tj. stanovování hranic: pokud svislítka použiji uvnitř závorek, bude první alternativa začínat za otvírací závorkou a poslední alternativa bude končit před zavírací závorkou. Ten uvozovkový příklad pak zapíšu podstatně jednodušeji takto: "([0-9]+|0x[0-9a-f]+|[0-9][0-9a-f]*h)". Tady už by mi ani nevadilo, kdybych měl místo uvozovek použít něco složitějšího…

Na podvýraz se lze odkazovat

Velmi užitečnou vlastností podvýrazu je, že se na ně dá odkazovat – to znamená, že v určitých situacích můžete programu říct, „sem zkopíruj obsah té a té závorky“.

Nejjednodušší použití je v textových editorech s podporou regulárních výrazů. Dejme tomu, že mám elektronickou knihu v prostém textu, kde co řádek, to odstavec, a já z ní chci udělat knihu ve formátu HTML (kde každý odstavec začíná textem <p> a končí textem </p>). Logika toho, co chci udělat, je jasná: chci vzít každý (neprázdný) řádek, na jeho začátek připlácnout <p> a na konec zase </p>. Jak to udělám? Použiji textový editor, který umí vyhledávat i nahrazovat s pomocí regulárních výrazů, a dám:

  • Najít ^(.+)$ – mezi začátkem a koncem řádku chci jeden nebo více libovolných znaků; ty znaky budou tvořit první podskupinu. (Proč první popíšu dále.)

  • Nahradit <p>\1</p> – celý nalezený text, tj. celý neprázdný řádek, nahradím. Symbol \1 v tomto kontextu znamená „obsah první nalezené podskupiny“, tj. vlastně „ty znaky, které tvořily řádek“. Ovšem před ně umístím <p> (protože to je napsáno před \1) a za ně </p> (protože to je napsáno za \1).

(Podrobněji viz článek Převod z TXT/PDB do HTML).

Podobným mechanismem se dá k podskupinám dostat i v programovacích jazycích – typicky se ovšem nepoužívá nahrazení, ale volání nějaké funkce „vrať_mi_Xtou_podskupinu(1)“.

Slíbil jsem vysvětlit, jak je to s počítáním podskupin. Je to vlastně docela jednoduché: Pokud to není speciálními postupy zakázáno, tak N-tá otvírací závorka začíná N-tou skupinu, a odpovídající zavírací závorka tuto skupinu ukončuje. Rychlý příklad: Ve výrazu (aaa)(b(c)d) bude první skupinou aaa, druhou skupinou bcd a třetí skupinou c (podle pořadí otvíracích závorek).

Na skupiny se dá odkazovat i přímo ve vyhledávaném výrazu. Dejme tomu, že budu hledat adresy v XHTML souboru. To normálně znamená, že hledám href="..." nebo src="...", přičemž mě zajímá to uvnitř uvozovek. Takže bych napsal velmi přímočaře výraz \s(href|src)="(.*?)" (prázdný znak, pak buď „href“ nebo „src“, pak rovnítko a uvozovku, pak bude všechno až do nejbližších uvozovek tvořit moji hledanou skupinu a ukončím to zase uvozovkami). Jenže v XHTML nemusí být atributy uzavřeny jen do uvozovek, ale také do apostrofů, takže bych měl vzápětí použít ještě jeden regulární výraz, který se bude lišit jen v tom, že na místě uvozovek bude mít apostrofy.

A nebo to udělám pomocí skupin přímo ve výrazu: \s(href|src)=(["'])(.*?)\2. Začátek výrazu je stejný, ale za rovnítkem se to začíná komplikovat: druhou skupinu budou tvořit buď uvozovky nebo apostrof. Třetí skupina je moje hledaná adresa. Za ní je \2, což znamená „tady bude to samé, co ve druhé nalezené skupině“ – tedy buď uvozovky nebo apostrof, podle toho, co bylo nalezeno. Ale nemůže se mi stát, že bych našel href="abc' (začátek vyznačen uvozovkou a konec apostrofem).

U podskupin je jedna zrada: různé implementace kladou různá omezení na to, jak se na podskupiny odkazuje a kolik takových podskupin lze použít (a některé implementace ani nedovolí používat odkazy na dřívější podskupiny jako v posledním příkladu) – je třeba celkem běžné, že můžete použít jen 9 podskupin (1 až 9) nebo že se na ně neodkazuje pomocí \1 ale $1. Pokud budete chtít podskupiny používat, doporučuji to zkonzultovat s dokumentací té regulární knihovny, kterou zrovna používáte.

Pro úplnost dodám, že existuje i nultá podskupina (\0), která vždy obsahuje celý nalezený výraz, i když v něm třeba závorky nejsou uvedeny vůbec – např. ve výrazu "([a-z]+)" použitém na text [autor="pepak"] bude první podskupinou pepak (podle závorek) a nultou podskupinou "pepak" (celý nalezený výraz, tzn. včetně uvozovek).

 

No a to by bylo ze základního použití závorek všechno. Příště se podíváme na jejich složitější, ale mnohdy nesmírně užitečné aplikace.

Podobné příspěvky:

2 Responses to “Regulární výrazy (3.) – podskupiny”

  1. avatar pepak napsal:

    Díky. Síla zvyku je hrozné svinstvo…

  2. avatar Martin Kopta napsal:

    Kulatým uvozovkám se běžně říká závorky. 😉

Leave a Reply

Themocracy iconWordPress Themes

css.php