Nazwane grupy przechwytywania w wyrażeniu regularnym JavaScript?


208

O ile wiem, nie ma czegoś takiego jak nazwane grupy przechwytywania w JavaScript. Jaki jest alternatywny sposób uzyskania podobnej funkcjonalności?


1
Grupy przechwytywania w javascript są numerowane. 1 USD to pierwsza grupa przechwycona, 2 USD, 3 USD ... do 99 USD, ale wygląda na to, że chcesz czegoś innego - co nie istnieje
Erik

24
@Erik, mówisz o ponumerowanych grupach przechwytujących, OP mówi o nazwanych grupach przechwytujących. Istnieją, ale chcemy wiedzieć, czy istnieje wsparcie dla nich w JS.
Alba Mendez,

4
Istnieje propozycja wprowadzenia nazwanego wyrażenia regularnego do JavaScript , ale może minąć wiele lat, zanim to zobaczymy, jeśli kiedykolwiek to zrobimy.
fregante

Firefox ukarał mnie za próbę użycia nazwanych grup przechwytywania na stronie ... naprawdę moja wina. stackoverflow.com/a/58221254/782034
Nick Grealy

Odpowiedzi:


134

ECMAScript 2018 wprowadza nazwane grupy przechwytywania do wyrażeń regularnych JavaScript.

Przykład:

  const auth = 'Bearer AUTHORIZATION_TOKEN'
  const { groups: { token } } = /Bearer (?<token>[^ $]*)/.exec(auth)
  console.log(token) // "Prints AUTHORIZATION_TOKEN"

Jeśli potrzebujesz obsługi starszych przeglądarek, możesz zrobić wszystko z normalnymi (numerowanymi) grupami przechwytywania, co możesz zrobić z nazwanymi grupami przechwytywania, wystarczy śledzić liczby - co może być kłopotliwe, jeśli kolejność przechwytywania grupy w twojej regex zmiany.

Są tylko dwie „strukturalne” zalety nazwanych grup przechwytywania, o których mogę myśleć:

  1. W niektórych odmianach wyrażeń regularnych (.NET i JGSoft, o ile wiem) możesz używać tej samej nazwy dla różnych grup w swoim wyrażeniu regularnym ( zobacz tutaj, gdzie to ma znaczenie ). Ale większość smaków wyrażeń regularnych i tak nie obsługuje tej funkcji.

  2. Jeśli potrzebujesz odwoływać się do numerowanych grup przechwytywania w sytuacji, gdy są otoczone cyframi, możesz mieć problem. Powiedzmy, że chcesz dodać zero do cyfry i dlatego chce wymienić (\d)z $10. W JavaScript to zadziała (pod warunkiem, że masz mniej niż 10 grup przechwytujących w wyrażeniu regularnym), ale Perl pomyśli, że szukasz numeru referencji 10zamiast numeru 1, po którym następuje 0. W Perlu możesz użyć ${1}0w tym przypadku.

Poza tym, nazwane grupy przechwytujące to po prostu „cukier składniowy”. Pomaga używać grup przechwytywania tylko wtedy, gdy naprawdę ich potrzebujesz i używać grup nie przechwytujących (?:...)we wszystkich innych okolicznościach.

Większy problem (moim zdaniem) z JavaScriptem polega na tym, że nie obsługuje pełnych wyrażeń regularnych, co znacznie ułatwiłoby tworzenie czytelnych, złożonych wyrażeń regularnych.

Biblioteka XRegExp Steve'a Levithana rozwiązuje te problemy.


5
Wiele smaków pozwala na wielokrotne używanie tej samej nazwy grupy przechwytywania w wyrażeniu regularnym. Ale tylko .NET i Perl 5.10+ sprawiają, że jest to szczególnie przydatne, ponieważ przechwytuje wartość przechwyconą przez ostatnią grupę nazwy, która uczestniczyła w meczu.
slevithan

103
Ogromną zaletą jest to, że możesz po prostu zmienić RegExp, bez mapowania liczb na zmienne. Grupy nie przechwytujące rozwiązują ten problem, z wyjątkiem jednego przypadku: co się stanie, jeśli zmieni się kolejność grup? Poza tym denerwujące jest umieszczenie tych dodatkowych znaków w innych grupach ...
Alba Mendez

