Unicode Han Databaseを使って簡体字を繁体字に切り替える


Wikipediaの中文(中国語)のページでは、繁体字簡体字を切り替えることが出来る。簡体字は主に中華人民共和国(Mainland China)で使われている文字で、繁体字は主に台湾や香港で使われている。簡体字と繁体字は等価交換できる。今回はそのコードを動かしてみよう。

簡体字の文字と繁体字の文字は、どちらもUnicode Character Database (UCD)に別々の文字として定義されている。「同じ文字」として同一のコードポイントが割り当てられているというようなことはない。Unicodeで定義されている膨大な漢字の情報は、Unicode仕様の一部として、Unicode Han Database (unihan)と呼ばれるデータベース上に定義されている。これはUnicodeコンソーシアムで行われたHan Unificationという活動の成果だ。unihanデーターベースは、zipファイルで5〜6MB近くある、それなりに大きいファイルだ。

このunihanデータベースでは、それぞれの文字について「フィールド」を定義している。フィールドの定義はUTR #38という文書にまとめられているが、その中にkTraditionalVariantとkSimplifiedVariantというものが定義されていて、これらには、その文字に対応する繁体字・簡体字の代替が規定されている。この情報は、unihanデータベースの中では、Unihan_Variants.txtというファイルで定義されている。例えば、U+343D (㐽) については、次のように定義されている。

U+343D kTraditionalVariant U+5051

‘㐽’に対応する繁体字は’偑’ということになるわけだ。もちろん、これに対応する定義がU+5051にもある。

U+5051 kSimplifiedVariant U+343D

というわけで、マッピングの情報をどうすればいいのかはわかった。ではコードにしてみよう。このUnihan_Variants.txtを読んで、簡体字-繁体字の2文字を繋げた文字列を作る。中にはUnicode BMP範囲外の文字もあるので、その場合はサロゲートペアを出力するようにする。

    static string GenerateTraditionalVariantsMap (string unihanVariantsFile)
    {
        StringBuilder sb = new StringBuilder ();
        int n = 0;
        foreach (var line in File.ReadAllLines (unihanVariantsFile)) {
            var ents = line.Split (null);
            if (ents.Length < 3 || ents [1] != "kTraditionalVariant")
                continue;
            AppendHexAsChar (sb, ents [0]);
            AppendHexAsChar (sb, ents [2]);
            if (++n % 16 == 0)
                sb.Append ('\n');
        }
        return sb.ToString ();
    }

    static void AppendHexAsChar (StringBuilder sb, string s)
    {
        int x = int.Parse (s.Substring (2), NumberStyles.HexNumber);
        if (x < 0x10000)
            sb.Append ((char) x);
        else
            sb.Append ((char) ((x - 0x10000) / 0x400 + 0xD800)).Append ((char) ((x - 0x10000) % 0x400 + 0xDC00));
    }

マッピングを読んで文字列から変換するコードは書いていないので今回はとりあえずここまで…じゃいくらなんでもアレなので一応。20万文字くらいあるstringからIndexOf()を呼んでいるので大変効率が悪いからKeyValuePairの配列を作ってArray.BinarySearchを呼んだほうがいいと思う。

    static string ConvertToTraditional (string variantsFile, string textFile)
    {
        var sb = new StringBuilder ();
        var db = GenerateTraditionalVariantsMap (variantsFile);
        Console.WriteLine ("Database size: " + db.Length);
        char hs = '\0';
        int c = 0;
        foreach (var ch in File.ReadAllText (textFile)) {
            if (char.IsSurrogate (ch)) {
                if (hs == '\0')
                    hs = ch;
                else {
                    int l = 0;
                    do {
                        l = db.IndexOf (hs, l, db.Length - l);
                        if (l < db.Length - 3 && db [l + 1] == ch) {
                            sb.Append (db [l + 2]).Append (db [l + 3]);
                            break;
                        }
                    } while (true);
                    if (l < 0)
                        sb.Append (hs).Append (ch);
                }
            }
            else {
                c = db.IndexOf (ch);
                sb.Append (c < 0 || c % 2 != 0 ? (char) ch : db [c + 1]);
            }
        }
        return sb.ToString ();  
    }

September 8, 2013
258 words


Categories

Tags

Author

Backlog