株式会社エス・スリー・フォー

ICU 2.x : UnicodeString による文字コード変換

ICU 2.x ではUNICODEによる文字列クラス UnicodeString が導入されました。

UnicodeString はマルチバイト文字列からコンストラクトでき、さらにメソッド
extract でマルチバイト文字列に変換できます。

このふたつを用いて任意の文字コード間の変換を行なってみましょう。

マルチバイト文字列 → UnicodeString

マルチバイト文字列からUnicodeStringすなわちUNICODEに変換するには:

  • UConverter を生成し、UnicodeStringのコンストラクタに与える
  • codepage文字列を UnicodeStringのコンストラクタに与える

の2通りの方法があります。後者の方が実装が簡単ですが、これはUnicodeStringのコンストラクタ内でUConverterを生成/変換後に破棄していますから、パフォーマンスは若干劣ります。

  • UnicodeString::UnicodeString(const char* codepageData, const char* codepage =0);

    codepageで表現されたマルチバイト文字列codepageDataからUnicodeStringを生成します。[2]

  • UnicodeString::UnicodeString(const char* src, int32_t srcLength, UConverter* cnv, UErrorCode& errorCode);

    cnvを用いてsrcから始まるsrcLengthバイトのマルチバイト文字列からUnicodeStringを生成します。[3]

    ※ 変換で発生したエラーはerrorCodeに設定されます。

UnicodeString → マルチバイト文字列

UnicodeStringからマルチバイト文字列に変換するには:

  • UConverter を生成し、メソッド extractに与える
  • codepage文字列をメソッド extract に与える

の2通りの方法があります。後者の方が実装が簡単ですが、コンストラクタと同様、メソッド内でUConverterを生成/変換後に破棄していますから、パフォーマンスは若干劣ります。

  • int32_t UnicodeString::extract(int32_t start, int32_t startLength, char* target, const char* codepage =0) const;

    startから始まるstartLength文字をcodepageで表現されたマルチバイト文字列に変換しtargetに格納します。[5]

  • int32_t UnicodeString::extract(char* dest, int32_t destCapacity, UConverter* cnv, UErrorCode& errorCode) const;

    cnvを用いてUNICODE文字列全体をマルチバイト文字列に変換し、destから始まるdestCapacityバイトの領域に格納します。[7]

    ※ 変換で発生したエラーはerrorCodeに設定されます。

いずれのメソッドも、変換後のバイト数を返します。 格納先を 0にしておけば、実際の変換を行なわず、変換結果の格納に必要な領域の大きさを返します(ただし末尾の ‘\0’を含まない)。[4] [6]

std::string のコンストラクタであらかじめ必要な大きさを与えることにより[8] [10]、char[]バッファを明示的に確保せず、std::stringをUNICODE→マルチバイト文字列変換の格納先に直接指定することもできます[9][11]

このとき、変換結果を格納するのに必要な容量をあらかじめ求めておかなければならないため、メソッドextractを2度呼ばなければなりません。

が、ある程度の余裕をもって十分な領域を確保しておくことができる場合もあります。

たとえば shift_jis に変換する場合、UNICODEでの1文字は shift_jisでは1もしくは2バイトに変換されますから、UNICODE文字列の文字数の2倍のバイト数の領域を確保しておけば十分です。

UnicodeString input;
  std::string result(input.length()*2, '\0'); // 十分な長さの '\0' で埋まった文字列
  input.extract(0, input.length(), &result[0], "shift_jis");
  result.erase(result.find('\0')); // 末尾の余分な '\0' を削除

CppUnitによるサンプル

#include <unicode/unistr.h> // UnicodeString
#include <unicode/ucnv.h>   // UConverter

#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestAssert.h>

/* CppUnitのための
 * UnicodeString の比較と文字列化 */
template<>
struct CppUnit::assertion_traits<icu::UnicodeString> {
  static bool equal(const icu::UnicodeString& x, const icu::UnicodeString& y) {
    return x == y ? true : false;
  }
  static std::string toString(const icu::UnicodeString& x) {
    std::string result(x.extract(0, x.length(), 0, "shift_jis"), '\0');
    x.extract(0, x.length(), &result[0], "shift_jis");
    return result;
  }
};

class UnicodeStringTest : public CppUnit::TestFixture {
private:

  UConverter* converter;
  UErrorCode  error;

  static std::wstring uinput;
  static std::string  sinput; 

public:

  virtual void setUp() { 
    error = U_ZERO_ERROR;
    converter = ucnv_open("shift_jis", &error);
    CPPUNIT_ASSERT( U_SUCCESS(error) );
  }

  virtual void tearDown() {
    ucnv_close(converter);
  }

  /* from UNICODE
     UnicodeString::UnicodeString(const UChar* text);
   */
  void testCtor0() {
    icu::UnicodeString result(uinput.c_str()); // [1]
    CPPUNIT_ASSERT_EQUAL(6L, result.length());
    for ( int i = 0; i < result.length(); ++i) {
      CPPUNIT_ASSERT( uinput[i] == result[i]);
    }
  }

