文字コード変換ファンクタ

重い腰を上げて、文字コード変換用のファンクタを書いてみました。世の中に出回っている C++ 用のライブラリをいくつか調べてみたのですが、どれもしっくりこなかったので、gauche文字コード変換部分を拝借して自作しました。unsigned char を一文字受け取っては、変換結果を std::string に蓄積していきます。

class sjis_to_eucj {
    typedef void (sjis_to_eucj::*handler)(unsigned char);

    std::string pool_;
    std::string& output_;
    handler state_;
    subst_eucj subst_;
    std::vector<unsigned char> input_;

    void neutral(unsigned char c);
    void double_byte(unsigned char c);

    void reset() { state_ = &sjis_to_eucj::neutral; }

public:
    sjis_to_eucj() : output_(pool_), state_(0), subst_(output_) { reset(); }
    sjis_to_eucj(std::string& dest) : output_(dest), state_(0), subst_(output_) { reset(); }
    void operator()(unsigned char c) { (this->*state_)(c); }
    std::string& result() { return output_; }
};

入力文字に応じて、neutral か double_byte のどちらかが invoke されます。subst_eucj は、変換できなかった時に「〓」、いわゆるゲタ文字を EUC_JP で出力するファンクタです。


こんな作りのファンクタが他にもあって、eucj_to_sjis や utf8_to_eucj、eucj_to_utf8、iso2022jp_to_eucj、eucj_to_iso2022jp をサポートします。ついでに、文字コードを自動判別してくれる any_to_* も用意したいところです。


実際に使う場面ですが、文字列なら

void sjis2eucj(const std::string& from, std::string& to) {
    std::for_each(from.begin(), from.end(), sjis_to_eucj(to));
}

といった関数を用意することで、EUC_JP に変換された文字列を to に格納できます。また、ファイル I/O の場合には

std::string buf;
sjis_to_eucj conv(buf);
char ch;
while(std::cin.get(ch)) {
    conv(ch);
    if(buf.size() > 4096) {
        std::cout << buf;
        buf.clear();
    }
}
std::cout << buf;

のようにして、標準入力を Shift-JIS から EUC_JP に変換して吐き出すことができます。


今のところ、不正なシーケンスに遭遇したら std::runtime_error を throw する仕様にしてますが、これが適切かどうかについては自信がありません。例外はあんまり使ったことがないので、もうちょっと経験を積む必要がありそうです。


元にした gauche のコードでは境界チェック用のマクロがあちこちに散乱しているのですが、ファンクタにしたのと、std::string に出力するようにしたおかげでそこらへんを一掃することができました。これは良い点。一方で、似たようなクラスがボコボコ増えてしまったのがどうしたものかと。うまい解決策が見つかれば、改善したいところです。


文字コード処理は泥臭いイメージがあって敬遠してたのですが、やってみるとやっぱり泥臭いものでした。ただ、SKK-JISYO.L を UTF8 に変換して Emacs でキレイに読み込めたりすると、それなりに達成感を味わうことができて楽しかったりもしますね ;-)


次は、UTF8 を簡単に扱えるようなクラスの実装を考えてみるつもりです。今月中に AquaSKK の新エンジンを出す予定でしたが、まだまだ先の話になりそうです。