Windows 1258 から UTF-8への変換
概要
この話題はあまりにマニアックすぎて、どれほどの人によって有用かわからないが、自分のためのメモとして書いてみる。
ベトナム語は、日本語同様、さまざまな文字コードが使われていることで悪名高い。最近は UTF-8 へ収束しつつあるようだが、数年前まではひどい状態だったらしい。Lac Viet 2002 という有名な英語<=>ベトナム語辞典で使われている文字コードが Windows 1258 である。(ベトナム語の文字コードについては、East Asian Character Sets Overview: Vietnameseがバイブル的存在) この Windows 1258 を UTF-8 に変換するコードを Ruby で書いてみた。
使い方
ソースコードを windows1258_converter.rb という名前で保存。
require 'windows1258_converter' Windows1258Converter.toutf8([Windows 1258 文字列]) # => UTF-8 文字列
これだけ。
ソースコード
=begin ベトナムの文字コードに関して、Windows 1258 から UTF-8 に変換する。 ■使い方 Windows1258Converter.toutf8([Windows 1258 文字列]) # => UTF-8 文字列 ■変換方法 まずは Windows 1258 から裸の Unicode である UCS-2 に変換し、ついで UTF-8 に変換する 1258 の特徴は、声調記号に独立した文字コードが当てられている点である。 厄介なのは、1258 では1バイトまたは2バイトのシーケンスが、2バイトの UCS-2 に置き換えられるということだ。 つまり可変長の文字列を変換する必要があるわけだ。 例をあげると 1258 UCS-2 a 61 00 61 a' E1 00 E1 a` E0 00 E0 a? E0 D2 1E A3 a~ E0 DE 00 E3 a. E0 F2 1E A1 声調記号のコードは次のとおり。 '(EC) `(CC) ?(D2) ~(DE) .(F2) 変換の戦略としては次のような感じ。 1. 1258 => UCS-2 の変換表を作る(ハッシュ) 2. 変換対象文字列の現在位置から向こう2バイトをキーとして、変換表に対応する UCS-2 表現が見つかるかどうか調べる。 3. 2 で見つかったら、それを UCS-2 表現とする。見つからなかったら次へ。 4. 変換対象文字列の現在位置から向こう1バイトをキーとして、変換表に対応する UCS-2 表現が見つかるかどうか調べる。 5. 4 で見つかったら、それを UCS-2 表現とする。見つからなかったら次へ。 6. 変換対象文字列の現在位置から向こう1バイトを取り出す。これを下位1バイト、上位1バイトを0とする2バイトの文字列をつくり、これを UCS-2 表現とする。 =end class Windows1258Converter #'(EC) `(CC) ?(D2) ~(DE) .(F2) @@map_source =<<EOS A 41 0041 A ' C1 00C1 A ` C0 00C0 A ? 41D2 1EA2 A ~ 41DE 00C3 A . 41F2 1EA0 a 61 0061 a ' E1 00E1 a ` E0 00E0 a ? 61D2 1EA3 a ~ 61DE 00E3 a . 61F2 1EA1 A( C3 0102 A( ' C3EC 1EAE A( ` C3CC 1EB0 A( ? C3D2 1EB2 A( ~ C3DE 1EB4 A( . C3F2 1EB6 a( E3 0103 a( ' E3EC 1EAF a( ` E3CC 1EB1 a( ? E3D2 1EB3 a( ~ E3DE 1EB5 a( . E3F2 1EB7 A^ C2 00C2 A^ ' C2EC 1EA4 A^ ` C2CC 1EA6 A^ ? C2D2 1EA8 A^ ~ C2DE 1EAA A^ . C2F2 1EAC a^ E2 00E2 a^ ' E2EC 1EA5 a^ ` E2CC 1EA7 a^ ? E2D2 1EA9 a^ ~ E2DE 1EAB a^ . E2F2 1EAD E 45 0045 E ' C9 00C9 E ` C8 00C8 E ? 45D2 1EBA E ~ 45DE 1EBC E . 45F2 1EB8 e 65 0065 e ' E9 00E9 e ` E8 00E8 e ? 65D2 1EBB e ~ 65DE 1EBD e . 65F2 1EB9 E^ CA 00CA E^ ' CAEC 1EBE E^ ` CACC 1EC0 E^ ? CAD2 1EC2 E^ ~ CADE 1EC4 E^ . CAF2 1EC6 e^ EA 00EA e^ ' EAEC 1EBF e^ ` EACC 1EC1 e^ ? EAD2 1EC3 e^ ~ EADE 1EC5 e^ . EAF2 1EC7 I 49 0049 I ' CD 00CD I ` 49CC 00CC I ? 49D2 1EC8 I ~ 49DE 0128 I . 49F2 1ECA i 69 0069 i ' ED 00ED i ` 69CC 00EC i ? 69D2 1EC9 i ~ 69DE 0129 i . 69F2 1ECB O 4F 004F O ' D3 00D3 O ` 4FCC 00D2 O ? 4FD2 1ECE O ~ 4FDE 00D5 O . 4FF2 1ECC o 6F 006F o ' F3 00F3 o ` 6FCC 00F2 o ? 6FD2 1ECF o ~ 6FDE 00F5 o . 6FF2 1ECD O^ D4 00D4 O^ ' D4EC 1ED0 O^ ` D4CC 1ED2 O^ ? D4D2 1ED4 O^ ~ D4DE 1ED6 O^ . D4F2 1ED8 o^ F4 00F4 o^ ' F4EC 1ED1 o^ ` F4CC 1ED3 o^ ? F4D2 1ED5 o^ ~ F4DE 1ED7 o^ . F4F2 1ED9 O+ D5 01A0 O+ ' D5EC 1EDA O+ ` D5CC 1EDC O+ ? D5D2 1EDE O+ ~ D5DE 1EE0 O+ . D5F2 1EE2 o+ F5 01A1 o+ ' F5EC 1EDB o+ ` F5CC 1EDD o+ ? F5D2 1EDF o+ ~ F5DE 1EE1 o+ . F5F2 1EE3 U 55 0055 U ' DA 00DA U ` D9 00D9 U ? 55D2 1EE6 U ~ 55DE 0168 U . 55F2 1EE4 u 75 0075 u ' FA 00FA u ` F9 00F9 u ? 75D2 1EE7 u ~ 75DE 0169 u . 75F2 1EE5 U+ DD 01AF U+ ' DDEC 1EE8 U+ ` DDCC 1EEA U+ ? DDD2 1EEC U+ ~ DDDE 1EEE U+ . DDF2 1EF0 u+ FD 01B0 u+ ' FDEC 1EE9 u+ ` FDCC 1EEB u+ ? FDD2 1EED u+ ~ FDDE 1EEF u+ . FDF2 1EF1 Y 59 0059 Y ' 59EC 00DD Y ` 59CC 1EF2 Y ? 59D2 1EF6 Y ~ 59DE 1EF8 Y . 59F2 1EF4 y 79 0079 y ' 79EC 00FD y ` 79CC 1EF3 y ? 79D2 1EF7 y ~ 79DE 1EF9 y . 79F2 1EF5 DD D0 0110 dd F0 0111 dong FE 20AB EOS @@instance = nil @map = nil def hex_to_str(s) s.gsub!(/ /, '') i = 0 r = '' while i < s.size n = s[i, 2].hex c = n.chr r += c i += 2 end r end def load_map @map = {} @@map_source.each do |line| ch, tone, w, u = line.chomp.split(/\s/) ws = hex_to_str(w) uc = u.hex @map[ws] = uc end end # 一文字のUCS-2 を UTF-8 に変換 def ucs2_to_utf8(c) raise if c < 0 return sprintf("%c", c) if c <= 0x7F if c <= 0x07FF c1 = 0b1100_0000 | (c >> 6) c2 = 0b1000_0000 | (c & 0b111111) return sprintf("%c%c", c1, c2) end if c <= 0xFFFF c1 = 0b1110_0000 | (c >> 12) c2 = 0b1000_0000 | ((c >> 6) & 0b111111) c3 = 0b1000_0000 | (c & 0b111111) return sprintf("%c%c%c", c1, c2, c3) end raise end # windows 1258 => utf-8 変換 def windows1258_to_utf8(w) i = 0 u = '' while i < w.size # v に ucs-2 表現が入る v = nil # 2バイトのキーを試す if w.size - i >= 2 key = w[i, 2] v = @map[key] i += 2 if v end # 1バイトのキーを試す if v.nil? key = w[i, 1] v = @map[key] i += 1 if v end # それでも見つからなかったら、0x00 を上位バイトに付け加えて2バイトの UCS-2 を作る if v.nil? v = w[i] i += 1 end uch = ucs2_to_utf8(v) u += uch end u end def initialize load_map end def self.instance unless @@instance @@instance = Windows1258Converter.new end @@instance end def self.toutf8(w) instance.windows1258_to_utf8(w) end end