はじめに
人生で初めて正規表現を書く機会がありましたが、想像通り難しかった&勉強になったので共有します。
要件
メールアドレスの以下4つの要件を満たす正規表現を作ることになりました。
既存要件
- 「@」の入力必須
新規要件
- ローカル部分の先頭のドットを禁止
- ローカル部分で連続するドットを禁止
- ローカル部分の末尾のドットを禁止する
(※ローカル部分とはメールアドレスの@以前を指します)
つまり、以下のようなメールアドレスは弾きたいということです。
✕ .sample@example.com
✕ s..ample@example.com
✕ sample.@example.com
まず第一歩
正規表現の知識が0だったので本当に初歩的なところから調べました。
文字 | 説明 |
---|---|
. | 任意の1文字 にマッチ |
+ | 直前の文字が 1回以上 繰り返す場合にマッチ |
* | 直前の文字が 0回以上 繰り返す場合にマッチ |
? | 直前の文字が 0個か1個 の場合にマッチ |
^ | 直後の文字が行の 先頭 にある場合にマッチ |
$ | 直前の文字が行の 末尾 にある場合にマッチ |
[...] | 角括弧に含まれるいずれか1文字にマッチ |
[^...] | 角括弧に含まれる文字以外にマッチ |
(全然足りないけど・・・)
(こんなレベル感・・・)
最初に完成したもの
/^(?=^(?!\.).*)(?=^(?:(?!\.\.).)*$)(?=(?!.*\.$).*$)/
ググって見つけた表現を訳もわからず駆使して組み合わせました。
今見ても全く何を書いているのか分かりませんが、覚えたての?=
(かつ)を使って3つの新規要件を満たそうとしている感じが伝わります。
そしてこの正規表現は「@」以前を切り取ってローカル部分のみバリデーションをかける想定の正規表現なので、「@」の入力チェックは別でJavaScriptでやる想定です。すごく面倒ですね。
https://regex101.com/
こちらのサイトで正規表現の挙動を確認できるのですが、一応3つの要件は満たされているものでした。
考え方が違った
どう見ても私が作った正規表現は、巷でよく見かける正規表現とはかけ離れていたため、再度考え直しました。
どうやら、考え方が全然違ったようです。
気を取り直してまずは、ローカル部分先頭のドットを禁止
する正規表現を考えてみます。
これはつまり、ドット以外の任意の1文字から始まる
と捉えることができるので、/^[^.]+.*@.*/
で良さそうです。
少しずつ分解してみると、^
(先頭)→[^.]+
(ドット以外の文字が1回以上)→.*
(何かしらの文字が0回以上)→@
→.+
(何かしらの文字が1回以上)、のようにチェックが行われています。
次に、ローカル部分で連続するドットを禁止
する正規表現は、ドットを入力する場合は、ドットとドット以外の任意の1文字セットで!
と考えられるので、上の正規表現に追加して、/^[^.]+((\.[^.]+)*)@.*/
で実装することができます。
追加された(\.[^.]+)
を分解してみると、\.
(ドット)→[^.]+
(ドット以外の文字が1回以上)というふうに、ドットの後に続く文字がドットではないことをチェックしています。
こうすると、ドットの後には必然的にドット以外の1文字が来るため、ローカル部分末尾のドットを禁止する
も同時に満たすことができました。
最後に、元の仕様を踏襲するために、@以前に何も入力されていなくてもバリデーションエラーが出ないよう、@以前は全て任意とすべく調整し、完成した正規表現がこちらです。
/^([^.]+(\.[^.]+)*)*@.*$/
しかし・・・
ディレクターさん「メールアドレス20文字以上入力すると画面フリーズするよ〜」
確認してみると、確かにエラー出てました。
Catastrophic backtracking has been detected and the execution of your expression has been halted. To find out more and what this is, please read the following article:
日本語訳:破壊的なバックトラッキングが検出され、式の実行が停止されました。・・・
破壊的なバックトラック(Catastrophic backtracking)
一部の正規表現は、実行時間が非常に長く JavaScript エンジンを"ハング"させることがあるそうです。
つまり正規表現にもパフォーマンスという概念があり(当たり前)、処理が重すぎて落ちました。
調べ始めたら沼にハマるので、興味がある方はぜひ、ご査収ください。
解決方法は色々あるみたいですが、私の場合@
以前の*
を?
にしたら直ったので、最終的にこちらに落ち着きました。
/^([^.]+(\.[^.]+)*)?@.*$/
最後に
まだまだ初歩的な部分しか学べていないですが、簡単な正規表現であれば読めるようになったので世界が広がりました。
もっとかっこいい正規表現を書けるように頑張ります。
それでは皆様良いお年を。