C++ で国立国会図書館検索

(1/1)
C++で国立国会図書館検索
PHP で Windows用国立国会図書館検索プログラムをつくる」で作った検索プログラムを C++に移植する。検索結果を画面上で並べ替えたり、CSV ファイルに保存することができる。

目次

サンプル・プログラム

圧縮ファイルの内容
searchndl.msiインストーラ
bin/searchndl.exe実行プログラム本体
bin/libcrypto-1_1-x64.dll
bin/libcurl-x64.dll
bin/libssl-1_1-x64.dll
実行時に必要になるDLL
bin/etc/help.chmヘルプ・ファイル
source/googlenewswin.cppソース・プログラム
source/resource.hリソース・ヘッダ
source/resource.rcリソース・ファイル
source/application.icoアプリケーション・アイコン

国立国会図書館サーチ

国立国会図書館が用意する WebAPI「国立国会図書館サーチ(OpenSearch)」を利用する。API 仕様は「WebAPI:国立国会図書館サーチ(OpenSearch)」をご覧いただきたい。

使用ライブラリ

国立国会図書館サーチにアクセスするために、オープンソースのライブラリ Boost C++ライブラリcURL (カール)  および OpenSSL が必要になる。導入方法等については、「C++ 開発環境の準備」をご覧いただきたい。

リソースの準備

Eclipse を起動し、新規プロジェクト searchndl を用意する。
ResEdit を起動し、resource.rc を用意する。

Eclipse に戻り、ソース・プログラム "searchndl.cpp" を追加する。
リンカー・フラグを -mwindows -static -lstdc++ -lgcc -lwinpthread -lcurl -lssl -llzma -lz -lws2_32 "C:\pleiades\eclipse\mingw\x86_64-w64-mingw32\bin\libcurl-x64.dll" に設定する。

解説:ヘッダファイル等

0011: // 初期化処理 ======================================================
0012: #include <iostream>
0013: #include <stdio.h>
0014: #include <stdlib.h>
0015: #include <tchar.h>
0016: #include <time.h>
0017: #include <sstream>
0018: #include <string>
0019: #include <winsock2.h>
0020: #include <windows.h>
0021: #include <commctrl.h>
0022: #include <richedit.h>
0023: #include <curl/curl.h>
0024: #include <boost/property_tree/xml_parser.hpp>
0025: #include <boost/format.hpp>
0026: #include "resource.h"
0027: 
0028: using namespace std;
0029: using namespace boost;
0030: using namespace boost::property_tree;
0031: 
0032: #define APPNAME     "国立国会図書館検索"    //アプリケーション名
0033: #define APPVERSION "1.0"                    //バージョン
0034: #define APPYEAR     "2020"                    //作成年
0035: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-09-01.shtm"  // 参考サイト
0036: #define REFERENCE_NDL "https://iss.ndl.go.jp/information/api/"   //国立国会図書館APIについて
0037: 
0038: //char*バッファサイズ
0039: #define SIZE_BUFF     512
0040: 
0041: //現在のインターフェイス
0042: static HINSTANCE hInst;
0043: 
0044: //親ウィンドウ
0045: static HWND hParent;
0046: 
0047: //エラー・メッセージ格納用
0048: string ErrorMessage;
0049: 
0050: //ヘルプ・ファイル
0051: #define HELPFILE ".\\etc\\help.chm"
0052: 
0053: //デフォルト保存ファイル名
0054: #define SAVEFILE "searchndl.csv"

cURLOpenSSLBoost C++ライブラリを利用する関係で、include するヘッダ・ファイルが多くなっている。

その他の定数は、自由に変更できる。

解説:データ構造

0056: //書籍情報格納用クラス
0057: #define SIZE_BOOKS     500        //格納上限
0058: class _Books {
0059: public:
0060:     bool flag = false;
0061:     char title[SIZE_BUFF + 1] = {};           //書籍名
0062:     char author[SIZE_BUFF + 1] = {};      //作者
0063:     char publisher[SIZE_BUFF + 1] = {};       //出版社
0064:     char pubdate[SIZE_BUFF + 1] = {};     //出版日
0065:     char link[SIZE_BUFF + 1] = {};            //リンクURL
0066:     char isbn[SIZE_BUFF + 1] = {};            //ISBN番号
0067:     char ndc9[SIZE_BUFF + 1] = {};            //NDC番号
0068: };
0069: unique_ptr<_BooksBooks[SIZE_BOOKS] = {};
0070: 
0071: //ソート用構造体
0072: struct _stBooks {
0073:     char *title;     //書籍名
0074:     char *author;        //作者
0075:     char *publisher; //出版社
0076:     char *pubdate;       //出版日
0077:     char *isbn;          //ISBN番号
0078:     char *ndc9;          //NDC番号
0079:     char *link;          //リンクURL
0080: };
0081: vector<_stBooksVbooks;

