見出し画像

Notes C API探訪: NSFSearch(関数)(その2)

さて、今回はNSFSearch関数のサンプルコードを通して、使い方を見てみます。まずは、最終的なサンプルコードで使用するヘルパー関数をいくつか紹介します。以前紹介したものもありますが、内容が更新されているので、こちらを正として下さい。

ex_osfile.hpp

この中には、OSPathNetConstruct関数のSTATUS処理を簡略化したosfile::constructPathNet関数を定義しています。戻り値にstd::optionalを利用して、失敗した場合にはstd::nulloptが返るようになっています。

#ifndef EX_OSFILE_HPP
#define EX_OSFILE_HPP

#include <string>
#include <optional>
#include "logger.h"

#ifdef NT
#pragma pack(push, 1)
#endif

#include <global.h>
#include <osfile.h>
#include <names.h>

#ifdef NT
#pragma pack(pop)
#endif

namespace osfile {

inline std::optional<std::string> constructPathNet(
   const std::string &path,
   const std::string &server,
   const std::string &port = ""
) {
 char netPath[MAXPATH + 1] = "";
 STATUS status = OSPathNetConstruct(
       port.empty() ? nullptr : port.c_str(),
       server.c_str(),
       path.c_str(),
       netPath
       );
 if (ERR(status) != NOERROR) {
   logger::printStatusMessage(status);
   return std::nullopt;
 }
 return std::string(netPath);
}

} // namespace osfile

#endif // EX_OSFILE_HPP

ex_nsfdb.hpp

この中も、STATUS処理を簡略化した関数を定義しています。ここでは、NSFDbOpenを簡略化した関数nsfdb::openDb、NSFDbCloseを簡略化した関数nsfdb::closeDbがあります。

#ifndef EX_NSFDB_HPP
#define EX_NSFDB_HPP

#include <string>
#include <optional>
#include "logger.h"

#ifdef NT
#pragma pack(push, 1)
#endif

#include <global.h>
#include <nsfdb.h>

#ifdef NT
#pragma pack(pop)
#endif

namespace nsfdb {

inline std::optional<DBHANDLE> openDb(const std::string &netPath) {
 DBHANDLE hDB = NULLHANDLE;
 STATUS status = NSFDbOpen(netPath.c_str(), &hDB);
 if (ERR(status) != NOERROR) {
   logger::printStatusMessage(status);
   return std::nullopt;
 }
 return hDB;
}

inline bool closeDb(DBHANDLE hDB) {
 STATUS status = NSFDbClose(hDB);
 if (ERR(status) != NOERROR) {
   logger::printStatusMessage(status);
   return false;
 }
 return true;
}

} // namespace nsfdb

#endif // EX_NSFDB_HPP

ex_nsfsearch.hpp

この中も、STATUS処理を簡略化した関数を定義しています。ここでは、NSFFormulaCompileを簡略化した関数nsfsearch::compile、NSFSearchを簡略化した関数nsfsearch::searchがあります。

#ifndef EX_NSFSEARCH_HPP
#define EX_NSFSEARCH_HPP

#include <string>
#include <optional>
#include <iostream>
#include "logger.h"

#ifdef NT
#pragma pack(push, 1)
#endif

#include <global.h>
#include <nsfsearc.h>
#include <nsfdb.h>

#ifdef NT
#pragma pack(pop)
#endif

