Іменовані групи захоплення в регулярному виразі JavaScript?


208

Наскільки я знаю, немає такого поняття, як названі групи захоплення в JavaScript. Який альтернативний спосіб отримати подібний функціонал?


1
Групи захоплення в JavaScript перебувають за номером .. $ 1 - перша захоплена група, $ 2, $ 3 ... до $ 99, але це здається, що ви хочете чогось іншого - чого не існує
Ерік

24
@Erik, ви говорите про нумеровані групи захоплення, OP - про названі групи захоплення. Вони існують, але ми хочемо знати, чи є підтримка в JS.
Альба Мендес

4
Є пропозиція включити названий регекс в JavaScript , але це може пройти через роки, коли ми це побачимо.
fregante

Firefox покарав мене за те, що я намагався використовувати названі групи захоплення на веб-сайті ... Моя власна вина. stackoverflow.com/a/58221254/782034
Нік Грілі

Відповіді:


134

ECMAScript 2018 вводить названі групи захоплення в регулярні вирази JavaScript.

Приклад:

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

Якщо вам потрібно підтримувати старі веб-переглядачі, ви можете робити все із звичайними (пронумерованими) групами захоплення, які ви можете зробити з названими групами захоплення, вам просто потрібно слідкувати за номерами - що може бути громіздким, якщо порядок захоплення групи у вашій зміни регулярного вираження.

Є лише дві "структурні" переваги названих груп захоплення, про які я можу придумати:

  1. У деяких ароматах регулярного вираження (.NET і JGSoft, наскільки я знаю), ви можете використовувати те саме ім'я для різних груп у вашому регулярному виразі ( див. Тут приклад, де це має значення ). Але більшість ароматів регулярного вираження так чи інакше не підтримують цю функціональність.

  2. Якщо вам потрібно посилатися на нумеровані групи захоплення в ситуації, коли вони оточені цифрами, ви можете отримати проблему. Скажімо, ви хочете додати нуль до цифри і тому хочете замінити (\d)на $10. У JavaScript це буде працювати (доки у вас в регексе буде менше 10 груп захоплення), але Perl подумає, що ви шукаєте номер зворотної референції 10замість номера 1, а потім - a 0. У Perl ви можете використовувати ${1}0в цьому випадку.

Крім того, названі групи захоплення - це лише «синтаксичний цукор». Це допомагає використовувати групи захоплення лише тоді, коли вони вам справді потрібні, а також використовувати групи, які не захоплюють, (?:...)за будь-яких інших обставин.

Більшою проблемою (на мою думку) у JavaScript є те, що він не підтримує багатослівних регулярних виразів, що значно спростить створення читаних, складних регулярних виразів.

Бібліотека XRegExp Стіва Левітана вирішує ці проблеми.


5
Багато аромати дозволяють використовувати одне і те ж ім'я групи захоплення кілька разів у регулярному вираженні. Але лише .NET і Perl 5.10+ роблять це особливо корисним, зберігаючи значення, захоплені останньою групою імені, яка брала участь у матчі.
slevithan

103
Величезна перевага полягає в тому, що ви можете просто змінити своє RegExp, не відображаючи число до змінної. Групи, що не захоплюють, вирішують цю проблему, за винятком одного випадку: що робити, якщо зміниться порядок угруповань? Крім того, це анонімно розміщувати ці додаткові ознаки на інших групах ...
Альба Мендес

55
Так званий синтаксичний цукор робить допомогу підсолодити читаність коду!
Mrchief

1
Я думаю, що є ще одна причина названих груп захоплення, яка є дійсно цінною. Наприклад, якщо ви хочете використовувати регулярний вираз для розбору дати з рядка, ви можете написати гнучку функцію, яка приймає значення і регулярний вираз. Поки регулярний вираз має назву фіксує рік, місяць і дату, ви можете пропустити масив регулярних виразів з мінімальним кодом.
Дьюї Возель

4
Станом на жовтень 2019 року Firefox, IE 11 та Microsoft Edge (до-Chromium) не підтримують названі групові захоплення. Більшість інших браузерів (навіть Opera і Samsung для мобільних пристроїв). caniuse.com/…
JDB все ще пам’ятає Моніку

63

Ви можете використовувати XRegExp , розширену, розширювану, крос-браузерну реалізацію регулярних виразів, включаючи підтримку додаткових синтаксисів, прапорів та методів:

  • Додає новий синтаксис тексту регулярного вираження та заміни, включаючи комплексну підтримку імені захоплення .
  • Додає два нові прапорці регулярних sвиразів:, щоб зрівняти крапку з усіма символами (також режимом доталі або однолінійного режиму), а також xдля вільного проміжку та коментарів (також розширений режим).
  • Забезпечує набір функцій і методів, що дозволяють складно обробляти регулярні викиди вітром.
  • Автоматично виправляє найпоширеніші невідповідності між веб-переглядачами у поведінці та синтаксисі.
  • Дозволяє легко створювати та використовувати плагіни, які додають новий синтаксис та прапори до мови регулярних виразів XRegExp.

60

Ще одне можливе рішення: створити об’єкт, що містить назви груп та індекси.

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

Потім використовуйте клавіші об'єкта для посилання на групи:

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

Це покращує читабельність / якість коду за допомогою результатів регулярного вираження, але не читабельність самого регулярного виразу.


58

У ES6 ви можете використовувати деструктування масиву, щоб спіймати ваші групи:

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

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

Зверніть увагу:

  • перша кома в останньому letпропускає перше значення результуючого масиву, яке є всім узгодженим рядком
  • || []після .exec()запобіжить помилки деструктурірующіе коли немає матчів (бо .exec()повернеться null)

