Именованные группы захвата в JavaScript regex?

Насколько я знаю, в JavaScript нет такой вещи, как именованные группы захвата. Каков альтернативный способ получения аналогичной функциональности?

Ответ 1

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, за которым следует 0. В Perl вы можете использовать ${1}0 в этом случае.

Кроме того, названные группы захвата являются просто "синтаксическим сахаром". Это помогает использовать группы захвата только тогда, когда они вам действительно нужны, и использовать группы без захвата (?:...) при любых других обстоятельствах.

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

Стив Левитан Библиотека XRegExp решает эти проблемы.

Ответ 2

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

  • Добавляет новое синтаксис синтаксиса regex и замены, включая всестороннюю поддержку named capture.
  • Добавляет два новых флага регулярных выражений: s, чтобы точка совпадала со всеми символами (так называемый режим dotall или singleline) и x, для свободного пробега и комментариев (также как расширенный режим).
  • Предоставляет набор функций и методов, которые упрощают обработку сложного регулярного выражения.
  • Автоматически исправляет наиболее часто встречающиеся непоследовательности между браузерами в поведении и синтаксисе regex.
  • Позволяет вам легко создавать и использовать плагины, которые добавляют новый синтаксис и флаги к регулярному языку выражения XRegExp.

Ответ 3

Другое возможное решение: создать объект, содержащий имена и индексы групп.

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

Затем используйте клавиши объектов для ссылки на группы:

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

Это улучшает читабельность/качество кода, используя результаты регулярного выражения, но не читаемость самого регулярного выражения.

Ответ 4

В 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)

Ответ 5

Обновление: оно наконец-то превратилось в JavaScript (ECMAScript 2018)!


Именованные группы захвата могут очень скоро превратиться в JavaScript.
Предложение об этом уже на третьем этапе.

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

Именованные группы можно получить из свойств свойства groups результата регулярного выражения. Нумерованные ссылки на группы также создаются, как и для неназванных групп. Например:

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

Ответ 6

Именование захваченных групп дает одну вещь: меньше путаницы со сложными регулярными выражениями.

Это действительно зависит от вашего прецедента, но, может быть, довольно-типография вашего регулярного выражения может помочь.

Или вы могли бы попытаться определить константы для ссылки на ваши захваченные группы.

Комментарии могут также помочь показать другим, кто читает ваш код, что вы сделали.

В остальном я должен согласиться с ответом Тимов.

Ответ 7

Существует библиотека node.js с именем named-regexp, которую вы можете использовать в своих проектах node.js (в браузере путем упаковки библиотеки с помощью browserify или других сценариев упаковки). Однако библиотеку нельзя использовать с регулярными выражениями, которые содержат неназванные группы захвата.

Если вы подсчитываете вводные скобки в своем регулярном выражении, вы можете создать отображение между именованными группами захвата и пронумерованными группами захвата в своем регулярном выражении и можете свободно смешивать и сопоставлять. Вам просто нужно удалить имена групп перед использованием регулярных выражений. Я написал три функции, которые демонстрируют это. Смотрите эту суть: https://gist.github.com/gbirke/2cc2370135b665eee3ef

Ответ 8

Пока вы не можете сделать это с помощью ванильного 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));

Ответ 9

Как сказал Тим Пицкер, ECMAScript 2018 вводит именованные группы захвата в регулярные выражения JavaScript. Но что я не нашел в приведенных выше ответах, так это как использовать именованную захваченную группу в самом регулярном выражении.

Вы можете использовать именованную захваченную группу с этим синтаксисом: \k<name>. например

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

и, как сказал Форивин, вы можете использовать захваченную группу в результате объекта следующим образом:

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>