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関数の使い方を紹介してみました。準備段階として必要なこと、検索条件の指定方法、検索できた結果へのアクセスなど、基本的なことはお伝えできたかと思います。次回以降で、これのバリエーションを紹介できたらと思っています。
注意: コードの利用においてチブル・システムズは一切の責任を負いません。自己責任でご利用をお願いいたします。