1
Перша кома полягає в тому, що перший елемент масиву, що повертається сіркою, є вхідним виразом, правда?
Еміліо Грисолія

1
String.prototype.matchповертає масив з: всього узгодженого рядка в позиції 0, а потім будь-яких груп після цього. Перша кома говорить "пропустіть елемент у позиції 0"
fregante

2
Моя улюблена відповідь тут для тих, хто має трансліпінг або ES6 + цілі. Це не обов'язково запобігає непослідовності помилок, а також названих індексів, якщо, наприклад, повторно використаний регулярний вираз зміниться, але я думаю, що стислість тут легко компенсує. Я вибрав RegExp.prototype.execбільше String.prototype.matchмісця, де може бути рядок nullабо undefined.
Майк Хілл

22

Оновлення: нарешті перетворили його на JavaScript (ECMAScript 2018)!


Названі групи захоплення можуть перетворити його на JavaScript дуже скоро.
Пропозиція щодо нього вже є на 3 етапі.

Групі захоплення можна присвоїти ім’я у кутових дужках, використовуючи (?<name>...)синтаксис, для будь-якого ідентифікатора. Регулярний вираз для дати тоді може бути записаний як/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u . Кожне ім'я повинно бути унікальним і дотримуватися граматики ECMAScript IdentifierName .

До іменованих груп можна отримати доступ із властивостей властивості груп результату регулярного виразу. Також створюються нумеровані посилання на групи, як і для неіменованих груп. Наприклад:

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';

Наразі це пропозиція на етапі 4.
GOTO 0

якщо ви використовуєте '18, може також все зійти з руйнуванням; let {year, month, day} = ((result) => ((result) ? result.groups : {}))(re.exec('2015-01-02'));
Hashbrown

6

Іменування захоплених груп забезпечує одне: менше плутанини зі складними регулярними виразами.

Це дійсно залежить від вашої справи використання, але, можливо, гарне друк вашого регексу може допомогти.

Або ви можете спробувати визначити константи для позначення захоплених груп.

Коментарі можуть також допомогти показати іншим, хто читає ваш код, що ви зробили.

В іншому я повинен погодитися з відповіддю Тімса.


5

Існує бібліотека node.js під назвою найменування-regexp яку ви могли б використовувати у своїх проектах node.js (у браузері, упаковуючи бібліотеку за допомогою браузера або інших сценаріїв упаковки). Однак бібліотеку не можна використовувати з регулярними виразами, які містять неіменовані групи захоплення.

Якщо ви підраховуєте вступні фіксуючі дужки в регулярному виразі, ви можете створити відображення між названими групами захоплення та пронумерованими групами захоплення у вашому регулярному виразі, і ви можете змішуватись і співставляти вільно. Потрібно просто видалити назви груп, перш ніж використовувати регулярний вираз. Я написав три функції, які це демонструють. Дивіться цю суть: https://gist.github.com/gbirke/2cc2370135b665eee3ef


Це дивно легкий, я спробую
fregante

Чи працює з вкладеними названими групами всередині регулярних груп у складних регулярних виразах?
ElSajko

Це не ідеально. Помилка, коли: getMap ("((a | b (: <foo> c)))"); foo має бути третьою групою, а не другою. /((a|b(c)))/g.exec("bc "); ["bc", "bc", "bc", "c"]
ElSajko

3

Як зазначив Тім Пітцкер, ECMAScript 2018 вводить названі групи захоплення в регулярні вирази JavaScript. Але те, що я не знайшов у наведених вище відповідях, це те, як використовувати названу захоплену групу в самому регексе.

Ви можете використовувати захоплене з ім'ям групи з цим синтаксисом: \k<name>. наприклад

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

і як сказав Forivin, ви можете використовувати захоплену групу в результаті об'єкта, як слід:

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

Хоча ви не можете зробити це за допомогою ванільного JavaScript, можливо, ви можете використовувати якусь Array.prototypeфункцію, як Array.prototype.reduceперетворити індексовані відповідники в названі, використовуючи якусь магію .

Очевидно, що наступне рішення знадобиться, щоб збіги відбувалися в порядку:

// @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}.+)");
Форівін

@Forivin Ясно, що ви можете піти далі та розвинути цю функцію. Зробити це не важко: D
Matías Fidemraizer

Ви можете розширити RegExpоб'єкт, додавши функцію до його прототипу.
Містер ТА

@ Mr.TA AFAIK, не рекомендується розширювати вбудовані об'єкти
Matías Fidemraizer

0

Не маєте ECMAScript 2018?

Моя мета полягала в тому, щоб він працював максимально схожим на те, до чого ми звикли з названими групами. Якщо в ECMAScript 2018 ви можете розмістити ?<groupname>всередині групи, щоб вказати іменовану групу, у моєму рішенні для старих javascript ви можете розмістити (?!=<groupname>)всередині групи, щоб зробити те саме. Отже, це додатковий набір дужок і додатковий !=. Досить близько!

Я все це перетворив на функцію прототипу рядка

Особливості

  • працює зі старшим JavaScript
  • немає додаткового коду
  • досить простий у використанні
  • Регекс все ще працює
  • групи задокументовані в межах самої регулярної виразки
  • Назви груп можуть мати пробіли
  • повертає об'єкт з результатами

Інструкції

  • місце (?!={groupname})всередині кожної групи, яку ви хочете назвати
  • не забудьте усунути будь-які групи, що не захоплюють (), поставивши їх ?:на початку. Вони не будуть названі.

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;
  }, {});
};    

використання

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
}

результат о

{
  "house number": "123",
  "street name": "Main",
  "street type": "St"
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.