
サンプル・プログラム
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.3.3 | 2023/03/11 | 使用ライブラリを更新 |
1.3.2 | 2023/01/08 | getDayToday() -- の取得パターンを変更した |
1.3.1 | 2022/10/16 | 紀元前パターンを追加 |
1.3.0 | 2022/09/03 | ウィンドウ位置保存 |
1.2.1 | 2022/09/03 | getDayToday() -- 1行4000文字以上なら読み飛ばし |
使用ライブラリ
リソースの準備
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.3.3" //バージョン
  42: #define APPYEAR "2020-23" //作成年・更新日
  43: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-11-01.shtm" // 参考サイト
  44:
  45: //char*バッファサイズ
  46: #define SIZE_BUFF 512
  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: }
解説:今日は何の日 取得
 344: /**
 345: * 今日は何の日 取得
 346: * @param int month, day 月,日
 347: * @param bool lfn 脚注を残す(省略時=FALSE)
 348: * @return int 情報件数
 349: */
 350: int getDayToday(int month, int day, bool lfn=FALSE) {
 351: //初期化
 352: for (int i = 0; i < SIZE_EVENTS; i++) {
 353: Events[i].reset();
 354: Events[i] = NULL;
 355: }
 356:
 357: //Wikipediaコンテンツ取得
 358: char url[SIZE_BUFF];
 359: string contents = "";
 360: getURL_Wikipedia(month, day, (char *)url, SIZE_BUFF);
 361: bool res = readWebContents(url, UserAgent, &contents);
 362: if (res == FALSE) {
 363: ErrorMessage = "Wikipediaにアクセスできません.";
 364: return (-1);
 365: }
 366:
 367: //コンテンツの解釈
 368: setlocale(LC_ALL, "Japanese");
 369: int cnt = -1;
 370: stringstream ss;
 371: string ss0;
 372: wstring mode = L"";
 373: wstring ws, ws2;
 374: wstringstream wss;
 375: static wsmatch mt1, mt2, mt3;
 376: static smatch mt4;
 377: wregex re1(_SW("<span[\\s\\S]+id=\"できごと\""));
 378: wregex re2(_SW("<span[\\s\\S]+id=\"誕生日\""));
 379: wregex re3(_SW("<span[\\s\\S]+id=\"忌日\""));
 380: wregex re5(_SW("<li>[^\\>]+([0-9]+(年|世紀).+)<\\/li>"));
 381: wregex re6(_SW("([^0-9]*)([0-9]+)年(\\s*[\((].[^\\))]+[)\\)].)?[ \\-]+(.+)")); //年 - 出来事
 382: wregex re7(_SW("([^0-9]*)([0-9]+)年(\\s*[\\((].[^\\))]+[)\\)].)?[ \\-]+([^、]+)、?([^(\\(]+)*([^0-9]*)([0-9]*)")); //年 - 名前、職業(生没年)
 383: wregex re8(_SW("<span[\\s\\S]+id=\"フィクションのできごと\">"));
 384: wregex re9(_SW("^\\([\\*\\+].")); //年 - 名前、職業(生没年)
 385: wregex re11(_SW("紀元前"));
 386: wregex re99(_SW("<span[\\s\\S]+class=\"vector-menu-heading-label\">他言語版")); //1行読み込み打ち切り
 387:
 388: ss << contents;
 389: while(ss && getline(ss, ss0)) {
 390: //1行をwstring変換
 391: ws = _UW(ss0);
 392: //4000文字以上なら処理しない
 393: if (ws.length() > 4000) {
 394: continue;
 395: }
 396: //マッチング処理
 397: if (regex_search(ws, mt1, re99)) {
 398: break;
 399: } else if (regex_search(ws, mt1, re1)) {
 400: mode = _SW("出来事");
 401: } else if (regex_search(ws, mt1, re8)) {
 402: mode = _SW("架空");
 403: } else if (regex_search(ws, mt1, re2)) {
 404: mode = _SW("誕生");
 405: } else if (regex_search(ws, mt1, re3)) {
 406: mode = _SW("死亡");
 407: } else if ((mode.length() != 0) && (regex_search(ws, mt1, re5) == TRUE)) {
 408: ws = wstrip_tags(ws);
 409: //出来事
 410: if ((mode == _SW("出来事")) && (regex_search(ws, mt2, re6) == TRUE)) {
 411: cnt++;
 412: Events[cnt] = make_unique<_Events>();
 413: Events[cnt]->category = mode;
 414: if (mt2[1].str().length() == 0) {
 415: Events[cnt]->year = atoi(_WS(mt2[2].str()).c_str());
 416: } else {
 417: Events[cnt]->year = 0 - atoi(_WS(mt2[2].str()).c_str());
 418: }
 419: Events[cnt]->era = mt2[3].str();
 420: Events[cnt]->event = mt2[4].str();
 421:
 422: //誕生・死亡
 423: } else if (regex_search(ws, mt2, re7)) {
 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: ws2 = mt2[6].str();
 434: if (regex_search(ws2, mt3, re11) == TRUE) {
 435: Events[cnt]->plus = L"-" + mt2[7].str();
 436: } else if (regex_search(ws2, mt3, re9) == TRUE) {
 437: Events[cnt]->plus = mt2[7].str();
 438: } else {
 439: Events[cnt]->plus = L"";
 440: }
 441: wss << mt2[4].str() << _SW("(") << mt2[5].str();
 442: if (mode == _SW("誕生")) {
 443: wss << _SW(")誕生");
 444: if (Events[cnt]->plus.length() != 0) {
 445: wss << _SW("(〜") << Events[cnt]->plus << _SW("年)");
 446: }
 447: } else {
 448: wss << _SW(")死去");
 449: if (Events[cnt]->plus.length() != 0) {
 450: wss << _SW("(") << Events[cnt]->plus << _SW("年〜")<< Events[cnt]->year << _SW("年)");
 451: }
 452: }
 453: Events[cnt]->event = wss.str();
 454: wss.str(L"");
 455: }
 456: }
 457: }
 458: return cnt;
 459: }
