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

l2osamch  于 2023-03-31  发布在  Java
关注(0)|答案(8)|浏览(120)

我想将/(a).(b)(c.)d/这样的正则表达式与"aabccde"进行匹配,并返回以下信息:

"a" at index = 0
"b" at index = 2
"cc" at index = 3

String.match返回匹配列表和完整匹配的开始索引,而不是每个捕获的索引。
编辑:一个不能使用普通indexOf的测试用例

regex: /(a).(.)/
string: "aaa"
expected result: "a" at 0, "a" at 2

注意:这个问题类似于Javascript Regex: How to find index of each subexpression?,但我不能修改正则表达式使每个子表达式都成为捕获组。

khbbv19g

khbbv19g1#

目前有一个proposal(阶段4)在原生Javascript中实现这一点:

ECMAScript的RegExp匹配索引

ECMAScript RegExp匹配索引提供了有关捕获的子字符串相对于输入字符串开始的开始和结束索引的附加信息。
...我们建议在数组结果上采用额外的indices属性(substrings array)。该属性本身是一个索引数组,包含每个捕获的子串的一对开始和结束索引。任何 unmatched 捕获组将是undefined,类似于它们在 *substrings数组 * 中的对应元素。此外,*indices数组 * 本身将具有包含每个命名的捕获组的开始和结束索引的groups属性。
下面是一个例子,说明了事情是如何工作的。以下代码段至少在Chrome中运行时没有错误:

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

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

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

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

// capture groups that are not matched return `undefined`:
const m2 = re1.exec("xaaay");
console.log(m2.indices[1]); // undefined
console.log(m2.indices.groups.Z); // undefined

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

const re = /(a).(b)(c.)d/d;
const str = 'aabccde';
const result = re.exec(str);
// indices[0], like result[0], describes the indices of the full match
const matchStart = result.indices[0][0];
result.forEach((matchedStr, i) => {
  const [startIndex, endIndex] = result.indices[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-----`);
});

输出:

aabccd from index 0 to 6 in the original string
From index 0 to 6 relative to the match start
-----
a from index 0 to 1 in the original string
From index 0 to 1 relative to the match start
-----
b from index 2 to 3 in the original string
From index 2 to 3 relative to the match start
-----
cc from index 3 to 5 in the original string
From index 3 to 5 relative to the match start

请记住,indices数组包含匹配组的索引 * 相对于字符串的开始 *,而不是相对于匹配的开始。
polyfill可用于here

wgmfuz8q

wgmfuz8q2#

我不久前为此写了MultiRegExp。只要你没有嵌套的捕获组,它应该可以做到这一点。它的工作原理是在RegExp中的捕获组之间插入捕获组,并使用所有中间组来计算请求的组位置。

var exp = new MultiRegExp(/(a).(b)(c.)d/);
exp.exec("aabccde");

应返回

{0: {index:0, text:'a'}, 1: {index:2, text:'b'}, 2: {index:3, text:'cc'}}

Live Version

uqxowvwt

uqxowvwt3#

我创建了一个小的正则表达式解析器,它也能够像魔咒一样解析嵌套的组。它很小,但很大。真的。就像Donalds的手一样。如果有人能测试它,我会很高兴,所以它会经过战斗测试。它可以在以下位置找到: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 } ]
eoxn13cs

eoxn13cs4#

更新答案:2022年

参见String.prototype.matchAll
matchAll()方法将字符串与正则表达式进行匹配,并返回iterator个匹配结果。
每个匹配项都是一个数组,匹配的文本作为第一项,然后每个括号捕获组都有一项。它还包括额外的属性indexinput

let regexp = /t(e)(st(\d?))/g;
let str = 'test1test2';

for (let match of str.matchAll(regexp)) {
  console.log(match)
}

// => ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', groups: undefined]
// => ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', groups: undefined]
k2fxgqgv

k2fxgqgv5#

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

txt = "aabccde";
re = /(a).(b)(c.)d/;

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

subs = re.exec(txt);

然后,你可以对每个子字符串的文本做一个简单的搜索。你必须在一个变量中保存最后一个子字符串的位置。我把这个变量命名为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);
}

**编辑:**感谢@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 ] ]
i86rm4rw

i86rm4rw6#

基于ecma正则表达式语法,我编写了一个解析器,它是RegExp类的扩展,除了解决这个问题(全索引exec方法)之外,还解决了JavaScript RegExp实现的其他限制,例如:基于组的搜索和替换。您可以test and download the implementation here(与NPM模块一样可用)。
该实现的工作原理如下(小示例):

//Retrieve content and position of: opening-, closing tags and body content for: non-nested html-tags.
var pattern = '(<([^ >]+)[^>]*>)([^<]*)(<\\/\\2>)';
var str = '<html><code class="html plain">first</code><div class="content">second</div></html>';
var regex = new Regex(pattern, 'g');
var result = regex.exec(str);

console.log(5 === result.length);
console.log('<code class="html plain">first</code>'=== result[0]);
console.log('<code class="html plain">'=== result[1]);
console.log('first'=== result[3]);
console.log('</code>'=== result[4]);
console.log(5=== result.index.length);
console.log(6=== result.index[0]);
console.log(6=== result.index[1]);
console.log(31=== result.index[3]);
console.log(36=== result.index[4]);

我也尝试了@velop的实现,但该实现似乎有缺陷,例如它不能正确处理反向引用,例如“/a(?:)bc(def(\1ghi)xyz)/g”-当在前面添加括号时,则后向引用**\1**需要相应地递增(在他的实现中不是这种情况)。

4bbkushb

4bbkushb7#

从2023年开始,你可以使用match()和这里提到的d标志来实现这一点。所以要解决原始示例,你只需在正则表达式的末尾添加一个d

let re = /(a).(b)(c.)d/d
let str = "aabccde"
let match = str.match(re)
console.log(match.indices) // [[0, 6], [0, 1], [2, 3], [3, 5]]

re = /(a).(.)/d
str = "aaa"
match = str.match(re)
console.log(match.indices) // [[0, 3], [0, 1], [2, 3]]

Fiddle here
注意,第一个数组是 * 整个 * 匹配的开始和结束。子组在其后。
我将命名这些组,然后在groups属性(match.indices.groups)下按名称访问它们的索引。

8hhllhi2

8hhllhi28#

我不太清楚您对搜索的具体要求是什么,但下面是如何在第一个示例中使用Regex.exec()和while循环获得所需输出的。

脚本语言

var myRe = /^a|b|c./g;
var str = "aabccde";
var myArray;
while ((myArray = myRe.exec(str)) !== null)
{
  var msg = '"' + myArray[0] + '" ';
  msg += "at index = " + (myRe.lastIndex - myArray[0].length);
  console.log(msg);
}

产出

"a" at index = 0
"b" at index = 2
"cc" at index = 3

使用lastIndex属性,可以减去当前匹配字符串的长度,以获得起始索引。

相关问题