Javascriptで正規表現のまとめ【応用編】

ここでは実際のケースで使えそうなものをまとめる。

メールアドレス

定義

アカウント名@ドメイン名

.+@.+

アカウント名の先頭は半角英数字

^[a-zA-Z0-9]

アカウント名のに文字目以降は半角英数字または.-_のいづれか
1文字のみの場合も考慮して*をつける

[a-zA-Z0-9.-_]*

ドメイン名の先頭は半角英数字

[a-zA-Z0-9]

ドメイン名のに文字目以降は半角英数字または.-_のいづれか

[a-zA-Z0-9.-_]+

ドメイン名は必ず.を含め、かつ.で終わらない

[a-zA-Z0-9][a-zA-Z0-9\.-_]+\.[a-zA-Z]{2,}$

これを組み合わせると、

var regexp = /^[a-zA-Z0-9][a-zA-Z0-9\.-_]*@[a-zA-Z0-9][a-zA-Z0-9\.-_]+\.[a-zA-Z]{2,}$/;

console.log(regexp.test('hoge@fuga.com'))
console.log(regexp.test('@fuga.com'))
console.log(regexp.test('_h.o.g.e@f-uga.com'))
console.log(regexp.test('ho._gr..e@fuga.com'))
console.log(regexp.test('ho._gr..e@fuga'))
console.log(regexp.test('ho._gr..e@fuga.hoge.jp'))

ただし!
メルアドについて完璧な正規表現は無いと言われている。
html5のinput[type=email]でさえ、電子メールの構文を定義するRFC5322対して意図的に違反する仕様となっている。つまり、無理ゲーってこと。

ちなみにHMTL5のinput[type=email]で動くバリデーションはこう。