WebAPI の応答(RSS ファイル)を PHP の連想配列のようにして使うために、「C++で Google ニュース検索」で作ったデータ構造を流用する。

解説:国立国会図書館サーチAPIのURLを取得する

0387: /**
0388:  * 国立国会図書館サーチAPI のURLを取得する
0389:  * @param string $query   タイトル(UTF-8;部分一致)
0390:  *                         またはISBN(10桁または13桁;完全一致または前方一致)
0391:  *                         【省略不可】
0392:  * @param string $creater 作成者(UTF-8;部分一致)
0393:  * @param string $from    開始出版年月日(YYYY-MM-DD)
0394:  * @param string $until   終了出版年月日(YYYY-MM-DD)
0395:  * @param int    $cnt     出力レコード上限値(省略時=50)
0396:  * @param int    $mediatype 資料種別(省略時=2:本)
0397:  * @return string WebAPI URL
0398: */
0399: void getURL_searchNDL(char *querychar *createrchar *urlsize_t sz) {
0400:     const string ndl = "https://iss.ndl.go.jp/api/opensearch?";
0401:     static char buff1[SIZE_BUFF + 1], buff2[SIZE_BUFF + 1];
0402: 
0403:     strncpy(buff1sjis_utf8((string)query).c_str(), SIZE_BUFF);
0404: 
0405:     CURL *curl = curl_easy_init();
0406:     strncpy(buff2curl_easy_escape(curl, (const char *)buff1strlen(buff1)), SIZE_BUFF);
0407: 
0408:     string ss = ndl + "title=" + (string)buff2 + "&mediatype=1";
0409:     strncpy(urlss.c_str(), SIZE_BUFF);
0410: 
0411:     curl_easy_cleanup(curl);
0412: }

国立国会図書館サーチ API の URL を生成する関数が getURL_searchNDL である。
前述のユーザー関数 sjis_utf8 を使って検索キーワードを UTF-8 に変換した後、cURL ライブラリにある curl_easy_escape 関数を使って URL エスケープする。なお、cURL ライブラリを使い始めるには curl_easy_init 関数を、終了するには curl_easy_cleanup 関数を呼び出す必要がある。

解説:検索結果を配列に格納する

