2006-11-07
● [プログラミング] C++でiconvを
まぁ、もっともC++らしい使い方としては、最近のperlみたくstreambufの拡張としてiconvを組み込むことなんでしょうが、でもiconvってコンストラクタで変換先・変換元の文字コードを指定したら最後までそれを使いまわす必要があるわけで、そうするとネットワークプログラミングのように「(入力など)処理する文字列」と「プログラム中にハードコードされている文字列」が異なる場合にはあまり向いていないようにも思う。
iconv(3)の伝統的な使い方は、
iconv_t cd = iconv_open(to_code, from_code);
でオープンしたデスクリプタを使って、ひたすら
size_t s = iconv(cd, inbuf, inbytesleft, outbuf, outbytesleft);
して、最後に
iconv_close(cd);
するんだけど、ポインタがたくさん出てくるし outbuf をどれだけ確保すれば良いかが直感的にわかりにくい。もちろん最長のパターンはおそらく "aあaあ" みたいな文字列を euc-jp → iso-2022-jp にする場合で、この場合だと6文字→18文字ってことで高々3倍なのはわかるんだけど。
そこで、勝手にバッファを確保してデストラクタで勝手に解放してくれるようなクラスってことで、iconverter を作ってみた。とりあえずこんなんでいいや。ストリームバッファの拡張だと柔軟性に欠ける。
iconv.hpp:
/*
* iconv.hpp: character-set converter (header)
* by Norihisa Washitake <nori at washitake.com>
* $Id: iconv.hpp,v 1.1.1.1 2006/10/30 07:43:09 wassy Exp $
*/
#include <iconv.h>
#include <string>
#include <iostream>
class iconverter
{
public:
iconverter();
~iconverter();
std::string convert(std::string src,
std::string tocode,
std::string fromcode);
protected:
iconv_t open(const char* tocode, const char* fromcode);
void close();
private:
iconv_t cd;
char* tocode_cache;
char* fromcode_cache;
};
iconv.cc:
/*
* iconv.cc: Character-set converter
* by Norihisa Washitake <nori at washitake.com>
* $Id: iconv.cc,v 1.1.1.1 2006/10/30 07:43:09 wassy Exp $
*/
#include <iconv.h>
#include <string>
#include <iostream>
#include "iconv.hpp"
iconverter::iconverter() {
cd = 0;
tocode_cache = NULL;
fromcode_cache = NULL;
}
iconverter::~iconverter() {
close();
}
iconv_t iconverter::open(const char* tocode, const char* fromcode)
{
if (cd > 0)
close();
iconv_t ret = iconv_open(tocode, fromcode);
if (ret > 0) {
cd = ret;
tocode_cache = new char[strlen(tocode) + 1];
strcpy(tocode_cache, tocode);
fromcode_cache = new char[strlen(fromcode) + 1];
strcpy(fromcode_cache, fromcode);
} else
cd = 0;
return cd;
}
void iconverter::close()
{
if (cd > 0) {
iconv_close(cd);
cd = 0;
}
if (tocode_cache) {
delete [] tocode_cache;
tocode_cache = NULL;
}
if (fromcode_cache) {
delete [] fromcode_cache;
fromcode_cache = NULL;
}
}
std::string iconverter::convert(std::string src,
std::string tocode,
std::string fromcode)
{
std::string ret;
if (!tocode_cache ||
!fromcode_cache ||
tocode != tocode_cache ||
fromcode != fromcode_cache) {
open(tocode.c_str(), fromcode.c_str());
}
char* psrc = const_cast<char*>(src.c_str());
size_t srclen = src.size();
char* dst = new char[srclen+1];
char* pdst = dst;
size_t dstlen = srclen;
size_t dstmax = srclen;
while (srclen > 0) {
char* psrc_org = psrc;
size_t n = ::iconv(cd, &psrc, &srclen, &pdst, &dstlen);
if ((n != (size_t)-1 && srclen == 0) || (errno == EINVAL)) {
srclen = 0;
ret.append(dst, 0, dstmax-dstlen);
} else {
switch (errno) {
case E2BIG:
ret.append(dst, 0, dstmax-dstlen);
pdst = dst;
dstlen = dstmax;
break;
case EILSEQ:
ret.append(dst, 0, dstmax-dstlen);
ret.append(psrc, 0, 1);
psrc++; srclen--;
pdst = dst;
dstlen = dstmax;
break;
default:
ret.append(psrc_org);
srclen = 0;
break;
}
}
}
pdst = dst;
dstlen = dstmax;
if (::iconv(cd, NULL, NULL, &pdst, &dstlen) != (size_t)-1) {
ret.append(dst, 0, dstmax-dstlen);
}
delete [] dst;
return ret;
}
使い方は、
#include "iconv.hpp" iconverter ic; string euc_str = ic.convert(sjis_str, "shift_jis", "euc-jp");
そんだけ。
しかしiconv(3)の問題点は、文字コードの判定ができないということで、このために結局独自アルゴリズムなりnkfなりを利用する必要があるってこと。うーん、これはなんだかよろしくない。かといってiconvで「適当な文字コードから(utf8へ)変換できるかどうか」で判定するのは重すぎる。もっとも、今回作ったのはメールのヘッダのBase64デコードなので、文字列中にちゃんとどの文字コードを使っているかが書いてあるから、そういう心配は要らないんですが。
キャスト演算子を使ったり使わなかったりしているのはご愛嬌。