サンプル・プログラム
daytodaywin.msi | インストーラ |
bin/daytodaywin.exe | 実行プログラム本体 |
bin/libcurl-x64.dll | 実行時に必要になるDLL |
bin/etc/help.chm | ヘルプ・ファイル |
sour/daytodaywin.cpp | ソース・プログラム |
sour/resource.h | リソース・ヘッダ |
sour/resource.rc | リソース・ファイル |
sour/application.ico | アプリケーション・アイコン |
sour/mystrings.cpp | 汎用文字列処理関数など(ソース) |
sour/mystrings.h | 汎用文字列処理関数など(ヘッダ) |
sour/makefile | ビルド |
バージョン | 更新日 | 内容 |
---|---|---|
1.4.1 | 2024/03/16 | 使用ライブラリ更新 |
1.4.0 | 2023/11/04 | 注釈番号を除去,使用ライブラリ更新 |
1.3.4 | 2023/06/18 | 使用ライブラリを更新 |
1.3.3 | 2023/03/11 | 使用ライブラリを更新 |
1.3.2 | 2023/01/08 | getDayToday() -- の取得パターンを変更した |
使用ライブラリ
リソースの準備
ResEdit を起動し、resource.rc を用意する。
Eclipse に戻り、ソース・プログラム "daytodaywin.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" を利用してほしい。
解説:ヘッダファイル等
11: // 初期化処理 ======================================================
12: #include <iostream>
13: #include <fstream>
14: #include <stdio.h>
15: #include <stdlib.h>
16: #include <tchar.h>
17: #include <time.h>
18: #include <sstream>
19: #include <string>
20: #include <iterator>
21: #include <list>
22: #include <regex>
23: #include <locale>
24: #include <winsock2.h>
25: #include <windows.h>
26: #include <commctrl.h>
27: #include <richedit.h>
28: #include <curl/curl.h>
29: #include <boost/format.hpp>
30: #include <boost/property_tree/xml_parser.hpp>
31: #include "mystrings.h"
32: #include "resource.h"
33:
34: using namespace std;
35: using namespace boost;
36: using namespace boost::property_tree;
37:
38: #define MAKER "pahoo.org" //作成者
39: #define APPNAME "daytodaywin" //アプリケーション名
40: #define APPNAMEJP "今日は何の日" //アプリケーション名(日本語)
41: #define APPVERSION "1.4.1" //バージョン
42: #define APPYEAR "2020-24" //作成年・更新日
43: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-11-01.shtm" // 参考サイト
44:
45: //char*バッファサイズ
46: #define SIZE_BUFF 5120
47:
48: //ListViewItemの最大文字長:変更不可
49: #define MAX_LISTVIEWITEM 258
50:
51: //リクエストURL(日本記念日協会)(※変更不可)
52: #define KINENBI_URL "https://www.kinenbi.gr.jp/"
53:
54: //記念日識別値(※変更不可)
55: #define KINENBI_YEAR (-99999)
56:
57: //現在のインターフェイス
58: static HINSTANCE hInst;
59:
60: //アプリケーション・ウィンドウ
61: HWND hParent;
62:
63: //アプリケーション・ウィンドウ位置
64: unsigned hParent_X, hParent_Y;
65:
66: //エラー・メッセージ格納用
67: string ErrorMessage;
68:
69: //ヘルプ・ファイル
70: #define HELPFILE ".\\etc\\help.chm"
71:
72: //デフォルト保存ファイル名
73: #define SAVEFILE "daytodaywin.csv"
74:
75: //UserAgent
76: string UserAgent;
その他、とくに注意記載が無い限り、定数は自由に変更できる。
解説:データ構造
78: //出来事格納用クラス
79: #define SIZE_EVENTS 500 //格納上限
80: class _Events {
81: public:
82: wstring category = L""; //区分
83: int year = 0; //西暦年
84: wstring era = L""; //元号
85: wstring event = L""; //出来事
86: wstring plus = L""; //追加情報
87: };
88: unique_ptr<_Events> Events[SIZE_EVENTS] = {};
89:
90: //ソート用構造体
91: struct _stEvents {
92: wchar_t* category; //区分
93: int *year; //西暦年
94: wchar_t* event; //出来事
95: size_t id; //Eventsの添字
96: };
97: vector<_stEvents> Vevents;
コンテンツの必要な部分をクラス _Events に収め、このクラスの実体であるオブジェクトへのポインタに unique_p 配列tr を利用する。出来事の数が未知なので、オブジェクトは必要に応じて動的に確保する(つまり、配列の要素数は不定)。
データ要素としては、日本語テキストを格納する要素にはワイド文字列型(wstring型)を割り当てた。西暦年は、intg型で格納しておく。
クラス _Events のメンバのうち、ソート可能な項目(文字列)へのポインタを vectorクラス へコピーする背景については、「C++ で Googleニュース検索」で述べたとおりである。
解説:Wikipedia URL取得
320: /**
321: * Wikipedia URL取得
322: * @param int month, day 月,日
323: * @param char* url URLを格納
324: * @param size_t sz urlの最大長
325: * @return なし
326: */
327: void getURL_Wikipedia(int month, int day, char *url, size_t sz) {
328: const string wiki = "https://ja.wikipedia.org/wiki/";
329: static char buff1[SIZE_BUFF + 1], buff2[SIZE_BUFF + 1];
330:
331: snprintf(buff1, SIZE_BUFF, "%d月%d日", month, day);
332: string s1 = sjis_utf8(buff1);
333:
334: CURL *curl = curl_easy_init();
335: strncpy(buff2, curl_easy_escape(curl, (const char *)s1.c_str(), strlen(s1.c_str())), SIZE_BUFF);
336:
337:
338: string ss = wiki + (string)buff2;
339: strncpy(url, ss.c_str(), SIZE_BUFF);
340:
341: curl_easy_cleanup(curl);
342: }
解説:今日は何の日 取得
354: /**
355: * 今日は何の日 取得
356: * @param int month, day 月,日
357: * @param bool lfn 脚注を残す(省略時=FALSE)
358: * @return int 情報件数
359: */
360: int getDayToday(int month, int day, bool lfn=FALSE) {
361: //初期化
362: for (int i = 0; i < SIZE_EVENTS; i++) {
363: Events[i].reset();
364: Events[i] = NULL;
365: }
366:
367: //Wikipediaコンテンツ取得
368: char url[SIZE_BUFF];
369: string contents = "";
370: getURL_Wikipedia(month, day, (char *)url, SIZE_BUFF);
371: bool res = readWebContents(url, UserAgent, &contents);
372: if (res == FALSE) {
373: ErrorMessage = "Wikipediaにアクセスできません.";
374: return (-1);
375: }
376:
377: //コンテンツの解釈
378: setlocale(LC_ALL, "Japanese");
379: int cnt = -1;
380: stringstream ss;
381: string ss0;
382: wstring mode = L"";
383: wstring ws, ws2;
384: wstringstream wss;
385: static wsmatch mt1, mt2, mt3;
386: static smatch mt4;
387: wregex re1(_SW("<span[\\s\\S]+id=\"できごと\""));
388: wregex re2(_SW("<span[\\s\\S]+id=\"誕生日\""));
389: wregex re3(_SW("<span[\\s\\S]+id=\"忌日\""));
390: wregex re5(_SW("<li>[^\\>]+([0-9]+(年|世紀).+)<\\/li>"));
391: wregex re6(_SW("([^0-9]*)([0-9]+)年(\\s*[\((].[^\\))]+[)\\)].)?[ \\-]+(.+)")); //年 - 出来事
392: wregex re7(_SW("([^0-9]*)([0-9]+)年(\\s*[\\((].[^\\))]+[)\\)].)?[ \\-]+([^、]+)、?([^(\\(]+)*([^0-9]*)([0-9]*)")); //年 - 名前、職業(生没年)
393: wregex re8(_SW("<span[\\s\\S]+id=\"フィクションのできごと\">"));
394: wregex re9(_SW("^\\([\\*\\+].")); //年 - 名前、職業(生没年)
395: wregex re11(_SW("紀元前"));
396: wregex re99(_SW("<span[\\s\\S]+class=\"vector-menu-heading-label\">他言語版")); //1行読み込み打ち切り
397:
398: ss << contents;
399: while(ss && getline(ss, ss0)) {
400: //1行をwstring変換
401: ws = _UW(ss0);
402: //4000文字以上なら処理しない
403: if (ws.length() > 4000) {
404: continue;
405: }
406: //マッチング処理
407: if (regex_search(ws, mt1, re99)) {
408: break;
409: } else if (regex_search(ws, mt1, re1)) {
410: mode = _SW("出来事");
411: } else if (regex_search(ws, mt1, re8)) {
412: mode = _SW("架空");
413: } else if (regex_search(ws, mt1, re2)) {
414: mode = _SW("誕生");
415: } else if (regex_search(ws, mt1, re3)) {
416: mode = _SW("死亡");
417: } else if ((mode.length() != 0) && (regex_search(ws, mt1, re5) == TRUE)) {
418: //HTMLタグ除去
419: ws = wstrip_tags(ws);
420: //注釈番号除去
421: ws = wstripAnnotations(ws);
422: //出来事
423: if ((mode == _SW("出来事")) && (regex_search(ws, mt2, re6) == TRUE)) {
424: cnt++;
425: Events[cnt] = make_unique<_Events>();
426: Events[cnt]->category = mode;
427: if (mt2[1].str().length() == 0) {
428: Events[cnt]->year = atoi(_WS(mt2[2].str()).c_str());
429: } else {
430: Events[cnt]->year = 0 - atoi(_WS(mt2[2].str()).c_str());
431: }
432: Events[cnt]->era = mt2[3].str();
433: Events[cnt]->event = mt2[4].str();
434:
435: //誕生・死亡
436: } else if (regex_search(ws, mt2, re7)) {
437: cnt++;
438: Events[cnt] = make_unique<_Events>();
439: Events[cnt]->category = mode;
440: if (mt2[1].str().length() == 0) {
441: Events[cnt]->year = atoi(_WS(mt2[2].str()).c_str());
442: } else {
443: Events[cnt]->year = 0 - atoi(_WS(mt2[2].str()).c_str());
444: }
445: Events[cnt]->era = mt2[3].str();
446: ws2 = mt2[6].str();
447: if (regex_search(ws2, mt3, re11) == TRUE) {
448: Events[cnt]->plus = L"-" + mt2[7].str();
449: } else if (regex_search(ws2, mt3, re9) == TRUE) {
450: Events[cnt]->plus = mt2[7].str();
451: } else {
452: Events[cnt]->plus = L"";
453: }
454: wss << mt2[4].str() << _SW("(") << mt2[5].str();
455: if (mode == _SW("誕生")) {
456: wss << _SW(")誕生");
457: if (Events[cnt]->plus.length() != 0) {
458: wss << _SW("(〜") << Events[cnt]->plus << _SW("年)");
459: }
460: } else {
461: wss << _SW(")死去");
462: if (Events[cnt]->plus.length() != 0) {
463: wss << _SW("(") << Events[cnt]->plus << _SW("年〜")<< Events[cnt]->year << _SW("年)");
464: }
465: }
466: Events[cnt]->event = wss.str();
467: wss.str(L"");
468: }
469: }
470: }
471: return cnt;
472: }
次に、この内容をスクレイピングしていくのだが、今回は、ワイド文字列に対する正規表現を使うことにした。ソースはSJISで書いているので、ユーザーマクロ関数 _SW を使ってワイド文字列に変換し、これを使って正規表現によるパターンマッチングを行う。ちなみに、C++に正規表現が正式導入されたのはC++11からで、Boost C++ライブラリをベースにしている。
パターン名 | 内容 |
---|---|
re1 | できごと |
re2 | 誕生日 |
re3 | 忌日 |
re5 | 西暦年 |
re6 | 西暦年(出来事) |
re7 | 年 - 名前、職業(生没年) |
re8 | フィクションのできごと |
re9 | 年 - 名前、職業(生没年) |
re11 | 紀元前の識別 |
re99 | 1行読み込み打ち切りパターン |
解説:記念日を取得
474: /**
475: * 指定月日の記念日を取得する
476: * @param int month, day 月日
477: * @param int cnt 情報を追加する配列の最初の番号
478: * @return int 情報の総数
479: */
480: int getAnniversary(int month, int day, int cnt=0) {
481: //コンテンツ読み込み
482: static char post[SIZE_BUFF + 1];
483: snprintf(post, SIZE_BUFF, "MD=%d&M=%d&D=%d", month, month, day);
484: string contents = "";
485: bool res = readWebContents(KINENBI_URL, UserAgent, &contents, post);
486:
487: if (res == FALSE) {
488: ErrorMessage = "日本記念日協会のサイトにアクセスできません.";
489: return (-1);
490: }
491:
492: //スクレイピング
493: wregex re1(_SW("<a\\s+class=\"winDetail\"\\s+href=\"([^\"]+)\"><font[^>]+>([^<]+)</font>"), wregex::icase);
494: static wsmatch mt1;
495:
496: stringstream ss;
497: wstring mode = _SW("記念日");
498: string ss0;
499: wstring ws;
500: ss << contents;
501: while(ss && getline(ss, ss0)) {
502: //1行をwstring変換
503: ws = _UW(ss0);
504: if (regex_search(ws, mt1, re1)) {
505: Events[cnt] = make_unique<_Events>();
506: Events[cnt]->category = mode;
507: Events[cnt]->event = mt1[2].str();
508: Events[cnt]->year = KINENBI_YEAR;
509: cnt++;
510: }
511: }
512:
513: return cnt;
514: }
「PHPで記念日を表示する」で紹介した関数 scrapingAnniversary と getAnniversary を合体させた。
前述の getDayToday で取り込んだ情報配列 Events に記念日を追加していく。
参考サイト
- PHPで今日は何の日か調べる:ぱふぅ家のホームページ
- PHPで記念日を表示する:ぱふぅ家のホームページ
- WiX によるWindowsインストーラー作成:ぱふぅ家のホームページ
- C++ 開発環境の準備:ぱふぅ家のホームページ
(2024年3月16日)使用ライブラリ更新
(2023年11月4日)注釈番号を除去,使用ライブラリ更新.
(2023年6月18日)使用ライブラリを更新.