C++で文字列コードを変換する

最近、jubeat analyzerの譜面からyubiosiの譜面への変換機を作った。
その中に、文字列コードの問題に会った。

jubeat analyzerの譜面は、Shift-JISコードのテキストファイルだ。
私の使っているOSは中国語のだ。だから読み込んだ文字列は化けになる。

ネットで何か検索したら、wfstreamにstd::localeオブジェクトを送って直接そのShift-JISのテキストファイルからUnicode型の文字列を取得できる。

std::locale jpLoc("japanese");
std::wfstream fs("c:\\nanika.txt", std::ios::in);
fs.imbue(jpLoc);

ここまで終わり?違う。もっと複雑な場合がある。

そのテキストファイルにShift-JISの文字列だけでなく、GBK(簡体字中国語用)の文字列もある。たとえ譜面に m= の部分。Shift-JISとして読み込んだら文字が化けた。

Shift-JISとGBK込んでる場合、簡単にimbueでできないんだ。

私はこう思う:C++でShift-JISのテキストファイルからwchar_tの文字列を取得できるとは、中にきっと何かのShift-JISからUCS2への変換機能がある。私はfstreamで行分けでメモリに読み込み、そして対応の文字列コードでUCS2の文字列に変換する。

じゃこの変換機能って何なんだろう。

最初私はwstringstreamでいろいろ試した。このように:

std::wstringstream wss;
wss.imbue(jpLoc);
wss << line;
std::wstring wline = wss.str();

だめだ、全然だめだ。

じゃあstd::wfstreamはShift-JISの文字列からUCS2の文字列に変換するとき、何をするんでしょうか。

私はこういうコードを書いて、デバッグモードでステップイン機能使ってそのコード変換の部分を探してみた。

std::locale jpLoc("japanese");
std::wfstream fs("c:\\nanika.txt", std::ios::in);
fs.imbue(jpLoc);
std::wstring wsline;
getline(fs, wsline);

そして、こんなところで

VS2012。コールスタックは:
std::basic_filebuf<wchar_t,std::char_traits<wchar_t> >::uflow() line 501 C++
std::basic_filebuf<wchar_t,std::char_traits<wchar_t> >::underflow() line 460 C++
std::basic_streambuf<wchar_t,std::char_traits<wchar_t> >::sgetc() line 153 C++
std::getline<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> >(std::basic_istream<wchar_t,std::char_traits<wchar_t> > && _Istr, std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> > & _Str, const wchar_t _Delim) line 414 C++
std::getline<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> >(std::basic_istream<wchar_t,std::char_traits<wchar_t> > & _Istr, std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> > & _Str) line 485 C++
文字列変換のコードを発見した

_Pcvt->in(_State,
&*_Str.begin(), &*_Str.begin() + _Str.size(), _Src,
&_Ch, &_Ch + 1, _Dest)
ほかのパラメーターはわりと理解しやすいが、その_State、_Src、_Destは何だ。

無論、ネットで検索。

_Stateとは、変換のステータスだ。このin関数はconst関数なので、変換状況を記録できない。その_Stateは、thisポインターのようなものとして使われる気がする。ShiftJIS漢字は2バイトなので、1バイトだけ送ったらその1バイトを保存しなければならないね。
_Srcと_Destは、変換進度を表すためパラメーターだ。もしエラーが発生したらこのポインターはエラー発生の時の変換進度だ。

逆のout関数もある。機能もその逆です。

後の問題は、_Pcvtはどこから取得できる。

_Pcvtの型は「const std::codecvt *」です。しかしstd::codecvtオブジェクトは自分で構造できないようだ。
どう見てもLocaleから取得です。本当に(ry

取得の方法もネットで検索して出る。この関数:


const std::codecvt<wchar_t, char, int>& std::use_facet< std::codecvt<wchar_t, char, int> >(const std::locale&)

試して:

#include <iostream>
#include <locale>
#include <vector>
#include <string>

using namespace std;

int main()
{
locale jpLoc("japanese");
typedef std::codecvt<wchar_t, char, std::mbstate_t> MyCodeCvt;
const MyCodeCvt& cvt = std::use_facet<MyCodeCvt>(jpLoc);

std::wstring wt(L"日本語");
std::vector<char> buf(wt.length() * 2 + 1);
const wchar_t* inpProgress;
char* outProgress;
mbstate_t state = 0;

cvt.out(state,
&wt[0], &wt[0] + wt.length(), inpProgress,
&buf[0], &buf[0] + buf.size(), outProgress);
if (inpProgress != &wt[0] + wt.length())
std::cerr << "convert failed!" << std::endl;
else {
*outProgress = 0;

std::string jpStr(&buf[0]);
std::cout << jpStr << std::endl;
}
return 0;
}

うふふ♥

追記:
1、locale jpLoc("japanese")で構造してfs.imbue(jpLoc)で使ったら、数字の出力はおかしくなった。fs << 1000ですれば"1,000"になる。文字列コードだけ使ってほかの機能は不要ならlocale jpLoc("japanese", std::locale::ctype)してください。ctypeは文字列の方って意味です。
2、C++の新標準にstd::wstring_convertがあるようだ。