0420: /**
0421:  * 検索結果を配列に格納する
0422:  * @param array $item 配列
0423:  * @param int   $i    カウンタ
0424:  * @return int 検索結果の件数
0425: */
0426: int searchBooks(char *title) {
0427:     //初期化
0428:     for (int i = 0; i < SIZE_BOOKSi++) {
0429:         Books[i].reset();
0430:         Books[i] = NULL;
0431:     }
0432: 
0433:     //cURLによる結果取得
0434:     CURL *curl;
0435:     CURLcode res = (CURLcode)0;
0436:     curl = curl_easy_init();
0437:     string chunk;
0438:     char url[SIZE_BUFF];
0439:     getURL_searchNDL(title, (char *)"", (char *)urlSIZE_BUFF);
0440: 
0441:     //cURLによる国立国会図書館サーチAPI
0442:     if (curl) {
0443:         curl_easy_setopt(curlCURLOPT_URLurl);
0444:         curl_easy_setopt(curlCURLOPT_SSL_VERIFYPEER, 0);
0445:         curl_easy_setopt(curlCURLOPT_WRITEFUNCTIONcallBackFunk);
0446:         curl_easy_setopt(curlCURLOPT_WRITEDATA, (string*)&chunk);
0447:         res = curl_easy_perform(curl);
0448:         curl_easy_cleanup(curl);
0449:     }
0450:     if (res != CURLE_OK) {
0451:         ErrorMessage = "国立国会図書館サーチAPIのエラー";
0452:         return (-1);
0453:     }
0454: 
0455:     //XML読み込み
0456:     std::stringstream ss;
0457:     ss << chunk;
0458:     ptree pt;
0459:     xml_parser::read_xml(sspt);
0460: 
0461:     //XML解釈
0462:     ptree tree;
0463:     struct tm dt;
0464:     char buff[SIZE_BUFF + 1];
0465:     int cnt = -1;
0466:     for (auto it : pt.get_child("rss.channel")) {
0467:         //item以外は読み飛ばし
0468:         if (it.first != "item")      continue;
0469:         cnt++;
0470:         Books[cnt] = make_unique<_Books>();
0471: 
0472:         //書籍名
0473:         if (optional<string>title = it.second.get_optional<string>("title")) {
0474:             strncpy(Books[cnt]->titleutf8_sjis((string)(title->c_str())).c_str(), SIZE_BUFF);
0475:         }
0476:         //巻数
0477:         if (optional<string>volume = it.second.get_optional<string>("dcndl:volume")) {
0478:             strncpy(buffutf8_sjis((string)(volume->c_str())).c_str(), SIZE_BUFF);
0479:             strcat(Books[cnt]->title, " ");
0480:             strcat(Books[cnt]->titlebuff);
0481:             strncat(Books[cnt]->title, (char *)"", SIZE_BUFF);
0482:         }
0483: 
0484:         //作者
0485:         if (optional<string>creator = it.second.get_optional<string>("dc:creator")) {
0486:             strncpy(Books[cnt]->authorutf8_sjis((string)(creator->c_str())).c_str(), SIZE_BUFF);
0487:         }
0488:         if (optional<string>author = it.second.get_optional<string>("author")) {
0489:             if (*Books[cnt]->author == 0) {
0490:                 strncpy(Books[cnt]->authorutf8_sjis((string)(author->c_str())).c_str(), SIZE_BUFF);
0491:             }
0492:         }
0493: 
0494:         //出版社
0495:         if (optional<string>publisher = it.second.get_optional<string>("dc:publisher")) {
0496:             strncpy(Books[cnt]->publisherutf8_sjis((string)(publisher->c_str())).c_str(), SIZE_BUFF);
0497:         }
0498: 
0499:         //出版日
0500:         if (optional<string>pubdate = it.second.get_optional<string>("pubDate")) {
0501:             {
0502:             istringstream ss(pubdate->c_str());
0503:             ss >> get_time(&dt, "%a, %d %b %Y");
0504:             strftime(buffSIZE_BUFF, "%Y-%m-%d", &dt);
0505:             }
0506:             strcpy(Books[cnt]->pubdatebuff);
0507:         }
0508:         if (optional<string>dcterms = it.second.get_optional<string>("dcterms:issued")) {
0509:             if (*Books[cnt]->pubdate == 0) {
0510:                 if (it.second.get_optional<string>("dcterms:issued.<xmlattr>.xsi:type") == (string)"dcterms:W3CDTF") {
0511:                     strncpy(Books[cnt]->pubdatedcterms->c_str(), SIZE_BUFF);
0512:                 }
0513:             }
0514:         }
0515: 
0516:         //リンクURL
0517:         if (optional<string>link = it.second.get_optional<string>("link")) {
0518:             strncpy(Books[cnt]->linklink->c_str(), SIZE_BUFF);
0519:         }
0520: 
0521:         //ISBN
0522:         if (optional<string>isbn = it.second.get_optional<string>("dc:identifier")) {
0523:             if (it.second.get_optional<string>("dc:identifier.<xmlattr>.xsi:type") == (string)"dcndl:ISBN") {
0524:                 strncpy(Books[cnt]->isbnisbn->c_str(), SIZE_BUFF);
0525:             }
0526:         }
0527: 
0528:         //NDC9
0529:         if (optional<string>ndc9 = it.second.get_optional<string>("dc:subject")) {
0530:             if (it.second.get_optional<string>("dc:subject.<xmlattr>.xsi:type") == (string)"dcndl:NDC9") {
0531:                 strncpy(Books[cnt]->ndc9ndc9->c_str(), SIZE_BUFF);
0532:             }
0533:         }
0534:     }
0535:     chunk.clear();
0536: 
0537:     return cnt;
0538: }

