Ruby で ISO-2022-JP の 「~」 を UTF-8 に変換するとどうなる??

Ruby で文字列「~」を扱った際、右往左往したので、そのときの記録を残しておきます。

ちなみに波ダッシュ(~)にまつわる話は有名で、調べれば各所に情報が転がっていると思いますが、どのようなものか簡単に説明します。

UTF-8 に置いて「~」という文字(正確には似た字形を持つ文字)は以下の2種類存在します。

0xbd 0x9e)

処理系によってどちらになるか異なり、またエディタによっては表示できないこともあります。

今回は UTF-8 に変換した結果を Sakura エディタ(ANSI 版)で見たときに、文字化けしていて、何が起こっているんだ!?という疑問から調査を始めました。

ちなみに調査の結果、Sakura エディタ(UNICODE 版)では文字化けせずにちゃんと表示されることがわかりました。

同じようにSakura エディタで「~」が文字化けして読めないという人は、この機会に

Sakura エディタを UNICODE 版に変えると良いと思います。

検証環境と検証結果

これは以下の環境での動作です。

  • Windows 7 Home Premium(64bit)
  • ruby 1.9.3p194 (2012-04-20) [i386-mingw32]

結論を先に書きます。

上記 Ruby の環境で ISO-2022-JP の「~」を UTF-8 に変換すると波ダッシュ(0xe3 0x80 0x9c)になります。


ISO-2022-JP の「~」文字を読み込んでみる

ISO-2022-JP の「~」という文字を読み込み、 バイト列を16進数で出力すると以下のようになります。

0x1b 0x24 0x42 0x21 0x41 0x1b 0x28 0x42

以下を参照して、このバイト列の意味を解釈すると

以下のように解釈できます。

#各バイトの16進数表現
10x1b1b, 24, 42 で JIS X 0208 の指示
20x241b, 24, 42 で JIS X 0208 の指示
30x421b, 24, 42 で JIS X 0208 の指示
40x21区を指示(0x21 - 0x20 = 0x01 = 1区)
50x41点を指示(0x41 - 0x20 = 0x21 = 33点)
60x1b1b, 28, 42 で ASCII の指示(終端では ASCII に戻すルール)
70x281b, 28, 42 で ASCII の指示(終端では ASCII に戻すルール)
80x421b, 28, 42 で ASCII の指示(終端では ASCII に戻すルール)

1,2,3 は符号化方式 JIS X 0208 の開始指示を 6,7,8 が符号化方式 ASCII の指示(終端)を表していて、 文字本体を表しているのは 4,5 だけです。

JIS X 0208 の区点番号は、 それぞれのバイトから 0x20(graphical 領域のサイズ分)を差し引いた番号になるので

  • 21 は1区
  • 41 は33点

を表します。

以下にある表の1区33点の文字を参照すると

1区33点が「~」を表していることがわかります。

ちなみに Ruby で「~」を読み込んで、そのバイト列を出力するプログラムの例を以下に挙げます。

# coding: UTF-8
# output_byte16.rb

while input_line = STDIN.gets
    input_line.bytes do |byte|
        puts "0x#{format("%x", byte)}"
    end
end

事前に「~」とだけ書き ISO-2022-JP で保存した input.txt という名前のファイルを用意しておき、以下のように標準入力で情報を入力して実行します。

ruby output_byte16.rb < input.rb

ISO-2022-JP の「~」を UTF-8 に変換してみる

変換したバイト列を出力するプログラムの例を挙げます。

# coding: UTF-8
# translate.rb

input_encode = "ISO-2022-JP"
output_encode = "UTF-8"

while input_line = STDIN.gets
    output_line = input_line.encode(output_encode, input_encode)
    puts "== input(#{input_encode}) =="
    input_line.bytes do |byte|
        puts "0x#{format("%x", byte)}"
    end
    puts "== output(#{output_encode}) =="
    output_line.bytes do |byte|
        puts "0x#{format("%x", byte)}"
    end
end

先ほどと同じように input.txt を用意しておいて以下のように実行します。

ruby translate.rb < input.txt

結果は以下のようになります。

== input(ISO-2022-JP) ==
0x1b
0x24
0x42
0x21
0x41
0x1b
0x28
0x42
== output(UTF-8) ==
0xe3
0x80
0x9c

変換した結果は波ダッシュ(0xe3 0x80 0x9c)であることがわかりました。


途中で参考として挙げた以下の本ものすごくお勧めです。

文字コードに関してわからないことが出たときにとても役に立ちます。

読み物としてすごくおもしろいのですし、一通り目を通しておくと、何かの問題にぶつかったときの入り口として最適です。

波ダッシュの問題はもちろん、Ruby の文字コードに関しても節が割り当てられており、古い歴史の話から、現代の話まで一通り抑えられます。