JavaScriptで長い正規表現を複数の行に分割する方法は?


138

JSLintルールに従って、各行の長さを80文字に保つために、JavaScriptコードで複数行に分割したい非常に長い正規表現があります。読書にはちょうど良いと思います。ここにパターンのサンプルがあります:

var pattern = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

4
メールアドレスを検証しようとしているようです。なぜ単純にしないの/\S+@\S+\.\S+/ですか?
Bart Kiers 2012

1
おそらく、正規表現なしで、または複数の小さな正規表現を使用して、これを行う方法を探す必要があります。それは、長い正規表現よりもはるかに読みやすくなります。正規表現が約20文字を超える場合は、おそらくより良い方法があります。
ForbesLindesay 2012

2
ワイドモニターで、最近80文字は古くなったのではないですか。
Oleg

7
@ OlegV.Volkovいいえ。人はサーバールームの仮想端末であるvimで分割ウィンドウを使用している可能性があります。全員があなたと同じビューポートでコーディングすることを想定するのは誤りです。さらに、行を80文字に制限すると、コードを小さな関数に分割する必要があります。
シニック

まあ、確かにこれをここで実行したいというあなたの動機はわかります。この正規表現が複数行に分割されると、Koolilncで実証されているように、すぐに読み取り可能な自己文書化コードの完璧な例になります。¬_¬–
マークアメリー

回答:


115

あなたはそれを文字列に変換し、を呼び出すことによって式を作成することができますnew RegExp()

var myRE = new RegExp (['^(([^<>()[\]\\.,;:\\s@\"]+(\\.[^<>(),[\]\\.,;:\\s@\"]+)*)',
                        '|(\\".+\\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                        '[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\\.)+',
                        '[a-zA-Z]{2,}))$'].join(''));

ノート:

  1. 式リテラルを文字列に変換するときは、文字列リテラルを評価するときにバックスラッシュが消費されるため、すべてのバックスラッシュをエスケープする必要があります。(詳細については、Kayoのコメントを参照してください。)
  2. RegExp 2番目のパラメーターとして修飾子を受け入れます

    /regex/g => new RegExp('regex', 'g')

[ 追加ES20xx(タグ付きテンプレート)]

ES20xxでは、タグ付きテンプレートを使用できます。スニペットをご覧ください。

注意:

  • ここでの欠点は、あなたが(いつも使う正規表現文字列でプレーンな空白を使用できないことです\s\s+\s{1,x}\t\nなど)。

(() => {
  const createRegExp = (str, opts) => 
    new RegExp(str.raw[0].replace(/\s/gm, ""), opts || "");
  const yourRE = createRegExp`
    ^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|
    (\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|
    (([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`;
  console.log(yourRE);
  const anotherLongRE = createRegExp`
    (\byyyy\b)|(\bm\b)|(\bd\b)|(\bh\b)|(\bmi\b)|(\bs\b)|(\bms\b)|
    (\bwd\b)|(\bmm\b)|(\bdd\b)|(\bhh\b)|(\bMI\b)|(\bS\b)|(\bMS\b)|
    (\bM\b)|(\bMM\b)|(\bdow\b)|(\bDOW\b)
    ${"gi"}`;
  console.log(anotherLongRE);
})();


4
A new RegExpは、複数行の正規表現に最適な方法です。:代わりに配列に参加するのは、あなただけの文字列連結演算子を使用することができますvar reg = new RegExp('^([a-' + 'z]+)$','i');
dakab

