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

时间:2011-03-20 08:02:44

标签: javascript regex

据我所知,JavaScript中没有命名捕获组。获得类似功能的另一种方法是什么?

10 个答案:

答案 0 :(得分:90)

ECMAScript 2018将named capturing groups引入JavaScript正则表达式。

如果您需要支持旧浏览器,您可以使用正常(编号)捕获组执行所有操作,您可以使用命名捕获组执行操作,只需跟踪数字 - 如果捕获顺序可能很麻烦正则表达式中的组更改。

我能想到的命名捕获组只有两个“结构”优势:

  1. 在一些正则表达式中(.NET和JGSoft,据我所知),您可以在正则表达式中使用相同的名称(see here for an example where this matters)。但是大多数正则表达式都不支持此功能。

  2. 如果您需要在被数字包围的情况下引用已编号的捕获组,则可能会出现问题。假设您要为数字添加零,因此希望将(\d)替换为$10。在JavaScript中,这将起作用(只要您的正则表达式中的捕获组少于10个),但Perl会认为您正在寻找反向引用号10而不是数字1,然后是0。在Perl中,您可以在这种情况下使用${1}0

  3. 除此之外,命名捕获组只是“语法糖”。只有在您真正需要时才使用捕获组,并在所有其他情况下使用非捕获组(?:...)

    使用JavaScript的更大问题(在我看来)它不支持详细的正则表达式,这将使可读,复杂的正则表达式的创建变得更加容易。

    Steve Levithan's XRegExp library解决了这些问题。

答案 1 :(得分:60)

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

  • 添加新的正则表达式和替换文本语法,包括对named capture的全面支持。
  • 添加两个新的正则表达式标志:s,使点匹配所有字符(也称为dotall或单行模式)和x,用于自由间距和注释(也称为扩展模式)。
  • 提供一系列功能和方法,使复杂的正则表达式处理变得轻而易举。
  • 自动修复正则表达式行为和语法中最常遇到的跨浏览器不一致问题。
  • 让您轻松创建和使用为XRegExp的正则表达式语言添加新语法和标志的插件。

答案 2 :(得分:55)

另一种可能的解决方案:创建一个包含组名和索引的对象。

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

然后,使用对象键引用组:

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

这使用正则表达式的结果提高了代码的可读性/质量,但不提高了正则表达式本身的可读性。

答案 3 :(得分:49)

在ES6中,您可以使用数组解构来捕获您的组:

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

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

注意:

  • 最后一个let中的第一个逗号跳过结果数组的第一个值,即整个匹配的字符串
  • || []之后的.match()将在没有匹配时阻止解构错误(因为.match()将返回null

答案 4 :(得分:13)

更新:它最终成为JavaScript(ECMAScript 2018)!

命名捕获组可以很快进入JavaScript The proposal for it is at stage 3 already.

可以使用(?<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';

答案 5 :(得分:6)

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

这实际上取决于你的用例,但也许漂亮打印你的正则表达式可能有所帮助。

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

评论可能也有助于向其他人展示您的代码,以及您所做的事情。

对于其他人,我必须同意蒂姆的回答。

答案 6 :(得分:5)

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

如果计算正则表达式中的开始捕获括号,则可以在命名捕获组和正则表达式中编号的捕获组之间创建映射,并可以自由混合和匹配。您只需在使用正则表达式之前删除组名称。我已经写了三个函数来证明这一点。请参阅此要点:https://gist.github.com/gbirke/2cc2370135b665eee3ef

答案 7 :(得分:2)

虽然您无法使用vanilla JavaScript执行此操作,但也许您可以使用 self.currentBlock().next().text() 之类的Array.prototype函数将索引匹配转换为使用某些 magic 的命名匹配。< / p>

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

Array.prototype.reduce

答案 8 :(得分:0)

正如 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>

答案 9 :(得分: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"
}