文字コード変換ライブラリICUの紹介
■はじめに
まずはじめに、「文字化けとは何か」と聞かれたらあなたは何と答えますか。それは、
日本に生を受けたプログラマが背負う宿命的な問題
と私なら答えるでしょう。(何、この冒頭w)
皆さん、Webページ制作の経験があったり、自然言語処理をかじった人なら必ず文字化けに苦しまれたことがあると思います。英語のようにアルファベットだけなら何の問題もないのですが、日本語のように漢字や仮名が混じっている場合には文字データを正しい文字コードでエンコーディングしないと文字化けを起こします。このように文字コードには複数あり、その指定ミスが文字化けの原因となります。
例えば、日本語の文字コードには、大きく分けて以下の4つがあります。
これにより、文字化けを直すには正しい文字コードに変換する必要がある訳ですが、個人的に使えそうだと思った文字コード変換ライブラリを発見したのでそれを紹介したいと思います。文字コード変換ライブラリと言ってもいろいろあるのですが、オープンソースという観点から今回はIBMのUnicodeを扱うためのライブラリであるICU(International Components for Unicode)について紹介したいと思います。
■ICU(International Components for Unicode)
ICUは、プログラムの国際化を支援するために作成された、世界中で使われている多くの文字コードとUnicodeの相互変換を提供するライブラリです。オープンソースであるため、自作のアプリケーションに移植して配布することも可能です。C/C++やJavaから使用することができます。
■ダウンロード
まずは以下のサイトからダウンロードします。
ICU - International Components for Unicode
「Download ICU」から辿って、各自の開発環境に対応した圧縮ファイルを選択して下さい。
C/C++版には「C」が、Java版には「J」がファイル名に付いているようです。
2012年11月現在の最新版はバージョン50.1です。
■インストール
●Windowsの場合
-
- 準備と環境設定
- VC++ 2008 を立ち上げます。
- 設定は、ダウンロードした圧縮ファイルを C:\ICU に解凍した場合、"インクルードファイル"に"C:\ICU\icu\source\common"を追記し、"ライブラリファイル"、"実行可能ファイル"に"C:\ICU\icu\source\lib"を追記します。
-
- VC++のプロジェクトの設定
- [プロジェクト]->[プロパティ]でプロパティダイアログを起動し、[構成プロパティ]->[リンカ]->[入力]で、"追加の依存ファイル"に"icuuc.lib"(デバッグモードの場合は、"icuude.lib")を追記します。今回は文字コードの変換処理だけを使用するので"icuuc.lib"(デバッグモードの場合は、"icuude.lib")だけです。
-
- DLLの配置
- ソリューションで、exeファイルが生成されるディレクトリに"icudt44.dll"と"icuuc44.dll"(デバッグモードの場合は、"icuuc44d.dll")をコピーします。今回は文字コードの変換処理だけを使用するのでこの2つだけです。
-
- サンプルプログラムのビルド
- VC++のプロジェクト作成で、空のコンソールアプリケーションを作成します。
- 作成したプロジェクトへ以下のソースコードを貼り付けたhppファイル、cppファイルなどを作成します。
- ビルドして、コンパイルおよびリンクができれば完了です。
●MacOSXの場合
-
- 圧縮ファイルの解凍
- ダウンロードした圧縮ファイル(ここではicu4c-49_1_2-src.tgzを選択)を好きなフォルダに入れて解凍します。
- $ tar -xvzf icu4c-49_1_2-src.tgz /(好きなフォルダ)
- $ cd /(好きなフォルダ)/icu/source
-
- ライブラリの構築
- 次に、ICUライブラリを構築します。
- $ ./configure
- $ make
-
- インストール
- $ sudo make install
-
- パスの追加
- 環境設定でライブラリパスとインクルードファイルパスにICUのそれぞれのパスを追記します。
-
- サンプルプログラムの実行
- 以下のソースコードを貼り付けたhppファイル、cppファイルなどを作成します。
- コンパイルおよびリンクができれば完了です。
- # 今は、コンパイルは通るがリンク時にエラーが発生して実行できていない状態です。
- # それが解決したら追記するので、もう少し待って下さい。
実は、ターミナルで、
$ sudo port installed
として確認すれば分かるように、MacOSXにはもともとOSが内部で使用するためにICUが組み込まれています。しかし、これをアプリケーション側から利用することはできないため、今までのような事を行う必要があるという訳です。
もし、ICUがソースコードにバンドルされていない場合は、portコマンドなどでインストールしておいて下さい。ついでに最新のlibxml2パッケージも必要となるのでここでインストールしておきましょう。
$ sudo port icu install
$ sudo port libxml2 install
■サンプルプログラムの説明
ここでは、文字コード変換プログラムを理解するために必要なAPIの説明を行っていきます。さらに詳しく知りたい人はICUのドキュメントを参照して下さい。
●前準備
- #include
- を記述します。
-
- UErrorCode : エラーコード
- 処理中に発生したエラーは全てここに書き込まれるようになっています。
●UConverterの生成と解放
-
- ucnv_open : UConverterの生成
- ここで変換の対象となる文字コード名を指定します。
-
- ucnv_close : UConverterの解放
●対Unicode変換
-
- ucnv_toUnicode : マルチバイト文字列をUnicode文字列へ変換
- あるマルチバイト文字列から別のマルチバイト文字列へ相互変換するために必要な前半のステップ
-
- ucnv_fromUnicode : Unicode文字列をマルチバイト文字列へ変換
- あるマルチバイト文字列から別のマルチバイト文字列へ相互変換するために必要な後半のステップ
●バッファの大きさ
-
- ucnv_getMinCharSize(UConverter* converter) : 指定された文字コードにおける1文字を表現するために必要な最小のバイト数
- マルチバイト文字列/Unicode文字列の相互変換をする場合に、変換結果を格納するバッファの大きさを求められます。
- 入力されるマルチバイト文字列のバイト数をこの値で割れば、出力Unicode文字列バッファに必要な文字数が得られます。
-
- ucnv_getMaxCharSize(UConverter* converter) : 指定された文字コードにおける1文字を表現するために必要な最大のバイト数
- マルチバイト文字列/Unicode文字列の相互変換をする場合に、変換結果を格納するバッファの大きさを求められます。
- 入力されるUnicode文字列の文字数にこの値を掛ければ、出力マルチバイト文字列バッファに必要な文字数が得られます。
■サンプルプログラム
以下に、ICUの文字コード変換APIを用いてeuc-jpで書かれた"日本"をshift_jisに変換するプログラムを示します。ここで、出力バッファの確保や引数の設定を行うラッパが用意されているので、次のような各変換がシンプルな呼び出しで利用できるようになっています。
- widen:マルチバイト -> Unicode
- const char* -> std::wstring
- std::string -> std::wstring
- narrow:Unicode -> マルチバイト
- const wchar_t* -> std::string
- std::wstring -> std::string
- convert:マルチバイト -> マルチバイト
- const char_t* -> std::string
- std::string -> std::string
使い方についてはヘッダ(UnicodeConverter.hpp)を参照して下さい。
●UnicodeConverter.hpp
#ifndef UNICODECONVERTER_H #define UNICODECONVERTER_H #include <unicode/utypes.h> #include <exception> #include <string> struct UConverter; enum UErrorCode; namespace s34 { // 'icu_error' exception class icu_error : public std::exception { UErrorCode error_; public: explicit icu_error(UErrorCode error) : error_(error) {} virtual const char* what() const throw(); //virtual const char* what() const; UErrorCode code() const { return error_; } }; // 'open_converter' UConverter* open_converter(const char* name, UErrorCode* error =0); // 'close_converter' void close_converter(UConverter*); // 'widen' : multibyte -> doublebyte std::wstring widen(const char* source, size_t sourceLength, UConverter* converter, bool flush =true, UErrorCode* error = 0); inline std::wstring widen(const char* source, UConverter* converter, bool flush =true, UErrorCode* error =0) { return widen(source, std::char_traits<char>::length(source), converter, flush, error); } inline std::wstring widen(const char* source, const char* sourceLimit, UConverter* converter, bool flush =true, UErrorCode* error =0) { return widen(source, sourceLimit - source, converter, flush, error); } inline std::wstring widen(const std::string& source, UConverter* converter, bool flush =true, UErrorCode* error =0) { return widen(source.data(), source.length(), converter, flush, error); } // 'narrow' : doublebyte -> multibyte std::string narrow(const wchar_t* source, size_t sourceLength, UConverter* converter, bool flush =true, UErrorCode* error =0); inline std::string narrow(const wchar_t* source, UConverter* converter, bool flush =true, UErrorCode* error =0) { return narrow(source, std::char_traits<wchar_t>::length(source), converter, flush, error); } inline std::string narrow(const wchar_t* source, const wchar_t* sourceLimit, UConverter* converter, bool flush =true, UErrorCode* error =0) { return narrow(source, sourceLimit - source, converter, flush, error); } inline std::string narrow(const std::wstring& source, UConverter* converter, bool flush =true, UErrorCode* error =0) { return narrow(source.data(), source.length(), converter, flush, error); } // 'convert' : multibyte -> multibyte std::string convert(const char* source, size_t sourceLength, UConverter* sourceConverter, UConverter* targetConverter, bool flush =true, UErrorCode* error =0); inline std::string convert(const char* source, UConverter* sourceConverter, UConverter* targetConverter, bool flush =true, UErrorCode* error =0) { return convert(source, std::char_traits<char>::length(source), sourceConverter, targetConverter, flush, error); } inline std::string convert(const char* source, const char* sourceLimit, UConverter* sourceConverter, UConverter* targetConverter, bool flush =true, UErrorCode* error =0) { return convert(source, sourceLimit - source, sourceConverter, targetConverter, flush, error); } inline std::string convert(std::string& source, UConverter* sourceConverter, UConverter* targetConverter, bool flush =true, UErrorCode* error =0) { return convert(source.data(), source.length(), sourceConverter, targetConverter, flush, error); } } #endif
●UnicodeConverter.cpp
#include <unicode/ucnv.h> #include "UnicodeConverter.h" namespace s34 { const char* icu_error::what() const { return u_errorName(error_); } // make_converter UConverter* open_converter(const char* name, UErrorCode* error) { UErrorCode innerError = U_ZERO_ERROR; UErrorCode* perror = error ? error : &innerError; UConverter* converter = ucnv_open(name, perror); if( U_FAILURE(*perror) && !error ) throw icu_error(innerError); return converter; } void close_converter(UConverter* converter) { ucnv_close(converter); } // widen : multibyte --> wide std::wstring widen(const char* source, size_t sourceLength, UConverter* converter, bool flush, UErrorCode* error) { const size_t poolLength = 256; wchar_t pool[poolLength]; size_t targetLength = sourceLength / ucnv_getMinCharSize(converter); wchar_t* buffer = ( targetLength > poolLength ) ? new wchar_t[targetLength] : pool; wchar_t* target = buffer; UErrorCode innerError = U_ZERO_ERROR; UErrorCode* perror = error ? error : &innerError; ucnv_toUnicode(converter, &target, target + targetLength, &source, source + sourceLength, 0, flush, perror); if(U_FAILURE(*perror) && !error) { if ( buffer != pool ) delete[] buffer; throw icu_error(innerError); } std::wstring result(buffer,target); if(buffer != pool) delete[] buffer; return result; } // narrow : wide --> multibyte std::string narrow(const wchar_t* source, size_t sourceLength, UConverter* converter, bool flush, UErrorCode* error) { const size_t poolLength = 256; char pool[poolLength]; size_t targetLength = sourceLength * ucnv_getMaxCharSize(converter); char* buffer = ( targetLength > poolLength ) ? new char[targetLength] : pool; char* target = buffer; UErrorCode innerError = U_ZERO_ERROR; UErrorCode* perror = error ? error : &innerError; ucnv_fromUnicode(converter, &target, target + targetLength, &source, source + sourceLength, 0, flush, perror); if(U_FAILURE(*perror) && !error) { if(buffer != pool) delete[] buffer; throw icu_error(innerError); } std::string result(buffer, target); if(buffer != pool) delete[] buffer; return result; } // convert : multibyte --> multibyte std::string convert(const char* source, size_t sourceLength, UConverter* sourceConverter, UConverter* targetConverter, bool flush, UErrorCode* error) { const size_t poolLength = 256; size_t targetLength; targetLength = sourceLength / ucnv_getMinCharSize(sourceConverter); wchar_t wpool[poolLength]; wchar_t* wbuffer = ( targetLength > poolLength ) ? new wchar_t[targetLength] : wpool; wchar_t* wtarget = wbuffer; UErrorCode innerError = U_ZERO_ERROR; UErrorCode* perror = error ? error : &innerError; ucnv_toUnicode(sourceConverter, &wtarget, wtarget + targetLength, &source, source + sourceLength, 0, flush, perror); if(U_FAILURE(*perror)) { if(wbuffer != wpool) delete[] wbuffer; if(!error) throw icu_error(innerError); return std::string(); } targetLength = (wtarget - wbuffer) * ucnv_getMaxCharSize(targetConverter); char pool[poolLength]; char* buffer = ( targetLength > poolLength ) ? new char[targetLength] : pool; const wchar_t* wsource = wbuffer; char* target = buffer; ucnv_fromUnicode(targetConverter, &target, target + targetLength, &wsource, wtarget, 0, flush, perror); if(U_FAILURE(*perror) && !error) { if(wbuffer != wpool) delete[] wbuffer; if(buffer != pool) delete[] buffer; throw icu_error(innerError); } std::string result(buffer, target); if(wbuffer != wpool) delete[] wbuffer; if(buffer != pool) delete[] buffer; return result; } }
●test.cpp
#include <iostream> #include <unicode/ucnv.h> #include "UnicodeConverter.h" int main() { std::string euc_jp = "\xC6\xFC\xCB\xDC"; try { UConverter* sourceConverter = s34::open_converter("euc-jp"); UConverter* targetConverter = s34::open_converter("shift_jis"); std::cout << s34::convert(euc_jp, sourceConverter, targetConverter) << std::endl; s34::close_converter(sourceConverter); s34::close_converter(targetConverter); } catch(s34::icu_error& ex) { std::cerr << ex.what() << std::endl; } return 0; }
■おわりに
お分かりの通り、プログラム内で文字コード変換が必要になった場合に、特にICUを使わなくても別の方法で行うこともできます。しかし、ICUを利用するメリットとして、「コードの移植性が高い」という点が挙げられます。そのために、何らかのアプリケーションに組み込んで配布することを目的に使用する人にとっては必要になると思ったので、今回紹介することにしました。
■参考にしたページ
株式会社エス・スリー・フォー » ICUの文字コード変換を使いたいのですが…
株式会社エス・スリー・フォー » ICU 2.x : UnicodeString による文字コード変換
文字コード変換ライブラリ「ICU」セットアップ: プログラマーの雑記帳
ICU(1) ICU Unicodeライブラリを古いVC++で環境設定する : OFF-SOFT.net