ワイの正規表現入門

# とあるWeb制作会社にて

ワイ「社長、こないだ頼まれたショッピングサイトの件なんですけど」
ワイ「**ご注文フォーム**のコーディング、完了しましたで！」

社長「おお、ありがとうな」

ワイ「ほな、飲みに行ってきますわ！」

社長「・・・**いや待てや**（まだ**15時**やし）」
社長「何やこのフォーム」

ワイ「何ですかいな」
ワイ「デザイン通り、完璧にコーディングできてますやん」

社長「いや**バリデーション**が全くされとらへんがな」

ワイ「**グラデュエーション？**」
ワイ「何を**卒業**するんでっか」

社長「**バリデーション**や」
社長「貴様をこの会社から**卒業させたろか**」

# バリデーションとは

社長「バリデーションちゅうのは、**不正な値**やないかどうか確認することや」
社長「例えば**数字**を入力してほしいフォームに、**文字列**を入力して送信されても困るやろ？」
社長「そういうのをちゃんとチェックするのが**フォームバリデーション**や」

ワイ「あ、ああ・・・そっちのバリデーションでっか」

社長「お前の作ったこのフォームを見てみいや」
社長「**数量**のところに**`ウン`個**って入力しても、そのまま注文画面に進めてしまうやないか」
社長「お客様に**何てモノ**をお届けするつもりや」

ワイ「（いや、そのお客様はむしろ**欲しがってますやん**）」

社長「今日は飲みに行ってええから、明日ちゃんとバリデーション機能も実装してくれや」

ワイ「おお・・・ホワイト会社や・・・」
ワイ「お先に失礼します・・・」

# 翌日

ワイ「なあ、ハスケル子ちゃん」
ワイ「昨日ワイが作った**ご注文フォーム**なんやけど」
ワイ「**フォーム・バ・リデーション**ができてないからダメやって」

ハスケル子「そ、そうですか」
ハスケル子「（**フォーム・バ・リデーション**・・・？）」
ハスケル子「でもまあ、そりゃそうですよね」
ハスケル子「確か、仕様書にも」

> 各入力項目にバリデーションを掛けてください

ハスケル子「って書いてありましたよ」

ワイ「うん」
ワイ「よう分からんかったから、背景に薄く**グラデーション**は掛けておいたんやけどね」
ワイ「やっぱアカンかったわ」

# バリデーション開始

ワイ「さあ、**バ・リデーション**をしていこか」

ワイ「まずは**数量**のバ・リデーションや」
ワイ「数値以外が入ってたら**NG**にしたい場合は・・・」
ワイ「確か**正規表現**いうのを使ってやるんや」

```js:JavaScript
const isValid = /正規表現/.test(userInput);
```

ワイ「↑こんな感じで`test`メソッドを使ってやれば`isValid`に**真偽値**が入ってくるはずや」
ワイ「`userInput`にはユーザーが入力した文字列が入ってくる想定やで」

ワイ「せやから・・・」

```js:JavaScript
const isValid = /[0-9]+/.test(userInput);
```

ワイ「↑こう書いてやればええな」
ワイ「`[0-9]+`、つまり**0~9**の中の**いずれかの文字**が**1個以上**ありまっせ、ってことや」
ワイ「`+`は、**直前の文字が1回もしくはそれ以上繰り返される**って意味やからな」
ワイ「よっしゃ完璧や」

ハスケル子「待ってください」

# 「含む」になっちゃう

ハスケル子「`/[0-9]+/`という正規表現だと」
ハスケル子「0〜9の繰り返しを**『含む』**って意味になっちゃうので」
ハスケル子「↓こんな文字列でも**OK**になっちゃいますよ」

> + 5万
> + ほげ10
> + ワイが1番や！！！

ワイ「Oh...」

ハスケル子「なので正規表現は`/^[0-9]+$/`の方がいいですね」
ハスケル子「`^`ていうのは**〜から始まる**って意味で」
ハスケル子「`$`ていうのは**〜で終わる**って意味です」
ハスケル子「今回の例で言うと」

> 0〜9の繰り返しで始まって、そのまま終わる文字列

ハスケル子「って感じです」
ハスケル子「つまり`^`と`$`で挟むと**部分一致**ではなく**完全一致**になるってことですね」

