web-dev-qa-db-ja.com

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:各部分式のインデックスを見つける方法? に似ていますが、すべての部分式をキャプチャグループにするように正規表現を変更することはできません。

28
user1527166

少し前に 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'}}

ライブバージョン

4
Delus

チャームのようにネストされたグループを解析することもできる小さな正規表現パーサーを作成しました。小さいけれど巨大です。いいえ、違います。ドナルドの手のように。誰かがテストできたら本当に嬉しいので、バトルテストを行います。次の場所にあります: 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 } ]
3
velop

つまり、テキストと正規表現があります。

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のおかげで、メカニズムが改善され、完全な機能が作成されました。

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
mquandalle

現在、ネイティブJavascriptでこれを実装するための 提案 (ステージ3)があります。

ECMAScriptの正規表現一致インデックス

ECMAScript RegExp Match Indiciesは、入力文字列の開始に関連する、キャプチャされた部分文字列の開始インデックスと終了インデックスに関する追加情報を提供します。

... RegExp.prototype.exec()の配列結果(substrings array)に追加のindicesプロパティを採用することを提案します。このプロパティ自体は、キャプチャされた各部分文字列の開始インデックスと終了インデックスのペアを含むインデックス配列になります。 nmatchedキャプチャグループはundefinedになり、substrings arrayの対応する要素と同様になります。さらに、indices array自体に、名前付きキャプチャグループごとの開始インデックスと終了インデックスを含む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の全員が承認したことを示しています。残っているのは、環境が出荷を開始して最終テストを実行できるようにすることだけです。公式基準に入れられます。

1

ecma正規表現構文 に基づいて、この問題(完全なインデックス付きexecメソッド)とJavaScript RegExp実装の他の制限に加えて解決する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(\ 1ghi)xyz)/ g"-前にparanthesisを追加してから、後方参照\ 1はそれに応じてインクリメントする必要があります(彼の実装ではそうではありません)。

1
becke.ch

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));
0
zelda11