Unicode正規化を実装する (2) 正規分解・互換分解
前回: Unicode正規化を実装する (1) UCDにふれる - アジョブジ星通信
さて、前回 UnicodeData.txt の読み方をやりましたので、これを使って実際に正規分解・互換分解を実装してみましょう。
サンプルコードと見比べながら説明を読んでいただければと思います。
目次
- 分解テーブルとCCCテーブルをつくる
- 分解テーブルに沿って分解する
- ハングル音節文字を分解する
- 正規順序(Canonical Ordering)に並べ替える
- 完成した正規化の実装をテストする
- 次回予告
分解テーブルとCCCテーブルをつくる
正規化の主な作業はテーブルのルックアップです。というわけでまずはテーブル作りから。
つくるテーブルは
キー | 値 |
---|---|
コードポイント | Decomposition_Type, Decomposition_Mapping |
と
キー | 値 |
---|---|
コードポイント | Canonical_Combining_Class |
です。
それぞれ、分解できないものや CCC が 0 のものをテーブルに入れる必要はありません。不要なメモリはつかわないっ!
分解テーブルに沿って分解する
分解は再帰的に行います。ある文字を分解マッピングが「XY」の 2 文字だったとしたら、さらに X と Y に対してそれぞれ分解を試みる必要があります。
前回の記事で説明したように、 Decomposition_Mapping にはコードポイントが並んでいるだけのものと、「<形式>」のあとにコードポイントが並んでいるものがあり、それぞれ正規分解マッピング、互換分解マッピングといいます。正規分解時には正規分解マッピングを使用し、互換分解時には両方のマッピングを使用します。
試しに U+0390(ΐ) を正規分解してみましょう。関連する文字の Decomposition_Mapping はこのようになっています。
コードポイント(昇順) | Decomposition_Mapping |
---|---|
0301 | |
0308 | |
0390 | 03CA 0301 |
03B9 | |
03CA | 03B9 0308 |
図で表すとこんな感じ。
0390 ├ 03CA │ ├ 03B9 │ └ 0308 └ 0301
というわけで U+0390 の正規分解結果は
03B9 0308 0301
となります。正規分解なので表示はまったく同じです。
ハングル音節文字を分解する
ハングルとかよく知らないので知らないんですけど、音節文字(よくみるハングル)は、初声(L)、中声(V)、終声(T)から成り立っていて、 Unicode にはこれらバラバラの字母と LV 型、 LVT 型の音節文字が含まれているそうです。知らんけど。
で、この音節文字には Decomposition_Mapping は設定されていません。しかし分解してあげる必要があります。どうやって?以下 Unicode 8.0.0 Core Specification 3.12 Conjoining Jamo Behavior に書いてあるサンプルコードです。
static final int SBase = 0xAC00, LBase = 0x1100, VBase = 0x1161, TBase = 0x11A7, LCount = 19, VCount = 21, TCount = 28, NCount = VCount * TCount, // 588 SCount = LCount * NCount; // 11172 public static String decomposeHangul(char s) { int SIndex = s - SBase; if (SIndex < 0 || SIndex >= SCount) { return String.valueOf(s); } StringBuffer result = new StringBuffer(); int L = LBase + SIndex / NCount; int V = VBase + (SIndex % NCount) / TCount; int T = TBase + SIndex % TCount; result.append((char)L); result.append((char)V); if (T != TBase) result.append((char)T); return result.toString(); }
どうしてこのようになるのか、それは計算すればパパっと求められる、そういう仕様にしたからです。説明放棄してるように見えるかもしれませんが、説明しようにもこうやって分解できるように仕様が制定されてるとしか言いようがないです。。
というわけで、 SIndex を計算して範囲内ならばハングル音節文字として分解、そうでないならば分解テーブルを使って分解するようにしましょう。
正規順序(Canonical Ordering)に並べ替える
分解が終わったら、次はソートのお時間です。これをやるまでが NFD, NFKD の仕様なので頑張りましょう。
やることは CCC を使ったソートです。 CCC が 0 ではない連続した 2 文字 A, B が CCC(A) > CCC(B)
ならば、順番を入れ替えて B, A とします。つまり CCC の昇順に並べ替えろということです(と言ってしまうと間違った実装をしてしまうので、定義を注意深く覚えておいてください)。
例はこちらを見ていただくとわかりやすいかと: http://nomenclator.la.coocan.jp/unicode/normalization.htm#ordering
完成した正規化の実装をテストする
UCD の NormalizationTest.txt には正規化できる全文字と、引っかかりやすい仕様の重箱の隅をつつくようなテストが収録されています。これをクリアできれば、あなたの実装は成功です。
使い方は、最初の方にコメントで書いてあるのでわかると思います。今回は NFD と NFKD の実装を行ったので、それに対するテストを実行してみましょう。
- NormalizationTest.txt の読み込みコード
- 分解テストのサンプル(合成も一緒に書いてあります)
次回予告
はい、お疲れ様でした。分解に関しては、本当に用意されているマッピングに沿ってやっていくだけなので簡単だと思います。
次回は合成について説明します。また3週間くらい空いちゃったらごめんね!!
追記: 3週間と言っていたら3ヶ月になってしまった