LoginSignup
2
1

More than 5 years have passed since last update.

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

Posted at

Qiitaを使うのは初めてなので、まずは気軽なネタを書こうと思う。

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 ();  
    }
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1