UTF-8 から Shift JIS に文字コードを変換する方法

目次

はじめに

Perl で CGI を作っていると文字コードに悩まされることが多いと思います。 Perl 5.8 (5.6 だっけ?) 以降では Encode モジュールが標準で使用できるためだいぶ楽になりましたし、それ以前でも Jcode.pm が使用できればほとんど問題はないのですが、たまにモジュール使用不可のサーバがあったりして困りますよね。 私は昔 nifty のサーバを使用していたのですが、Perl モジュールが使用できず困った経験があります。 (今は使えるのかもしれませんが・・・。)

Jcode.pm が使えない環境で文字コードの変換をしようと思うと、jcode.pl を使うことになると思います。 jcode.pl はおそらくどんな環境でも使用できると思うのですが、UTF-8 を扱えないという致命的な欠点があります。 そこで私が nifty を使用していたときに自分で作って使っていた UTF-8 から Shift JIS に文字コードを変換するサブルーチンを改造したので公開してみます。
何で今更そんな改造を、って感じですが。 実は 2 年前に当時使用していたサブルーチンを ブログ で公開して結構検索で見にくる人が多くて。あんまりちゃんとしてなかったので、そのうちちゃんとしたものを書かなきゃなー、と思っていたところ、先日 十六夜日記 さんが使用して下さったのでさすがにそのままにしておけずちょこっと改造した次第です。

まあ UTF-8 と Shift JIS の変換なんて変換テーブル作って変換するしかないのでやってることは簡単なんですが。 昔のは pack / unpack 関数を使ってて動作が遅かったので pack / unpack 関数をできるだけ使わないように変更し、変換テーブルを Shift JIS のものではなく CP932 のものを使用するようにしました。

使い方

詳しい説明は後回しにしてとりあえず使い方。

上の 2 つのファイルをダウンロードしてください。表示用に拡張子を ".txt" にしているので、".pl" に変更してください。で、ライブラリの PATH が通る位置に置いて下さい。後は以下のように require して関数を使用するだけです。

require "utf8ToSjis.pl"; # require する
# $utf8_octetStream に UTF-8 エンコードされた文字列が格納されているとする
&utf8ToSjis::convert($utf8_octetStream); # これで変換完了
# 引数として与えた変数の中身を書き換えます

"utf8ToSjisTable.pl" は "utf8ToSjis.pl" の内部で require するのでわざわざ require する必要はありません。また、"utf8ToSjis.pl" 内部で require するため、上で書いたようにライブラリの PATH が通る位置におく必要があります。

例えば、"jcode.pl" を require する場合は、親ディレクトリに "jcode.pl" を置いて、require "../jcode.pl"; と呼び出すことが出来ましたが、今回の場合はその方法での呼び出し方は不可能です。もしもライブラリの PATH が通る位置以外に置きたい場合はライブラリの PATH にその位置を追加してください。

ライブラリの PATH とは、use 関数や require 関数を使用した時に perl がモジュールを探しに行く場所で特殊変数 @INC に格納されています。通常は標準ライブラリのインストールディレクトリとカレントディレクトリになっていると思いますが、ここに自分で追加することが出来ます。
use 関数はコンパイル時に評価されるために use lib を使用して @INC にパスの追加をしなければなりませんが、今回は実行時に評価される require 関数なので、普通に @INC に追加するだけで構いません。例えば親ディレクトリに "utf8ToSjisTable.pl" と "utf8ToSjis.pl" を置きたい場合は以下のようになります。

push(@INC, "../"); # require する前に @INC に親ディレクトリを追加する
require "utf8ToSjis.pl"; # require する. 自動的に @INC に登録されているパスが探索される
# 後はさっきと同じように使用することが出来る
&utf8ToSjis::convert($utf8_octetStream); # 変換

ちなみに "utf8ToSjisTable.pl" の中身は変換テーブルのデータなのですが、変換テーブルはバイナリデータをそのままぶち込んでるので下手にテキストエディタでいじると壊れるかもしれません。ご注意ください。

変換テーブル

Shift JIS, EUC-JP, iso-2022-jp 間の文字コード変換では一定のアルゴリズムでコード変換を行うことが出来ますが、UTF-8 はそれらとは異なる文字集合 (Unicode) を使用しているため、同様に変換する、というわけにはいきません。

そのため、UTF-8 から Shift JIS へのコード変換には変換テーブルを使用しています。使用している変換テーブルは、cp932 to Unicode table (Table version: 2.01) を元に作成しました。CP932 (Wikipedia) というのは、Microsoft が Shift JIS を拡張したもので、Windows で Shift JIS というと普通この CP932 になります。

使用している変換テーブルが CP932 と Unicode の変換テーブルなので、CP932 で表せる文字は CP932 に変換されます。(つまり正確に言うと Shift JIS じゃないですね。) CP932 で表せない文字は "%u" に続けて変換前の文字コードの 16 進数字列を出力します。

CP932 で表せる文字、というのは Shift JIS の文字だけでなくいわゆる機種依存文字と呼ばれるものも含まれています。NEC 特殊文字や IBM 拡張文字がそれです。有名なものでは "①" というものがあります。"①" は Unicode に登録されている文字なので、UTF-8 では機種依存文字ではありません。ですが、(通常の) Shift JIS では表せない文字なので、Shift JIS (正確には CP932) として "①" を表現すると、Windows でしか表現できない、ということになります。

この機種依存文字の扱いですが、%u に文字コードを続けて出力するよりはましだろうということで CP932 のコードに変換するようにしましたが、当然それ以外の動作を望まれることもあると思います。そこで変換テーブルの変更の仕方を書いておきます。

まず、変換テーブルはハッシュで実現しており、"utf8ToSjis" package に結び付けられた table という名前のハッシュです。そして、key に UTF-8 のバイトデータ、value に Shift JIS (CP932) のバイトデータを格納しています。先の "①" は UTF-8 コードでは E2 91 A0 という 3 bytes で表され、CP932 では 87 40 という 2 bytes で表されるので、以下のように変換テーブルのハッシュの値を表示させると、CP932 の "①" が表示されるはずです。

require "utf8ToSjis.pl"; # require する
# この時点で変換テーブルのハッシュは存在するので、アクセスできる
# 変換テーブルのハッシュは "utf8ToSjis" パッケージで table という名前
print $utf8ToSjis::table{"\xE2\x91\xA0"}; # UTF-8 で "①" を表す \xE2\x91\xA0 を key にする
# => CP932 で "①" が表示される

# ただし "①" が環境依存なので表示されないかもしれません。
# その場合は正規の Shift JIS で表される文字を試してみてください。
# 例えば "\xe3\x81\x82" を key にすると Shift JIS で "あ" が表示されるはずです

ハッシュにアクセスできるということはハッシュの中身を書き換えることも出来るということです。例えば、"①" を普通の "1" に変換する場合、

require "utf8ToSjis.pl"; # require する
$utf8ToSjis::table{"\xE2\x91\xA0"} = "1"; # "①" の変換テーブルの書き換え

# 上のように直接文字を代入しても構いませんが、文字コードに気をつけてください
# プログラムの文字コードで代入されます

として変換テーブルを書き換えることが出来ます。このように、書き換えたり未定義の文字を追加したりできます。当然複数文字にすることも出来ます。

require "utf8ToSjis.pl"; # require する
$utf8ToSjis::table{"\xE2\x91\xA0"} = "(1)"; # "①" の変換テーブルの書き換え

# 上のようにすると、"①" を変換すると "(1)" になります

バージョン情報

いずれまた改造するかもしれないから一応バージョン番号ふっときます。現在 version 2.0 かな。