💡

「かな」のバリデーション

2024/11/14に公開

読み仮名のフィールドのバリデーション、意外とめんどくさいぜ。

考え方

2つの文字セットが必要になる。つまり、クライアントで受け入れる文字セットと、データベースに保存する際に利用できる文字セット。後者は前者の部分集合になる。

クライアントで入力された文字列をデータベースに保存する(あるいはデータベースでないかもしれない)際に、入力値を normalize する。日本語のマルチバイト文字は「見た目上同じ文字だがバイナリ上は異なる表現がされている」ケースがあって、要するに「同表記異値字」が存在する。(これは今思いついた造語)

normalize の処理はクライアント文字セットからデータベース文字セットへの写像と考えても良い。

データベース文字セットが最も重要で、そこに対して実装コストのバランスが取れている normalize を書き、それによって変換可能な文字セットがすなわちクライアント文字セットになる。

データベース文字セット

読み仮名なので「カタカナだけ許容すればOK!」とはならない。し、その「カタカナ」についても真面目に考える必要がある。

カタカナ

 	0	1	2	3	4	5	6	7	8	9	A	B	C	D	E	F
U+30Ax	゠	ァ	ア	ィ	イ	ゥ	ウ	ェ	エ	ォ	オ	カ	ガ	キ	ギ	ク
U+30Bx	グ	ケ	ゲ	コ	ゴ	サ	ザ	シ	ジ	ス	ズ	セ	ゼ	ソ	ゾ	タ
U+30Cx	ダ	チ	ヂ	ッ	ツ	ヅ	テ	デ	ト	ド	ナ	ニ	ヌ	ネ	ノ	ハ
U+30Dx	バ	パ	ヒ	ビ	ピ	フ	ブ	プ	ヘ	ベ	ペ	ホ	ボ	ポ	マ	ミ
U+30Ex	ム	メ	モ	ャ	ヤ	ュ	ユ	ョ	ヨ	ラ	リ	ル	レ	ロ	ヮ	ワ
U+30Fx	ヰ	ヱ	ヲ	ン	ヴ	ヵ	ヶ	ヷ	ヸ	ヹ	ヺ	・	ー	ヽ	ヾ	ヿ

https://en.wikipedia.org/wiki/Katakana_(Unicode_block)

あなたが実装しようとしている「読み仮名」に「, , , , 」は含まれるだろうか。

(U+30FB) Katakana Middle Dot は普通に日本語IMEで入力できる記号で、普通に入力すれば中点はこれになる。例えば人名に Katakana Middle Dot は使わないだろうが、住所では建物名に使われているケースがある。また、人名の姓名を区切る記号として Katakana Middle Dot が使われることも考えられる。

(U+30FB) Katakana-Hiragana Prolonged Sound Mark は長音記号だ。類似する記号に (U+FF0D) Fullwidth Hyphen-Minus や (U+FF70) Halfwidth Katakana-Hiragana Prolonged Sound Mark などがある。人名でも住所でも使われる。

(U+30FD) Katakana Iteration Mark および (U+30FE) Katakana Voiced Iteration Mark はいわゆる「踊り字」で、「キツヽキ」や「グヾれカス」のように使います。例が思いつかない。基本的にこれらの文字は受け入れなくても問題ない(キツツキやググれカスのように、普通に入力すればいいため)が、果たして本当にそうであるかは検討すべきだ。

(U+30FF) Katakana Digraph Koto。これについては忘れて良い。また、実は先頭にいる (U+30A0) Katakana-Hiragana Double Hyphen についても忘れていいだろう。

ざっくり言えば、「U+30A1 - U+30FC」の範囲が「カタカナ」になるだろう。中点を除く場合は「U+30A1 - U+30FA + U+30FC」となる。

あるいは , , , についても、不要なら取り除いたほうが良いかもしれない。これらは字としては人名につかえるものもあるが、読み仮名を入力するときは別の表記で代用できる。, については人名でつかえるのかよくわからない……。

ちなみにカタカナについては「半角カタカナ」もあるので、これについても考慮する必要がある。が、データベース文字セットに限るなら半角カタカナは受け入れないで良いと思う。

英語アルファベット・アラビア数字

住所の場合、主に建物名で英語アルファベットとアラビア数字が使えたほうがいい。とくにアラビア数字は必須で、建物名にアルファベットが使われているケースは多い。あるいは、「A棟、B棟」のように使われるケースもあるし、部屋番号にアルファベットが割り振られるケースも日本中を探せばありそうだ。

真面目に考えるなら、英語以外の言語のアルファベットを受け入れるかどうかも決めなければならないが、普通に考えるならば英語アルファベットのみで十分だと考えられる。

データベース文字セットとして英語アルファベット・アラビア数字を許容する場合、全角に統一するのか、半角に統一するのか、あるいは両方を許容するのかを検討しなければならない。

各種記号