namespace nsfsearch {

inline std::optional<FORMULAHANDLE> compile(const std::string &text) {
 FORMULAHANDLE hFormula = NULLHANDLE;
 STATUS compileStatus = NOERROR;
 WORD wLen = 0, wLine = 0, wColumn = 0, wOffset = 0, wOffsetLen = 0;
 STATUS status = NSFFormulaCompile(
       nullptr, 0,
       text.c_str(), static_cast<WORD>(text.length()),
       &hFormula,
       &wLen,
       &compileStatus,
       &wLine,
       &wColumn,
       &wOffset,
       &wOffsetLen);
 if (ERR(status) != NOERROR) {
   logger::printStatusMessage(status);
   logger::printStatusMessage(compileStatus);
   std::cerr << "Line=" << wLine
             << ", Column=" << wColumn
             << ", Offset=" << wOffset
             << ", OffsetLen=" << wOffsetLen
             << "\n===> "
             << std::string(text).substr(wOffset, wOffsetLen)
             << std::endl;
   return std::nullopt;
 }
 return hFormula;
}

inline bool search(
   DBHANDLE hDB,
   FORMULAHANDLE hFormula,
   const std::string &viewTitle,
   WORD searchFlags,
   WORD noteClass,
   TIMEDATE *pSince,
   NSFSEARCHPROC enumRoutine,
   void *pParam,
   TIMEDATE *pUntil
   ) {
 STATUS status = NSFSearch(
       hDB,
       hFormula,
       viewTitle.empty() ? nullptr : const_cast<char*>(viewTitle.c_str()),
       searchFlags,
       noteClass,
       pSince,
       enumRoutine,
       pParam,
       pUntil
       );
 if (ERR(status) != NOERROR) {
   logger::printStatusMessage(status);
   return false;
 }
 return true;
}

} // namespace nsfsearch

#endif // EX_NSFSEARCH_HPP

items.h

これは以前紹介していますが、内容を更新しました。

#ifndef ITEMS_H
#define ITEMS_H

#include <vector>
#include <string>

#ifdef NT
#pragma pack(push, 1)
#endif

#include <global.h>

#ifdef NT
#pragma pack(pop)
#endif

namespace items {

std::vector<std::string> toStringList(void *pPrefix, DWORD size = 0);

} // namespace items

#endif // ITEMS_H

items.cpp

文字列化できるデータのタイプを増やしています。

#include "items.h"
#include "ex_range.h"

#ifdef NT
#pragma pack(push, 1)
#endif

#include <textlist.h>
#include <misc.h>

#ifdef NT
#pragma pack(pop)
#endif

namespace items {

std::vector<std::string> toStringList(void *pPrefix, DWORD size) {
 std::vector<std::string> list;
 WORD prefix = *reinterpret_cast<WORD*>(pPrefix);
 char *pValue = reinterpret_cast<char*>(pPrefix) + sizeof(WORD);

 switch (prefix) {

 case TYPE_NUMBER: {
   char buffer[MAXALPHANUMBER + 1] = "";
   WORD len = 0;
   ConvertFLOATToText(
         nullptr,
         nullptr,
         reinterpret_cast<NUMBER*>(pValue),
         buffer,
         MAXALPHANUMBER,
         &len
         );
   list.push_back(std::string(buffer, len));
 } break;

 case TYPE_NUMBER_RANGE: {
   int count = static_cast<int>(
         RangeGetNumItem<NumberRangeTraits>(pPrefix, TRUE)
         );
   for (int i = 0; i < count; ++i) {
     NUMBER number = 0;
     RangeGetItem<NumberRangeTraits>(pPrefix, TRUE, i, &number);
     char buffer[MAXALPHANUMBER + 1] = "";
     WORD len = 0;
     ConvertFLOATToText(
           nullptr,
           nullptr,
           &number,
           buffer,
           MAXALPHANUMBER,
           &len
           );
     list.push_back(std::string(buffer, len));
   }
 } break;

 case TYPE_TIME: {
   char buffer[MAXALPHATIMEDATE + 1] = "";
   WORD len = 0;
   ConvertTIMEDATEToText(
         nullptr,
         nullptr,
         reinterpret_cast<TIMEDATE*>(pValue),
         buffer,
         MAXALPHATIMEDATE,
         &len
         );
   list.push_back(std::string(buffer, len));
 } break;

 case TYPE_TIME_RANGE: {
   int countItem = static_cast<int>(
         RangeGetNumItem<TimeDateRangeTraits>(pPrefix, TRUE)
         );
   for (int i = 0; i < countItem; ++i) {
     TIMEDATE td;
     RangeGetItem<TimeDateRangeTraits>(pPrefix, TRUE, i, &td);
     char buffer[MAXALPHATIMEDATE + 1] = "";
     WORD len = 0;
     ConvertTIMEDATEToText(
           nullptr,
           nullptr,
           &td,
           buffer,
           MAXALPHATIMEDATE,
           &len
           );
     list.push_back(std::string(buffer, len));
   }
   int countPair = static_cast<int>(
         RangeGetNumPair<TimeDateRangeTraits>(pPrefix, TRUE)
         );
   for (int i = 0; i < countPair; ++i) {
     TIMEDATE_PAIR td;
     RangeGetPair<TimeDateRangeTraits>(pPrefix, TRUE, i, &td);
     char buffer[MAXALPHATIMEDATEPAIR + 1] = "";
     WORD len = 0;
     ConvertTIMEDATEPAIRToText(
           nullptr,
           nullptr,
           &td,
           buffer,
           MAXALPHATIMEDATEPAIR,
           &len
           );
     list.push_back(std::string(buffer, len));
   }
 } break;

 case TYPE_TEXT: {
   list.push_back(std::string(pValue, size - sizeof(WORD)));
 } break;

 case TYPE_TEXT_LIST: {
   int count = static_cast<int>(ListGetNumEntries(pPrefix, TRUE));
   for (int i = 0; i < count; ++i) {
     char *pText = nullptr;
     WORD textLen = 0;
     ListGetText(pPrefix, TRUE, i, &pText, &textLen);
     list.push_back(std::string(pText, textLen));
   }
 } break;

 default: {
   list.push_back("(Undefined type)");
 }

 }
 return list;
}

}

