正規表現でグループの繰り返しがあるときのキャプチャは最後の結果だけになる


概要

やりたかった正規表現による抽出(それ自体繰り返しされるグループを抽出)は(おそらく)(正規表現だけでは)できないということがわかった。その代わり目的は正規表現においては先読みによる置換で達成できるが、GASはサポートしていない。splitなどで代替するのが良いか。

やりたかったこと

spreadsheetにある、
παρά (+gen) from, by; (+dat) with; (+acc) beside = 194
という文字列から
παρά (+gen) (+dat) (+acc)
という文字列を得たい。

やったこと

  • spreadsheetの関数でregexextractを使う。
  • apps scriptでre.exec(text)してみる。
  • 先読み!
  • splitゴリ押し

結果

SparedSheetで頑張る!

=regexextract(A1,"([^ ]+)(?:(?:[^\(]+)(\(.*?\)))*")

これはだめ。セルにエラー(#REF!)が出る
Error
Array result was not expanded because it would overwrite data in A1.

ちなみに

=regexextract(A1,"([^ ]+)(?:(?:[^\(]+)(?:\(.*?\)))*")

とすればπαράだけが出る。要するにマッチしてキャプチャする要素が一つだけなら行けるらしい。
それ以上の時はApps Scriptを書けばいいのかと思って次に、

Apps Scriptで頑張る!

const text = "εἰς (εἷς_1) (+acc) into, to, for (prep) = 1767";
/([^ ]+)(?:(?:[^\(]+)(\(.*?\)))*/.exec(text).forEach(function(element){
  Logger.log(element);
});

を走らせてみる。しかし結果は、

[20-08-03 11:18:33:886 JST] εἰς
[20-08-03 11:18:33:888 JST] (prep)

となる。これはcaptureを全てにしてみると何が起きてるかわかる。

/([^ ]+)(([^\(]+)(\(.*?\)))*/.exec(text).forEach(function(element){
  Logger.log(element);
});

とすると

[20-08-03 11:21:27:879 JST] εἰς (εἷς_1) (+acc) into, to, for (prep)
[20-08-03 11:21:27:881 JST] εἰς
[20-08-03 11:21:27:882 JST]  into, to, for (prep)
[20-08-03 11:21:27:883 JST]  into, to, for 
[20-08-03 11:21:27:885 JST] (prep)

となる。要するにcapturingは最後の結果しか保持してくれない。後方参照について何か誤解していたみたいだ。

先人の知恵

https://stackoverflow.com/questions/37003623/how-to-capture-multiple-repeated-groups#:~:text=6%20Answers&text=With%20one%20group%20in%20the,that%20matches%20it%20gets%20stored.

先読みで頑張る!

正規表現のこといろいろ調べてたらなんか先読みあと読みって概念があるらしいことに気づいて、これ使えるんじゃね?って思ってやったら...

GASのマニュアル

https://support.google.com/docs/answer/3098245
ここに

メモ
Google サービスでは RE2 正規表現を使用しています。Google スプレッドシートでは、Unicode 文字クラスのマッチング以外の RE2 を使用できます。詳しくは、RE2 正規表現の使い方についての説明をご覧ください。

とある。
そしてRE2では、
https://github.com/google/re2/blob/master/doc/syntax.txt

(?=re) before text matching «re» NOT SUPPORTED
(?!re) before text not matching «re» NOT SUPPORTED
(?<=re) after text matching «re» NOT SUPPORTED
(?<!re) after text not matching «re» NOT SUPPORTED

だそうです(シュン)。

splitゴリ押し

結局これだよ

function EXTRACT_WORD_INFORMATION(string) {
  const splitedBySpace = string.split(" ");
  const word = splitedBySpace[0];
  const followings = splitedBySpace.slice(1);
  const informations = followings.filter(function(element){
    return /\(.+\)/.test(element);
  });
  const result = word + " " + informations.join(" ");
  Logger.log(result);
  return result;
}

教訓

正規表現だけに頼ってはだめ。