Подтвердить что ты не робот

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

Я хочу совместить регулярное выражение типа /(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: как найти индекс каждого подвыражения?, но я не могу изменить регулярное выражение, чтобы сделать каждое подвыражение группой захвата.

4b9b3361

Ответ 1

Я написал 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'}}

Текущая версия

Ответ 2

Итак, у вас есть текст и регулярное выражение:

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);
}

EDIT: Благодаря @nhahtdh, я улучшил mecanism и сделал полную функцию:

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 ] ]

Ответ 3

Я создал немного регулярного выражения Parser, который также способен анализировать вложенные группы, как шарм. Он маленький, но огромный. Нет. Как руки Дональда. Я был бы очень доволен, если бы кто-то мог его протестировать, так что это будет битва. Его можно найти по адресу: 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 } ]

Ответ 4

На основе синтаксиса регулярного выражения ecma Я написал парсер, соответствующий расширению класса RegExp, который решает помимо этой проблемы (полный indexed exec), а также другие ограничения реализации JavaScript RegExp, например: поиск и замена на основе групп. Вы можете протестировать и загрузить реализацию здесь (также доступен как модуль 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 (\1 ghi) xyz)/g" - при добавлении парадельта спереди, тогда необходимо увеличить соответственно обратную ссылку \1 (что не соответствует его реализации).

Ответ 5

В настоящее время есть предложение (этап 3), чтобы реализовать это в нативном Javascript:

RegExp Match Indices for ECMAScript

ECMAScript RegExp Match Indicies предоставляют дополнительную информацию о начальных и конечных индексах захваченных подстрок относительно начала входной строки.

... Мы предлагаем принять дополнительное свойство indices для результата массива (массива подстрок) из RegExp.prototype.exec(). Это свойство само по себе будет массивом индексов, содержащим пару начальных и конечных индексов для каждой захваченной подстроки. Любые несопоставленные группы захвата будут undefined, аналогично их соответствующему элементу в массиве подстрок. Кроме того, массив индексов сам по себе будет иметь свойство groups, содержащее начальный и конечный индексы для каждой именованной группы захвата.

Вот пример того, как все будет работать:

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

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

Итак, для кода в вопросе мы могли бы сделать:

const re = /(a).(b)(c.)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-----');
});

Выход:

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 4 to 6 in the original string
From index 4 to 6 relative to the match start

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


Предложение в настоящее время находится на стадии 3, что указывает на то, что текст спецификации завершен, и все в TC39, кто должен одобрить его, сделали это - все, что остается, - это чтобы среды начали отправлять его, чтобы можно было провести окончательные испытания, а затем будет введен в официальный стандарт.

Ответ 6

С помощью RegExp.prototype.exec() и поиска правильных индексов результата:

let regex1 = /([a-z]+):([0-9]+)/g;
let str1 = 'hello:123';
let array1;
let resultArray = []

while ((array1 = regex1.exec(str1)) !== null) {
  const quantityFound = (Object.keys(array1).length - 3); // 3 default keys
  for (var i = 1; i<quantityFound; i++) { // start in 1 to avoid the complete found result 'hello:123'
    const found = array1[i];
    arraySingleResult = [found, str1.indexOf(found)];
    resultArray.push(arraySingleResult);
  }
}
console.log('result:', JSON.stringify(resultArray));

Ответ 7

Я точно не знаю, каковы ваши требования для вашего поиска, но вот как вы могли бы получить желаемый результат в своем первом примере, используя Regex.exec() и while-loop.

JavaScript

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, вы можете вычесть длину текущей сопоставленной строки, чтобы получить начальный индекс.