PHPのpreg_match_all()のように、JavaScriptで複数の出現箇所を正規表現と一致させるにはどうすればよいですか?


160

&またはで区切られたキー=値のペアで構成されるURLエンコードされた文字列を解析しようとしています&

以下は、最初の出現にのみ一致し、キーと値を別々の結果要素に分解します。

var result = mystring.match(/(?:&|&)?([^=]+)=([^&]+)/)

文字列「1111342 = Adam%20Franco&348572 = Bob%20Jones」の結果は次のようになります。

['1111342', 'Adam%20Franco']

グローバルフラグ 'g'を使用すると、すべての出現に一致しますが、完全に一致した部分文字列のみが返され、分離されたキーと値は返されません。

var result = mystring.match(/(?:&|&)?([^=]+)=([^&]+)/g)

文字列「1111342 = Adam%20Franco&348572 = Bob%20Jones」の結果は次のようになります。

['1111342=Adam%20Franco', '&348572=Bob%20Jones']

文字列を分割し&て各キー/値ペアを個別に分割することもできますが、JavaScriptの正規表現サポートを使用し/(?:&|&)?([^=]+)=([^&]+)/て、PHPのpreg_match_all()関数と同様に、パターンの複数の出現に一致する方法はありますか?

私は次のようにサブマッチを分けて結果を得るいくつかの方法を目指しています:

[['1111342', '348572'], ['Adam%20Franco', 'Bob%20Jones']]

または

[['1111342', 'Adam%20Franco'], ['348572', 'Bob%20Jones']]

9
replaceここでの使用を推奨する人がいないのは少し奇妙です。var data = {}; mystring.replace(/(?:&|&)?([^=]+)=([^&]+)/g, function(a,b,c,d) { data[c] = d; });完了しました。JavaScriptの「matchAll」は、文字列ではなく置換ハンドラー関数で「置換」されます。
マイク 'Pomax'カマーマンズ14

2020年にこの質問がまだ見つかっている場合、答えは「正規表現を使用しないでください。URLSearchParamsを使用してください。これにより、これらすべてが自動的に行われます」。
Mike 'Pomax' Kamermans

回答:


161

コメントから引き上げ

2020年のコメント:正規表現を使用する代わりにURLSearchParams、これですべてを実行できるようになりました。そのため、正規表現はもちろん、カスタムコードも必要ありません。

Mike 'Pomax' Kamermans

ブラウザのサポートはここにリストされていますhttps://caniuse.com/#feat=urlsearchparams


サブグループを使用してパラメーターの名前と値を個別にキャプチャし、次の正規表現を提案しますre.exec()

