伝説のツール「NotesPeek」をQtでリメイクする(その11・範囲型の読み込み)
前回は、Notes APIにおける複数値、LIST型とRANGE型について説明しました。テキストの複数値はLIST型を、数値と日時はRANGE型を使って複数値を表現しています。RANGE型は2種類のリストを持っており、1つは単数値のリスト、もう1つは「範囲」というペア値のリストを同時に持ちます。そしてこのペア値は、数値も日時もLowerとUpperという値を持っています。
数値の複数値も、日時の複数値も、値が数値か日時かの違いのみで、それ以外の構造はまったく同じです。ということは、C++テンプレートで実装するにはうってつけでしょう。
それでは、どのように実装してみたのか、ソースコードを見ながら説明していきます。
今回、複数値も含めてNSFSearch関数で取得したサマリーバッファを格納するクラスとして、ntlx::SummaryMapクラスを定義しました。
// ntlx/summarymap.h抜粋 namespace ntlx { /** * @brief 名前付きサマリーバッファ用データクラス */ class NTLXSHARED_EXPORT SummaryMap { public: /** * @brief デフォルトコンストラクタ */ SummaryMap(); /** * @brief コピーコンストラクタ * @param other コピー元 */ SummaryMap(const SummaryMap& other); /** * @brief 代入演算子 * @param other 代入元 * @return 自身への参照 */ SummaryMap& operator=(const SummaryMap& other); /** * @brief 検索マッチ情報 * @return SEARCH_MATCH型データへの参照 */ const SEARCH_MATCH& match() const { return match_; } QMap<QString, QVariant>& map() { return map_; } /** * @brief load NSFSearch関数のコールバック情報を取り込む * @param pMatch 検索マッチ情報 * @param pItemTable サマリーバッファへのポインタ */ void load(const SEARCH_MATCH* const pMatch, ITEM_TABLE* pItemTable); /** * @brief 型タイプに合わせてデータを取得する * @param type 型タイプ * @param ptr データへのポインタ * @param len データ長 * @return データのQVariantオブジェクト */ static QVariant getValue(WORD type, char* ptr, USHORT len); private: SEARCH_MATCH match_; QMap<QString, QVariant> map_; }; /** * @brief サマリーバッファのリストクラス */ typedef QList<SummaryMap> SummaryList; } // namespace ntlx
ただし、今回はgetValueメソッドから数値リスト、日時リストを処理するためのテンプレート関数にフォーカスします。getValueメソッドの中で、数値リストと日時リストを呼び出す場所を抜粋します。
// summarymap.cpp抜粋 case TYPE_NUMBER: return *(NUMBER*)ptr; case TYPE_NUMBER_RANGE: return getRangeValue<Number, NUMBER, NUMBER_PAIR>(ptr, "number"); case TYPE_TIME: { TimeDate td; td.value_ = *(TIMEDATE*)ptr; return td.toQDateTime(); } break; case TYPE_TIME_RANGE: return getRangeValue<TimeDate, TIMEDATE, TIMEDATE_PAIR>(ptr, "timedate");
ptrはデータの開始ポインタを表しています。サマリーバッファにおける値の開始位置は、WORD型のデータ種別を表すので、実際の開始位置はsizeof(WORD)分だけずれます。
NUMBER型(数値)の場合は、ポインタの開始位置をNUMBER型として取得すればOKです。
TIMEDATE型(日時)の場合は、TIMEDATE構造体のポインタに置き換えればいいのですが、以前に定義したTimeDateクラスを応用して、QDateTimeオブジェクトに変換しています。
それでは、本題のRANGE(範囲)型のデータを取得してみましょう。
数値と日時範囲の取得には、関数テンプレートgetRangeValueを使うことにします。この関数は、クラス、単一データリストの型、ペアデータ(範囲データ)リストの型をパラメータに取ります。
// summarymap.cpp抜粋 template<class T, typename S, typename P> QVariant getRangeValue(char* ptr, const QString& typeName) { RANGE* pRange = (RANGE*)ptr; S* pSingle = (S*)(pRange + 1); P* pPair = (P*)(pSingle + pRange->ListEntries); ...
ポインタの最初の位置からRANGE型を取得します。ポインタにおいて、変数のサイズ分だけポインタを移動するには、データ型ポインタに1を足します(詳細はWebで)。RANGE型の次のポインタにあるのは「単一データのリスト」のスタート地点です。さらに、単一データからRANGE.ListEntries分ポインタを進めると、ペアデータ(範囲データ)のスタート地点を取得できます。
... QList<QVariant> list; for (USHORT i = 0; i < pRange->ListEntries; ++i, ++pSingle) { T item(*pSingle); list.append(item.toVariant()); } ...
最初に単一データを取得します。RANGE.ListEntries分ループを回して、Tクラスのオブジェクトを作成し、リストにそのQVariant値を追加します。TクラスとはNumberクラスかTimeDateクラスを表します。T.toVariant()メソッドについては後述します。
... QList<QVariant> pairList; for (USHORT i = 0; i < pRange->RangeEntries; ++i, ++pPair) { T lower(pPair->Lower); T upper(pPair->Upper); QMap<QString, QVariant> pair; pair.insert("lower", lower.toVariant()); pair.insert("upper", upper.toVariant()); pairList.append(pair); } ...
続いてペアデータを取得します。RANGE.RangeEntries分ループを回して、TクラスのP.Lower、P.Upperそれぞれに対応したオブジェクトを作成し、それらをQVariant値に変換してQMapオブジェクトを作成し、リストに追加します。なお、ここでQPairを使う方がシンプルですが、デフォルトではQPairをQVariantに変換できないため、ここではQMapを使用しています。
... QMap<QString, QVariant> value; value.insert("list", list); value.insert("range", pairList); value.insert("type", typeName); return value; }
最後に、取得した単一データリスト、ペアデータリストをQMapのlist、rangeのデータとして挿入します。単にこのマップデータを返しただけでは数値なのか日時なのかわからないので、最後にtypeとして型名文字列を挿入しています。
ここでさきほどのT.toVariant()メソッドについて。このメソッドはTオブジェクトからQVariantデータを取得するのが目的になりますが、実を言えば、このようなテンプレートのために設けたと言っても過言ではありません。TIMEDATEについては、TimeDateクラスで以下のように実装しています。
// timedate.cpp抜粋 QVariant TimeDate::toVariant() const { return QVariant(toQDateTime()); }
TimeDate.toQVariantは、TIMEDATE値をQVariantに対応しているQDateTimeオブジェクトに変換してQVariantを返します。
もう一方のNUMBER(数値型)ですが、今までラッパークラスを設けてきませんでした。そこで、今回C++テンプレートのためにラッパークラスを設けました。
// ntlx/number.h #ifndef NTLX_NUMBER_H #define NTLX_NUMBER_H #include "ntlx_global.h" #include "ntlx/status.h" class QVariant; namespace ntlx { /** * @brief 数値ラッパークラス */ class NTLXSHARED_EXPORT Number { public: /** * @brief デフォルトコンストラクタ */ Number(); /** * @brief コンストラクタ(NUMBER型から) * @param value NUMBER値 */ Number(const NUMBER& value); /** * @brief コピーコンストラクタ * @param other コピー元 */ Number(const Number& other); /** * @brief 代入演算子 * @param other 代入元 * @return 自身への参照 */ Number& operator=(const Number& other); /** * @brief 文字列に変換する * @return 数値文字列 */ QString toString() const; /** * @brief QVariantオブジェクトに変換する * @return QVariant値 */ QVariant toVariant() const; /** * @brief QVariant型からNumber型に変換する * @param var QVariant値 * @return Numberオブジェクト */ static Number fromVariant(const QVariant& var); private: NUMBER value_; }; } // namespace ntlx #endif // NTLX_NUMBER_H
// number.cpp #include "ntlx/number.h" #include <QVariant> namespace ntlx { Number::Number() : value_(0.) { } Number::Number(const NUMBER &value) : value_(value) { } Number::Number(const Number &other) : value_(other.value_) { } Number& Number::operator=(const Number& other) { if (this == &other) return *this; value_ = other.value_; return *this; } QString Number::toString() const { return QString::number(value_); } QVariant Number::toVariant() const { return QVariant(value_); } Number Number::fromVariant(const QVariant &var) { return Number(var.toDouble()); } } // namespace ntlx
これにより、少なくとも関数テンプレートgetRangeValueにおいては、日時型TIMEDATEと数値型NUMBERは、範囲型TIMEDATE_PAIRとNUMBER_PAIRとともに、TimeDateクラスとNumberクラスを伴って同等の扱いをすることが可能になりました。
今回は、Cポインタ、関数テンプレート、QtのQVariantといういろいろな手法を取り入れて、数値と日時の「範囲型」データの取得に挑んでみました。NTLXライブラリの追加・変更点はまだいくつかありますので、順に説明していきたいと思います。