ワイ「おお、ありがとう」
ワイ「危ない危ない・・・」
ワイ「**`ウ〇コが10`個**、とかでも**OK**になってしまうところやったわ・・・」
ワイ「配達員さんも困るで」

ハスケル子「ちなみにこの正規表現だと、1文字目が`0`でも**OK**扱いになります」
ハスケル子「なので、`001`とかも**OK**になりますね」
ハスケル子「それを**NG**にしたい場合は」
ハスケル子「`/^[1-9][0-9]*$/`」
ハスケル子「↑こうですね」

> + 最初に**1〜9**が1つある　→　`^[1-9]`
> + その後ろに**0〜9**が0個以上ある　→　`[0-9]*`
> + そのまま終わる　→　`$`

ワイ「そっかそっか」
ワイ「`*`は、**直前の文字の0回以上の繰り返し**ってことやもんな」
ワイ「なるほどなぁ〜」

# 繰り返しの回数を指定する

ワイ「次は**姓・名**を入力するフォームのバリデーションやな」
ワイ「ええと、**姓**の入力ルールとしては」
ワイ「**3文字以内**であること・・・それくらいチェックしておけばええかな」

ハスケル子「いや**勅使河原**さんとかどうするんですか」
ハスケル子「仕様書ちゃんと読んでください」
ハスケル子「姓は**`1〜30文字`**って書いてありますよ」

ワイ「おお、ありがとうやで」
ワイ「30文字以内ってのはどうやって調べればええんやろ・・・」

ハスケル子「ええと」

> + 任意の文字　→　`.`
> + それが**1〜30**個あること　→　`{1,30}`
> + 「含む」ではなく「完全一致」　→　`^`と`$`で挟む

ハスケル子「って感じだから」

```js:JavaScript
const isValid = /^.{1,30}$/.test(userInput);
```

ハスケル子「↑こうですね」

ワイ「今回も**完全一致**なんやね」

ハスケル子「はい」
ハスケル子「だって**『含む』**だと**35文字**でも**OK**になってしまいますからね」

ワイ「なるほどや」

ハスケル子「でも、ここは無理に正規表現を使う必要ないかもですね」

ワイ「ん？どういうこと？」

# 無理に正規表現で頑張る必要はない

ハスケル子「つまり↓こんな感じで書いてもいいってことです」

```js:JavaScript
const isValid = 0 < userInput.length && userInput.length <= 30;
```

ハスケル子「単に`length`で済みますからね」
ハスケル子「無理に正規表現で頑張るより」
ハスケル子「JSの便利なメソッドたちを活用していきましょう」

ワイ「なるほどな」

ハスケル子「ちなみに**サロゲートペア**[^1]のことも考えるなら」
ハスケル子「`userInput.length`より」
ハスケル子「`[...userInput].length`の方がいいですね」

ワイ「**デリケートビア**・・・」
ワイ「ああ、**腐りやすいビール**のことやな？」

ハスケル子「**サロゲートペア**です」
ハスケル子「絵文字や一部の漢字は**4バイト文字**なので、1文字で**2文字分**にカウントされちゃう、ってやつです」

ワイ「なるほどな」
ワイ「最近は**絵文字を含んだ苗字**の人もおるもんな」

ハスケル子「そうですか」

ワイ「（相変わらずツッコミ冷たいな・・・）」

ハスケル子「（**腐ったビールでも飲んでればいいのに**）」

# 指定した選択肢の中の文字列のみOKにする

ワイ「次は、**年号**のバリデーションやな」
ワイ「**大正・昭和・平成・令和**だけをOKにしたいんや」
ワイ「せやから・・・」

```js:JavaScript
const isValid = /^大正$|^昭和$|^平成$|^令和$/.test(userInput);
```

ワイ「↑こうやな」
ワイ「記号の`|`は**『または』**って意味やから」

> + `^大正$`
> + または`^昭和$`
> + または`^平成$`
> + または`^令和$`

ワイ「ってことや」

ハスケル子「それでもいいですけど、もっと短く書けますよ」

ワイ「そうなん？」
ワイ「えーと」
ワイ「`/^大正|昭和|平成|令和$/`とか？」