カタカナで示した (U+30FB) Katakana Middle Dot、 (U+30FB) Katakana-Hiragana Prolonged Sound Mark などは許容する可能性のたかい記号の筆頭だろうが、他にも様々な記号が考えられる。ただし、表記揺れについては normalize での議論なので、個々では考慮しない。

例えば法人名のカナ表記を入力するフィールドなら、 (U+FF08) Fullwidth Left Parenthesis および (U+FF09) Fullwidth Right Parenthesis の入力も受け入れたほうがいい。「カ)シーカーハウス」のように記述するのが一般的だからだ。

normalize

クライアントで入力された文字列をデータベースに保存できる文字列に変換する処理。結論から言えば Unicode Normalize(後述)をやっておけばだいたい良いのだけど、まずは考慮事項について説明する。

半角・全角の統一

英語アルファベットやアラビア数字を受け入れる場合、半角と全角のどちらに寄せるのかを決める。

また、半角カタカナを全角カタカナに寄せるかどうかも決める。寄せない場合はクライアントで半角カタカナの入力を弾くことになるし、寄せる場合は許容することになる。

ひらがな・カタカナの統一

ひらがな・カタカナのどちらかに寄せる。基本的にはカタカナに寄せればいいと思う。

 	0	1	2	3	4	5	6	7	8	9	A	B	C	D	E	F
U+304x		ぁ	あ	ぃ	い	ぅ	う	ぇ	え	ぉ	お	か	が	き	ぎ	く
U+305x	ぐ	け	げ	こ	ご	さ	ざ	し	じ	す	ず	せ	ぜ	そ	ぞ	た
U+306x	だ	ち	ぢ	っ	つ	づ	て	で	と	ど	な	に	ぬ	ね	の	は
U+307x	ば	ぱ	ひ	び	ぴ	ふ	ぶ	ぷ	へ	べ	ぺ	ほ	ぼ	ぽ	ま	み
U+308x	む	め	も	ゃ	や	ゅ	ゆ	ょ	よ	ら	り	る	れ	ろ	ゎ	わ
U+309x	ゐ	ゑ	を	ん	ゔ	ゕ	ゖ			゙	゚	゛	゜	ゝ	ゞ	ゟ

https://en.wikipedia.org/wiki/Hiragana_(Unicode_block)

ひらがなをカタカナに変換するには 0x60 を加算すれば良い。 0x304B + 0x60 = 0x30AB

ただしこれで変換できるのは U+3097 までで、それ以降はひらがなとカタカナは対応していない。(踊り字 , については対応 , がある)

記号の表記揺れに対応する

例えば Middle Dot 中点にも様々な種類がある。

Typeface Name Encoding
· Middle Dot U+00B7
Canadian Syllabics Final Middle Dot U+1427
Word Separator Middle Dot U+2E31
Halfwidth Katakana Middle Dot U+FF65

これらを「どの範囲の文字をどの文字に」寄せていくのかを決める。……決めるというか、Unicode Normalize に従うので十分だと思う。

結合文字と合成済み文字

ガ である。(ちゃんとなってる? もしかしたら Zenn で保存するときに正規化されてしまっているかも)

日本語のカタカナやひらがなの文字を表現する方法は2種類あり、すなわち「結合文字」と「合成済み文字」である。結合文字とは「元になった文字」と「濁点」を組み合わせて表現される。例えば結合文字では ガU+30ABU+3099 を連続して配置することで表現される。 U+3099 は Combining Katakana-Hiragana Voiced Sound Mark で、直前の文字を濁点文字にするための文字だ。一方で、単に を単に U+30AC で表すのが合成済み文字である。

これを統一しておかないと、例えば「同じ文字列なのに検索でヒットしない」などの不具合が発生する。

合成済み文字のほうが見た目の文字数とバイト列の長さに差がでにくいので、合成済み文字のほうが良いのではないかと思っているが、本当のところどちらのほうがいいのかは真面目に調べたことがない。

Unicode Normalize / Unicode 正規化

これらの様々に入力揺れしがちな文字列をある程度統一的に正規化するための一般的な正規化ルールがあり、Unicode Normalize として大抵のプログラミング言語でサポートされている。例えば Ruby なら unicode_normalize メソッドが使える。

Unicode Normalize には4つの形式があり、それぞれ NFD、NFC、NFKD、NFKC となっている。例えばカタカナの結合文字をカタカナの合成文字にするには、NFC または NFKC を使う。(この2つは文字を一度分解して再結合する)

クライアント文字セット

normalize の設計を踏まえて、「実際に設計した normalize 処理でデータベース文字セットに寄せられる文字セット」が生まれる。この文字セットの部分集合がクライアント文字セットになる。適切なバリデーションを設計しよう。

normalize は半角カタカナを全角カタカナに寄せられるけど、半角カタカナの入力は受け入れない、といったケースがあり得る。バリデーションの実装の難易度などによる。


力尽きた。

Discussion