function getUrlParams(url) {
  var re = /(?:\?|&(?:amp;)?)([^=&#]+)(?:=?([^&#]*))/g,
      match, params = {},
      decode = function (s) {return decodeURIComponent(s.replace(/\+/g, " "));};

  if (typeof url == "undefined") url = document.location.href;

  while (match = re.exec(url)) {
    params[decode(match[1])] = decode(match[2]);
  }
  return params;
}

var result = getUrlParams("http://maps.google.de/maps?f=q&source=s_q&hl=de&geocode=&q=Frankfurt+am+Main&sll=50.106047,8.679886&sspn=0.370369,0.833588&ie=UTF8&ll=50.116616,8.680573&spn=0.35972,0.833588&z=11&iwloc=addr");

result オブジェクトです:

{
  f: "q"
  ジオコード:「」
  hl: "de"
  例:「UTF8」
  iwloc: "addr"
  ll: "50.116616,8.680573"
  q:「フランクフルトアムマイン」
  sll: "50.106047,8.679886"
  ソース: "s_q"
  spn: "0.35972,0.833588"
  sspn: "0.370369,0.833588"
  z:「11」
}

正規表現は次のように分類されます。

(?:#非キャプチャグループ
  \?|&# "?" または「&」
  (?:amp;)?#(誤ってHTMLエンコードされたURLの場合は「&」を許可)
)#非キャプチャグループの終了
(#グループ1
  [^ =&#] +# "="、 "&"または "#"以外の任意の文字。少なくとも一度は
)#end group 1-これはパラメーターの名前になります
(?:#非キャプチャグループ
  =?# "="、オプション
  (#グループ2
    [^&#] *#「&」または「#」以外の任意の文字。何度でも
  )#end group 2-これはパラメーターの値になります
)#非キャプチャグループの終了

23
これは私が望んでいたことです。JavaScriptのドキュメントで見たことがないのは、exec()メソッドが2回以上呼び出された場合、次の結果セットを返し続けるということです。素晴らしいヒントをありがとう!
アダムフランコ

1
これは次の理由で行われます:regular-expressions.info/javascript.html(読む:「JavaScript RegExpオブジェクトの使用方法」)
Tomalak

1
このコードにはバグがあります。「while」の後のセミコロンは削除する必要があります。
Jan Willem B

1
なぜなら、私が実際に彼らのコンテンツに興味があるのなら、私は通常、通常の(つまり、キャプチャする)グループのみを使用するからです。
Tomalak 2013

1
@KnightYoshiはい。JavaScriptで任意の式は、(同様にそれ自身の結果を生成x = y割り当てるであろうyx、また製造しますy)。その知識をif (match = re.exec(url))次のように適用すると、このA)割り当てが行われ B)の結果がre.exec(url)に返されますwhile。今すぐre.exec返しnullfalsy値で一致、存在しない場合。したがって、実際には、一致がある限りループは継続します。
Tomalak 2014

67

グローバル検索には「g」スイッチを使用する必要があります

var result = mystring.match(/(&|&)?([^=]+)=([^&]+)/g)

33
これは実際には問題を解決しません:「グローバルフラグ 'g'を使用すると、すべての出現に一致しますが、完全に一致した部分文字列のみが返され、分離されたキーと値は返されません。」
アダムフランコ

40

2020編集

URLSearchParamsを使用してください。このジョブでは、いかなる種類のカスタムコードも必要なくなりました。ブラウザは、単一のコンストラクタでこれを行うことができます:

const str = "1111342=Adam%20Franco&348572=Bob%20Jones";
const data = new URLSearchParams(str);
for (pair of data) console.log(pair)

収量

Array [ "1111342", "Adam Franco" ]
Array [ "348572", "Bob Jones" ]

したがって、これに正規表現を使用する理由はもうありません。

元の答え

実行execスタイルマッチングに付属する「ブラインドマッチング」に依存したくない場合、JavaScriptにはすべてに一致する機能が組み込まれていますが、これはreplace関数呼び出しの一部です。グループ」処理機能

var data = {};

var getKeyValue = function(fullPattern, group1, group2, group3) {
  data[group2] = group3;
};

mystring.replace(/(?:&|&)?([^=]+)=([^&]+)/g, getKeyValue);

完了しました。

キャプチャグループ処理関数を使用して実際に置換文字列を返すのではなく(置換処理の場合、最初の引数は完全なパターンマッチで、後続の引数は個々のキャプチャグループです)、グループ2と3のキャプチャを取得し、そのペアをキャッシュします。

したがって、複雑な解析関数を作成するのではなく、JavaScriptの「matchAll」関数は単に置換ハンドラー関数で「置換」され、パターンマッチングの効率が大幅に向上することに注意してください。


紐がありますsomething "this one" and "that one"。二重引用符で囲まれたすべての文字列をリストに配置したい(つまり、[これ、その1])。これまでのところmystring.match(/"(.*?)"/)、最初のものを検出するのにうまく機能していますが、私はあなたの解決策を単一の捕獲グループに適応させる方法を知りません。
nu everest 2014年

2
コメントで解決しようとするのではなく、Stackoverflowに質問を投稿するように聞こえます。
マイク 'Pomax'カマーマンズ14年

私は、新しい質問を作成しました:stackoverflow.com/questions/26174122/...
NUエベレスト

1
なぜこの回答に賛成票が少ないのかはわかりませんが、質問に対する最良の回答です。
カリン、2015

こんにちは@ Mike'Pomax'Kamermans。コミュニティガイドラインでは、エントリを編集して改善することを特に推奨しています。参照:stackoverflow.com/help/behavior。あなたの答えの核心は非常に役に立ちますが、「matchAllは置換することを忘れないでください」という言葉は明確ではなく、コード(自明ではない)が機能する理由の説明ではないことがわかりました。適切な担当者に依頼する必要があると思ったので、改善されたテキストで複製するのではなく、回答を編集しました。この質問の元の質問者として、私が受け入れたい場合は、この回答(および編集)の受け入れを元に戻します。
Adam Franco

21

グループをキャプチャするために、私はpreg_match_allPHPでの使用に慣れており、その機能をここで再現しようとしました:

<script>

// Return all pattern matches with captured groups
RegExp.prototype.execAll = function(string) {
    var match = null;
    var matches = new Array();
    while (match = this.exec(string)) {
        var matchArray = [];
        for (i in match) {
            if (parseInt(i) == i) {
                matchArray.push(match[i]);
            }
        }
        matches.push(matchArray);
    }
    return matches;
}

// Example
var someTxt = 'abc123 def456 ghi890';
var results = /[a-z]+(\d+)/g.execAll(someTxt);

// Output
[["abc123", "123"],
 ["def456", "456"],
 ["ghi890", "890"]]

</script>

3
@teh_senausグローバル修飾子を指定する必要があります。/g他の方法で実行exec()すると、現在のインデックスは変更されず、永久にループします。
Aram Kocharyan、2014年

このコードmyRe.test(str)を検証するために呼び出し、次にexecAllを実行しようとすると、2番目の一致でスターが付けられ、最初の一致が失われます。
fdrv 2016年

@fdrvループを開始する前に、lastIndexをゼロにリセットする必要があります。this.lastIndex= 0;
CF

15

gグローバルマッチの修飾子を設定します。

/…/g

11
これは実際には問題を解決しません:「グローバルフラグ 'g'を使用すると、すべての出現に一致しますが、完全に一致した部分文字列のみが返され、分離されたキーと値は返されません。」
アダムフランコ

11

ソース:https :
//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec

連続する一致を見つける

正規表現で「g」フラグを使用している場合は、exec()メソッドを複数回使用して、同じ文字列で連続する一致を見つけることができます。そうすると、正規表現のlastIndexプロパティで指定されたstrの部分文字列から検索が開始されます(test()もlastIndexプロパティを進めます)。たとえば、次のスクリプトがあるとします。

var myRe = /ab*/g;
var str = 'abbcdefabh';
var myArray;
while ((myArray = myRe.exec(str)) !== null) {
  var msg = 'Found ' + myArray[0] + '. ';
  msg += 'Next match starts at ' + myRe.lastIndex;
  console.log(msg);
}

このスクリプトは、次のテキストを表示します。

Found abb. Next match starts at 3
Found ab. Next match starts at 912

注:正規表現リテラル(またはRegExpコンストラクター)をwhile条件内に配置しないでください。繰り返しごとにlastIndexプロパティがリセットされるために一致する場合、無限ループが発生します。また、グローバルフラグが設定されていることを確認してください。そうしないと、ここでもループが発生します。


このコードを検証するために呼び出してmyRe.test(str)を実行し、その後しばらく試行すると、2番目の一致でスターが付けられ、最初の一致が失われます。
fdrv 2016年

String.prototype.matchgフラグと組み合わせることもでき'abbcdefabh'.match(/ab*/g)ます: 戻り値['abb', 'ab']
thom_nic 2016年

2

誰か(私のような)が配列サポート(つまり、複数選択)を備えたTomalakのメソッドを必要とする場合は、次のようになります。

function getUrlParams(url) {
  var re = /(?:\?|&(?:amp;)?)([^=&#]+)(?:=?([^&#]*))/g,
      match, params = {},
      decode = function (s) {return decodeURIComponent(s.replace(/\+/g, " "));};

  if (typeof url == "undefined") url = document.location.href;

  while (match = re.exec(url)) {
    if( params[decode(match[1])] ) {
        if( typeof params[decode(match[1])] != 'object' ) {
            params[decode(match[1])] = new Array( params[decode(match[1])], decode(match[2]) );
        } else {
            params[decode(match[1])].push(decode(match[2]));
        }
    }
    else
        params[decode(match[1])] = decode(match[2]);
  }
  return params;
}
var urlParams = getUrlParams(location.search);

入力 ?my=1&my=2&my=things

結果1,2,things(以前に返されたもののみ)


1

タイトルで示されている提案された質問に固執するために、実際にはを使用して文字列内の各一致を反復できますString.prototype.replace()。たとえば、次の例では、正規表現に基づいてすべての単語の配列を取得しています。

function getWords(str) {
  var arr = [];
  str.replace(/\w+/g, function(m) {
    arr.push(m);
  });
  return arr;
}

var words = getWords("Where in the world is Carmen Sandiego?");
// > ["Where", "in", "the", "world", "is", "Carmen", "Sandiego"]

キャプチャグループまたは各マッチのインデックスさえ取得したい場合は、それも実行できます。以下は、一致全体、最初のキャプチャグループ、およびインデックスとともに各一致が返される方法を示しています。

function getWords(str) {
  var arr = [];
  str.replace(/\w+(?=(.*))/g, function(m, remaining, index) {
    arr.push({ match: m, remainder: remaining, index: index });
  });
  return arr;
}

var words = getWords("Where in the world is Carmen Sandiego?");

上記を実行wordsすると、次のようになります。

[
  {
    "match": "Where",
    "remainder": " in the world is Carmen Sandiego?",
    "index": 0
  },
  {
    "match": "in",
    "remainder": " the world is Carmen Sandiego?",
    "index": 6
  },
  {
    "match": "the",
    "remainder": " world is Carmen Sandiego?",
    "index": 9
  },
  {
    "match": "world",
    "remainder": " is Carmen Sandiego?",
    "index": 13
  },
  {
    "match": "is",
    "remainder": " Carmen Sandiego?",
    "index": 19
  },
  {
    "match": "Carmen",
    "remainder": " Sandiego?",
    "index": 22
  },
  {
    "match": "Sandiego",
    "remainder": "?",
    "index": 29
  }
]

PHPで使用可能なものと同様の複数の出現に一致させるために、preg_match_allこのタイプの考え方を使用して独自のものを作成したり、のようなものを使用したりできますYourJS.matchAll()。YourJSは、多かれ少なかれ、この関数を次のように定義しています。

function matchAll(str, rgx) {
  var arr, extras, matches = [];
  str.replace(rgx.global ? rgx : new RegExp(rgx.source, (rgx + '').replace(/[\s\S]+\//g , 'g')), function() {
    matches.push(arr = [].slice.call(arguments));
    extras = arr.splice(-2);
    arr.index = extras[0];
    arr.input = extras[1];
  });
  return matches[0] ? matches : null;
}

URLのクエリ文字列を解析したいので、YourJS.parseQS()yourjs.com/snippets/56)のようなものを使用することもできますが、他の多くのライブラリもこの機能を提供しています。
Chris West、

置換を返すことになっているループの外側のスコープから変数を変更することは、ちょっと悪いことです。あなたの誤用ここに置き換える
フアン・メンデス

1

あなたがmapこれを使用してうまくいくことができるならば、これは4行の解決策です:

var mystring = '1111342=Adam%20Franco&348572=Bob%20Jones';

var result = mystring.match(/(&|&amp;)?([^=]+)=([^&]+)/g) || [];
result = result.map(function(i) {
  return i.match(/(&|&amp;)?([^=]+)=([^&]+)/);
});

console.log(result);

かなり、効率的ではありませんが、少なくともコンパクトです。;)


1

使用window.URL

> s = 'http://www.example.com/index.html?1111342=Adam%20Franco&348572=Bob%20Jones'
> u = new URL(s)
> Array.from(u.searchParams.entries())
[["1111342", "Adam Franco"], ["348572", "Bob Jones"]]

1

Héllo2020から。String.prototype.matchAll()に注目してみましょう。

let regexp = /(?:&|&amp;)?([^=]+)=([^&]+)/g;
let str = '1111342=Adam%20Franco&348572=Bob%20Jones';

for (let match of str.matchAll(regexp)) {
    let [full, key, value] = match;
    console.log(key + ' => ' + value);
}

出力:

1111342 => Adam%20Franco
348572 => Bob%20Jones

最後に!注意事項:「第11版ECMAScript 2020では、文字列のmatchAllメソッドが導入され、グローバル正規表現によって生成されたすべての一致オブジェクトのイテレータが生成されます」。回答にリンクされているサイトによると、ほとんどのブラウザーとnodeJSは現在サポートしていますが、IE、Safari、Samsung Internetはサポートしていません。うまくいけばサポートはすぐに広がりますが、しばらくの間YMMVです。
アダムフランコ

0

同じ名前を使用して複数のパラメーターをキャプチャするために、トマラックのメソッドのwhileループを次のように変更しました。

  while (match = re.exec(url)) {
    var pName = decode(match[1]);
    var pValue = decode(match[2]);
    params[pName] ? params[pName].push(pValue) : params[pName] = [pValue];
  }

入力: ?firstname=george&lastname=bush&firstname=bill&lastname=clinton

戻り値: {firstname : ["george", "bill"], lastname : ["bush", "clinton"]}


私はあなたのアイデアが好きですが、?cinema=1234&film=12&film=34私が期待するように、単一のパラメータではうまく機能しません{cinema: 1234, film: [12, 34]}。これを反映するように回答を編集しました。
TWiStErRob 2013

0

まあ...私は同様の問題がありました... RegExpでインクリメンタル/ステップ検索が必要です(例:検索を開始...処理を実行...最後の一致まで検索を続行)

たくさんのインターネット検索の後...いつものように(これは今習慣になっています)私はStackOverflowに行き、答えを見つけました...

Whatsは言及されておらず、言及すべき事項は " lastIndex" RegExpオブジェクトが " lastIndex"プロパティを実装する理由を理解しました


0

それを分割することは私にとって最良のオプションのように見えます:

'1111342=Adam%20Franco&348572=Bob%20Jones'.split('&').map(x => x.match(/(?:&|&amp;)?([^=]+)=([^&]+)/))

0

正規表現の地獄を回避するために、最初の一致を見つけることができます。チャンクを切り取ってから、部分文字列で次の一致を見つけようとします。これはC#では次のようになります。申し訳ありませんが、JavaScriptに移植していません。

        long count = 0;
        var remainder = data;
        Match match = null;
        do
        {
            match = _rgx.Match(remainder);
            if (match.Success)
            {
                count++;
                remainder = remainder.Substring(match.Index + 1, remainder.Length - (match.Index+1));
            }
        } while (match.Success);
        return count;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.