Sejauh yang saya tahu tidak ada yang namanya grup penangkap bernama dalam JavaScript. Apa cara alternatif untuk mendapatkan fungsionalitas serupa?
Sejauh yang saya tahu tidak ada yang namanya grup penangkap bernama dalam JavaScript. Apa cara alternatif untuk mendapatkan fungsionalitas serupa?
Jawaban:
ECMAScript 2018 memperkenalkan kelompok penangkap bernama ke dalam regex JavaScript.
Contoh:
const auth = 'Bearer AUTHORIZATION_TOKEN'
const { groups: { token } } = /Bearer (?<token>[^ $]*)/.exec(auth)
console.log(token) // "Prints AUTHORIZATION_TOKEN"
Jika Anda perlu mendukung browser lama, Anda dapat melakukan semuanya dengan grup penangkap normal (bernomor) yang dapat Anda lakukan dengan grup penangkap bernama, Anda hanya perlu melacak angka - yang mungkin rumit jika urutan grup penangkap dalam Anda perubahan regex.
Hanya ada dua keuntungan "struktural" dari kelompok penangkap bernama yang dapat saya pikirkan:
Dalam beberapa rasa regex (.NET dan JGSoft, sejauh yang saya tahu), Anda dapat menggunakan nama yang sama untuk grup yang berbeda di regex Anda ( lihat di sini untuk contoh di mana ini penting ). Tetapi sebagian besar rasa regex tidak mendukung fungsi ini.
Jika Anda perlu merujuk ke grup penangkap bernomor dalam situasi di mana mereka dikelilingi oleh angka, Anda bisa mendapatkan masalah. Katakanlah Anda ingin menambahkan nol ke digit dan karenanya ingin mengganti (\d)
dengan $10
. Dalam JavaScript, ini akan berfungsi (selama Anda memiliki kurang dari 10 grup yang menangkap di regex Anda), tetapi Perl akan berpikir Anda sedang mencari nomor referensi 10
bukan angka 1
, diikuti oleh a 0
. Di Perl, Anda dapat menggunakan ${1}0
dalam hal ini.
Selain itu, kelompok penangkap bernama hanya "gula sintaksis". Ini membantu untuk menggunakan grup menangkap hanya ketika Anda benar-benar membutuhkannya dan menggunakan grup yang tidak menangkap (?:...)
dalam semua keadaan lain.
Masalah yang lebih besar (menurut saya) dengan JavaScript adalah bahwa ia tidak mendukung regex verbose yang akan membuat pembuatan ekspresi reguler yang mudah dibaca dan kompleks menjadi jauh lebih mudah.
Pustaka XRegExp Steve Levithan memecahkan masalah ini.
Anda dapat menggunakan XRegExp , implementasi lintas-reguler dari ekspresi reguler yang diperbesar, dapat diperluas, lintas-browser, termasuk dukungan untuk sintaks tambahan, flag, dan metode:
s
:, untuk membuat dot cocok dengan semua karakter (alias mode dotall atau singleline), dan x
, untuk spasi bebas dan komentar (alias mode diperluas).Solusi lain yang mungkin: buat objek yang berisi nama dan indeks grup.
var regex = new RegExp("(.*) (.*)");
var regexGroups = { FirstName: 1, LastName: 2 };
Kemudian, gunakan tombol objek untuk referensi grup:
var m = regex.exec("John Smith");
var f = m[regexGroups.FirstName];
Ini meningkatkan keterbacaan / kualitas kode menggunakan hasil dari regex, tetapi bukan keterbacaan dari regex itu sendiri.
Di ES6 Anda dapat menggunakan array restrukturisasi untuk menangkap grup Anda:
let text = '27 months';
let regex = /(\d+)\s*(days?|months?|years?)/;
let [, count, unit] = regex.exec(text) || [];
// count === '27'
// unit === 'months'
Memperhatikan:
let
melewatkan nilai pertama dari array yang dihasilkan, yang merupakan keseluruhan string yang cocok|| []
setelah .exec()
akan mencegah kesalahan destrukturisasi ketika tidak ada pertandingan (karena .exec()
akan kembali null
)String.prototype.match
mengembalikan array dengan: seluruh string yang cocok di posisi 0, lalu grup apa pun setelah itu. Koma pertama mengatakan "lewati elemen di posisi 0"
RegExp.prototype.exec
lebih String.prototype.match
di tempat-tempat di mana string mungkin null
atau undefined
.
Pembaruan: Akhirnya berhasil masuk ke dalam JavaScript (ECMAScript 2018)!
Grup penangkap yang dinamai dapat membuatnya menjadi JavaScript segera.
Proposal untuk itu sudah pada tahap 3.
Grup tangkap dapat diberi nama di dalam kurung sudut menggunakan (?<name>...)
sintaks, untuk nama pengidentifikasi apa pun. Ekspresi reguler untuk kencan kemudian dapat ditulis sebagai /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
. Setiap nama harus unik dan mengikuti tata bahasa untuk ECMAScript IdentifierName .
Grup yang diberi nama dapat diakses dari properti properti grup dari hasil ekspresi reguler. Referensi bernomor untuk grup juga dibuat, seperti halnya untuk grup yang tidak disebutkan namanya. Sebagai contoh:
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'));
Memberi nama grup yang ditangkap memberikan satu hal: lebih sedikit kebingungan dengan ekspresi reguler yang kompleks.
Ini benar-benar tergantung pada kasus penggunaan Anda, tetapi mungkin pencetakan cukup regex Anda bisa membantu.
Atau Anda bisa mencoba dan mendefinisikan konstanta untuk merujuk ke grup yang Anda tangkap.
Komentar mungkin juga membantu menunjukkan kepada orang lain yang membaca kode Anda, apa yang telah Anda lakukan.
Selebihnya saya harus setuju dengan jawaban Tims.
Ada perpustakaan node.js bernama bernama-regexp yang bisa Anda gunakan dalam proyek node.js Anda (aktif di browser dengan mengemas perpustakaan dengan browserify atau skrip pengemasan lainnya). Namun, perpustakaan tidak dapat digunakan dengan ekspresi reguler yang berisi grup penangkap tanpa nama.
Jika Anda menghitung kawat penangkap pembuka dalam ekspresi reguler Anda, Anda dapat membuat pemetaan antara grup penangkap yang diberi nama dan grup penangkap yang diberi nomor di regex Anda dan dapat mencampur dan mencocokkan secara bebas. Anda hanya perlu menghapus nama grup sebelum menggunakan regex. Saya telah menulis tiga fungsi yang menunjukkan itu. Lihat inti ini: https://gist.github.com/gbirke/2cc2370135b665eee3ef
Seperti yang dikatakan Tim Pietzcker , ECMAScript 2018 memperkenalkan kelompok penangkap bernama ke dalam regex JavaScript. Tetapi apa yang saya tidak temukan dalam jawaban di atas adalah bagaimana menggunakan kelompok yang ditangkap nama di regex itu sendiri.
Anda dapat menggunakan nama kelompok yang diambil dengan sintaks ini: \k<name>
. sebagai contoh
var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/
dan seperti yang dikatakan Forivin , Anda dapat menggunakan grup yang ditangkap di hasil objek sebagai berikut:
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>
Meskipun Anda tidak dapat melakukan ini dengan JavaScript vanilla, mungkin Anda dapat menggunakan beberapa Array.prototype
fungsi seperti Array.prototype.reduce
mengubah kecocokan yang diindeks menjadi yang bernama menggunakan beberapa sihir .
Jelas, solusi berikut akan membutuhkan pencocokan agar:
// @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
objek dengan menambahkan fungsi ke prototipe-nya.
Tidak punya ECMAScript 2018?
Tujuan saya adalah membuatnya bekerja semirip mungkin dengan yang biasa kami lakukan dengan grup yang disebutkan. Sedangkan dalam ECMAScript 2018 Anda dapat menempatkan ?<groupname>
di dalam grup untuk menunjukkan grup bernama, dalam solusi saya untuk javascript yang lebih lama, Anda dapat menempatkan (?!=<groupname>)
di dalam grup untuk melakukan hal yang sama. Jadi ini adalah seperangkat kurung tambahan dan tambahan !=
. Cukup dekat!
Saya membungkus semuanya menjadi fungsi prototipe string
fitur
Instruksi
(?!={groupname})
di dalam setiap grup yang ingin Anda beri nama()
dengan menempatkan ?:
di awal kelompok itu. Ini tidak akan disebutkan namanya.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;
}, {});
};
pemakaian
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
}
hasil o
{
"house number": "123",
"street name": "Main",
"street type": "St"
}