55
Tzw cukier syntaktyczny robi pomoc słodzenia czytelności kodu!
Mrchief

1
Myślę, że istnieje inny powód, dla którego nazwane grupy przechwytywania są naprawdę cenne. Na przykład, jeśli chcesz użyć wyrażenia regularnego do parsowania daty z ciągu, możesz napisać elastyczną funkcję, która pobiera wartość i wyrażenie regularne. Tak długo, jak regex nazywa przechwytywania dla roku, miesiąca i daty, możesz przeglądać tablicę wyrażeń regularnych z minimalnym kodem.
Dewey Vozel

4
Od października 2019 r. Firefox, IE 11 i Microsoft Edge (przed Chromium) nie obsługują przechwytywania nazwanych grup. Większość innych przeglądarek (nawet Opera i Samsung Mobile) działa. caniuse.com/…
JDB wciąż pamięta Monicę

63

Możesz użyć XRegExp , rozszerzonej, rozszerzalnej implementacji wyrażeń regularnych w różnych przeglądarkach, w tym obsługi dodatkowej składni, flag i metod:

  • Dodaje nową składnię wyrażeń regularnych i zastępczych, w tym kompleksową obsługę przechwytywania nazwanego .
  • Dodaje dwie nowe flagi swyrażeń regularnych:, aby kropka pasowała do wszystkich znaków (inaczej tryb dotall lub singleline), orazx , dla wolnych odstępów i komentarzy (aka tryb rozszerzony).
  • Zapewnia zestaw funkcji i metod, dzięki którym złożone przetwarzanie wyrażeń regularnych jest dziecinnie proste.
  • Automagicznie naprawia najczęściej spotykane niespójności w różnych przeglądarkach w zakresie wyrażeń regularnych i składni.
  • Umożliwia łatwe tworzenie i używanie wtyczek, które dodają nową składnię i flagi do języka wyrażeń regularnych XRegExp.

60

Inne możliwe rozwiązanie: utwórz obiekt zawierający nazwy grup i indeksy.

var regex = new RegExp("(.*) (.*)");
var regexGroups = { FirstName: 1, LastName: 2 };

Następnie użyj kluczy obiektu, aby odnieść się do grup:

var m = regex.exec("John Smith");
var f = m[regexGroups.FirstName];

Poprawia to czytelność / jakość kodu przy użyciu wyników wyrażenia regularnego, ale nie czytelność samego wyrażenia regularnego.


58

W ES6 możesz użyć restrukturyzacji tablic, aby złapać swoje grupy:

let text = '27 months';
let regex = /(\d+)\s*(days?|months?|years?)/;
let [, count, unit] = regex.exec(text) || [];

// count === '27'
// unit === 'months'

Ogłoszenie:

  • pierwszy przecinek w ostatnim letpomija pierwszą wartość wynikowej tablicy, czyli cały dopasowany ciąg
  • || []po .exec()zapobiegnie destructuring błąd, gdy nie znaleziono żadnego meczu (bo .exec()wróci null)

1
Pierwszy przecinek wynika z tego, że pierwszym elementem tablicy zwróconym przez dopasowanie jest wyrażenie wejściowe, prawda?
Emilio Grisolía

1
String.prototype.matchzwraca tablicę z: całym dopasowanym łańcuchem w pozycji 0, a następnie dowolnymi grupami po tym. Pierwszy przecinek mówi „pomiń element w pozycji 0”
fregante

2
Moja ulubiona odpowiedź tutaj dla osób z transpilacją lub celami ES6 +. Niekoniecznie zapobiega to błędom niezgodności, a także nazwanym indeksom, jeśli np. Ponownie użyte wyrażenia regularne zmienią się, ale myślę, że zwięzłość tutaj łatwo to zrekompensuje. Ja zdecydowaliśmy się na RegExp.prototype.execprzejęcia String.prototype.matchw miejscach, gdzie mogą być łańcuch nulllub undefined.
Mike Hill

22

Aktualizacja: w końcu został włączony do JavaScript (ECMAScript 2018)!


Nazwane grupy przechwytujące mogą wkrótce znaleźć się w JavaScript.
Propozycja jest już na etapie 3.