検索結果を _Books 配列へ格納するユーザー関数が searchBooks である。
まず、前述のユーザー関数 getURL_searchNDL と cURL ライブラリを使い、RSS ファイルを string 変数 chunk に読み込む。

次に、RSS は XML ファイルの一種であるから、「C++で CPU 情報を取得」で紹介した方法を使って XML解釈を進める。

解説:ニュース記事のソート

_Books 配列をソートする方法は、「C++で Google ニュース検索」で作ったソート方法を流用する。

解説:検索結果をCSVファイルに保存

_Books 配列を CSV ファイルに保存する方法は、「C++で Google ニュース検索」で作った保存方法を流用する。

解説:ニュース一覧作成

_Books 配列の内容は ListView クラスを使って一覧表示する。「C++で Google ニュース検索」で作った一覧表示方法を流用する。

解説:イベントハンドラ:メインウィンドウ

0831: /**
0832:  * イベントハンドラ:メインウィンドウ
0833:  * @param HWND    hDlg        ウィンドウハンドル
0834:  * @paramm UINT    uMsg        メッセージ情報
0835:  * @param WPARAM  wParam      追加メッセージ情報
0836:  * @paramL PARAM   lParam     追加メッセージ情報
0837:  * @return INT_PTR CALLBACK   コールバック
0838: */
0839: INT_PTR CALLBACK processMain(HWND hDlgUINT uMsgWPARAM wParamLPARAM lParam) {
0840:     HICON hIcon;
0841:     char *str;
0842:     string query;
0843:     LVITEM item;
0844:     LV_HITTESTINFO lvinfo;
0845:     NM_LISTVIEW *pNMLV;
0846:     TCHAR buff[SIZE_BUFF + 1];
0847:     static int subsort[100];
0848: 
0849:     switch(uMsg) {
0850:         //ダイアログ初期化
0851:         case WM_INITDIALOG:
0852:             hParent = hDlg;
0853:             hIcon = (HICON)LoadImage(hInstMAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
0854:             SendMessage(hParentWM_SETICONICON_SMALL, (LPARAM)hIcon);
0855:             makeListViewFrame(GetDlgItem(hDlgIDC_LISTVIEW_BOOKS));
0856:             break;
0857: 
0858:         //ボタン押下
0859:         case WM_COMMAND:
0860:              switch (LOWORD(wParam)) {
0861:                 //実行
0862:                 case IDM_EXEC:
0863:                 case IDC_BUTTON_SEARCH:
0864:                     ErrorMessage = "";
0865:                     //カーソルを砂時計に
0866:                     SetCursor(LoadCursor(NULLIDC_WAIT));
0867:                     query = getStrEditBox(hDlgIDC_EDIT_QUERY);
0868:                     //書籍検索
0869:                     searchBooks((char *)query.c_str());
0870:                     sortBooks(SORT_NONE);
0871:                     //一覧表示
0872:                     makeListView(GetDlgItem(hDlgIDC_LISTVIEW_BOOKS));
0873:                     setStrEditBox(hDlgIDC_TEXT_ERRORErrorMessage);
0874:                     break;
0875:                 //保存
0876:                 case IDC_BUTTON_SAVE:
0877:                 case IDM_SAVE:
0878:                     saveCSV();
0879:                     break;
0880:                 //ヘルプ
0881:                 case IDM_HELP:
0882:                     ShellExecute(hParent_T("open"), _T(HELPFILE), NULLNULLSW_RESTORE);
0883:                     break;
0884:                 //バージョン表示
0885:                 case IDM_VERSION:
0886:                     createHelp(hParentprocessHelp);
0887:                     break;
0888:                 //技術情報
0889:                 case IDM_PAHOO:
0890:                     ShellExecute(NULL_T("open"), _T(REFERENCE), NULLNULLSW_RESTORE);
0891:                     break;
0892:                 //国立国会図書館APIについて
0893:                 case IDM_NDL:
0894:                     ShellExecute(NULL_T("open"), _T(REFERENCE_NDL), NULLNULLSW_RESTORE);
0895:                     break;
0896:                 //コピー
0897:                 case IDM_COPY:
0898:                     setClipboardData(getStrEditBox(hParentIDC_EDIT_QUERY));
0899:                     break;
0900:                 //貼り付け
0901:                 case IDM_PASTE:
0902:                     str = getClipboardData();
0903:                     if (str != NULL) {
0904:                         setStrEditBox(hParentIDC_EDIT_QUERYstr);
0905:                     }
0906:                     break;
0907:                 //切り取り
0908:                 case IDM_DELETE:
0909:                     setStrEditBox(hParentIDC_EDIT_QUERY, "");
0910:                     break;
0911:                 //プログラム終了
0912:                 case IDM_QUIT:
0913:                     EndDialog(hParent, 0);
0914:                     return 0;
0915:                 default:
0916:                     return 1;
0917:             }
0918:             break;
0919: 
0920:         //通知
0921:         case WM_NOTIFY:
0922:             switch(((LPNMHDR)lParam)->idFrom) {
0923:                 case IDC_LISTVIEW_BOOKS:
0924:                     switch (((LPNMLISTVIEW)lParam)->hdr.code) {
0925:                         //一覧のラベルがクリック
0926:                         case LVN_COLUMNCLICK:
0927:                             pNMLV = (NM_LISTVIEW *)lParam;
0928:                             if (subsort[pNMLV->iSubItem] == SORT_ASC) {
0929:                                 subsort[pNMLV->iSubItem] = SORT_DEC;
0930:                             } else {
0931:                                 subsort[pNMLV->iSubItem] = SORT_ASC;
0932:                             }
0933:                             //書籍一覧の並べ替え
0934:                             sortBooks(pNMLV->iSubItem * 2 + subsort[pNMLV->iSubItem]);
0935:                             //一覧表示
0936:                             makeListView(GetDlgItem(hDlgIDC_LISTVIEW_BOOKS));
0937:                             break;
0938:                         //書籍一覧の1行を選択
0939:                         case LVN_ITEMCHANGED:
0940:                         //書籍一覧の1行をダブルクリック
0941:                         //case NM_DBLCLK:
0942:                             GetCursorPos((LPPOINT)&lvinfo.pt);
0943:                             ScreenToClient(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &lvinfo.pt);
0944:                             ListView_HitTest(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &lvinfo);
0945:                             if ((lvinfo.flags & LVHT_ONITEM) != 0) {
0946:                                 item.mask = TVIF_HANDLE | TVIF_TEXT;
0947:                                 item.iItem = lvinfo.iItem;
0948:                                 item.iSubItem = COL_LINK;
0949:                                 item.pszText = buff;
0950:                                 item.cchTextMax = SIZE_BUFF;
0951:                                 ListView_GetItem(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &item);
0952:                                 //ブラウザ起動
0953:                                 ShellExecute(hParent_T("open"), _T(buff), NULLNULLSW_RESTORE);
0954:                             }
0955:                             break;
0956:                         default:
0957:                             break;
0958:                     }
0959:                     break;
0960:                 default:
0961:                     break;
0962:             }
0963:             break;
0964: 
0965:         //プログラム終了
0966:         case WM_CLOSE:
0967:             EndDialog(hParent, 0);
0968:             return 0;
0969:     }
0970:     return 0;
0971: }

メインウィンドウに関わるイベント・ハンドラが processMain である。いくつかのボタンとメニューは共通の機能を担っている。
「C++で Google ニュース検索」で作ったイベントハンドラ:メインウィンドウを流用している。

解説:Windowsメインプログラム

0974: /**
0975:  * Windowsメインプログラム
0976:  * @param HINSTANCE hInstance
0977:  * @paramm HINSTANCE hPrevInstance
0978:  * @param LPSTR lpCmdLine
0979:  * @paramL int nShowCmd
0980:  * @return int リターンコード
0981: */
0982: int WINAPI WinMain(HINSTANCE hInstanceHINSTANCE hPrevInstanceLPSTR lpCmdLineint nShowCmd) {
0983:     LoadLibrary("RICHED20.DLL");
0984:     hInst = hInstance;
0985:     DialogBox(hInstanceMAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)processMain);
0986:     return 0;
0987: }

検索キーワードを入力するエディットボックスは、コントロールキーが使えるように RichEdit にしてある。プログラムの冒頭で "RICHED20.DLL" を読み込んでやる必要がある。

参考サイト

(この項おわり)
header