  /* from shift_jis using codepage
     UnicodeString::UnicodeString(const char* codepageData, 
                                  const char* codepage =0);
   */
  void testCtor1() {
    icu::UnicodeString expected(uinput.c_str());
    icu::UnicodeString result(sinput.c_str(), "shift_jis"); // [2]
    CPPUNIT_ASSERT_EQUAL( expected, result);
  }

  /* from shift_jis using UConverter
     UnicodeString::UnicodeString(const char* src, 
                                  int32_t srcLength, 
                                  UConverter* cnv, 
                                  UErrorCode& errorCode);
   */
  void testCtor2() {
    icu::UnicodeString expected(uinput.c_str());
    icu::UnicodeString result(sinput.c_str(), sinput.size(), 
                              converter, error = U_ZERO_ERROR); // [3]
    CPPUNIT_ASSERT( U_SUCCESS(error) );
    CPPUNIT_ASSERT_EQUAL( expected, result);
  }

  /* to shift_jis using codepage
     int32_t UnicodeString::extract(int32_t start, 
                                    int32_t startLength, 
                                    char* target, 
                                    const char* codepage =0) const;
   */
  void testConvert0() {
    const std::string  expected = sinput;
    icu::UnicodeString input(uinput.c_str());
    int32_t convertedLength = input.extract(0, input.length(), 0, "shift_jis"); // [4]
    char* result = new char[convertedLength + 1];
    input.extract(0, input.length(), result, "shift_jis"); // [5]
    CPPUNIT_ASSERT_EQUAL( expected, std::string(result) );
    delete[] result;
  }

  /* to shift_jis using converter
     int32_t UnicodeString::extract(char* dest, 
                                    int32_t destCapacity, 
                                    UConverter* cnv, 
                                    UErrorCode& errorCode) const;
   */
  void testConvert1() {
    const std::string  expected = sinput;
    icu::UnicodeString input(uinput.c_str());
    int32_t convertedLength = input.extract(0, 0, converter, error = U_ZERO_ERROR); // [6]
    char* result = new char[convertedLength+1];
    input.extract(result, convertedLength+1, converter, error = U_ZERO_ERROR); // [7]
    CPPUNIT_ASSERT( U_SUCCESS(error) );
    CPPUNIT_ASSERT_EQUAL( expected, std::string(result) );
    delete[] result;
  }

  /* to shift_jis no-using buffer
   */
  void testConvert2() {
    const std::string  expected = sinput;
    icu::UnicodeString input(uinput.c_str());
    { // using codepage
    std::string result(input.extract(0, input.length(), 0, "shift_jis"), '\0'); // [8]
    input.extract(0, input.length(), &result[0], "shift_jis"); // [9]
    CPPUNIT_ASSERT_EQUAL( expected, result);
    }
    { // using UConverter
    std::string result(input.extract(0, 0, 
                       converter, error = U_ZERO_ERROR), '\0'); // [10]
    input.extract(&result[0], result.size(), 
                       converter, error = U_ZERO_ERROR); // [11]
    CPPUNIT_ASSERT( U_SUCCESS(error) );
    CPPUNIT_ASSERT_EQUAL( expected, result);
    }
  }

  /* euc-jp to shift_jis
   */
  void testConvert3() {
    std::string euc_jp = "\xC6\xFC\xCB\xDC";
    std::string shift_jis = "日本";
    UConverter* fromConverter = ucnv_open("euc-jp", &error);
    UConverter* toConverter   = ucnv_open("shift_jis", &error);
    CPPUNIT_ASSERT( U_SUCCESS(error) );
    icu::UnicodeString ustr(euc_jp.c_str(), euc_jp.size(), 
                            fromConverter, error = U_ZERO_ERROR); // [12]
    std::string result(ustr.extract(0, 0, toConverter, error = U_ZERO_ERROR), '\0');
    ustr.extract(&result[0], result.size(), toConverter, error = U_ZERO_ERROR);
    ucnv_close(fromConverter);
    ucnv_close(toConverter);
    CPPUNIT_ASSERT_EQUAL( shift_jis, result );
  }

  CPPUNIT_TEST_SUITE(UnicodeStringTest);
  CPPUNIT_TEST(testCtor0);
  CPPUNIT_TEST(testCtor1);
  CPPUNIT_TEST(testCtor2);
  CPPUNIT_TEST(testConvert0);
  CPPUNIT_TEST(testConvert1);
  CPPUNIT_TEST(testConvert2);
  CPPUNIT_TEST(testConvert3);
  CPPUNIT_TEST_SUITE_END();
};

std::wstring UnicodeStringTest::uinput = L"ガンバレ日本";
std::string  UnicodeStringTest::sinput =  "ガンバレ日本";

CPPUNIT_TEST_SUITE_REGISTRATION(UnicodeStringTest);