Grupie przechwytywania można nadać nazwę w nawiasach kątowych za pomocą (?<name>...)składni dla dowolnej nazwy identyfikatora. Wyrażenie regularne dla daty można następnie zapisać jako /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u. Każda nazwa powinna być unikalna i zgodna z gramatyką dla ECMAScript IdentifierName .

Dostęp do nazwanych grup można uzyskać z właściwości właściwości grup wyniku wyrażenia regularnego. Tworzone są również numerowane odniesienia do grup, tak jak w przypadku grup nienazwanych. Na przykład:

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';

// result[0] === '2015-01-02';
// result[1] === '2015';
// result[2] === '01';
// result[3] === '02';

Obecnie jest to propozycja etapu 4.
GOTO 0

jeśli używasz '18, równie dobrze może się przydać restrukturyzacja; let {year, month, day} = ((result) => ((result) ? result.groups : {}))(re.exec('2015-01-02'));
Hashbrown

6

Nazewnictwo przechwyconych grup zapewnia jedną rzecz: mniej zamieszania ze złożonymi wyrażeniami regularnymi.

To naprawdę zależy od przypadku użycia, ale być może ładne wydrukowanie wyrażenia regularnego może pomóc.

Możesz też spróbować zdefiniować stałe w celu odniesienia do przechwyconych grup.

Komentarze mogą wtedy również pomóc pokazać innym, którzy czytają Twój kod, co zrobiłeś.

Co do reszty, muszę zgodzić się z odpowiedzią Tims.


5

Istnieje biblioteka node.js o nazwie named-regexp , której można użyć w projektach node.js (włączonej w przeglądarce, pakując bibliotekę w przeglądarkę lub inne skrypty do pakowania). Jednak biblioteki nie można używać z wyrażeniami regularnymi, które zawierają niewymienione grupy przechwytujące.

Jeśli policzysz otwierające nawiasy klamrowe w wyrażeniu regularnym, możesz utworzyć odwzorowanie między nazwanymi grupami przechwytującymi a ponumerowanymi grupami przechwytującymi w wyrażeniu regularnym i możesz dowolnie mieszać i dopasowywać. Musisz tylko usunąć nazwy grup przed użyciem wyrażenia regularnego. Napisałem trzy funkcje, które to pokazują. Zobacz tę treść: https://gist.github.com/gbirke/2cc2370135b665eee3ef


To zaskakujące lekkie, spróbuję
fregante

Czy działa z zagnieżdżonymi nazwanymi grupami wewnątrz grup regularnych w złożonych wyrażeniach regularnych?
ElSajko

To nie jest idealne. Błąd, gdy: getMap ("((a | b (: <foo> c)))"); foo powinna być trzecią grupą, a nie drugą. /((a|b(c)))/g.exec("bc "); [„bc”, „bc”, „bc”, „c”]
ElSajko,

3

Jak powiedział Tim Pietzcker , ECMAScript 2018 wprowadza nazwane grupy przechwytywania do wyrażeń regularnych JavaScript. Ale w powyższych odpowiedziach nie znalazłem sposobu użycia nazwanej przechwyconej grupy w samym wyrażeniu regularnym.

można użyć nazwie przechwycony grupę z tej składni: \k<name>. na przykład

var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/

i jak powiedział Forivin , możesz użyć przechwyconej grupy w wyniku obiektu w następujący sposób:

let result = regexObj.exec('2019-28-06 year is 2019');
// result.groups.year === '2019';
// result.groups.month === '06';
// result.groups.day === '28';

  var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/mgi;

function check(){
    var inp = document.getElementById("tinput").value;
    let result = regexObj.exec(inp);
    document.getElementById("year").innerHTML = result.groups.year;
    document.getElementById("month").innerHTML = result.groups.month;
    document.getElementById("day").innerHTML = result.groups.day;
}
td, th{
  border: solid 2px #ccc;
}
<input id="tinput" type="text" value="2019-28-06 year is 2019"/>
<br/>
<br/>
<span>Pattern: "(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>";
<br/>
<br/>
<button onclick="check()">Check!</button>
<br/>
<br/>
<table>
  <thead>
    <tr>
      <th>
        <span>Year</span>
      </th>
      <th>
        <span>Month</span>
      </th>
      <th>
        <span>Day</span>
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        <span id="year"></span>
      </td>
      <td>
        <span id="month"></span>
      </td>
      <td>
        <span id="day"></span>
      </td>
    </tr>
  </tbody>
