O ile wiem, nie ma czegoś takiego jak nazwane grupy przechwytywania w JavaScript. Jaki jest alternatywny sposób uzyskania podobnej funkcjonalności?
O ile wiem, nie ma czegoś takiego jak nazwane grupy przechwytywania w JavaScript. Jaki jest alternatywny sposób uzyskania podobnej funkcjonalności?
Odpowiedzi:
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ć:
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.
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 10
zamiast numeru 1
, po którym następuje 0
. W Perlu możesz użyć ${1}0
w 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.
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:
s
wyraż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).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.
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:
let
pomija 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
)String.prototype.match
zwraca 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”
RegExp.prototype.exec
przejęcia String.prototype.match
w miejscach, gdzie mogą być łańcuch null
lub undefined
.
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';
let {year, month, day} = ((result) => ((result) ? result.groups : {}))(re.exec('2015-01-02'));
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.
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
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>
Chociaż nie możesz tego zrobić za pomocą waniliowego JavaScript, być może możesz użyć Array.prototype
funkcji, takiej jak Array.prototype.reduce
zamienianie 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));
var assocArray = Regex("hello alex, I am dennis", "hello ({hisName}.+), I am ({yourName}.+)");
RegExp
obiekt, dodając funkcję do jego prototypu.
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
Instrukcje
(?!={groupname})
w każdej grupie, którą chcesz nazwać()
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"
}