Unicode::Normalize で遊ぶ

Unicode の規格では,文字の合字(リガチャ等)等を統一的に扱えるように,「正規化」という処理が仕様として定まっています。この正規化処理のうち「互換性分解」という処理を行うと副作用として半角カナを全角カナに変換できます(逆に全角カナ→半角カナはできません)。

#!/usr/bin/perl

use strict;
use utf8;
use Encode;
use Unicode::Normalize;

my $src = 'ポンジュース';
my $dst = Unicode::Normalize::NFKC($src);

print Encode::encode('utf8', "${src} => ${dst}\n");
# OUTPUT is: ポンジュース => ポンジュース

Unicode の正規化についてはperl5.8のUnicodeサポート および http://homepage1.nifty.com/nomenclator/unicode/normalization.htm が詳しいです。特に後者に


UAX #15によれば、NFKDおよびNFKCは、互換分解によって多くの書式上の差異をなくしてしまうため、任意のテキストに対して無闇に用いてはいけない (Normalization forms KC and KD must not be blindly applied to arbitrary text.) が、大文字・小文字を揃える処理のように考えるのが良く、例えば、特定の文脈においてテキストの核心の意味を特定したり、必ずしも適切ではないテキストを修正したりするのに有用とされています。
と書いてあります通り,本来はたとえばデータベース等で同じようなフィールド値(ⅢとIIIとか)を検索したい場合に用いるものですので,むやみやたらと用いるべきではないそうです。ウェブアプリでのフォーム入力は適用範囲としてまぁまぁ当てはまる気もしますが。
さて。
普通リガチャというと欧文文字でのアセント付き文字やfiなどを指しますが,日本語でも複数の文字を組み合わせた記号を使ったりします。たとえば,「㈱」とか。
こういった文字たちを,互換分解するとどうなるのか,ためしてみました。

#!/usr/bin/perl

use strict;
use utf8;
use Encode;
use Unicode::Normalize ();

my @chars = (
  "\x{00a9}" => 'copyright',
  "\x{00be}" => '4分の3',
  "\x{00ab}" => '<<',
  "\x{2103}" => '℃',
  "\x{2121}" => 'TEL',
  "\x{2122}" => 'トレードマーク',
  "\x{2166}" => 'ローマ数字 大7',
  "\x{2177}" => 'ローマ数字 小8',
  "\x{2460}" => '丸付き数字 1',
  "\x{2475}" => '括弧付き数字 2',
  "\x{248a}" => '3.',
  "\x{249f}" => '(d)',
  "\x{24ba}" => '丸付き英字 E',
  "\x{24d5}" => '丸付き英字 f',
  "\x{266d}" => '音楽記号 フラット',
  "\x{266f}" => '音楽記号 シャープ',
  "\x{2749}" => 'balloon spoked asterisk',
  "\x{277c}" => '黒丸数字 7',
  "\x{3004}" => 'JISマーク',
  "\x{3020}" => 'ポストンくん',
  "\x{3036}" => 'マル〒',
  "\x{3037}" => 'ダブルエックス',
  "\x{3227}" => '(八)',
  "\x{3240}" => '(祭)',
  "\x{3299}" => 'マル秘',
  "\x{32c5}" => '6月',
  "\x{33e4}" => '5日',
  "\x{32da}" => 'マルサ',
  "\x{334c}" => 'メガトン',
  "\x{3370}" => '24点',
  "\x{337b}" => '平成',
  "\x{337f}" => '株式会社',
  "\x{3393}" => 'ギガヘルツ',
  "\x{33a0}" => '平方センチメートル',
);

while (@chars) {
  my $chr = shift @chars;
  my $desc = shift @chars;

  my $nfkc = Unicode::Normalize::NFKC($chr);
  next if $chr eq $nfkc;
  print Encode::encode('utf8', "${chr}\t${nfkc}\t${desc}\n");
}

こんなにも使わなさそうな(失礼)記号がたくさん定義されているんだなとも思いますが,これの実行例は

¾	3⁄4	4分の3
℃	°C	℃
℡	TEL	TEL
™	TM	トレードマーク
Ⅶ	VII	ローマ数字 大7
醞	viii	ローマ数字 小8
①	1	丸付き数字 1
⑵	(2)	括弧付き数字 2
⒊	3.	3.
⒟	(d)	(d)
Ⓔ	E	丸付き英字 E
ⓕ	f	丸付き英字 f
〶	〒	マル〒
㈧	(八)	(八)
㉀	(祭)	(祭)
㊙	秘	マル秘
㋅	6月	6月
㏤	5日	5日
㋚	サ	マルサ
㍌	メガトン	メガトン
㍰	24点	24点
㍻	平成	平成
㍿	株式会社	株式会社
㎓	GHz	ギガヘルツ
㎠	cm2	平方センチメートル

ブラウザですべてを正しく表示できない可能性も高いので,UTF-8 が使えるターミナルをお持ちの方はご自分で試して頂けるとより楽しめると思います。


「㈱」とか「Ⅲ」とか入力で使うなよ!とお怒りの(昔ながらの?)業務アプリ制作者の方や,検索システムを構築してるという方は,気にとめておくとみんなハッピーになれるかもしれません。