正規表現でひらがな・カタカナ・漢字を判定|「ー」「々」が弾かれる理由
フォームのフリガナ欄で /^[ぁ-ん]+$/ を使ったところ、「らーめん」や「ゔぁいおりん」が弾かれた、という相談はよくあります。漢字の判定に [一-龥] を使うと「佐々木」が通りません。
結論から言うと、範囲指定には取りこぼしがあり、用途が「入力チェック」なら u フラグ付きの Unicode プロパティか、許可する文字を明示した範囲指定のどちらかに寄せるのが安全です。この記事では、手元の Node.js で実測した結果をもとに、どの文字がなぜ弾かれるのかと、そのまま使えるパターンを整理します。
結論:用途別のおすすめパターン
先に答えを表にします。詳しい理由は後の章で説明します。
| 用途 | 推奨パターン | 備考 |
|---|---|---|
| ひらがなだけ(フリガナ欄) | /^[ぁ-ゖゝゞー]+$/ | 「ー」「ゔ」「ゝ」を含む。濁点の分離形は通らない |
| カタカナだけ(フリガナ欄) | /^[ァ-ヺー]+$/ | 「ヴ」「ー」を含む。半角カナは通らない |
| 漢字を含むか調べる | /\p{sc=Han}/u | サロゲートペアの漢字(𠮷など)も拾える |
| 漢字だけで構成(氏名など) | /^[\p{sc=Han}々〆]+$/u | 「々」は Han 扱いだが「〆」は別扱いなので足す |
| ひらがなを抜き出す | /\p{sc=Hiragana}+/gu | 「ー」は拾わない。後述 |
ぁ のような書き方に抵抗があれば ぁ-ゖ と書いても同じです。ただし、見た目が似た文字を取り違えやすいため、コードポイントで書いたほうが後から読み返しやすいと考えられます。
なぜ [ぁ-ん] では「らーめん」が通らないのか
ひらがなの範囲指定でいちばん多い失敗は、長音記号「ー」を忘れることです。「ー」は U+30FC で、カタカナのブロックに属する文字です。ひらがなの範囲 ぁ-ん(U+3041〜U+3093)には含まれません。
実測した結果が次のとおりです(Node.js v20.20.1 で /^[ぁ-ん]+$/ を試験)。
| 入力 | コードポイント | 結果 |
|---|---|---|
| ひらがな | U+3072 U+3089 U+304C U+306A | 通る |
| らーめん | U+3089 U+30FC U+3081 U+3093 | 弾かれる |
| ゔぁいおりん | U+3094 U+3041 … | 弾かれる |
| ゝ(踊り字) | U+309D | 弾かれる |
| ゞ | U+309E | 弾かれる |
| ゕ | U+3095 | 弾かれる |
「ん」(U+3093)で範囲が終わっているため、その直後にある「ゔ」(U+3094)や小書きの「ゕ」「ゖ」も外れます。範囲の終端を ん ではなく ゖ(U+3096)にするだけで、「ゔ」は通るようになります。
カタカナも同じ構造
カタカナの [ァ-ン] でも同様です。
| 入力 | [ァ-ン] | [ァ-ンー] |
|---|---|---|
| カタカナ | 通る | 通る |
| ラーメン | 弾かれる | 通る |
| ヴァイオリン | 弾かれる | 弾かれる |
| ヵ・ヶ | 弾かれる | 弾かれる |
| カタカナ(半角) | 弾かれる | 弾かれる |
「ヴ」(U+30F4)は「ン」(U+30F3)の次にあるため、ー を足しても通りません。終端を ヺ(U+30FA)まで伸ばす必要があります。
半角カナは別のブロック(U+FF66〜U+FF9F)にあるため、そもそも範囲外です。半角カナを許可するか、全角に変換してから判定するかは、フォームの仕様として先に決めておくほうがよいでしょう。全角・半角の変換は全角・半角が混在したデータを一括で揃える方法や全角⇔半角変換ツールで試せます。
漢字の [一-龥] で「々」「𠮷」が弾かれる
漢字の判定として広く出回っているのが [一-龥] です。これは U+4E00〜U+9FA5 の範囲指定で、次の文字が漏れます。
| 入力 | 範囲 [一-龥] | \p{sc=Han}(u フラグ付き) |
|---|---|---|
| 漢字 | 通る | 通る |
| 髙橋 | 通る | 通る |
| 齋藤 | 通る | 通る |
| 佐々木 | 弾かれる(「々」U+3005) | 通る |
| 𠮷野家 | 弾かれる(「𠮷」U+20BB7) | 通る |
| 〇 | 弾かれる(U+3007) | 通る |
| 〆 | 弾かれる(U+3006) | 弾かれる |
| 鿕(U+9FD5) | 弾かれる | 通る |
氏名の入力欄に [一-龥] を使うと、「佐々木」さんが登録できなくなります。人の名前に使う文字は、一般的な漢字の範囲からはみ出すことがあるため、「漢字だけ」という制限自体を見直す選択肢もあります。
「々」は通るのに「〆」は通らない
\p{sc=Han} は Unicode のスクリプト属性が Han の文字に一致します。実測では「々」「〇」は一致し、「〆」は一致しませんでした。「〆」を許可したい場合は、[\p{sc=Han}々〆] のように明示的に足します。
どの文字が Han 扱いになるかは Unicode のバージョンに依存するため、実行環境によって結果が変わる可能性があります。今回の実測は Node.js v20.20.1 のみで、ブラウザごとの差は確認していません。
サロゲートペアと u フラグの落とし穴
「𠮷」(U+20BB7)のように、U+FFFF を超えるコードポイントの文字は、JavaScript の文字列では2つのコード単位(サロゲートペア)で表されます。実測では次のとおりでした。
'𠮷'.length // 2
[...'𠮷'].length // 1
/^.$/.test('𠮷') // false
/^.$/u.test('𠮷') // true
u フラグを付けないと、正規表現は文字列をコード単位で扱います。そのため . が1文字に一致せず、文字クラスの中にサロゲートペアの文字を書いても意図どおりに動きません。
\p{...} は u フラグなしだとエラーにならない
見落としやすい点です。u フラグを付け忘れると、\p{sc=Han} は構文エラーにならず、文字どおりの「p{sc=Han}」という文字列として解釈されます。実測結果です。
/^\p{sc=Han}+$/.test('漢字') // false
/^\p{sc=Han}+$/.test('p{sc=Han}') // true
エラーが出ないため、動作確認で「漢字が一致しない」と気づくまで原因が分かりにくくなります。\p を使うときは、必ず末尾が /u になっているかを確認してください。
当サイトの正規表現テスターでは \p{...} を試せません
正規表現テスターのフラグ欄には g・i・m・s のチェックボックスがあり、u フラグの選択肢はありません(ソースコードで確認しました)。そのため \p{sc=Han} の挙動はテスターでは再現できず、ブラウザの開発者ツールのコンソールや Node.js で試す必要があります。
一方、[ぁ-ゖゝゞー] のようなコードポイント指定の範囲は u フラグなしでも動くため、テスターで確認できます。パターンを組み立てる段階ではテスターを使い、\p を使う場合だけコンソールで最終確認する、という分担が現実的です。
実務の落とし穴:「ー」は sc では取れず、scx では取れすぎる
ここからが、他所であまり書かれていない部分です。Unicode プロパティには、スクリプト属性の sc(Script)と、より広い scx(Script_Extensions)があります。
「ー」(U+30FC)は、sc ではひらがなにもカタカナにも属しません。 実測結果を並べます。
| 入力 | \p{sc=Hiragana} | \p{scx=Hiragana} | \p{scx=Katakana} |
|---|---|---|---|
| らーめん | 弾かれる | 通る | 弾かれる |
| ラーメン(カタカナ側) | 弾かれる | 弾かれる | 通る |
| ゔ・ゝ | 通る | 通る | 弾かれる |
sc=Hiragana で「らーめん」を判定すると失敗します。では scx=Hiragana にすれば解決かというと、取りすぎも起きます。実測では、\p{scx=Hiragana} は次の文字にも一致しました。
| 文字 | コードポイント | scx=Hiragana |
|---|---|---|
| ー(半角の長音) | U+FF70 | 一致する |
| ・(中黒) | U+30FB | 一致する |
| ゛(全角の濁点) | U+309B | 一致する |
| 〃(同上記号) | U+3003 | 一致する |
つまり /^\p{scx=Hiragana}+$/u は、「らーめん」(途中が半角の長音)も通してしまいます。フリガナ欄で半角が混じるのを避けたいなら、scx に頼らず、冒頭の表にあるコードポイント指定の範囲のほうが厳密です。実測では、[ぁ-ゖゝゞー] は「らーめん」を通し、「らーめん」を弾きました。
濁点の分離形(NFD)は別問題
Mac から渡されたデータでは、「が」が「か」と結合用濁点(U+3099)の2文字に分かれている場合があります。この形は [ぁ-ん] でも、コードポイント指定の範囲でも弾かれます。
実測では、\p{scx=Hiragana} は分離形の「か゛」を通しました。逆に言えば、scx を使うと NFD のデータが紛れ込んでも検証を素通りします。後続の処理(重複チェックや検索)で不一致が起きる可能性があるため、判定の前に s.normalize('NFC') を通すのが無難です。原因と直し方はMacのファイル名がWindowsで濁点が分離する原因で詳しく書いています。
const isHiraganaOnly = (input) =>
/^[ぁ-ゖゝゞー]+$/.test(input.normalize('NFC'));
isHiraganaOnly('らーめん'); // true
isHiraganaOnly('らーめん'); // false(半角の長音)
isHiraganaOnly('ラーメン'); // false
文字の種類ごとに抜き出す書き方
判定ではなく「文章から漢字だけ抜き出したい」場合は、g と u を併用します。実測結果です。
'漢字とかなカナ'.match(/\p{sc=Han}+/gu)
// [ '漢字' ]
'ひらがなとカタカナ'.match(/\p{sc=Katakana}+/gu)
// [ 'カタカナ' ]
「ラーメン」のように長音を含むカタカナ語を連続したまとまりとして抜き出したい場合は、sc だと「ー」で途切れる点に注意してください。そのときは [\p{sc=Katakana}ー]+ のように長音を足します。
文字数の数え方も揃える
氏名欄などで文字数制限をかけるときは、.length ではなく [...s].length を使います。実測では「佐々木𠮷」は .length が5、[...s].length が4でした。文字数の数え方については文字数カウントツールでも、文字数とバイト数の違いを確認できます。
全角英数字や空白はどうなるか
見落としやすい例も実測しました。全角の「a」「A」「1」や全角スペース(U+3000)は、\p{sc=Han}・\p{sc=Hiragana} のどちらにも一致しません。
したがって「ひらがなと空白だけ」を許可するなら、スペースを明示的に追加します。
// 姓と名の間に全角スペースまたは半角スペースを許可する
/^[ぁ-ゖゝゞー]+[ ][ぁ-ゖゝゞー]+$/
入力欄の途中に紛れた余分な空白は、判定の前に取り除くか、まず入力を正規化するほうが、利用者に「エラーです」と返す回数を減らせます。
フォームでの使い方:厳しくしすぎない
フリガナ欄の判定を厳密にしても、入力ミスの多くは防げません。「ひらがなで入力してください」と出して利用者が困るくらいなら、次のような設計も選択肢です。
- カタカナ入力欄にひらがなが入力されたら、エラーにせず自動でカタカナに変換する
- 半角カナは全角に変換して受け付ける
- 氏名欄の漢字チェックは行わず、空欄と極端な長さだけを検査する
- 旧字体・異体字・踊り字など、通らない文字が出ることを前提に、確認画面を挟む
どこまで許可するかは、データの使い道(郵送、名寄せ、問い合わせ)によって変わります。判定ルールは仕様として明文化し、弾いた場合にどの文字が原因かをメッセージに出すと、利用者の迷いが減ります。
メールアドレスの検証についても考え方は共通で、厳密にしすぎると実在する入力を弾きます。詳しくはメールアドレスの正規表現を参照してください。正規表現の基本は正規表現の基本と実務でよく使うパターン集にまとめています。
この記事の検証範囲
- 数値・真偽値の結果は、Node.js v20.20.1 で実際に実行して確認したものです
- ブラウザ(Chrome、Safari、Firefox)での挙動は未検証です。Unicode プロパティの対応状況やバージョン差で、結果が異なる可能性があります
\p{...}が使える環境は比較的新しいものに限られると考えられます。古い環境に対応する場合は、コードポイント指定の範囲を使うほうが無難です- 正規表現テスターのフラグ欄については、ソースコード上のチェックボックスを確認しました
まとめ
[ぁ-ん]は「ー」「ゔ」「ゝ」を弾く。ひらがなは[ぁ-ゖゝゞー]のようにコードポイントで範囲を明示する[ァ-ン]は「ー」「ヴ」を弾く。カタカナは[ァ-ヺー]を使い、半角カナは別途扱いを決める- 漢字の
[一-龥]は「々」「𠮷」を弾く。\p{sc=Han}をuフラグ付きで使い、「〆」は必要なら足す uフラグを付け忘れると\p{...}は文字どおりの文字列扱いになり、エラーも出ない。当サイトの正規表現テスターにはuの選択肢がないため、\pはコンソールで確認する- 判定の前に
normalize('NFC')を通し、フォームでは厳密に弾くより自動変換と確認画面で補う