サンプルコード

本題です。まずはNSFSearch関数のコールバック関数を定義します。

#include <iostream>
#include "logger.h"
#include "items.h"
#include "ex_osfile.hpp"
#include "ex_nsfdb.hpp"
#include "ex_nsfsearch.hpp"

#ifdef NT
#pragma pack(push, 1)
#endif

#include <nsfnote.h>
#include <osmem.h>

#ifdef NT
#pragma pack(pop)
#endif

/**
* @brief NSFSearch関数用コールバック
* @param pMatchOrg SEARCH_MATCH構造体へのポインタ
* @param pSummary 名前付きサマリーバッファへのポインタ
*/
STATUS LNCALLBACK search(void *, SEARCH_MATCH *pMatchOrg, ITEM_TABLE *pSummary) {

 // SEARCH_MATCH構造体のコピー
 SEARCH_MATCH match;
 memcpy(&match, pMatchOrg, sizeof(SEARCH_MATCH));

 // ITEM構造体への最初のポインタ
 ITEM *pItem = reinterpret_cast<ITEM*>(pSummary + 1);

 // 名前と値のある領域への最初のポインタ
 char *pData = reinterpret_cast<char*>(pItem + pSummary->Items);

 for (int i = 0; i < pSummary->Items; ++i) {

   // 名前を取得する
   std::string name(pData, pItem->NameLength);

   // ポインタを値の位置に移動する
   pData += pItem->NameLength;

   // 値の先頭はデータタイププレフィックス
   WORD *pType = reinterpret_cast<WORD*>(pData);

   // 名前、データタイププレフィックス、値の長さを出力
   std::cout << "Name: " << name
             << ", type=" << std::hex << *pType
             << ", len=" << std::dec << pItem->ValueLength
             << ", value=";

   // 値を文字列配列に変換する
   std::vector<std::string> list
       = items::toStringList(pData, pItem->ValueLength);

   // 配列の区切り文字
   std::string delim = "";

   // 値を出力する
   for (std::string item : list) {
     std::cout << delim << item;
     if (delim != ",") { delim = ","; }
   }
   std::cout << std::endl;

   // ポインタを次のアイテムの名前に移動
   pData += pItem->ValueLength;

   // ITEM構造体を次に移動する
   pItem += 1;
 }
 return NOERROR;
}