43
注意:上記の回答を使用すると、長い正規表現リテラルが複数行に分割される可能性があります。ただし、正規表現リテラル(で定義//)を単純にコピーして、文字列引数としてRegExpコンストラクターに貼り付けることはできないため、注意が必要です。これは、文字列リテラルを評価するときにバックスラッシュ文字が消費されるためです。例:/Hey\sthere/に置き換えることはできませんnew RegExp("Hey\sthere")。代わりにnew RegExp("Hey\\sthere")、余分なバックスラッシュに注意してください!したがって、私は1つの長い行に長い正規表現リテラルを残すだけを好む
Kayo

5
これを行うためのさらに明確な方法は、意味のあるサブセクションを保持する名前付き変数を作成し、それらを文字列または配列として結合することです。これによりRegExp、理解しやすい方法でを構築できます。
Chris Krycho 2014年

117

@KooiInc回答を拡張すると、のsourceプロパティを使用して、すべての特殊文字を手動でエスケープすることを回避できますRegExpオブジェクトのます。

例:

var urlRegex= new RegExp(''
  + /(?:(?:(https?|ftp):)?\/\/)/.source     // protocol
  + /(?:([^:\n\r]+):([^@\n\r]+)@)?/.source  // user:pass
  + /(?:(?:www\.)?([^\/\n\r]+))/.source     // domain
  + /(\/[^?\n\r]+)?/.source                 // request
  + /(\?[^#\n\r]*)?/.source                 // query
  + /(#?[^\n\r]*)?/.source                  // anchor
);

または、.sourceプロパティの繰り返しを避けたい場合は、次のArray.map()関数を使用して実行できます。

var urlRegex= new RegExp([
  /(?:(?:(https?|ftp):)?\/\/)/      // protocol
  ,/(?:([^:\n\r]+):([^@\n\r]+)@)?/  // user:pass
  ,/(?:(?:www\.)?([^\/\n\r]+))/     // domain
  ,/(\/[^?\n\r]+)?/                 // request
  ,/(\?[^#\n\r]*)?/                 // query
  ,/(#?[^\n\r]*)?/                  // anchor
].map(function(r) {return r.source}).join(''));

ES6では、マップ機能を次のように削減できます。 .map(r => r.source)


3
まさに私が探していたもの、超きれい。ありがとう!
マリアンザゴルイコ2016年

10
これは長い正規表現にコメントを追加するのにとても便利です。ただし、一致する括弧が同じ行にあることによって制限されます。
Nathan S. Watson-Haigh

間違いなく、これ!各サブ正規表現にコメントを付ける機能を備えた非常に素晴らしい。
GaryO

おかげで、それはソースを正規表現関数に入れるのに役立ちました
コード

非常に賢い。おかげで、このアイデアは私に大いに役立ちました。combineRegex = (...regex) => new RegExp(regex.map(r => r.source).join(""))combineRegex(/regex1/, /regex2/, ...)
余談

25

での文字列の使用 new RegExpすべてのバックスラッシュをエスケープする必要があるためでのは厄介です。より小さな正規表現を記述して、それらを連結することができます。

この正規表現を分割しましょう

/^foo(.*)\bar$/

関数を使って後でもっと美しくします

function multilineRegExp(regs, options) {
    return new RegExp(regs.map(
        function(reg){ return reg.source; }
    ).join(''), options);
}

そして今ロックしましょう

var r = multilineRegExp([
     /^foo/,  // we can add comments too
     /(.*)/,
     /\bar$/
]);

コストがかかるので、実際の正規表現を1回だけ作成してから、それを使用してみてください。


これは非常に便利です。追加のエスケープを行う必要がないだけでなく、サブ正規表現の特別な構文ハイライトを維持することもできます。
ケザック

ただし、1つの注意点があります。サブ正規表現が自己完結型であることを確認するか、それぞれを新しいブラケットグループで囲む必要があります。例:multilineRegExp([/a|b/, /c|d])結果は/a|bc|d/になりますが、を意味していました(a|b)(c|d)
ケザック

6

ここには良い答えがありますが、完全を期すために、プロトタイプチェーンとの継承のJavascriptのコア機能に言及する必要があります。このようなものはアイデアを説明します:

RegExp.prototype.append = function(re) {
  return new RegExp(this.source + re.source, this.flags);
};

let regex = /[a-z]/g
.append(/[A-Z]/)
.append(/[0-9]/);

console.log(regex); //=> /[a-z][A-Z][0-9]/g


これがここでの最良の答えです。
parttimeturtle

5

テンプレートリテラルの驚異的な世界のおかげで、ES6で大きな、複数行、コメントの多い、意味的にネストされた正規表現を書くことができるようになりました。

//build regexes without worrying about
// - double-backslashing
// - adding whitespace for readability
// - adding in comments
let clean = (piece) => (piece
    .replace(/((^|\n)(?:[^\/\\]|\/[^*\/]|\\.)*?)\s*\/\*(?:[^*]|\*[^\/])*(\*\/|)/g, '$1')
    .replace(/((^|\n)(?:[^\/\\]|\/[^\/]|\\.)*?)\s*\/\/[^\n]*/g, '$1')
    .replace(/\n\s*/g, '')
);
window.regex = ({raw}, ...interpolations) => (
    new RegExp(interpolations.reduce(
        (regex, insert, index) => (regex + insert + clean(raw[index + 1])),
        clean(raw[0])
    ))
);

これを使用して、次のような正規表現を書くことができます:

let re = regex`I'm a special regex{3} //with a comment!`;

アウトプット

/I'm a special regex{3}/

またはマルチラインはどうですか?

'123hello'
    .match(regex`
        //so this is a regex

        //here I am matching some numbers
        (\d+)

        //Oh! See how I didn't need to double backslash that \d?
        ([a-z]{1,3}) /*note to self, this is group #2*/
    `)
    [2]

helきちんとした出力!
「実際に改行を検索する必要がある場合はどうなりますか?」それから、\nばかげた使い方をしてください!
FirefoxとChromeで作業しています。


わかりました、「もう少し複雑なことはどうですか?」
確かに、これは私が取り組んでいたオブジェクト破壊JSパーサーの一部です

regex`^\s*
    (
        //closing the object
        (\})|

        //starting from open or comma you can...
        (?:[,{]\s*)(?:
            //have a rest operator
            (\.\.\.)
            |
            //have a property key
            (
                //a non-negative integer
                \b\d+\b
                |
                //any unencapsulated string of the following
                \b[A-Za-z$_][\w$]*\b
                |
                //a quoted string
                //this is #5!
                ("|')(?:
                    //that contains any non-escape, non-quote character
                    (?!\5|\\).
                    |
                    //or any escape sequence
                    (?:\\.)
                //finished by the quote
                )*\5
            )
            //after a property key, we can go inside
            \s*(:|)
      |
      \s*(?={)
        )
    )
    ((?:
        //after closing we expect either
        // - the parent's comma/close,
        // - or the end of the string
        \s*(?:[,}\]=]|$)
        |
        //after the rest operator we expect the close
        \s*\}
        |
        //after diving into a key we expect that object to open
        \s*[{[:]
        |
        //otherwise we saw only a key, we now expect a comma or close
        \s*[,}{]
    ).*)
$`

出力します /^\s*((\})|(?:[,{]\s*)(?:(\.\.\.)|(\b\d+\b|\b[A-Za-z$_][\w$]*\b|("|')(?:(?!\5|\\).|(?:\\.))*\5)\s*(:|)|\s*(?={)))((?:\s*(?:[,}\]=]|$)|\s*\}|\s*[{[:]|\s*[,}{]).*)$/

そして、小さなデモでそれを実行していますか?

let input = '{why, hello, there, "you   huge \\"", 17, {big,smelly}}';
for (
    let parsed;
    parsed = input.match(r);
    input = parsed[parsed.length - 1]
) console.log(parsed[1]);

正常に出力

{why
, hello
, there
, "you   huge \""
, 17
,
{big
,smelly
}
}

引用符で囲まれた文字列が正常にキャプチャされていることに注意してください。
私はそれをChromeとFirefoxでテストしました。

興味があれば、私がやっていたことそのデモンストレーションをチェックアウトできます
Firefoxは後方参照や名前付きグループをサポートしていないため、Chromeでのみ機能します。したがって、この回答で示されている例は実際には無効化されたバージョンであり、無効な文字列を受け入れるように簡単に騙される可能性があることに注意してください。


1
これをNodeJSパッケージとしてエクスポートすることを考える必要があります。これはすばらしいことです
rmobis

1
私は自分でやったことはありませんが、ここにかなり完全なチュートリアルがあります:zellwk.com/blog/publish-to-npm。ページの最後にあるnpをチェックすることをお勧めします。私はそれを使ったことがありませんが、シンドレソルフスはこれらのことの魔術師なので、私はそれを逃さないでしょう。
rmobis

4

上記の正規表現には、正常に動作しないいくつかの黒いスラッシュがありません。そこで、正規表現を編集しました。電子メールの検証に99.99%機能するこの正規表現を検討してください。

let EMAIL_REGEXP = 
new RegExp (['^(([^<>()[\\]\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\.,;:\\s@\"]+)*)',
                    '|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                    '[0-9]{1,3}\])|(([a-zA-Z\\-0-9]+\\.)+',
                    '[a-zA-Z]{2,}))$'].join(''));

1

Arrayを回避joinするには、次の構文を使用することもできます。

var pattern = new RegExp('^(([^<>()[\]\\.,;:\s@\"]+' +
  '(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@' +
  '((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|' +
  '(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$');

0

個人的には、それほど複雑ではない正規表現を使用します。

/\S+@\S+\.\S+/

確かに、現在のパターンほど正確ではありませんが、何を達成しようとしていますか?ユーザーが入力する可能性のある偶発的なエラーをキャッチしようとしていますか、それともユーザーが無効なアドレスを入力しようとするのではないかと心配していますか?初めての場合は、もっと簡単なパターンを探します。後者の場合は、そのアドレスに送信された電子メールに返信することによる検証が、より適切なオプションになる場合があります。

ただし、現在のパターンを使用する場合は、次のように小さなサブパターンから構築することで、(IMO)を読みやすく(そして保守しやすく!)なります。

var box1 = "([^<>()[\]\\\\.,;:\s@\"]+(\\.[^<>()[\\]\\\\.,;:\s@\"]+)*)";
var box2 = "(\".+\")";

var host1 = "(\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])";
var host2 = "(([a-zA-Z\-0-9]+\\.)+[a-zA-Z]{2,})";

var regex = new RegExp("^(" + box1 + "|" + box2 + ")@(" + host1 + "|" + host2 + ")$");

21
反対投票-正規表現の複雑さを軽減することについてのあなたのコメントは有効ですが、OPは特に「長い正規表現を複数の行に分割する」方法を求めています。したがって、あなたのアドバイスは有効ですが、間違った理由で与えられました。たとえば、プログラミング言語を回避するためのビジネスロジックの変更。さらに、あなたが与えたコード例はかなり醜いです。
sleepycal 2014年

4
@sleepycal私はバートが質問に答えたと思います。彼の回答の最後のセクションを参照してください。彼は質問に答えただけでなく、代替案を示しました。
Nidhin David、2016

0

単純に文字列操作を使用できます。

var pattenString = "^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|"+
"(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|"+
"(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$";
var patten = new RegExp(pattenString);

0

すべてをカプセル化し、キャプチャグループと文字セットを分割するためのサポートを実装することで、Korunの答えを改善しようとしました-この方法をより汎用的にします。

このスニペットを使用するにcombineRegexは、組み合わせる必要がある正規表現オブジェクトを引数とする可変個関数を呼び出す必要があります。その実装は下部にあります。

キャプチャグループを直接分割することはできません。ただし、括弧が1つしかない部分があるためです。ブラウザーは例外で失敗します。

代わりに、配列内のキャプチャグループのコンテンツを単に渡しています。combineRegex配列が検出されると、括弧が自動的に追加されます。

さらに、数量詞は何かに従う必要があります。なんらかの理由で正規表現を数量詞の前で分割する必要がある場合は、1組の括弧を追加する必要があります。これらは自動的に削除されます。重要なのは、空のキャプチャグループはほとんど役に立たないことであり、このように数量詞は何か参照する必要があります。同じ方法は、非キャプチャグループ(に/(?:abc)/なる[/()?:abc/])などにも使用できます。

これは、簡単な例を使用して最もよく説明されています。

var regex = /abcd(efghi)+jkl/;

になるでしょう:

var regex = combineRegex(
    /ab/,
    /cd/,
    [
        /ef/,
        /ghi/
    ],
    /()+jkl/    // Note the added '()' in front of '+'
);

文字セットを分割する必要がある場合{"":[regex1, regex2, ...]}は、配列([regex1, regex2, ...])の代わりにオブジェクト()を使用できます。キーのコンテンツは、オブジェクトにキーが1つだけ含まれている限り、何でもかまいません。最初の文字を数量詞として解釈できる場合は、代わりにダミーの開始として()使用する必要があることに注意してください]。家に/[+?]/なる{"":[/]+?/]}

以下は、スニペットとより完全な例です。

function combineRegexStr(dummy, ...regex)
{
    return regex.map(r => {
        if(Array.isArray(r))
            return "("+combineRegexStr(dummy, ...r).replace(dummy, "")+")";
        else if(Object.getPrototypeOf(r) === Object.getPrototypeOf({}))
            return "["+combineRegexStr(/^\]/, ...(Object.entries(r)[0][1]))+"]";
        else 
            return r.source.replace(dummy, "");
    }).join("");
}
function combineRegex(...regex)
{
    return new RegExp(combineRegexStr(/^\(\)/, ...regex));
}

//Usage:
//Original:
console.log(/abcd(?:ef[+A-Z0-9]gh)+$/.source);
//Same as:
console.log(
  combineRegex(
    /ab/,
    /cd/,
    [
      /()?:ef/,
      {"": [/]+A-Z/, /0-9/]},
      /gh/
    ],
    /()+$/
  ).source
);


0

@Hashbrownの素晴らしい答えは私を正しい軌道に乗せました。こちらも私のバージョンです。このブログからインスピレーションを得ています

function regexp(...args) {
  function cleanup(string) {
    // remove whitespace, single and multi-line comments
    return string.replace(/\s+|\/\/.*|\/\*[\s\S]*?\*\//g, '');
  }

  function escape(string) {
    // escape regular expression
    return string.replace(/[-.*+?^${}()|[\]\\]/g, '\\$&');
  }

  function create(flags, strings, ...values) {
    let pattern = '';
    for (let i = 0; i < values.length; ++i) {
      pattern += cleanup(strings.raw[i]);  // strings are cleaned up
      pattern += escape(values[i]);        // values are escaped
    }
    pattern += cleanup(strings.raw[values.length]);
    return RegExp(pattern, flags);
  }

  if (Array.isArray(args[0])) {
    // used as a template tag (no flags)
    return create('', ...args);
  }

  // used as a function (with flags)
  return create.bind(void 0, args[0]);
}

次のように使用します。

regexp('i')`
  //so this is a regex

  //here I am matching some numbers
  (\d+)

  //Oh! See how I didn't need to double backslash that \d?
  ([a-z]{1,3}) /*note to self, this is group #2*/
`

このRegExpオブジェクトを作成するには:

/(\d+)([a-z]{1,3})/i
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.