rubyでJISコードを扱う


やあ (´・ω・`)
ようこそ、バーボンハウスへ。
このテキーラはサービスだから、まず飲んで落ち着いて欲しい。

うん、「また」なんだ。済まない。
仏の顔もって言うしね、謝って許してもらおうとも思っていない。

でも、このタイトルを見たとき、君は、きっと言葉では言い表せない
「可能性」みたいなものを感じてくれたと思う。
全銀と戦うの中で、そういう気持ちを忘れないで欲しい
そう思って、この記事を書いたんだ。

じゃあ、注文を聞こうか。

JISコードはISO-2022-JPのこと?

ウィキペディアで「JISコード」を検索するとISO-2022-JPのページに飛ばされます。
しかし、文字コード表をみるとASCIIコードと半角カタカナのコードが重複しています。
例えば、「ア」だと0x31で「1」も0x31と表現しています。
0x311バイトだけだと「1」になるんですが、じゃあ、「ア」を表示したい場合はどうするかというと
エスケープシーケンスを使い0x1b284931〜として複数バイトを用いて表現します。
しかし、某全銀フォーマットだと1文字1バイトとなっているため、これだと半角カタカナ使えないじゃん、という話になります。

ここでちょっとコードを書いて確認してみましょう。

> require 'kconv'

> '1'.tojis.unpack('H*').join
=> "31"

次から、ちょっと想定と違ってきます。

> 'ア'.tojis.unpack('H*').join
=> "1b244225221b2842"

0x1b284931〜じゃないですね。じつは勝手に全角に変換しちゃってます。
※ちなみに、後ろ3バイトの0x1b2842は半角カタカナの終了(ASCIIの開始──ようするにコードエリアマップの切り替え用のエスケープシーケンス)

> 'ア'.tojis.unpack('H*').join
=> "1b244225221b2842"

なので、ドキュメントに書いてあるとおりにNKFで変換してみましょう。

> require 'nkf'

> NKF.nkf('-jxm0', 'ア').unpack('H*').join
=> "1b2849311b2842"

前と後ろの3バイト(0x1b28490x1b2842)はエスケープシーケンスなので残った0x31がISO-2022-JPの半角カタカナエリアマップにおける「ア」となり、やっと目的のバイトコードにたどり着きました。
が、1バイトどころか7バイトになってしまいます。

JISコードはJIS X0201のことだった

じゃあ、JISコード1バイトで半角カタカナを表示するにはどうすればいいのか?
最初にウィキペディアでJISコードを検索したのが罠で、
実は扱うべき文字コードはISO-2022-JPではなくJIS X0201が正しいのでした。
JIS X0201の文字コード表をみるとASCII文字と半角カタカナがちゃんと別のコードとしてマッピングされています。
この文字コードをRubyで扱うにはどうすればいいか?実際にコード上で確認してみましょう。

> NKF.nkf('-s -x -Z4', 'ア').unpack('H*').join
=> "b1"
> NKF.nkf('-s -x -Z4', 'ア').unpack('H*').join
=> "b1"

0xb1JIS X0201の文字コード表を確認すると、ちゃんと「ア」の文字コードとなっています。
NKFのオプションはそれぞれ

  • -s シフトJISに変換
  • -x 通常行われる半角カナから全角カナの変換を行わない(上記#tojisによる変換で半角カタカナが全角になっていたのはこれが原因)
  • -Z4 全角カナを半角カナに変換

という意味になります。
ちなみにここにあるスペースと¥マーク以外の記号は全角から半角のコードに変換してくれます。

これでJISコード1バイトで半角文字を扱うことができるようになりました。

JIS X0201はシフトJISの一部

NKFのオプションでシフトJISの変換を指定していましたが、
JIS X0201の文字コード表シフトJISの文字コード表を見比べるとASCII文字と半角カタカナのコードは同じだということがわかります。
「じゃあ結局、最初からSJISに変換するだけでよかったじゃん」となるところですが、そのとおりです。
ただ、Kconvによる変換だと、暗黙の半角→全角変換が行われ…

> 'ア'.tosjis.unpack('H*').join
=> "8341"
> 'ア'.tosjis.unpack('H*').join
=> "8341"

…となり、結局NKFを利用するところまでは必然となります。