Zachycování skupin

Část vzoru může být uzavřena v závorkách (...) . Toto se nazývá „skupina zachycení“.

To má dva efekty:

  1. Umožňuje získat část shody jako samostatnou položku v poli výsledků.
  2. Pokud za závorku vložíme kvantifikátor, použije se na závorku jako celek.

Příklady

Podívejme se, jak závorky fungují v příkladech.

Příklad:gogogo

Bez závorek, vzor go+ znamená g znak následovaný o opakovat jednou nebo vícekrát. Například goooo nebo gooooooooo .

Závorky seskupují znaky dohromady, takže (go)+ znamená go , gogo , gogogo a tak dále.

alert( 'Gogogo now!'.match(/(go)+/ig) ); // "Gogogo"

Příklad:doména

Udělejme něco složitějšího – regulární výraz pro hledání domény webu.

Například:

mail.com
users.mail.com
smith.users.mail.com

Jak vidíme, doména se skládá z opakovaných slov, tečka za každým kromě toho posledního.

V regulárních výrazech je to (\w+\.)+\w+ :

let regexp = /(\w+\.)+\w+/g;

alert( "site.com my.site.com".match(regexp) ); // site.com,my.site.com

Vyhledávání funguje, ale vzor nemůže odpovídat doméně se spojovníkem, např. my-site.com , protože pomlčka nepatří do třídy \w .

Můžeme to opravit nahrazením \w s [\w-] v každém slově kromě posledního:([\w-]+\.)+\w+ .

Příklad:e-mail

Předchozí příklad lze rozšířit. Na jeho základě můžeme vytvořit regulární výraz pro e-maily.

Formát e-mailu je:name@domain . Jméno může být libovolné slovo, pomlčky a tečky jsou povoleny. V regulárních výrazech je to [-.\w]+ .

Vzor:

let regexp = /[-.\w]+@([\w-]+\.)+[\w-]+/g;

alert("[email protected] @ [email protected]".match(regexp)); // [email protected], [email protected]

Tento regulární výraz není dokonalý, ale většinou funguje a pomáhá opravit náhodné překlepy. Jedinou skutečně spolehlivou kontrolu e-mailu lze provést pouze zasláním dopisu.

Obsah shody v závorkách

Závorky jsou číslovány zleva doprava. Vyhledávač si zapamatuje obsah odpovídající každému z nich a umožní jej získat ve výsledku.

Metoda str.match(regexp) , pokud regexp nemá žádný příznak g , vyhledá první shodu a vrátí ji jako pole:

  1. Na indexu 0 :úplný zápas.
  2. Na indexu 1 :obsah prvních závorek.
  3. Na indexu 2 :obsah druhé závorky.
  4. …a tak dále…

Rádi bychom například našli HTML značky <.*?> a zpracovat je. Bylo by vhodné mít obsah značky (co je uvnitř úhlů) v samostatné proměnné.

Uzavřeme vnitřní obsah do závorek takto:<(.*?)> .

Nyní získáme obě značky jako celek <h1> a jeho obsah h1 ve výsledném poli:

let str = '<h1>Hello, world!</h1>';

let tag = str.match(/<(.*?)>/);

alert( tag[0] ); // <h1>
alert( tag[1] ); // h1

Vnořené skupiny

Závorky lze vnořovat. V tomto případě jde číslování také zleva doprava.

Například při hledání značky v <span class="my"> mohlo by nás zajímat:

  1. Obsah značky jako celek:span class="my" .
  2. Název značky:span .
  3. Atributy značky:class="my" .

Přidejme k nim závorky:<(([a-z]+)\s*([^>]*))> .

Zde je návod, jak jsou číslovány (zleva doprava, pomocí úvodní závorky):

V akci:

let str = '<span class="my">';

let regexp = /<(([a-z]+)\s*([^>]*))>/;

let result = str.match(regexp);
alert(result[0]); // <span class="my">
alert(result[1]); // span class="my"
alert(result[2]); // span
alert(result[3]); // class="my"

Nulový index result vždy drží plnou shodu.