ハスケル子「惜しいですね」
ハスケル子「それだと」

> + `大正`から始まる文字列
> + または`昭和`を含む文字列
> + または`平成`を含む文字列
> + または`令和`で終わる文字列

ハスケル子「上記の全部に当てはまってしまいます」
ハスケル子「つまり」

> + 大正生まれやで！
> + 多分平成かなぁ
> + キラキラの令和

ハスケル子「↑こんな文字列まで**OK**になっちゃいます」

ワイ「ガバガバやん・・・」
ワイ「`^`や`$`の方が、`|`より先に評価されてしまうんやな・・・」

ハスケル子「はい」
ハスケル子「なので」

```js:JavaScript
const isValid = /^(大正|昭和|平成|令和)$/.test(userInput);
```

ハスケル子「↑こうですね」

ワイ「`^(大正|昭和|平成|令和)$`かぁ」
ワイ「括弧をつけるんやね」

ハスケル子「括弧で**グループ化**すると」
ハスケル子「その中は**先に評価して**もらえます」
ハスケル子「なので・・・」

> + `大正`または`昭和`または`平成`または`令和`
> + `^`と`$`で挟んであるので「含む」ではなく「完全一致」

ハスケル子「ちゃんと↑こういう意味に評価してくれるんです」
ハスケル子「でも、この場合も無理に正規表現にしなくてもいいですね」

```js:JavaScript
const isValid = ["大正", "昭和", "平成", "令和"].includes(userInput);
```

ハスケル子「↑これでいいわけですからね」

# 否定先読み

ワイ「さて次は」

> * `無職`から始まる文字列であること
> * ただし`無職やめ太郎`から始まる文字列はNG

ワイ「こういう**バ・リデーション**や」

ハスケル子「（**どんなご注文フォームなんだろう**）」
ハスケル子「（やめ太郎さん、**出禁**にされてるのかな・・・）」

ワイ「こういう**否定**みたいなのは正規表現でどうやって表すんやろ」

ハスケル子「ええと」

```js:JavaScript
const isValid = /^無職(?!やめ太郎)/.test(userInput);
```

ハスケル子「↑こうですね」
ハスケル子「`(?!やめ太郎)`は**否定先読み**っていう書き方です」

> + `無職`から始まる　→　`^無職`
> + ただし後ろに`やめ太郎`が続くのは**NG**　→　`(?!やめ太郎)`

ハスケル子「↑こういう意味です」

# 否定後読み

ワイ「次は」

> + `太郎`を含むこと
> + ただし`やめ太郎`はNG

ワイ「↑この条件や」

ハスケル子「（これは**厳し目な出禁**だな・・・）」

```js:JavaScript
const isValid = /(?<!やめ)太郎/.test(userInput);
```

ワイ「↑こうやな！」

ハスケル子「合ってますけど、その**否定後読み**は**ES2018**で追加されたものなので」
ハスケル子「**Firefox**や**Safari**で使えないですね・・・」

ワイ「おお、そうなんか・・・」

ハスケル子「なので」

```js:JavaScript
const isValid = /太郎/.test(userInput) && !/やめ太郎/.test(userInput);
```

ハスケル子「こんな感じにしておきましょう」

ワイ「おお、2つ組み合わせるのもええね」


# ほかにも色々

ワイ「次は」

> + 1st, 2nd, 3rd, 4th
> + 11th, 12th, 13th, 14th
> + 21st, 22nd, 23rd, 24th

ワイ「↑こんなやつだけを**OK**にしたいんや！」
ワイ「英語の**序数**ってやつやな」

ハスケル子「ええと」

```js:JavaScript
const isValid = /^(([0-9]*[02-9])?(1st|2nd|3rd)|([0-9]*([04-9]|1[1-3])th))$/.test(userInput) && /^[1-9]/.test(userInput);
```

ハスケル子「↑こうですね」

ワイ「おお」
ワイ「けっこう長いな」

ハスケル子「はい」
ハスケル子「日本語で説明すると」

> + 1文字以上の数字の後ろに`st`,`nd`,`rd`,`th`がつく形式

ハスケル子「↑ざっくりこんなルールですよね」
ハスケル子「細かく定義すると・・・」

