获取JavaScript正则表达式中每个捕获的索引

use*_*166 28 javascript regex capturing-group

我想匹配像一个正则表达式/(a).(b)(c.)d/使用"aabccde",并获得以下信息反馈:

"a" at index = 0
"b" at index = 2
"cc" at index = 3
Run Code Online (Sandbox Code Playgroud)

我怎样才能做到这一点?String.match返回匹配列表和完整匹配开始的索引,而不是每个捕获的索引.

编辑:一个不适用于普通indexOf的测试用例

regex: /(a).(.)/
string: "aaa"
expected result: "a" at 0, "a" at 2
Run Code Online (Sandbox Code Playgroud)

注意:问题类似于Javascript Regex:如何查找每个子表达式的索引?,但我不能修改正则表达式,使每个子表达式成为一个捕获组.

Cer*_*nce 7

目前有一个提案(第 3 阶段)在原生 Javascript 中实现这一点:

ECMAScript 的 RegExp 匹配索引

ECMAScript RegExp 匹配索引提供了关于捕获的子字符串相对于输入字符串开头的开始和结束索引的附加信息。

...我们建议indices在 的数组结果(子字符串数组)上采用一个附加属性RegExp.prototype.exec()。此属性本身将是一个索引数组,其中包含每个捕获的子字符串的一对开始和结束索引。任何不匹配的捕获组都将undefined类似于它们在substrings 数组中的相应元素。此外,索引数组本身会有一个组属性,其中包含每个命名捕获组的开始和结束索引。

下面是一个如何工作的例子:

const re1 = /a+(?<Z>z)?/d;

// indices are relative to start of the input string:
const s1 = "xaaaz";
const m1 = re1.exec(s1);
m1.indices[0][0] === 1;
m1.indices[0][1] === 5;
s1.slice(...m1.indices[0]) === "aaaz";

m1.indices[1][0] === 4;
m1.indices[1][1] === 5;
s1.slice(...m1.indices[1]) === "z";

m1.indices.groups["Z"][0] === 4;
m1.indices.groups["Z"][1] === 5;
s1.slice(...m1.indices.groups["Z"]) === "z";

// capture groups that are not matched return `undefined`:
const m2 = re1.exec("xaaay");
m2.indices[1] === undefined;
m2.indices.groups["Z"] === undefined;
Run Code Online (Sandbox Code Playgroud)

因此,对于问题中的代码,我们可以这样做:

const re = /(a).(b)(c.)d/d;
const str = 'aabccde';
const result = re.exec(str);
// indicies[0], like result[0], describes the indicies of the full match
const matchStart = result.indicies[0][0];
result.forEach((matchedStr, i) => {
  const [startIndex, endIndex] = result.indicies[i];
  console.log(`${matchedStr} from index ${startIndex} to ${endIndex} in the original string`);
  console.log(`From index ${startIndex - matchStart} to ${endIndex - matchStart} relative to the match start\n-----`);
});
Run Code Online (Sandbox Code Playgroud)

输出:

const re1 = /a+(?<Z>z)?/d;

// indices are relative to start of the input string:
const s1 = "xaaaz";
const m1 = re1.exec(s1);
m1.indices[0][0] === 1;
m1.indices[0][1] === 5;
s1.slice(...m1.indices[0]) === "aaaz";

m1.indices[1][0] === 4;
m1.indices[1][1] === 5;
s1.slice(...m1.indices[1]) === "z";

m1.indices.groups["Z"][0] === 4;
m1.indices.groups["Z"][1] === 5;
s1.slice(...m1.indices.groups["Z"]) === "z";

// capture groups that are not matched return `undefined`:
const m2 = re1.exec("xaaay");
m2.indices[1] === undefined;
m2.indices.groups["Z"] === undefined;
Run Code Online (Sandbox Code Playgroud)

请记住,该indicies数组包含相对于字符串开头的匹配组的索引,而不是相对于匹配开头的索引。


