NCAPI & C++11+ & RX & Qt #11 ~LMBCS対応文字列クラス

前回の続きになります。

ソースコードこちらで、git clone後にgit checkout v0.0.3をしてください。

QByteArray

QStringがワイド文字列(UTF-16)を扱うクラスであるならば、QByteArrayはシングルバイト、マルチバイト文字列を扱うクラスです。ただ、その性質上バイナリコンテナとしても扱えるので、とても重宝します。

なぜこのクラスをこのタイミングで紹介するかというと、LMBCS自体を何で管理しようかと考えたときに、QByteArrayが適しているんじゃないかな~というわけです。

notespp::TranslateLmbcsクラス

Notes C API関数OSTranslateをラップするクラスです。名前にLmbcsと付けてしまっていますが、LMBCS以外にもOSTranslate関数は変換モードを持っているので、将来的にはリネームする可能性大ですww。

// ヘッダー部

/**
 * @brief LMBCS変換関数オブジェクトの基底クラス
 */
class TranslateLmbcs
{
public:
  /**
   * @brief コンストラクタ
   * @param bufferSize バッファサイズ
   */
  TranslateLmbcs(WORD bufferSize = MAXWORD - 1);

  /**
   * @brief 文字列を指定したモードで変換する。
   * @param mode 変換モード
   * @param source 変換元の文字列
   * @return 変換後の文字列
   */
  QByteArray translate(WORD mode, const QByteArray &source) const;

protected:
  WORD bufferSize_;
};

// ソース部
TranslateLmbcs::TranslateLmbcs(WORD bufferSize)
  : bufferSize_(bufferSize)
{}

QByteArray TranslateLmbcs::translate(
    WORD mode,
    const QByteArray &source
    ) const
{
  CharArrayPtr buffer(new char[bufferSize_]);
  WORD len = OSTranslate(
        mode
        , source.constData()
        , static_cast<WORD>(source.size())
        , buffer.data()
        , bufferSize_
        );
  return QByteArray(buffer.data(), static_cast<int>(len));
}

CharArrayPtr

CharArrayPtrは別名で、本来は次のようなシグネチャを持っています。

/**
 * @brief 文字列配列用スコープドスマートポインタ
 */
using CharArrayPtr = QScopedPointer<char, QScopedPointerArrayDeleter<char>>;

QScopedPointerは、Qt版std::unique_ptrです。std::unique_ptrC++11からの仕様です。スマートポインタと呼ばれる、割り当てたメモリをプログラマに代わって解放してくれる賢いヤツです。charだけなら簡単な記述が、配列になるとこんな長いシグネチャになってしまうので、短い別名を付けました。変換後の文字列のバッファ用に使用しています。

notespp::LmbcsToUnicode、notespp::UnicodeToLmbcs関数オブジェクト

TranslateLmbcsを継承して、読んで字のごとく、LMBCS文字列からUnicode文字列へ、Unicode文字列からLMBCS文字列へ変換してくれます。

// ヘッダー部
/**
 * @brief LMBCS文字列からUnicode(UTF-16)に変換する関数オブジェクト
 */
class NOTESPPSHARED_EXPORT LmbcsToUnicode
    : public TranslateLmbcs
{
public:
  LmbcsToUnicode(WORD bufferSize = MAXWORD - 1);

  QByteArray operator ()(const QByteArray &source) const;
};

/**
 * @brief Unicode(UTF-16)からLMBCS文字列に変換する関数オブジェクト
 */
class NOTESPPSHARED_EXPORT UnicodeToLmbcs
    : public TranslateLmbcs
{
public:
  UnicodeToLmbcs(WORD bufferSize = MAXWORD - 1);

  QByteArray operator ()(const QByteArray &source) const;
};

// ソース部
LmbcsToUnicode::LmbcsToUnicode(WORD bufferSize)
  : TranslateLmbcs(bufferSize)
{}

QByteArray LmbcsToUnicode::operator ()(const QByteArray &source) const
{
  return translate(OS_TRANSLATE_LMBCS_TO_UNICODE, source);
}

UnicodeToLmbcs::UnicodeToLmbcs(WORD bufferSize)
  : TranslateLmbcs(bufferSize)
{}

QByteArray UnicodeToLmbcs::operator ()(const QByteArray &source) const
{
  return translate(OS_TRANSLATE_UNICODE_TO_LMBCS, source);
}

notespp::Stringクラス

以上を踏まえて、LMBCS対応文字列クラス、notespp::Stringの実装を見てみましょう。

// ヘッダー部
/**
 * @brief LMBCS文字列
 * @class String
 */
class NOTESPPSHARED_EXPORT String
{
public:
  /**
   * @brief デフォルトコンストラクタ
   */
  String();

  /**
   * @brief バイナリから作成するコンストラクタ
   * @param pData LMBCS文字列へのポインタ
   * @param size LMBCS文字列のバイト長、ヌル終端していれば省略可能
   */
  String(const char *pData, int size = -1);

  /**
   * @brief QStringに変換する。
   * @return 変換したQString
   */
  QString toQString() const;

  /**
   * @brief 文字数を返す。
   * @return LMBCS文字列の文字数(バイト数ではない)
   */
  int charSize() const;

  /**
   * @brief バイナリデータへのポインタを返す。
   * @return バイナリデータへのポインタ
   */
  const char *constData() const;

  /**
   * @brief QStringからString文字列を作成する。
   * @param qstr 変換するQStringオブジェクト
   * @return 変換したStringオブジェクト
   */
  static String fromQString(const QString &qstr);

private:
  QByteArray bytes_;
};

// ソース部
String::String()
  : bytes_()
{}

String::String(const char *pData, int size)
  : bytes_(pData, size)
{}

QString String::toQString() const
{
  // 必要なバッファサイズを割り出す。
  WORD bufferSize = static_cast<WORD>(std::min<int>(
        charSize() * sizeof(ushort),
        static_cast<int>(MAXWORD - 1)
        ));

  // 関数オブジェクトLmbcsToUnicodeでUnicode(UTF-16)に変換する。
  QByteArray rawUnicode = LmbcsToUnicode(bufferSize)(bytes_);

  // バイナリ状のUTF-16からQStringオブジェクトを作成する。
  return QString::fromUtf16(
        reinterpret_cast<ushort*>(const_cast<char*>(rawUnicode.constData())),
        rawUnicode.size() / sizeof(ushort)
        );
}

int String::charSize() const
{
  QByteArray s = bytes_.left(MAXWORD - 1);
  WORD numChars = 0;
  NLS_STATUS status = NLS_string_chars(
        reinterpret_cast<const BYTE*>(s.constData())
        , NLS_NULLTERM
        , &numChars
        , OSGetLMBCSCLS()
        );
  if (status != NLS_SUCCESS)
    return -1;

  return static_cast<int>(numChars);
}

const char *String::constData() const
{
  return bytes_.constData();
}

String String::fromQString(const QString &qstr)
{
  // 必要なバッファサイズを割り出す。
  WORD bufferSize = static_cast<WORD>(std::min<int>(
        qstr.size() * 3,
        static_cast<int>(MAXWORD - 1)
        ));

  // QStringをUnicode(UTF-16)にする。
  QByteArray rawUnicode(
        reinterpret_cast<const char*>(qstr.utf16()),
        qstr.size() * 2
        );

  // LMBCS化してStringオブジェクトに変換して返す。
  QByteArray rawLmbcs = UnicodeToLmbcs(bufferSize)(rawUnicode);
  return String(rawLmbcs.constData(), rawLmbcs.size());
}