在JavaScript正则表达式中命名捕获组?


208

据我所知,JavaScript中没有所谓的捕获组。获得类似功能的替代方法是什么?


1
JavaScript中的捕获组按数字表示。.$ 1是第一个捕获组,$ 2,$ 3 ...最高$ 99,但是听起来您想要其他东西-不存在
Erik

24
@Erik您在谈论编号捕获组,OP在谈论命名捕获组。它们存在,但是我们想知道JS是否支持它们。
阿尔巴·门德斯

4
有一个建议将命名的regex引入JavaScript,但如果有的话,可能要过好几年才能看到。
fregante

Firefox惩罚了我尝试在网站上使用命名捕获组的行为……这是我自己的错。stackoverflow.com/a/58221254/782034
Nick Grealy,

Answers:


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而不是number 1,后跟一个0。在Perl中,您可以${1}0在这种情况下使用。

除此之外,命名的捕获组只是“语法糖”。仅当您确实需要捕获组时才使用捕获组,并(?:...)在所有其他情况下使用非捕获组会有所帮助。

JavaScript的更大问题(在我看来)是它不支持冗长的正则表达式,这会使创建可读的,复杂的正则表达式更加容易。

Steve Levithan的XRegExp库解决了这些问题。


5
许多形式允许在正则表达式中多次使用相同的捕获组名称。但是只有.NET和Perl 5.10+可以通过保留参与比赛的名称的最后一组所捕获的值来使其特别有用。
slevithan 2012年

103
巨大的优势是:您可以更改RegExp,而无需数字到变量的映射。非捕获组可以解决此问题,除了一种情况:如果组的顺序发生变化该怎么办?另外,将这些多余的字符放在其他组上也是令人讨厌的
Alba Mendez,

55
所谓的语法糖 确实有助于提高代码的可读性!
Mrchief

1
我认为命名捕获组还有另一个非常有价值的原因。例如,如果要使用正则表达式从字符串中解析日期,则可以编写一个灵活的函数,该函数接受值和正则表达式。只要正则表达式为年,月和日命名捕获,您就可以使用最少的代码遍历一系列正则表达式。
杜威·沃泽尔

4
截至2019年10月,Firefox,IE 11和Microsoft Edge(Chromium之前的版本)不支持命名组捕获。大多数其他浏览器(甚至Opera和Samsung mobile)都可以。caniuse.com/...
加多宝还记得莫妮卡

63

您可以使用XRegExp,它是正则表达式的增强,可扩展,跨浏览器的实现,包括对其他语法,标志和方法的支持:

  • 添加新的正则表达式和替换文本语法,包括对named Capture的全面支持。
  • 添加两个新的正则表达式标志: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跳过结果数组的第一个值,即整个匹配的字符串
  • 当没有匹配项时,|| []after .exec()将防止销毁错误(因为.exec()将返回null

1
第一个逗号是因为match返回的数组的第一个元素是输入表达式,对吗?
EmilioGrisolía'16

1
String.prototype.match返回以下数组:整个匹配的字符串位于位置0,然后是其后的任何组。第一个逗号说“在位置0跳过元素”
fregante

2
对于那些具有转译目标或ES6 +目标的人,我最喜欢的答案。如果例如重用的正则表达式发生更改,这并不一定可以防止出现不一致错误以及命名索引,但是我认为此处的简洁可以轻松地弥补这一点。我选择在字符串可能为或的地方RegExp.prototype.exec结束。String.prototype.matchnullundefined
Mike Hill

22

更新:终于将其纳入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';

目前是第4阶段的提案。
GOTO

如果您使用的是'18,则最好进行销毁;let {year, month, day} = ((result) => ((result) ? result.groups : {}))(re.exec('2015-01-02'));
哈什布朗

6

命名捕获的组可以提供一件事:减少对复杂正则表达式的混淆。

这确实取决于您的用例,但是漂亮地打印正则表达式可能会有所帮助。

或者,您可以尝试定义常量以引用捕获的组。

然后,注释也可能有助于向其他人显示您的代码,以及您所做的工作。

对于其余的我必须同意蒂姆斯的回答。


5

您可以在node.js项目中使用一个名为named-regexp的node.js库(在浏览器中,通过使用browserify或其他打包脚本打包该库)。但是,该库不能与包含未命名捕获组的正则表达式一起使用。

如果您在正则表达式中计算开头的捕获括号,则可以在正则表达式中的命名捕获组和编号的捕获组之间创建映射,并且可以自由地混合和匹配。您只需要在使用正则表达式之前删除组名即可。我编写了三个函数来演示这一点。看到这个要点: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

正如Tim Pietzcker所说的那样,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使用某些magic将索引的匹配项转换为已命名的匹配项。

显然,以下解决方案将需要按顺序进行匹配:

// @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 2015年

@Forivin显然,您可以进一步开发此功能。它不会是很难得到它的工作:d
马蒂亚斯Fidemraizer

您可以RegExp通过向其原型添加函数来扩展对象。
TA先生

@ Mr.TA据我所知,这不是建议延长内置对象
马蒂亚斯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
}

o的结果

{
  "house number": "123",
  "street name": "Main",
  "street type": "St"
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.