目次
サンプル・プログラム
searchndl.msi | インストーラ |
bin/searchndl.exe | 実行プログラム本体 |
bin/libcurl-x64.dll | 実行時に必要になるDLL |
bin/etc/help.chm | ヘルプ・ファイル |
sour/searchndl.cpp | ソース・プログラム |
sour/resource.h | リソース・ヘッダ |
sour/resource.rc | リソース・ファイル |
sour/application.ico | アプリケーション・アイコン |
sour/makefile | ビルド |
バージョン | 更新日 | 内容 |
---|---|---|
1.3.2 | 2024/11/16 | 使用ライブラリ更新 |
1.3.1 | 2024/07/27 | 使用ライブラリ更新 |
1.3.0 | 2024/03/09 | API 1.1版に対応,使用ライブラリ更新 |
1.2.4 | 2023/10/14 | 使用ライブラリ更新 |
1.2.3 | 2023/06/10 | 使用ライブラリ更新,ISBN検索できない不具合修正 |
国立国会図書館サーチ
使用ライブラリ
リソースの準備
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" に設定する。
MSYS2 コマンドラインからビルドするのであれば、"makefile" を利用してほしい。
解説:ヘッダファイル等
searchndl.cpp
11: // 初期化処理 ======================================================
12: #include <iostream>
13: #include <stdio.h>
14: #include <stdlib.h>
15: #include <tchar.h>
16: #include <time.h>
17: #include <sstream>
18: #include <regex>
19: #include <string>
20: #include <winsock2.h>
21: #include <windows.h>
22: #include <shlobj.h>
23: #include <commctrl.h>
24: #include <richedit.h>
25: #include <curl/curl.h>
26: #include <boost/property_tree/xml_parser.hpp>
27: #include <boost/format.hpp>
28: #include "resource.h"
29:
30: using namespace std;
31: using namespace boost;
32: using namespace boost::property_tree;
33:
34: #define MAKER "pahoo.org" //作成者
35: #define APPNAME "gsearchndl" //アプリケーション名
36: #define APPNAMEJP "国立国会図書館検索" //アプリケーション名(日本語)
37: #define APPVERSION "1.3.2" //バージョン
38: #define APPYEAR "2020-2024" //作成年
39: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-09-01.shtm" // 参考サイト
40: #define REFERENCE_NDL "https://iss.ndl.go.jp/information/api/" //国立国会図書館APIについて
41:
42: //国立国会図書館API
43: #define URL_NDLAPI "https://ndlsearch.ndl.go.jp/api/opensearch?";
44:
45: //char*バッファサイズ
46: #define SIZE_BUFF 5120
47:
48: //現在のインターフェイス
49: static HINSTANCE hInst;
50:
51: //アプリケーション・ウィンドウ
52: HWND hParent;
53:
54: //アプリケーション・ウィンドウ位置
55: unsigned hParent_X, hParent_Y;
56:
57: //エラー・メッセージ格納用
58: string ErrorMessage;
59:
60: //検索キー格納用
61: string Query;
62:
63: //ヘルプ・ファイル
64: #define HELPFILE ".\\etc\\help.chm"
65:
66: //デフォルト保存ファイル名
67: #define SAVEFILE "searchndl.csv"
68:
69: //UserAgent
70: string UserAgent;
解説:データ構造
searchndl.cpp
72: //書籍情報格納用クラス
73: #define SIZE_BOOKS 500 //格納上限
74: class _Books {
75: public:
76: bool flag = false;
77: char title[SIZE_BUFF + 1] = {}; //書籍名
78: char author[SIZE_BUFF + 1] = {}; //作者
79: char publisher[SIZE_BUFF + 1] = {}; //出版社
80: char pubdate[SIZE_BUFF + 1] = {}; //出版日
81: char link[SIZE_BUFF + 1] = {}; //リンクURL
82: char isbn[SIZE_BUFF + 1] = {}; //ISBN番号
83: char ndc9[SIZE_BUFF + 1] = {}; //NDC番号
84: };
85: unique_ptr<_Books> Books[SIZE_BOOKS] = {};
86:
87: //ソート用構造体
88: struct _stBooks {
89: char *title; //書籍名
90: char *author; //作者
91: char *publisher; //出版社
92: char *pubdate; //出版日
93: char *isbn; //ISBN番号
94: char *ndc9; //NDC番号
95: char *link; //リンクURL
96: };
97: vector<_stBooks> Vbooks;
解説:国立国会図書館サーチAPIのURLを取得する
searchndl.cpp
611: /**
612: * 国立国会図書館サーチAPI のURLを取得する
613: * @param char* query タイトル(SJIS;部分一致)
614: * またはISBN(10桁または13桁;完全一致または前方一致)
615: * 【省略不可】
616: * @param char* creater 作成者(SJIS;部分一致)
617: * @param char* url URLを格納
618: * @param size_t sz urlの最大長
619: * @return なし
620: */
621: void getURL_searchNDL(char *query, char *creater, char *url, size_t sz) {
622: const string ndl = URL_NDLAPI;
623: static char buff1[SIZE_BUFF + 1], buff2[SIZE_BUFF + 1];
624:
625: strncpy(buff1, sjis_utf8((string)query).c_str(), SIZE_BUFF);
626:
627: CURL *curl = curl_easy_init();
628: strncpy(buff2, curl_easy_escape(curl, (const char *)buff1, strlen(buff1)), SIZE_BUFF);
629:
630: //ISBNパターン
631: regex re("^[0-9]+$");
632: smatch mt;
633: string ss;
634: ss = (string)buff2;
635: if (regex_match(ss, mt, re)) {
636: ss = ndl + "isbn=" + (string)buff2;
637: //書名
638: } else {
639: ss = ndl + "title=" + (string)buff2;
640: }
641: strncpy(url, ss.c_str(), SIZE_BUFF);
642:
643: curl_easy_cleanup(curl);
644: }
前述のユーザー関数 sjis_utf8 を使って検索キーワードをUTF-8に変換した後、cURLライブラリにある curl_easy_escape 関数を使ってURLエスケープする。なお、cURLライブラリを使い始めるには curl_easy_init 関数を、終了するには curl_easy_cleanup 関数を呼び出す必要がある。
解説:検索結果を配列に格納する
searchndl.cpp
652: /**
653: * 検索結果を配列に格納する
654: * @param array $item 配列
655: * @param int $i カウンタ
656: * @return int 検索結果の件数
657: */
658: int searchBooks(char *title) {
659: //初期化
660: for (int i = 0; i < SIZE_BOOKS; i++) {
661: Books[i].reset();
662: Books[i] = NULL;
663: }
664:
665: //cURLによる結果取得
666: CURL *curl;
667: CURLcode res = (CURLcode)0;
668: curl = curl_easy_init();
669: string chunk;
670: char url[SIZE_BUFF];
671: getURL_searchNDL(title, (char *)"", (char *)url, SIZE_BUFF);
672:
673: //cURLによる国立国会図書館サーチAPI
674: if (curl) {
675: curl_easy_setopt(curl, CURLOPT_URL, url);
676: curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
677: curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callBackFunk);
678: curl_easy_setopt(curl, CURLOPT_WRITEDATA, (string*)&chunk);
679: res = curl_easy_perform(curl);
680: curl_easy_cleanup(curl);
681: }
682: if (res != CURLE_OK) {
683: ErrorMessage = "国立国会図書館サーチAPIのエラー";
684: return (-1);
685: }
686:
687: //XML読み込み
688: std::stringstream ss;
689: ss << chunk;
690: ptree pt;
691: xml_parser::read_xml(ss, pt);
692:
693: //XML解釈
694: ptree tree;
695: struct tm dt;
696: char buff[SIZE_BUFF + 1];
697: int cnt = -1;
698: for (auto it : pt.get_child("rss.channel")) {
699: //item以外は読み飛ばし
700: if (it.first != "item") continue;
701: cnt++;
702: Books[cnt] = make_unique<_Books>();
703:
704: //書籍名
705: if (optional<string>title = it.second.get_optional<string>("title")) {
706: strncpy(Books[cnt]->title, utf8_sjis((string)(title->c_str())).c_str(), SIZE_BUFF);
707: }
708: //巻数
709: if (optional<string>volume = it.second.get_optional<string>("dcndl:volume")) {
710: strncpy(buff, utf8_sjis((string)(volume->c_str())).c_str(), SIZE_BUFF);
711: strcat(Books[cnt]->title, " ");
712: strcat(Books[cnt]->title, buff);
713: strncat(Books[cnt]->title, (char *)"巻", SIZE_BUFF);
714: }
715:
716: //作者
717: if (optional<string>creator = it.second.get_optional<string>("dc:creator")) {
718: strncpy(Books[cnt]->author, utf8_sjis((string)(creator->c_str())).c_str(), SIZE_BUFF);
719: }
720: if (optional<string>author = it.second.get_optional<string>("author")) {
721: if (*Books[cnt]->author == 0) {
722: strncpy(Books[cnt]->author, utf8_sjis((string)(author->c_str())).c_str(), SIZE_BUFF);
723: }
724: }
725:
726: //出版社
727: if (optional<string>publisher = it.second.get_optional<string>("dc:publisher")) {
728: strncpy(Books[cnt]->publisher, utf8_sjis((string)(publisher->c_str())).c_str(), SIZE_BUFF);
729: }
730:
731: //出版日
732: if (optional<string>pubdate = it.second.get_optional<string>("pubDate")) {
733: {
734: istringstream ss(pubdate->c_str());
735: ss >> get_time(&dt, "%a, %d %b %Y");
736: strftime(buff, SIZE_BUFF, "%Y-%m-%d", &dt);
737: }
738: strcpy(Books[cnt]->pubdate, buff);
739: }
740: if (optional<string>dcterms = it.second.get_optional<string>("dcterms:issued")) {
741: if (*Books[cnt]->pubdate == 0) {
742: if (it.second.get_optional<string>("dcterms:issued.<xmlattr>.xsi:type") == (string)"dcterms:W3CDTF") {
743: strncpy(Books[cnt]->pubdate, dcterms->c_str(), SIZE_BUFF);
744: }
745: }
746: }
747:
748: //リンクURL
749: if (optional<string>link = it.second.get_optional<string>("link")) {
750: strncpy(Books[cnt]->link, link->c_str(), SIZE_BUFF);
751: }
752:
753: //ISBN
754: if (optional<string>isbn = it.second.get_optional<string>("dc:identifier")) {
755: if (it.second.get_optional<string>("dc:identifier.<xmlattr>.xsi:type") == (string)"dcndl:ISBN") {
756: strncpy(Books[cnt]->isbn, isbn->c_str(), SIZE_BUFF);
757: }
758: }
759:
760: //NDC9
761: if (optional<string>ndc9 = it.second.get_optional<string>("dc:subject")) {
762: if (it.second.get_optional<string>("dc:subject.<xmlattr>.xsi:type") == (string)"dcndl:NDC9") {
763: strncpy(Books[cnt]->ndc9, ndc9->c_str(), SIZE_BUFF);
764: }
765: }
766: }
767: chunk.clear();
768:
769: return cnt;
770: }
まず、前述のユーザー関数 getURL_searchNDL とcURLライブラリを使い、RSSファイルをstring変数 chunk に読み込む。
次に、RSSはXMLファイルの一種であるから、「C++でCPU情報を取得」で紹介した方法を使ってXML解釈を進める。
解説:ニュース記事のソート
解説:検索結果をCSVファイルに保存
解説:イベントハンドラ:メインウィンドウ
searchndl.cpp
1064: /**
1065: * イベントハンドラ:メインウィンドウ
1066: * @param HWND hDlg 親ウィンドウ・ハンドラ
1067: * @paramm UINT uMsg メッセージ識別子
1068: * @param WPARAM wParam メッセージの最初のパラメータ
1069: * @paramL PARAM lParam メッセージの2番目のパラメータ
1070: * @return INT_PTR CALLBACK TRUE:メッセージ処理完了/FALSE:未完了
1071: */
1072: INT_PTR CALLBACK processMain(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
1073: HICON hIcon;
1074: char *str;
1075: LVITEM item;
1076: LV_HITTESTINFO lvinfo;
1077: NM_LISTVIEW *pNMLV;
1078: TCHAR buff[SIZE_BUFF + 1];
1079: static int subsort[100];
1080:
1081: switch(uMsg) {
1082: //ダイアログ初期化
1083: case WM_INITDIALOG:
1084: hParent = hDlg;
1085: hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
1086: SendMessage(hParent, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
1087: ErrorMessage = "";
1088: //オプション読み込み
1089: loadParameter();
1090: setStrEditBox(hDlg, IDC_EDIT_QUERY, Query);
1091: //アプリケーション・ウィンドウ移動
1092: SetWindowPos(hParent, NULL, hParent_X, hParent_Y, 0, 0, (SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER));
1093: makeListViewFrame(GetDlgItem(hDlg, IDC_LISTVIEW_BOOKS));
1094: break;
1095:
1096: //ボタン押下
1097: case WM_COMMAND:
1098: switch (LOWORD(wParam)) {
1099: //実行
1100: case IDM_EXEC:
1101: case IDC_BUTTON_SEARCH:
1102: ErrorMessage = "";
1103: //カーソルを砂時計に
1104: SetCursor(LoadCursor(NULL, IDC_WAIT));
1105: Query = getStrEditBox(hDlg, IDC_EDIT_QUERY);
1106: //書籍検索
1107: searchBooks((char *)Query.c_str());
1108: sortBooks(SORT_NONE);
1109: //一覧表示
1110: makeListView(GetDlgItem(hDlg, IDC_LISTVIEW_BOOKS));
1111: setStrEditBox(hDlg, IDC_TEXT_ERROR, ErrorMessage);
1112: break;
1113: //保存
1114: case IDC_BUTTON_SAVE:
1115: case IDM_SAVE:
1116: saveCSV();
1117: break;
1118: //設定クリア+アプリ終了
1119: case IDM_CLEAR_PARAMETER:
1120: delParameter();
1121: EndDialog(hParent, 0);
1122: return 0;
1123: break;
1124: //ヘルプ
1125: case IDM_HELP:
1126: ShellExecute(hParent, _T("open"), _T(HELPFILE), NULL, NULL, SW_RESTORE);
1127: break;
1128: //バージョン表示
1129: case IDM_VERSION:
1130: createHelp(hParent, processHelp);
1131: break;
1132: //技術情報
1133: case IDM_PAHOO:
1134: ShellExecute(NULL, _T("open"), _T(REFERENCE), NULL, NULL, SW_RESTORE);
1135: break;
1136: //国立国会図書館APIについて
1137: case IDM_NDL:
1138: ShellExecute(NULL, _T("open"), _T(REFERENCE_NDL), NULL, NULL, SW_RESTORE);
1139: break;
1140: //コピー
1141: case IDM_COPY:
1142: setClipboardData(getStrEditBox(hParent, IDC_EDIT_QUERY));
1143: break;
1144: //貼り付け
1145: case IDM_PASTE:
1146: str = getClipboardData();
1147: if (str != NULL) {
1148: setStrEditBox(hParent, IDC_EDIT_QUERY, str);
1149: }
1150: break;
1151: //切り取り
1152: case IDM_DELETE:
1153: setStrEditBox(hParent, IDC_EDIT_QUERY, "");
1154: break;
1155: //プログラム終了
1156: case IDM_QUIT:
1157: //オプション保存
1158: saveParameter();
1159: EndDialog(hParent, 0);
1160: return FALSE;
1161: default:
1162: return 1;
1163: }
1164: break;
1165:
1166: //通知
1167: case WM_NOTIFY:
1168: switch(((LPNMHDR)lParam)->idFrom) {
1169: case IDC_LISTVIEW_BOOKS:
1170: switch (((LPNMLISTVIEW)lParam)->hdr.code) {
1171: //一覧のラベルがクリック
1172: case LVN_COLUMNCLICK:
1173: pNMLV = (NM_LISTVIEW *)lParam;
1174: if (subsort[pNMLV->iSubItem] == SORT_ASC) {
1175: subsort[pNMLV->iSubItem] = SORT_DEC;
1176: } else {
1177: subsort[pNMLV->iSubItem] = SORT_ASC;
1178: }
1179: //書籍一覧の並べ替え
1180: sortBooks(pNMLV->iSubItem * 2 + subsort[pNMLV->iSubItem]);
1181: //一覧表示
1182: makeListView(GetDlgItem(hDlg, IDC_LISTVIEW_BOOKS));
1183: break;
1184: //書籍一覧の1行を選択
1185: case LVN_ITEMCHANGED:
1186: //書籍一覧の1行をダブルクリック
1187: //case NM_DBLCLK:
1188: GetCursorPos((LPPOINT)&lvinfo.pt);
1189: ScreenToClient(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &lvinfo.pt);
1190: ListView_HitTest(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &lvinfo);
1191: if ((lvinfo.flags & LVHT_ONITEM) != 0) {
1192: item.mask = TVIF_HANDLE | TVIF_TEXT;
1193: item.iItem = lvinfo.iItem;
1194: item.iSubItem = COL_LINK;
1195: item.pszText = buff;
1196: item.cchTextMax = SIZE_BUFF;
1197: ListView_GetItem(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &item);
1198: //ブラウザ起動
1199: ShellExecute(hParent, _T("open"), _T(buff), NULL, NULL, SW_RESTORE);
1200: }
1201: break;
1202: default:
1203: break;
1204: }
1205: break;
1206: default:
1207: break;
1208: }
1209: break;
1210:
1211: //プログラム終了
1212: case WM_CLOSE:
1213: //オプション保存
1214: saveParameter();
1215: EndDialog(hParent, 0);
1216: return FALSE;
1217: }
1218: return FALSE;
1219: }
「C++でGoogleニュース検索」で作ったイベントハンドラ:メインウィンドウを流用している。
解説:Windowsメインプログラム
searchndl.cpp
1222: /**
1223: * Windowsメインプログラム
1224: * @param HINSTANCE hInstance インスタンスハンドル
1225: * @paramm HINSTANCE hPrevInstance 未使用(常にNULL):Win16時代の名残
1226: * @param LPSTR lpCmdLine コマンドライン引数
1227: * @paramL int nShowCmd ウィンドウの表示方法
1228: * @return int リターンコード
1229: */
1230: int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
1231: LoadLibrary("RICHED20.DLL");
1232:
1233: //UserAgent生成
1234: static OSVERSIONINFOEX os;
1235: GetVersion2(&os);
1236: UserAgent = (string)"Mozilla/5.0 (" + APPNAME + "/"
1237: + APPVERSION + "/" + MAKER
1238: + ", Windows NT " + to_string(os.dwMajorVersion) + "."
1239: + to_string(os.dwMinorVersion) + ")";
1240:
1241: hInst = hInstance;
1242: DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)processMain);
1243: return 0;
1244: }
参考サイト
- PHPで国立国会図書館を検索:ぱふぅ家のホームページ
- Visual C++における文字コード変換:C++と色々
- リストビュー:インコのWindows SDK
(2024年11月16日)使用ライブラリ更新
(2024年7月27日)使用ライブラリ更新
(2024年3月9日)API 1.1版に対応,使用ライブラリ更新