次に、この内容をスクレイピングしていくのだが、今回は、ワイド文字列に対する正規表現を使うことにした。ソースはSJISで書いているので、ユーザーマクロ関数 _SW を使ってワイド文字列に変換し、これを使って正規表現によるパターンマッチングを行う。ちなみに、C++に正規表現が正式導入されたのはC++11からで、Boost C++ライブラリをベースにしている。
パターン名 | 内容 |
---|---|
re1 | できごと |
re2 | 誕生日 |
re3 | 忌日 |
re5 | 西暦年 |
re6 | 西暦年(出来事) |
re7 | 年 - 名前、職業(生没年) |
re8 | フィクションのできごと |
re9 | 年 - 名前、職業(生没年) |
re11 | 紀元前の識別 |
re99 | 1行読み込み打ち切りパターン |
解説:記念日を取得
 461: /**
 462: * 指定月日の記念日を取得する
 463: * @param int month, day 月日
 464: * @param int cnt 情報を追加する配列の最初の番号
 465: * @return int 情報の総数
 466: */
 467: int getAnniversary(int month, int day, int cnt=0) {
 468: //コンテンツ読み込み
 469: static char post[SIZE_BUFF + 1];
 470: snprintf(post, SIZE_BUFF, "MD=%d&M=%d&D=%d", month, month, day);
 471: string contents = "";
 472: bool res = readWebContents(KINENBI_URL, UserAgent, &contents, post);
 473:
 474: if (res == FALSE) {
 475: ErrorMessage = "日本記念日協会のサイトにアクセスできません.";
 476: return (-1);
 477: }
 478:
 479: //スクレイピング
 480: wregex re1(_SW("<a\\s+class=\"winDetail\"\\s+href=\"([^\"]+)\"><font[^>]+>([^<]+)</font>"), wregex::icase);
 481: static wsmatch mt1;
 482:
 483: stringstream ss;
 484: wstring mode = _SW("記念日");
 485: string ss0;
 486: wstring ws;
 487: ss << contents;
 488: while(ss && getline(ss, ss0)) {
 489: //1行をwstring変換
 490: ws = _UW(ss0);
 491: if (regex_search(ws, mt1, re1)) {
 492: Events[cnt] = make_unique<_Events>();
 493: Events[cnt]->category = mode;
 494: Events[cnt]->event = mt1[2].str();
 495: Events[cnt]->year = KINENBI_YEAR;
 496: cnt++;
 497: }
 498: }
 499:
 500: return cnt;
 501: }
「PHPで記念日を表示する」で紹介した関数 scrapingAnniversary と getAnniversary を合体させた。
前述の getDayToday で取り込んだ情報配列 Events に記念日を追加していく。
参考サイト
- PHPで今日は何の日か調べる:ぱふぅ家のホームページ
- PHPで今日は何の日か調べる(Windowsアプリ版):ぱふぅ家のホームページ
- PHPで記念日を表示する:ぱふぅ家のホームページ
- WiX によるWindowsインストーラー作成:ぱふぅ家のホームページ
- C++ 開発環境の準備:ぱふぅ家のホームページ
(2023年3月11日)使用ライブラリを更新.
(2023年1月8日)WikipediaのHTMLタグ変更に対応
(2022年10月16日)紀元前パターンを追加
(2022年9月3日)Wikipedia読込時に終了してしまう不具合修正,ライブラリ等を最新版に更新,ウィンドウ位置を保存.
(2022年3月26日)Wikipedia読込時に終了してしまう不具合修正, libcurl・OpenSSLを最新版に更新.