^[a-zA-Z0-9\.!#$%&'*+/=?^_'{|}~-]+@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9-]+)*$

実務では、HMTL5準拠のバリデーションてことで上に従えばよさそうかな。

電話番号

条件

0から始まり、10-11桁

^0\d[9,10]$

なので、

var regexp = /^0\d{9,10}$/;
console.log(regexp.test('09088883333'));
console.log(regexp.test('79088883333'));

ハイフンを含める場合は、

var regexp = /^0\d{1,3}-\d{3,4}-\d{3,4}$/;
console.log(regexp.test('79088883333'));
console.log(regexp.test('090-8888-3333'));

URL

http, httpsで始まる
:// が続く

^https?:\/\/[\w/:%#\$&\?\(\)~\.=\+\-];$

こんなかんじに。

var regexp = /^https?:\/\/[\w/:%#\$&\?\(\)~\.=\+\-]+$/;
console.log(regexp.test('http://hoge.com'))
console.log(regexp.test('https://hoge.com'))
console.log(regexp.test('//hoge.com'))

応用版

特定ドメイン(hoge.co.jp)のみマッチ

var regexp = /^https?:\/\/([\w/:%#\$&\?\(\)~\.=\+\-]+\.)?hoge\.co\.jp$/;
console.log(regexp.test('http://hoge.co.jp'))
console.log(regexp.test('http://fuga.hoge.co.jp'))
console.log(regexp.test('http://fuga.hage.co.jp'))

日付

yyyy/mm/ddの形で、うるう年等日付の妥当性は問わない

年度

\d{4}

月は1-12
0埋めは問わない

0?[1-9]|1[0-2]

日は1-31
0埋めは問わない

0?[1-9]|1[0-9]|2[0-9]|3[0-1]

又は

0?[1-9]|[12][0-9]|3[01]

まとめると、

var regexp = /^\d{4}\/(0?[1-9]|1[0-2])\/(0?[1-9]|[12][0-9]|3[01])$/;
console.log(regexp.test('2015/51/12'))
console.log(regexp.test('2015/01/12'))
console.log(regexp.test('2015/1/30'))
console.log(regexp.test('2015/1/32'))

時刻

hh:mm:ss
のフォーマットを仮定

var regexp = /^(0?[1-9]|1[1-9]|2[1-3]):(0?[1-9]|[0-5][0-9]):(0?[1-9]|[0-5][0-9])$/;
console.log(regexp.test('2015:51:12'))
console.log(regexp.test('21:11:12'))
console.log(regexp.test('21:61:12'))
console.log(regexp.test('21:61:123'))
console.log(regexp.test('21:11:23'))
console.log(regexp.test('1:1:3'))

全角文字のみ

全角文字 = 1バイト文字ではない、マルチバイト文字
こう書く

[^\x00-\x7Eァ-ン゙゚]+

\xは、16進数を検索するためのメタ文字。
\x00-\x7Eは、半角カナ以外の半各地を文字コードで範囲指定
ァ-ン゙゚は、半角カナを範囲指定
^で、真逆を指す。

var regexp = /[^\x00-\x7Eァ-ン゙゚]+/;
console.log(regexp.test('あいうえお'))
console.log(regexp.test('aiueo'))

他の方法もある。

マルチバイト文字が含まれているか。

var exp1 = /[^-~。-゚]/;

マルチバイト文字のみかどうか。

var exp2 = /^[^-~。-゚]+$/;
console.log(exp2.test('あいうeo'))

制御コードも半角カナも全部排除する場合

[^\x01-\x7E\xA1-\xDF]

HTMLタグの検索

効率よくするためには、HTMLタグの定義をある程度特定すべき。
・<>で囲まれている
・プロパティはシングル・ダブルクォートで囲まれている

<(".*?"|'.*?'|[^'"])*?>

最短マッチを使う。最短マッチとは、通常はマッチする最長もものを抽出するが、一番手前のものに変更すること。
例:hoge.jpの直後のディレクトリまでを検索。通常のままだと、最後のbarまでマッチしてしまう。

var url = 'http://hoge.jp/fuga/hoge/boo/bar'
url.match(/^http:\/\/hoge.jp\/(.*)\//);
var url = 'http://hoge.jp/fuga/hoge/boo/bar';
url.match(/^http:\/\/hoge.jp\/(.*?)\//);

後方参照

グループ化した場合、\1や\2など\nと記述することで、n番目のグループにマッチする文字列を参照することが出来る。

下の例では、\1でh1-h4のタグを参照する。

var html = '<body>' + '<h1>heading</h1>' + '<h2>heading</h2>' + '<h2>miss</h3>' + '<h3>heading</h3>' + '<h4>heading</h4>' + '</body>';
console.log(html.replace(/<(h[1-4])>.*?<\/\1>/g, ''));

下の例では、\1でtrを、\2でthまたはtdを参照している。

var html = '<table>' + '<tr></tr>' + '<tr><th>header</th></tr>' + '<tr><td>data</td></tr>' + '</table>';
console.log(html.replace(/<(tr)><(th|td)>.*?<\/\2><\/\1>/g, ''));

グループマッチによる参照を利用した置換

グループ化でのマッチ箇所を、置換文字列からも参照するサンプル。
見慣れた$1や$2などで参照できるということ。
これはよく使うやつ。

var csv = '1,山田,20191012,タロウ\n' + '2,スズキ,20170931,次郎\n' + '3,花田,20140102,五郎\n';
console.log(csv.replace(/^(\d),(.*?),(\d+),(.*?)$/gm, '$1, $3, $2, $4'));
var html = '<body>' + '<h1>heading1</h1>' + '<h2>heading2</h2>' + '<h3>heading3</h3>' + '<h4>heading4</h4>' + '</body>';
console.log(html.replace(/<(h[1-4])>(.*?)<\/\1>/gm, '$1 : $2 \n'));

特定のパターンを含まない行の検索

否定先読み(?!xxx)を使う。
例えば、ログの特定の文字列を抜き出すようなこと。

★を含む行を検索する

var log = '[2018-02-34 12:33:44] execute start \n' +
'[2018-02-34 12:33:44] loop start \n' +
'[2018-02-34 12:33:44] ★ x \n' +
'[2018-02-34 12:33:44] loop start \n' +
'[2018-02-34 12:33:44] ★ x \n' +
'[2018-02-34 12:33:44] loop end \n';
console.log(log.replace(/^(?!.*★).*$/gm, ''))

error, warnを含む行を検索する。

var log = '[2018-02-34 12:33:44] execute start \n' +
'[2018-02-34 12:33:44][error] hogehoge \n' +
'[2018-02-34 12:33:44][info] hogehoge \n' +
'[2018-02-34 12:33:44][user] hogehoge \n' +
'[2018-02-34 12:33:44][warn] hogehoge \n' +
'[2018-02-34 12:33:44][info] hogehoge \n' +
'[2018-02-34 12:33:44][success] hogehoge \n' +
'[2018-02-34 12:33:44][info] hogehoge \n' +
'[2018-02-34 12:33:44][critical] hogehoge \n';
console.log(log.replace(/^(?!.*(error|warn)).*$/gm, ''))