たまにメールアドレスの形式を正規表現で表すのは不可能とかというのを目にするのですが、そんなことはありません。入れ子がなければたいていの文字列の形式は正規表現で表すことができます。
ということで、RFC5321, 5322 からメールアドレスの正規表現を書いてみました。
/\A([0-9a-z!\#$%&'*+\-\/=?^_`{|}~]+(\.[0-9a-z!\#$%&'*+\-\/=?^_`{|}~]+)*|\"([\x20\x21\x23-\x5b\x5d-\x7e]|\\[\x20-\x7e])*\")@[0-9a-z]([0-9a-z-]*[0-9a-z])?(\.[0-9a-z]([0-9a-z-]*[0-9a-z])?)*\z/i
ちょっと長いですけど、最近の Ruby だと (?<hoge>)
と \g<hoge>
を使うことで、同じ正規表現の繰り返しを簡単に書くことができるので、それを使って書きなおしてみます。
/\A((?<atom>[0-9a-z!\#$%&'*+\-\/=?^_`{|}~]+)(\.\g<atom>)*|\"([\x20\x21\x23-\x5b\x5d-\x7e]|\\[\x20-\x7e])*\")@(?<sub_domain>[0-9a-z]([0-9a-z-]*[0-9a-z])?)(\.\g<sub_domain>)*\z/i
\g
を使えば入れ子も表現することができるみたいですが、やったことはありません。
わかりにくいので改行&インデントしてコメントを入れて見ました。正規表現リテラルに x オプションを入れるとこのような表記ができます。
\g
や x オプションが Ruby 以外で使えるかどうかは知りません。
/\A # local-part ( # dot-atom (?<atom>[0-9a-z!\#$%&'*+\-\/=?^_`{|}~]+) (\.\g<atom>)* | # quoted-string \"([\x20\x21\x23-\x5b\x5d-\x7e] |\\[\x20-\x7e])*\" )@ # domain (?<sub_domain>[0-9a-z]([0-9a-z-]*[0-9a-z])?) (\.\g<sub_domain>)* \z/ix
これで少しはわかりやすくなったとは思いますが、さすがに暗記するのは難しいです。
ということで、この正規表現を印刷したTシャツやマグカップなどを作ってみました。これがあればいつでもメールアドレス形式のチェックができますね!
[追記]
「入れ子がなければたいていの文字列の形式は正規表現で表すことができます」と書いたんですけど、文字数とかの制約も表せませんでした。
RFC5321 では local-part の最大長は 64, domain の最大長は 255, メールアドレス全体の最大長は 256 という制約がありますが、これは正規表現で表すことはできません。
…と思ったのですが、Twitter で教えてもらって、lookahead (?=...)
を使えば文字数制限も指定することができました。
sub-domain が最大63文字というのにも対応してます。
/\A # 全体で256文字以下 (?=.{,256}\z) # local-partは64文字以下でdomainは255文字以下 (?=.{,64}@.{,255}\z) # local-part ( # dot-atom (?<atom>[0-9a-z!\#$%&'*+\-\/=?^_`{|}~]+) (\.\g<atom>)* | # quoted-string \"([\x09\x20\x21\x23-\x5b\x5d-\x7e] |\\[\x09\x20-\x7e])*\" )@ # domain (?<sub_domain>[0-9a-z]([0-9a-z-]{,61}[0-9a-z])?) (\.\g<sub_domain>)* \z/ix
しかし、こんなネタで何故かブクマが200超えてるんですけど、世の中何がウケるかわかりませんな。
反応している人はだいたい、
- ヘッダの From や To フィールドをパースしたい人
- メールアドレス登録時にバリデーションしたい人
- 日本語メールアドレスは?
- Tシャツいかす!
に分類できるみたいです。
ちなみに私はTシャツのネタ以上のことは考えてなかったので 4 です。
コメント(CFWS)とか表示名(display-name)が気になる人はきっと 1 なんでしょう。
ふつうにメールアドレスと言った時には、コメントや表示名は含まないと思うので、今回はそれは考慮しませんでした。
ヘッダの From や To フィールドをパースしようと思ったら流石に正規表現じゃ無理だと思います。コメントは入れ子だし、グループ(group)もありますし。
ケータイにありがちなピリオドの連続とかの変なアドレスが気になる人は 2 ですね。
私も以前は「ピリオド連続ローカルパートとか死ねばいいのに」と思ってたのですが、最近は考えが変わりました。
ローカルパートに特殊な記号とかピリオドの連続を含めたい場合は「"」で括れば可能です。でもそれはプロトコルの都合なので、人間様がそれに合わせてやる必要はありません。アプリが頑張ればいいんです。
たとえば、「@」の左側をローカルパートとみなして、ローカルパートに「"」で括らないと使えないような文字の並びがあれば、アプリ側で quoted-string 形式に変換してやればいいだけの話です。
日本語メールアドレスは RFC 6530〜6533 あたりを見ればいいんでしょうけど、まだちゃんと読んでません。まだ当分は普及しないんじゃないかと思ってます。
Tシャツとその他のグッズは suzuri で作りました。suzuri はお気楽でいいですね。suzuri についてはまた今度書きます。