Äást vzoru můžeme uzavÅÃt do závorek (...). Tato Äást se pak nazývá âzachytávacà skupinaâ.
Má dva efekty:
- UmožÅuje nám zÃskat Äást shody jako samostatný prvek pole výsledků.
- Jestliže za závorky umÃstÃme kvantifikátor, aplikuje se na závorky jako na celek.
PÅÃklady
Na pÅÃkladech se podÃvejme, jak závorky fungujÃ.
PÅÃklad: gogogo
Vzor go+ bez závorek znamená znak g, po nÄmž následuje jednou nebo vÃcekrát opakované o, napÅÃklad goooo nebo gooooooooo.
Závorky seskupujà znaky dohromady, takže (go)+ znamená go, gogo, gogogo a tak dále.
alert( 'Gogogo teÄ!'.match(/(go)+/ig) ); // "Gogogo"
PÅÃklad: doména
VytvoÅme nÄco složitÄjÅ¡Ãho â regulárnà výraz pro hledánà domény webového sÃdla.
PÅÃklad:
mail.com
users.mail.com
smith.users.mail.com
Jak vidÃme, doména se skládá z opakovaných slov a po každém z nich kromÄ poslednÃho následuje teÄka.
V regulárnÃch výrazech to je (\w+\.)+\w+:
let rv = /(\w+\.)+\w+/g;
alert( "site.com my.site.com".match(rv) ); // site.com,my.site.com
Hledánà funguje, ale vzor nenajde doménu s pomlÄkou, napÅ. my-site.com, protože pomlÄka nepatÅà do tÅÃdy \w.
Můžeme to opravit nahrazenÃm \w za [\w-] v každém slovÄ kromÄ poslednÃho: ([\w-]+\.)+\w+.
PÅÃklad: e-mail
PÅedchozà pÅÃklad můžeme rozÅ¡ÃÅit. Můžeme na jeho základÄ vytvoÅit regulárnà výraz pro e-maily.
Formát e-mailu je: jméno@doména. Jméno může být libovolné slovo, jsou povoleny i pomlÄky a teÄky. V regulárnÃch výrazech to je [-.\w]+.
Vzor:
let rv = /[-.\w]+@([\w-]+\.)+[\w-]+/g;
alert("[email protected] @ [email protected]".match(rv)); // [email protected], [email protected]
Tento regulárnà výraz nenà dokonalý, ale vÄtÅ¡inou funguje a pomáhá opravovat neúmyslné pÅeklepy. Jediná skuteÄnÄ spolehlivá kontrola e-mailu je poslat na nÄj zprávu.
Obsah závorek ve shodÄ
Závorky jsou ÄÃslovány zleva doprava. Vyhledávacà motor si pamatuje obsah každé z nich v nalezené shodÄ a umožÅuje ho naÄÃst ve výsledku.
Metoda ÅetÄzec.match(rv), pokud rv nemá pÅÃznak g, najde prvnà shodu a vrátà ji jako pole:
- Na indexu
0: celá shoda. - Na indexu
1: obsah prvnÃch závorek. - Na indexu
2: obsah druhých závorek. - â¦a tak dáleâ¦
NapÅÃklad chceme najÃt HTML znaÄky <.*?> a zpracovat je. Bylo by vhodné mÃt obsah znaÄky (to, co je v lomených závorkách) v samostatné promÄnné.
UzavÅeme vnitÅnà obsah do závorek: <(.*?)>.
Nynà ve výsledném poli zÃskáme jak znaÄku jako celek <h1>, tak jejà obsah h1:
let ÅetÄzec = '<h1>Ahoj, svÄte!</h1>';
let znaÄka = ÅetÄzec.match(/<(.*?)>/);
alert( znaÄka[0] ); // <h1>
alert( znaÄka[1] ); // h1
VnoÅené skupiny
Závorky mohou být vnoÅené. I v takovém pÅÃpadÄ se ÄÃslujà zleva doprava.
NapÅÃklad když hledáme znaÄku ve <span class="moje">, může nás zajÃmat:
- Obsah znaÄky jako celek:
span class="moje". - Název znaÄky:
span. - Atributy znaÄky:
class="moje".
PÅidejme pro nÄ závorky: <(([a-z]+)\s*([^>]*))>.
Budou oÄÃslovány následovnÄ (zleva doprava podle otevÃracà závorky):
V akci:
let ÅetÄzec = '<span class="moje">';
let rv = /<(([a-z]+)\s*([^>]*))>/;
let výsledek = ÅetÄzec.match(rv);
alert(výsledek[0]); // <span class="moje">
alert(výsledek[1]); // span class="moje"
alert(výsledek[2]); // span
alert(výsledek[3]); // class="moje"
Nulový index pole výsledek obsahuje vždy celou shodu.
Pak následujà skupiny, oÄÃslované zleva doprava podle otevÃracà závorky. Prvnà skupina je vrácena ve výsledek[1]. V tomto pÅÃpadÄ obsahuje celý obsah znaÄky.
Pak výsledek[2] obsahuje skupinu ze druhé otevÃracà závorky ([a-z]+) â název znaÄky, pak výsledek[3] obsahuje znaÄku: ([^>]*).
Obsah každé skupiny v ÅetÄzci:
Nepovinné skupiny
I když je skupina nepovinná a ve shodÄ se nevyskytuje (napÅ. má kvantifikátor (...)?), odpovÃdajÃcà prvek pole výsledek bude pÅÃtomen a bude se rovnat undefined.
Uvažujme napÅÃklad regulárnà výraz a(z)?(c)?. Hledá "a", po nÄmž může následovat "z", po nÄmž může následovat "c".
Pokud jej spustÃme na ÅetÄzci obsahujÃcÃm jediné pÃsmeno a, výsledek bude:
let shoda = 'a'.match(/a(z)?(c)?/);
alert( shoda.length ); // 3
alert( shoda[0] ); // a (celá shoda)
alert( shoda[1] ); // undefined
alert( shoda[2] ); // undefined
Pole má délku 3, ale všechny skupiny jsou prázdné.
A zde je složitÄjšà shoda pro ÅetÄzec ac:
let shoda = 'ac'.match(/a(z)?(c)?/)
alert( shoda.length ); // 3
alert( shoda[0] ); // ac (celá shoda)
alert( shoda[1] ); // undefined, protože pro (z)? tam nic nenÃ
alert( shoda[2] ); // c
Délka pole je stále stejná: 3. Pro skupinu (z)? tam vÅ¡ak nic nenÃ, takže výsledek je ["ac", undefined, "c"].
Hledánà všech shod se skupinami: matchAll
matchAll je nová metoda, možná bude zapotÅebà polyfillMetoda matchAll nenà podporována ve starých prohlÞeÄÃch.
Může být zapotÅebà polyfill, napÅÃklad https://github.com/ljharb/String.prototype.matchAll.
Když hledáme vÅ¡echny shody (pÅÃznak g), metoda match nevracà obsahy skupin.
NajdÄme napÅÃklad vÅ¡echny znaÄky v ÅetÄzci:
let ÅetÄzec = '<h1> <h2>';
let znaÄky = ÅetÄzec.match(/<(.*?)>/g);
alert( znaÄky ); // <h1>,<h2>
Výsledkem je pole shod, ale bez detailů o každé z nich. V praxi vÅ¡ak zpravidla ve výsledku potÅebujeme obsahy zachytávacÃch skupin.
Abychom je zÃskali, mÄli bychom hledat metodou ÅetÄzec.matchAll(rv).
Byla do JavaScriptu pÅidána až dlouho po match jako jejà ânová a vylepÅ¡ená verzeâ.
Hledá shody stejnÄ jako match, ale jsou tady 3 rozdÃly:
- Nevracà pole, ale iterovatelný objekt.
- Pokud je uveden pÅÃznak
g, vracà každou shodu jako pole se skupinami. - Nejsou-li žádné shody, nevracÃ
null, ale prázdný iterovatelný objekt.
PÅÃklad:
let výsledky = '<h1> <h2>'.matchAll(/<(.*?)>/gi);
// výsledky nenà pole, ale iterovatelný objekt
alert(výsledky); // [object RegExp String Iterator]
alert(výsledky[0]); // undefined (*)
výsledky = Array.from(výsledky); // zmÄnÃme ho na pole
alert(výsledky[0]); // <h1>,h1 (1. znaÄka)
alert(výsledky[1]); // <h2>,h2 (2. znaÄka)
Jak vidÃme, prvnà rozdÃl je velmi důležitý, jak je ukázáno na Åádku (*). Nemůžeme naÄÃst shodu jako výsledky[0], protože tento objekt je pseudopole. Můžeme z nÄj udÄlat opravdové Array pomocà Array.from. Dalšà podrobnosti o pseudopolÃch a iterovatelných objektech naleznete v Älánku Iterovatelné objekty.
Pokud výsledky procházÃme v cyklu, nenà Array.from nutné:
let výsledky = '<h1> <h2>'.matchAll(/<(.*?)>/gi);
for(let výsledek of výsledky) {
alert(výsledek);
// prvnà alert: <h1>,h1
// druhý: <h2>,h2
}
â¦Nebo pÅi použità destrukturace:
let [znaÄka1, znaÄka2] = '<h1> <h2>'.matchAll(/<(.*?)>/gi);
Každá shoda, vrácená metodou matchAll, má stejný formát, jako by ji vrátila metoda match bez pÅÃznaku g: je to pole s dalÅ¡Ãmi vlastnostmi index (index shody v ÅetÄzci) a input (zdrojový ÅetÄzec):
let výsledky = '<h1> <h2>'.matchAll(/<(.*?)>/gi);
let [znaÄka1, znaÄka2] = výsledky;
alert( znaÄka1[0] ); // <h1>
alert( znaÄka1[1] ); // h1
alert( znaÄka1.index ); // 0
alert( znaÄka1.input ); // <h1> <h2>
matchAll iterovatelný objekt a ne pole?ProÄ je tato metoda navržena zrovna takto? Důvod je jednoduchý â kvůli optimalizaci.
Volánà matchAll neprovádà hledánÃ. MÃsto toho vrátà iterovatelný objekt, zpoÄátku bez výsledků. Hledánà je provádÄno pokaždé, kdy nad nÃm iterujeme, napÅ. v cyklu.
Bude tedy nalezeno tolik výsledků, kolik potÅebujeme, ne vÃce.
NapÅÃklad v textu může být 100 shod, ale v cyklu for..of nalezneme 5 z nich a pak se rozhodneme, že to staÄÃ, a zavoláme break. Motor pak nebude ztrácet Äas hledánÃm dalÅ¡Ãch 95 shod.
Pojmenované skupiny
Pamatovat si skupiny podle ÄÃsel je tÄžké. U jednoduchých vzorů to dokážeme, ale u složitÄjÅ¡Ãch je poÄÃtánà závorek nepraktické. Máme mnohem lepšà možnost: pojmenovat závorky.
UÄinÃme to uvedenÃm ?<jméno> hned za otevÃracà závorkou.
Hledejme napÅÃklad datum ve formátu ârok-mÄsÃc-denâ:
let rvDatum = /(?<rok>[0-9]{4})-(?<mÄsÃc>[0-9]{2})-(?<den>[0-9]{2})/;
let ÅetÄzec = "2019-04-30";
let skupiny = ÅetÄzec.match(rvDatum).groups;
alert(skupiny.rok); // 2019
alert(skupiny.mÄsÃc); // 04
alert(skupiny.den); // 30
Jak vidÃte, skupiny se nacházejà ve vlastnosti shody .groups.
Chceme-li najÃt vÅ¡echna data, můžeme pÅidat pÅÃznak g.
PotÅebujeme také matchAll k zÃskánà celých shod spoleÄnÄ se skupinami:
let rvDatum = /(?<rok>[0-9]{4})-(?<mÄsÃc>[0-9]{2})-(?<den>[0-9]{2})/g;
let ÅetÄzec = "2019-10-30 2020-01-01";
let výsledky = ÅetÄzec.matchAll(rvDatum);
for(let výsledek of výsledky) {
let {rok, mÄsÃc, den} = výsledek.groups;
alert(`${den}.${mÄsÃc}.${rok}`);
// prvnà alert: 30.10.2019
// druhý: 01.01.2020
}
Zachytávacà skupiny pÅi nahrazovánÃ
Metoda ÅetÄzec.replace(rv, náhrada), která nahrazuje vÅ¡echny shody s regulárnÃm výrazem rv v ÅetÄzci ÅetÄzec, umožÅuje použÃt obsah závorek v ÅetÄzci náhrada. To se provádà pomocà $n, kde n je ÄÃslo skupiny.
PÅÃklad:
let ÅetÄzec = "Jan Novak";
let rv = /(\w+) (\w+)/;
alert( ÅetÄzec.replace(rv, '$2, $1') ); // Novak, Jan
Odkaz na pojmenované závorky bude $<jméno>.
NapÅÃklad pÅeformátujme data z formátu ârok-mÄsÃc-denâ na âden.mÄsÃc.rokâ:
let rv = /(?<rok>[0-9]{4})-(?<mÄsÃc>[0-9]{2})-(?<den>[0-9]{2})/g;
let ÅetÄzec = "2019-10-30, 2020-01-01";
alert( ÅetÄzec.replace(rv, '$<den>.$<mÄsÃc>.$<rok>') );
// 30.10.2019, 01.01.2020
Nezachytávacà skupiny s ?:
NÄkdy potÅebujeme závorky k tomu, abychom správnÄ aplikovali kvantifikátor, ale jejich obsah nechceme mÃt ve výsledcÃch.
Skupinu můžeme vylouÄit z výsledků uvedenÃm ?: na jejÃm zaÄátku.
NapÅÃklad když chceme najÃt (go)+, ale nechceme mÃt obsah závorek (go) jako samostatný prvek pole, můžeme napsat: (?:go)+.
V následujÃcÃm pÅÃkladu zÃskáme jako samostatný prvek shody jedinÄ jméno Jan:
let ÅetÄzec = "Gogogo Jan!";
// ?: vylouÄà 'go' ze zachytávánÃ
let rv = /(?:go)+ (\w+)/i;
let výsledek = ÅetÄzec.match(rv);
alert( výsledek[0] ); // Gogogo Jan (celá shoda)
alert( výsledek[1] ); // Jan
alert( výsledek.length ); // 2 (pole neobsahuje dalšà prvky)
ShrnutÃ
Závorky seskupujà dohromady Äást regulárnÃho výrazu, takže kvantifikátor se na nÄ aplikuje jako na celek.
Závorkové skupiny jsou oÄÃslovány zleva doprava a mohou být pojmenovány pomocà (?<jméno>...).
Obsah, kterému odpovÃdá skupina, můžeme zÃskat ve výsledcÃch:
- Metoda
ÅetÄzec.matchvracà zachytávacà skupiny jen bez pÅÃznakug. - Metoda
ÅetÄzec.matchAllvracà zachytávacà skupiny vždy.
Pokud závorky nemajà jméno, je jejich obsah dostupný v poli shod podle jejich ÄÃsla. Pojmenované závorky jsou k dispozici i ve vlastnosti groups.
Obsah závorek můžeme použÃt i v nahrazovacÃm ÅetÄzci v metodÄ ÅetÄzec.replace: podle ÄÃsla $n nebo jména $<jméno>.
Skupinu můžeme vylouÄit z ÄÃslovánà uvedenÃm ?: na jejÃm zaÄátku. To použÃváme, když potÅebujeme aplikovat kvantifikátor na celou skupinu, ale nechceme ji jako samostatný prvek v poli výsledků. Na takové závorky se také nemůžeme odkazovat v nahrazovacÃm ÅetÄzci.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)