Pak skupiny, očíslované zleva doprava otevírací závorkou. První skupina je vrácena jako result[1] . Zde uzavírá celý obsah tagu.

Poté v result[2] jde skupina z druhé úvodní závorky ([a-z]+) – název značky, poté v result[3] tag:([^>]*) .

Obsah každé skupiny v řetězci:

Volitelné skupiny

I když je skupina volitelná a ve shodě neexistuje (např. má kvantifikátor (...)? ), odpovídající result položka pole je přítomna a rovná se undefined .

Vezměme si například regulární výraz a(z)?(c)? . Hledá "a" volitelně následovaný "z" volitelně následovaný "c" .

Pokud jej spustíme na řetězci s jedním písmenem a , pak výsledek je:

let match = 'a'.match(/a(z)?(c)?/);

alert( match.length ); // 3
alert( match[0] ); // a (whole match)
alert( match[1] ); // undefined
alert( match[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 match = 'ac'.match(/a(z)?(c)?/)

alert( match.length ); // 3
alert( match[0] ); // ac (whole match)
alert( match[1] ); // undefined, because there's nothing for (z)?
alert( match[2] ); // c

Délka pole je trvalá:3 . Ale pro skupinu (z)? není nic , takže výsledek je ["ac", undefined, "c"] .

Vyhledávání všech shod se skupinami:matchAll

matchAll je nová metoda, může být zapotřebí polyfill

Metoda matchAll není podporováno ve starých prohlížečích.

Může být vyžadována polyfill, například https://github.com/ljharb/String.prototype.matchAll.

Když hledáme všechny shody (příznak g ), match metoda nevrací obsah pro skupiny.

Pojďme například najít všechny značky v řetězci:

let str = '<h1> <h2>';

let tags = str.match(/<(.*?)>/g);

alert( tags ); // <h1>,<h2>

Výsledkem je řada shod, ale bez podrobností o každé z nich. Ale v praxi obvykle potřebujeme obsah zachytávacích skupin ve výsledku.

Abychom je získali, měli bychom hledat pomocí metody str.matchAll(regexp) .

Byl přidán do jazyka JavaScript dlouho po match , jako jeho „nová a vylepšená verze“.

Stejně jako match , hledá shody, ale jsou zde 3 rozdíly:

  1. Nevrací pole, ale iterovatelný objekt.
  2. Když je příznak g je přítomen, vrátí každou shodu jako pole se skupinami.
  3. Pokud neexistují žádné shody, nevrátí null , ale prázdný iterovatelný objekt.

Například:

let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi);

// results - is not an array, but an iterable object
alert(results); // [object RegExp String Iterator]

alert(results[0]); // undefined (*)

results = Array.from(results); // let's turn it into array

alert(results[0]); // <h1>,h1 (1st tag)
alert(results[1]); // <h2>,h2 (2nd tag)

Jak vidíme, první rozdíl je velmi důležitý, jak ukazuje řádek (*) . Nemůžeme najít shodu jako results[0] , protože tento objekt není pseudoarray. Můžeme z něj udělat skutečný Array pomocí Array.from . Další podrobnosti o pseudopolích a iterovatelných položkách naleznete v článku Iterables.

Array.from není potřeba pokud procházíme výsledky:

let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi);

for(let result of results) {
  alert(result);
  // first alert: <h1>,h1
  // second: <h2>,h2
}

…Nebo pomocí ničení:

let [tag1, tag2] = '<h1> <h2>'.matchAll(/<(.*?)>/gi);

Každá shoda vrácena matchAll , má stejný formát jako match bez příznaku g :je to pole s dalšími vlastnostmi index (shoda indexu v řetězci) a input (zdrojový řetězec):

let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi);

let [tag1, tag2] = results;

alert( tag1[0] ); // <h1>
alert( tag1[1] ); // h1
alert( tag1.index ); // 0
alert( tag1.input ); // <h1> <h2>
Proč je výsledek matchAll iterovatelný objekt, nikoli pole?

Proč je metoda navržena tak? Důvod je jednoduchý – optimalizace.