</table>


2

Chociaż nie możesz tego zrobić za pomocą waniliowego JavaScript, być może możesz użyć Array.prototypefunkcji, takiej jak Array.prototype.reducezamienianie indeksowanych dopasowań w nazwane przy użyciu magii .

Oczywiście następujące rozwiązanie będzie wymagało, aby dopasowania występowały w kolejności:

// @text Contains the text to match
// @regex A regular expression object (f.e. /.+/)
// @matchNames An array of literal strings where each item
//             is the name of each group
function namedRegexMatch(text, regex, matchNames) {
  var matches = regex.exec(text);

  return matches.reduce(function(result, match, index) {
    if (index > 0)
      // This substraction is required because we count 
      // match indexes from 1, because 0 is the entire matched string
      result[matchNames[index - 1]] = match;

    return result;
  }, {});
}

var myString = "Hello Alex, I am John";

var namedMatches = namedRegexMatch(
  myString,
  /Hello ([a-z]+), I am ([a-z]+)/i, 
  ["firstPersonName", "secondPersonName"]
);

alert(JSON.stringify(namedMatches));


To fajnie. Po prostu myślę ... czy nie byłoby możliwe utworzenie funkcji wyrażenia regularnego, która akceptuje niestandardowe wyrażenie regularne? var assocArray = Regex("hello alex, I am dennis", "hello ({hisName}.+), I am ({yourName}.+)");
Abyś

@Forivin Oczywiście możesz pójść dalej i rozwinąć tę funkcję. Nie byłoby trudno uruchomić: D
Matías Fidemraizer

Możesz rozszerzyć RegExpobiekt, dodając funkcję do jego prototypu.
Pan TA

@ Mr.TA AFAIK, nie zaleca się rozszerzania wbudowanych obiektów
Matías Fidemraizer

0

Nie masz ECMAScript 2018?

Moim celem było, aby działał jak najbardziej zbliżony do tego, do czego jesteśmy przyzwyczajeni z nazwanymi grupami. Podczas gdy w ECMAScript 2018 możesz umieścić ?<groupname>wewnątrz grupy, aby wskazać nazwaną grupę, w moim rozwiązaniu dla starszego javascript możesz umieścić (?!=<groupname>)wewnątrz grupy, aby zrobić to samo. Jest to więc dodatkowy zestaw nawiasów i dodatkowy !=. Całkiem blisko!

Owinęłam to wszystko w funkcję prototypu łańcucha

cechy

  • działa ze starszym javascript
  • bez dodatkowego kodu
  • bardzo prosty w użyciu
  • Regex nadal działa
  • grupy są dokumentowane w samym wyrażeniu regularnym
  • nazwy grup mogą mieć spacje
  • zwraca obiekt z wynikami

Instrukcje

  • umieść (?!={groupname})w każdej grupie, którą chcesz nazwać
  • pamiętaj, aby wyeliminować wszystkie grupy, ()których nie udało się przechwycić, umieszczając je ?:na początku tej grupy. Nie zostaną nazwane.

arrays.js

// @@pattern - includes injections of (?!={groupname}) for each group
// @@returns - an object with a property for each group having the group's match as the value 
String.prototype.matchWithGroups = function (pattern) {
  var matches = this.match(pattern);
  return pattern
  // get the pattern as a string
  .toString()
  // suss out the groups
  .match(/<(.+?)>/g)
  // remove the braces
  .map(function(group) {
    return group.match(/<(.+)>/)[1];
  })
  // create an object with a property for each group having the group's match as the value 
  .reduce(function(acc, curr, index, arr) {
    acc[curr] = matches[index + 1];
    return acc;
  }, {});
};    

stosowanie

function testRegGroups() {
  var s = '123 Main St';
  var pattern = /((?!=<house number>)\d+)\s((?!=<street name>)\w+)\s((?!=<street type>)\w+)/;
  var o = s.matchWithGroups(pattern); // {'house number':"123", 'street name':"Main", 'street type':"St"}
  var j = JSON.stringify(o);
  var housenum = o['house number']; // 123
}

wynik o

{
  "house number": "123",
  "street name": "Main",
  "street type": "St"
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.