この中では、サマリーバッファを手動でパースして、フィールドの名前、タイプ、値の長さ、文字列配列化された値を標準出力に表示するようにしています。
サマリーバッファを使うことで、文書のオープン処理を省略しています。

次に、データベースパスの構築、データベースのオープン、@式のコンパイル、検索という一連の動作をひとまとめにした関数です。

/**
* @brief 文書検索
* @param server サーバ名
* @param path パス
* @param sFormula 検索式(文字列)
* @return
*/
bool nsfSearch::searchNote(
   const std::string &server,
   const std::string &path,
   const std::string &sFormula
   ) {

 // ネットパスを構築
 auto netPath = osfile::constructPathNet(path, server);
 if (!netPath) {
   return false;
 }
 std::cout << "netPath=" << netPath.value() << std::endl;

 // データベースをオープン
 auto hDB = nsfdb::openDb(netPath.value());
 if (!hDB) {
   return false;
 }
 std::cout << "opened; " << netPath.value() << std::endl;

 // 検索式をコンパイル
 auto hFormula = nsfsearch::compile(sFormula);
 if (!hFormula) {
   nsfdb::closeDb(hDB.value());
   return false;
 }
 std::cout << "compiled; " << sFormula << std::endl;

 // 検索実行
 if (!nsfsearch::search(
       hDB.value(),
       hFormula.value(),
       "",
       SEARCH_SUMMARY,
       NOTE_CLASS_DOCUMENT,
       nullptr,
       search,
       nullptr,
       nullptr
       )) {
   OSMemFree(hFormula.value());
   nsfdb::closeDb(hDB.value());
   return false;
 }

 // 終了処理
 OSMemFree(hFormula.value());
 nsfdb::closeDb(hDB.value());

 return true;
}

検索条件のフラグに、SEARCH_SUMMARYを指定することを忘れないようにします。これがないと、サマリーバッファは空っぽになります。

最後に、これを呼び出す関数です。

/**
* @brief エントリポイント
*/
void nsfSearch::run() {
 if (!searchNote("", "docs.nsf", "Subject=\"Test\"")) {
   return;
 }
}

ローカルにdocs.nsf(文書ライブラリテンプレートを使用)があり、タイトルにTestと入っている文書を検索して、中身を表示します。これの実行結果を以下に示します。

netPath=docs.nsf
opened; docs.nsf
compiled; Subject="Test"
Name: Form, type=500, len=10, value=Document
Name: From, type=500, len=28, value=CN=admin notes9/O=chiburu9
Name: AltFrom, type=500, len=28, value=CN=admin notes9/O=chiburu9
Name: AltLang, type=500, len=2, value=
Name: Status, type=300, len=10, value=1
Name: CurrentEditor, type=500, len=2, value=
Name: CurrentUser, type=500, len=28, value=CN=admin notes9/O=chiburu9
Name: Resubmit, type=500, len=3, value=0
Name: ReviewerList, type=500, len=2, value=
Name: ReviewType, type=500, len=3, value=1
Name: ReviewWindow, type=500, len=3, value=0
Name: NotifyAfter, type=500, len=3, value=0
Name: readers, type=500, len=2, value=
Name: Subject, type=500, len=6, value=Test
Name: Categories, type=500, len=2, value=
Name: SubmitNow, type=500, len=3, value=0
Name: ReviewerLog, type=500, len=2, value=
Name: $UpdatedBy, type=501, len=32, value=CN=admin notes9/O=chiburu9

まとめ

今回は、論より証拠、実際のソースコードと実行結果で、NSFSearch関数の使い方を紹介してみました。準備段階として必要なこと、検索条件の指定方法、検索できた結果へのアクセスなど、基本的なことはお伝えできたかと思います。次回以降で、これのバリエーションを紹介できたらと思っています。

注意: コードの利用においてチブル・システムズは一切の責任を負いません。自己責任でご利用をお願いいたします。

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