アジョブジ星通信

進捗が出た頃に更新されるブログ。

Unicode正規化を実装する (1) UCDにふれる

前回の記事のライブラリを発展させた ToriatamaText というライブラリをリリースしました。

これを作るにあたって Unicode 正規化 Form C を実装したので、ブログに仕様とかを書いておこうという思いです。サンプルコードは C# で書いていきますが、 C# に限った話ではないので、これから Unicode 正規化を実装しようとする人には参考になると思います。というか日本語情報なさすぎでしょ。

注意

この記事に間違ったことが書いてあり、それにより読者に何らかの損害があったとしても、「知らんがな」としか言いようがないです。英語力が足りなさすぎて、 Unicode の仕様をすべて読み込むことができなかったので、動かばいいや(= テストが通ればいいや)というスタンスでやっていっています。

C#er「String.Normalize使えばいいじゃん」

PCL も .NET Core も扱ったこと無い甘ちゃんが!!いいか! String.Normalize は Windows API に依存してるからあんまりポートされてないんやで!!

Unicode 正規化を実装するにはどのようなことをすればいいか

Unicode 正規化には 4 種類の形式がありますが、それぞれ以下の様な意味があります。

NFC
正規合成。正規分解を行い、それを再度元に戻していくことで、もっとも単純で短い形にしていく。
NFD
正規分解。Unicodeの仕様で定められている通りに1文字を1~数文字に分解し、これまた仕様で定められているように並べ替える。アクセント記号とかああいうのをバラバラにしていく感覚。
NFKC
互換合成。互換分解を行い、それを正規合成する。
NFKD
互換分解。NFCよりももっとバラバラにする。「ª」が「a」になったりと、意味のある書式まで変換したりする。

「正規」は意味や表示を変えない、「互換」は文字を抽出していく、というスタンスだと思われます。

で、これを実装する方法ですが、主な作業は変換テーブルを作ってとにかく変換する、ということです。幸い、すべての情報は Unicode Character Database (UCD) として unicode.org で公開されているので、実装者がやることは、そのデータを読み取り変換テーブルを作成するのを自動化するプログラムを書くことになるでしょう。

UCD をダウンロードする

最新バージョン(現在は 9.0.0)は以下のディレクトリからダウンロードすることができます。
http://www.unicode.org/Public/UCD/latest/ucd/

Unicode のバージョンを指定したい場合はここから
http://www.unicode.org/Public/

UnicodeData.txt の読み方

UCD の中でも各文字について多くの情報を持っているファイルが UnicodeData.txt です。説明用に数行ピックアップしたのでご覧ください。

304B;HIRAGANA LETTER KA;Lo;0;L;;;;;N;;;;;
304C;HIRAGANA LETTER GA;Lo;0;L;304B 3099;;;;N;;;;;
3099;COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK;Mn;8;NSM;;;;;N;NON-SPACING KATAKANA-HIRAGANA VOICED SOUND MARK;;;;
00AA;FEMININE ORDINAL INDICATOR;Lo;0;L;<super> 0061;;;;N;;;;;
0061;LATIN SMALL LETTER A;Ll;0;L;;;;;N;;;0041;;0041

見ての通り、「;」で句切られていろいろなデータが入っていますね。一番左側がコードポイント、つまり Unicode 上での番号(16進数)です。これを 0 番目とすると、 1 番目に名前が入っていますね。

正規化処理で必要な情報は 3 番目と 5 番目です。

まずは 5 番目のほうから見て行きましょう。

コードポイント 実際の文字 5番目の値
304B
304C 304B 3099
3099
00AA ª <super> 0061
0061 a

5 番目には「Decomposition_Type」および「Decomposition_Mapping」という名前がついています。これは分解を行う方法を指示するものです。

では試しに、コードポイント 304C 「が」を分解してみましょう。値が「304B 3099」なので「304B」と「3099」の2文字に分けることができました。データ上 2 文字にわけることができましたが、表示してみると 1 文字に見えます。

が

このように表示が変わっていないことから、これは正規分解です。

次に 00AA を見ていきましょう。「<super> 0061」と、コードポイント以外に何か書いてありますね。これはこの文字が 0061 に対する Superscript form つまり上付き文字の形式になったものだよ!ということを表しています。

それではこの通りに分解してみましょう。と言っても 1 文字なので 0061 に置き換わるだけですね。結果はこんな感じ。

a

上付き文字ではなく普通の「a」ですね。表示が変わってしまいました。これが互換分解です。

と、ちょっと分解の話をしたところで次は 3 番目を見ていきましょう。 3 番目には「Canonical_Combining_Class」という名前がついています。長いのでこの先では「CCC」と呼んでいきます。

コードポイント 実際の文字 3番目の値(10進数)
304B 0
304C 0
3099 8
00AA ª 0
0061 a 0

見ての通り 3099 以外は 0 ですね。実はこの値は他の文字とくっついて効果を表すタイプの文字にしか設定されていません。それ以外は 0 になっています。 CCC はくっついて効果を表すタイプの文字の種類を表しています。 8 は「Kana_Voicing」という名前がついていて、文字通りひらがな・カタカナにくっつくやつです。正規化では、この番号を使って分解後の並べ替えを行います。

次回予告

とりあえず、書けそうなところまで書きました。他人に説明できる文章にするにはもっと詳しく調べる必要があるので、ちょっと時間がかかると思いますお待ちください。

第2回は分解、第3回は合成、第4回は高速化手法を説明していく予定です。お楽しみに(書くと決まったわけではない)

参考

Unicode正規化
ググった感じもっとも詳しい日本語解説です。

The Unicode Standard Version 8.0 Core Specification Chapter 3 Conformance
3.11 Normalization Forms の章に書いてあるのが正規化の完全な仕様です。というかこれを読めるなら僕の解説要らないよね。
追記: 9.0.0 がリリースされましたが、仕様書は8月まで待てとのこのとです。

UAX #15: Unicode Normalization Forms
仕様以外の補足情報として公開されている文書です。実装のヒントとかいっぱい書いてあるっぽいけど、英語読めない。

UAX #44: Unicode Character Database
UCD の読み方について書いてある文書です。 Unicode を扱うときには常ににらめっこするやつです。