見出し画像

Notes C API探訪: NLS_string_bytes/NLS_string_chars(関数)

ASCIIのような1バイトだけで構成される文字列では、文字数=バイト数です。UTF16のように2バイトだけで構成されるワイド文字セットでは、文字数=バイト数×2と単純に計算できます。しかし、シフトJISやUTF-8のような「マルチバイト文字セット」では、1文字のサイズが一定しないため、先頭から数えていくほかありません。LMBCSも同様で、ASCIIとASCII以外で数え方を変える必要があります。

Notes C APIには、この問題を解決できる便利な関数が用意されています。LMBCS文字列の文字数からバイト数を計算するNLS_string_bytes、バイト数から文字数を計算するNLS_string_charsがそれです。これらはNLS_PINFOが対応する文字セットに応じて計算するので、LMBCSに限った話ではないのですが、LMBCSはLotus製品特有なので、これらの関数に頼らざるを得ないのです。

NLS_string_bytes

NLS_string_bytes関数は、指定した文字列のバイト数を計算します。

#include <nls.h>

NLS_STATUS  LNPUBLIC NLS_string_bytes     (
  const BYTE far * pString,
  WORD             NumChars,
  WORD far *       pNumBytes,
  NLS_PINFO        pInfo);

pStringは、数えたい文字列を指定します。
NumCharsは、数えたい文字数を指定します。
pNumBytesは、指定した文字数に対応するバイト数を返す変数へのポインタを指定します。
pInfoは、計算を適用する文字セット情報を指定します。

この関数では、特にNULL終端文字を認識するわけではなさそうなので、NLS_string_charsで上限を調べてから使用した方がよさそうです。

NLS_string_chars

NLS_string_chars関数は、指定した文字列の文字数を計算します。

#include <nls.h>

NLS_STATUS  LNPUBLIC NLS_string_chars     (
  const BYTE far * pString,
  WORD             NumBytes,
  WORD far *       pNumChars,
  NLS_PINFO        pInfo);

pStringは、数えたい文字列を指定します。
NumBytesは、数えたいバイト数を指定します。
pNumCharsは、指定したバイト数に対応する文字数を返す変数へのポインタを指定します。
pInfoは、計算を適用する文字セット情報を指定します。

マルチバイト文字セットでは、指定したバイト数が文字の途中になることがあります。その時は、NLS_INVALIDDATAというステータスを返しますが、数えられた文字数分だけはpNumCharsに返してくれるので、バイト途中の文字を考慮する必要がなければ、ステータスエラーでも文字数は数えられます。

ラップ例

// nxpp/include/nxpp/nls/nxpp_nlssize.hpp

#ifndef NXPP_NLSSIZE_HPP
#define NXPP_NLSSIZE_HPP

#include "./nxpp_nlsstatus.hpp"

namespace nxpp::nls {

/**
* @brief バイト配列の文字数相当のバイト数を取得します。
* @param s 対象のバイト配列
* @param chars バイト数として測りたい文字数
* @param pInfo バイト配列の文字セット情報
* @return バイト数
* @throw Status NLS_STATUSを内包したクラス
*/
inline WORD getStringBytes_w(const char *str, WORD chars, NLS_PINFO pInfo) {
 if (chars == 0) return 0;
 WORD bytes = 0;
 Status status = NLS_string_bytes(
       reinterpret_cast<const BYTE*>(str),
       chars,
       &bytes,
       pInfo
       );
 if (!status) throw status;
 return bytes;
}

/**
* @brief バイト配列の文字数を取得します。
* @param s 対象のバイト配列
* @param bytes 文字数として測りたいバイト数
* @param pInfo バイト配列の文字セット情報
* @return 文字数
* @throw Status NLS_STATUSを内包したクラス
*/
inline WORD getStringChars_w(const char *str, WORD bytes, NLS_PINFO pInfo) {
 if (bytes == 0) return 0;
 auto b = bytes;
 WORD chars = 0;
 Status status = NLS_string_chars(
       reinterpret_cast<const BYTE*>(str),
       b,
       &chars,
       pInfo
       );
 // 無効なデータ以外の失敗の時
 if (!status && status.value() != NLS_INVALIDDATA) {
   throw status;
 }
 return chars;
}

/**
* @brief 文字セットに応じて、マルチバイト文字列の区切り位置を調整したバイト数を求めます。
* @param s 対象のバイト配列
* @param bytes 計測対象のバイト数
* @param pInfo バイト配列の文字セット情報
* @return 調整したバイト数
* @throw Status NLS_STATUSを内包したクラス
*/
inline WORD adjustByteSize_w(const char *str, WORD bytes, NLS_PINFO pInfo) {
 auto chars = getStringChars_w(str, bytes, pInfo);
 return getStringBytes_w(str, chars, pInfo);
}

} // namespace nxpp::nls

#endif // NXPP_NLSSIZE_HPP

ここでは、特にadjustByteSize_wについてお話しします。
文字列を扱うクラスの最大長が64キロバイトに制限されているなんて、ここ数年聞いたことがありませんが、Notes C APIの文字セット変換用の関数(OSTranslate、NLS_translate)は、Ver.9の時点で依然としてあります。そのため、素直にstd::wstringやQStringとLMBCS文字列を変換する際に、WORD型のサイズ制限が足かせになります。
LMBCS文字列をstd::wstringやQStringのようなUTF16文字列に変換するには、64キロバイト以下で区切りながらOSTranslate、NLS_translate関数で変換し、つなぎ合わせる必要がありますが、適当にぶった切っては文字をロストしてしまいます。そのためにこのadjustByteSize_wが必要になります。

まとめ

LMBCSという文字セットは、Unicodeで統一されつつある文字コード界においてはすでに負の遺産といってもいいでしょう。Notes/DominoがUnicodeにネイティブに対応する・・・夢か、幻か。まあ、そんなわけで、地味ですが非常に重要なNLS_string_bytes、NLS_string_chars両関数を覚えておいていただけると光栄です。

この記事が気に入ったらサポートをしてみませんか?