> + **下1桁**が`1`の時は末尾に`st`がつくべき
> + **下1桁**が`2`の時は末尾に`nd`がつくべき
> + **下1桁**が`3`の時は末尾に`rd`がつくべき
> + ただし**下2桁**が`11,12,13`の時は`th`がつくべき
> + それ以外の場合は末尾に`th`がつくべき
> + **1~9**から始まるべき（0からは始まらない）

ハスケル子「↑こんなルールですよね」

ワイ「せやな」

ハスケル子「1桁なら割とシンプルで」

```js:JavaScript
const isValid = /^(1st|2nd|3rd|[4-9]th)$/.test(userInput);
```

ハスケル子「↑こんな感じです」

ワイ「なるほどな」

> + `1st`または`2nd`または`3rd`のパターン　→　`1st|2nd|3rd`
> + または**4〜9**の後ろに`th`がつくパターン　→　`[4-9]th`
> + それを`^()$`に入れて**完全一致**にする

ワイ「↑ってことやな」

ハスケル子「はい」
ハスケル子「それが2桁以上になると」

> + ただし**下2桁**が`11,12,13`の時は`th`がつくべき
> + **1~9**から始まるべき（0からは始まらない）

ハスケル子「↑こんなルールが加わるので」

```js:JavaScript
const isValid = /^(([0-9]*[02-9])?(1st|2nd|3rd)|([0-9]*([04-9]|1[1-3])th))$/.test(userInput);
```

ハスケル子「↑こんなに長くなります」

ワイ「う〜ん・・・」
ワイ「`11,12,13`の場合は`11st,12nd,13rd`やなくて`11th,12th,13th`になるわけやから・・・」

> + `1st,2nd,3rd`の前に1は来ない。つまり**0**または**2〜9**が来る。  
→　`[02-9]`
> + 下1桁が**0**または**4〜9**の時は`th`がつく。または**下2桁**が`11,12,13`の時も`th`がつく。  
→　`([04-9]|1[1-3])th`

ワイ「↑ってことかぁ・・・」

ハスケル子「はい」
ハスケル子「でも、これでもまだ・・・」

> + **1~9**から始まるべき（0からは始まらない）

ハスケル子「↑このルールが満たせていません」

ワイ「まじか・・・」

ハスケル子「そこも全て正規表現で表そうとすると、更に長くなってしまうので・・・」

```js:JavaScript
 && /^[1-9]/.test(userInput);
```

ハスケル子「↑この条件を加えることで**0始まり**をブロックしています」

ワイ「なるほどな」

> + **1〜9**から始まらなあかんよ　→　`^[1-9]`

ワイ「↑ってことやな」

ハスケル子「はい」
ハスケル子「今回は割と長めの正規表現で頑張りましたが」
ハスケル子「実際にはもっと分割して`&&`で繋いだ方がいいですね」

ワイ「なるほどなぁ」
ワイ「実務のコードで**長〜い**正規表現を書かれても、読まれへんもんな・・・」

# 正規表現完全理解ワイ

ワイ「とにかく」
ワイ「正規表現、**完全に理解**したわ」

ハスケル子「これでやめ太郎さんも正規表現いっぱい書けますね！」

ワイ「おう！」
ワイ「**理論上は可能や！**」

# 何でそんなにできるようになったん？

ワイ「ていうかハスケル子ちゃん」
ワイ「何でそんなに正規表現できるようになったん？」

ハスケル子「[**このサイト**](https://regexr.com)で練習しました」

ワイ「おお」
ワイ「正規表現を試しに書いて」
ワイ「実際にどういった文字列が該当するのかが分かるんやな」
ワイ「ワイもこのサイトで勉強してみるわ・・・！」

#### やめ太郎の戦いは続く・・・ 

# 他にもおすすめサイト

+ [uhyohyo.net　JavaScript初級者から中級者になろう]
(https://uhyohyo.net/javascript/4_2.html)
+ [正規表現 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions)


[^1]: 絵文字や一部の漢字は**サロゲートペア文字**と呼ばれる**4バイト文字**なため、1文字で**2文字**とカウントされてしまいます。
[^2]: ちなみに、この正規表現でチェックできるのは100thまでです。