Volání na číslo matchAll neprovede vyhledávání. Místo toho vrátí iterovatelný objekt bez počátečních výsledků. Vyhledávání se provádí pokaždé, když přes něj iterujeme, např. ve smyčce.

Bude tedy nalezeno tolik výsledků, kolik je potřeba, ne více.

Např. v textu je potenciálně 100 shod, ale v for..of smyčky, našli jsme jich 5, pak jsme se rozhodli, že to stačí, a vytvořili jsme break . Pak motor nebude trávit čas hledáním dalších 95 shod.

Pojmenované skupiny

Zapamatovat si skupiny podle jejich počtu je těžké. U jednoduchých vzorů je to proveditelné, ale u složitějších je počítání závorek nepohodlné. Máme mnohem lepší možnost:dát jména do závorek.

To se provede zadáním ?<name> bezprostředně za úvodní závorkou.

Hledejme například datum ve formátu „rok-měsíc-den“:

let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
let str = "2019-04-30";

let groups = str.match(dateRegexp).groups;

alert(groups.year); // 2019
alert(groups.month); // 04
alert(groups.day); // 30

Jak můžete vidět, skupiny sídlí v .groups vlastnost zápasu.

Chcete-li vyhledat všechna data, můžeme přidat příznak g .

Budeme také potřebovat matchAll k získání úplných zápasů spolu se skupinami:

let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g;

let str = "2019-10-30 2020-01-01";

let results = str.matchAll(dateRegexp);

for(let result of results) {
  let {year, month, day} = result.groups;

  alert(`${day}.${month}.${year}`);
  // first alert: 30.10.2019
  // second: 01.01.2020
}

Nahrazení skupin

Metoda str.replace(regexp, replacement) který nahradí všechny shody kódem regexp v str umožňuje používat obsah závorek v replacement tětiva. To se provádí pomocí $n , kde n je číslo skupiny.

Například,

let str = "John Bull";
let regexp = /(\w+) (\w+)/;

alert( str.replace(regexp, '$2, $1') ); // Bull, John

Pro pojmenované závorky bude odkaz $<name> .

Přeformátujme například data z „rok-měsíc-den“ na „den.měsíc.rok“:

let regexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g;

let str = "2019-10-30, 2020-01-01";

alert( str.replace(regexp, '$<day>.$<month>.$<year>') );
// 30.10.2019, 01.01.2020

Nezachycující skupiny s ?:

Někdy potřebujeme závorky, abychom správně použili kvantifikátor, ale nechceme, aby jejich obsah byl ve výsledcích.

Skupinu lze vyloučit přidáním ?: na začátku.

Například, pokud chceme najít (go)+ , ale nechcete obsah závorek (go ) jako samostatnou položku pole můžeme napsat:(?:go)+ .

V níže uvedeném příkladu dostaneme pouze název John jako samostatný člen zápasu:

let str = "Gogogo John!";

// ?: excludes 'go' from capturing
let regexp = /(?:go)+ (\w+)/i;

let result = str.match(regexp);

alert( result[0] ); // Gogogo John (full match)
alert( result[1] ); // John
alert( result.length ); // 2 (no more items in the array)

Shrnutí

Závorky seskupují část regulárního výrazu, takže kvantifikátor se na něj vztahuje jako na celek.

Skupiny v závorkách jsou číslovány zleva doprava a volitelně je lze pojmenovat (?<name>...) .

Obsah odpovídající skupině lze získat ve výsledcích:

  • Metoda str.match vrací zachycené skupiny pouze bez příznaku g .
  • Metoda str.matchAll vždy vrátí zachycující skupiny.

Pokud závorky nemají název, pak je jejich obsah dostupný v poli shody podle čísla. Pojmenované závorky jsou také dostupné ve vlastnosti groups .

Můžeme také použít obsah závorek v náhradním řetězci v str.replace :číslem $n nebo název $<name> .

Skupinu lze z číslování vyloučit přidáním ?: ve svém startu. To se používá, když potřebujeme použít kvantifikátor na celou skupinu, ale nechceme ho jako samostatnou položku v poli výsledků. Na takové závorky také nemůžeme odkazovat v náhradním řetězci.