该提案目前处于第 3 阶段,这表明规范文本已经完成,并且 TC39 中需要批准它的每个人都已经这样做了 - 剩下的就是让环境开始交付它,以便可以完成最终测试,然后它将纳入官方标准。

一个 polyfill在这里可用。


Del*_*lus 6

不久前我为此编写了MultiRegExp。只要您没有嵌套的捕获组,它就可以解决问题。它通过在 RegExp 中的捕获组之间插入捕获组并使用所有中间组来计算请求的组位置来工作。

var exp = new MultiRegExp(/(a).(b)(c.)d/);
exp.exec("aabccde");
Run Code Online (Sandbox Code Playgroud)

应该回来

{0: {index:0, text:'a'}, 1: {index:2, text:'b'}, 2: {index:3, text:'cc'}}
Run Code Online (Sandbox Code Playgroud)

现场版


vel*_*lop 5

我创建了一个小的正则表达式解析器,它也能够像魅力一样解析嵌套组。它很小但很大。不完全是。就像唐纳德的手一样。如果有人可以测试它,我会非常高兴,因此它将经过实战测试。可以在以下位置找到:https : //github.com/valorize/MultiRegExp2

用法:

let regex = /a(?: )bc(def(ghi)xyz)/g;
let regex2 = new MultiRegExp2(regex);

let matches = regex2.execForAllGroups('ababa bcdefghixyzXXXX'));

Will output:
[ { match: 'defghixyz', start: 8, end: 17 },
  { match: 'ghi', start: 11, end: 14 } ]
Run Code Online (Sandbox Code Playgroud)


mqu*_*lle 1

所以,你有一个文本和一个正则表达式:

txt = "aabccde";
re = /(a).(b)(c.)d/;
Run Code Online (Sandbox Code Playgroud)

第一步是获取与正则表达式匹配的所有子字符串的列表:

subs = re.exec(txt);
Run Code Online (Sandbox Code Playgroud)

然后,您可以对每个子字符串的文本进行简单搜索。您必须将最后一个子字符串的位置保存在变量中。我已将这个变量命名为cursor

var cursor = subs.index;
for (var i = 1; i < subs.length; i++){
    sub = subs[i];
    index = txt.indexOf(sub, cursor);
    cursor = index + sub.length;


    console.log(sub + ' at index ' + index);
}
Run Code Online (Sandbox Code Playgroud)

编辑:感谢@nhahtdh,我改进了机制并制作了完整的功能:

String.prototype.matchIndex = function(re){
    var res  = [];
    var subs = this.match(re);

    for (var cursor = subs.index, l = subs.length, i = 1; i < l; i++){
        var index = cursor;

        if (i+1 !== l && subs[i] !== subs[i+1]) {
            nextIndex = this.indexOf(subs[i+1], cursor);
            while (true) {
                currentIndex = this.indexOf(subs[i], index);
                if (currentIndex !== -1 && currentIndex <= nextIndex)
                    index = currentIndex + 1;
                else
                    break;
            }
            index--;
        } else {
            index = this.indexOf(subs[i], cursor);
        }
        cursor = index + subs[i].length;

        res.push([subs[i], index]);
    }
    return res;
}


console.log("aabccde".matchIndex(/(a).(b)(c.)d/));
// [ [ 'a', 1 ], [ 'b', 2 ], [ 'cc', 3 ] ]

console.log("aaa".matchIndex(/(a).(.)/));
// [ [ 'a', 0 ], [ 'a', 1 ] ] <-- problem here

console.log("bababaaaaa".matchIndex(/(ba)+.(a*)/));
// [ [ 'ba', 4 ], [ 'aaa', 6 ] ]
Run Code Online (Sandbox Code Playgroud)

  • 这绝对不是一般情况的解决方案。例如 `text = "babaaaaa"` 和 `re = /(ba)+.(a*)/` (9认同)
  • 这仍然是错误的。`aaa` 应位于索引 7(对于最后一个测试用例)。(我怀疑是否存在不分析正则表达式的简单通用解决方案)。 (2认同)