C++ でテキスト中の和暦・西暦年号を統一する

(1/1)
>C++でテキスト中の和暦・西暦年号を統一
日本語テキストに混在する和暦・西暦を統一し、「2020 年(令和 2 年)11 月 14 日」のように西暦・和暦混在のテキストに変換したり、西暦だけ、和暦だけに変換するアプリケーションである。年月日は半角・全角・漢数字から選ぶこともできる。変換したテキストは、クリップボードにコピーしたり、テキストファイルに保存することができる。
また、コマンドライン版アプリケーションを同梱しており、バッチや他のプログラムから流し込んだテキストを変換することができる。
PHP でテキスト中の和暦・西暦年号を統一(Windows アプリ版)」で作った PHP プログラムを C++に移植したものである。

目次

サンプル・プログラム

圧縮ファイルの内容
nengowin.msiインストーラ
bin/nengowin.exe実行プログラム本体(GUI版)
bin/nmtxt.exe実行プログラム本体(CUI版)
bin/option.txt変換オプションを保存するファイル
bin/etc/era.txt西暦⇔元号変換表
bin/etc/help.chmヘルプ・ファイル
sour/nengowin.cppソース・プログラム
sour/resource.hリソース・ヘッダ
sour/resource.rcリソース・ファイル(GUI版)
sour/resource2.rcリソース・ファイル(CUI版)
sour/application.icoアプリケーション・アイコン(GUI版)
sour/application2.icoアプリケーション・アイコン(CUI版)
sour/pahooNormalizeText.cppテキスト正規化クラス(ソース)
sour/pahooNormalizeText.hppテキスト正規化クラス(ヘッダ)

使用ライブラリ

CUI版でコマンダイン・オプションを操作する場合などに、オープンソースのライブラリ Boost C++ライブラリが必要になる。Boost C++ライブラリの導入方法等については、「C++ 開発環境の準備」をご覧いただきたい。

リソースの準備

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

Eclipse に戻り、ソース・プログラム "nengowin.cpp" を追加する。
リンカー・フラグを -mwindows -static-libstdc++ -static-libgcc -lpthread -lwinpthread -static "(任意のパス)\libboost_program_options-mt.dll" -static "(任意のパス)\libmecab-2.dll" に設定する。
また、CUI版をビルドするために、構成 CMD を追加し、リンカー・フラグを -static-libstdc++ -static-libgcc -lpthread -static "(任意のパス)\libboost_program_options-mt.dll" -static "(任意のパス)\libmecab-2.dll"" に設定する。

プログラムの流れ

>C++でテキスト中の和暦・西暦年号を統一
プログラムの流れは上図の通りである。
変換ボタンがクリックされたら、後述する変換オプションの値に応じ、seirekiwarekimixture を呼び出す。
各々のメソッドは、内部で正規表現によるパターンマッチを行い、年号に合致する部分文字列を順次変換してゆく。

解説:テキスト正規化クラス

0049: //和暦・西暦年号変換オプション
0050: #define OPTION_MODE_AD     'A'        //西暦に統一
0051: #define OPTION_MODE_ERA     'W'        //和暦に統一
0052: #define OPTION_MODE_ADERA     'C'        //西暦(和暦)
0053: #define OPTION_SCOPE_1     '1'        //奈良時代以降
0054: #define OPTION_SCOPE_2     '2'        //明治以降
0055: #define OPTION_SCOPE_0     '0'        //変換しない
0056: #define OPTION_AD_HAN     'h'        //数字変換(西暦):半角数字
0057: #define OPTION_AD_ZEN     'z'        //  同上   :全角数字
0058: #define OPTION_AD_KAN1     'k'        //  同上   :漢数字(単純)
0059: #define OPTION_AD_KAN2     'l'        //  同上   :漢数字
0060: #define OPTION_ERA_HAN     'H'        //数字変換(和暦):半角数字
0061: #define OPTION_ERA_ZEN     'Z'        //  同上   :全角数字
0062: #define OPTION_ERA_KAN1     'K'        //  同上   :漢数字(単純)
0063: #define OPTION_ERA_KAN2     'L'        //  同上   :漢数字
0064: 
0065: //年号変換オプションの初期値
0066: #define CONVOPT_INIT     "C1hH"

変換オプションは、英文字 1 文字で識別するようにしている。これを同時に複数指定することができる。ただし、a と A のように排他的であるオプションは、同時に指定しても意味をなさない。

解説:西暦⇔元号変換

1098: /**
1099:  * 西暦⇔元号変換
1100:  * @param wstring sour   オリジナル・テキスト
1101:  * @param char*   option 変換オプション
1102:  * @return wstring 変換後テキスト
1103: */
1104: wstring pahooNormalizeText::convNengo(wstring wsourconst charoption) {
1105:     wstring wdest = L"";
1106: 
1107:     //変換範囲
1108:     if (strchr(optionOPTION_SCOPE_1) != NULL) {
1109:         setScope(START_SCOPE1END_SCOPE);
1110:     } else if (strchr(optionOPTION_SCOPE_2) != NULL) {
1111:         setScope(START_SCOPE2END_SCOPE);
1112:     } else if (strchr(optionOPTION_SCOPE_0) != NULL) {
1113:         setScope(END_SCOPEEND_SCOPE);
1114:     }
1115: 
1116:     //西暦に統一
1117:     if (strchr(optionOPTION_MODE_AD) != NULL) {
1118:         wdest = this->seireki(wsouroptionTRUE);
1119:     //和暦に統一
1120:     } else if (strchr(optionOPTION_MODE_ERA) != NULL) {
1121:         wdest = this->wareki(wsouroptionTRUE);
1122:     //西暦(和暦)
1123:     } else if (strchr(optionOPTION_MODE_ADERA) != NULL) {
1124:         wdest = this->mixture(wsouroptionTRUE);
1125:     }
1126:     //月日
1127:     wdest = this->monthday(wdestoption);
1128: 
1129:     return wdest;
1130: }

変換の入口は convNengo メソッドである。

解説:西暦に統一

0960: /**
0961:  * 西暦に統一
0962:  * @param wstring sour   オリジナル・テキスト
0963:  * @param char*   option 変換オプション
0964:  * @param bool    flag   TRUE=エスケープ文字を消去/FALSE=消去しない
0965:  * @return wstring 変換後テキスト
0966: */
0967: wstring pahooNormalizeText::seireki(wstring wsourconst charoption=NULLbool flag=TRUE) {
0968:     wregex re(_SW("([^0-90-9〇一二三四五六七八九十\百千万あ-ん、-?!-¥]{0,4})\\s*([0-90-9〇元一二三四五六七八九十\百千万]+)年\\s*([0-90-9〇一二三四五六七八九十\]*)月?([0-90-9〇一二三四五六七八九十\]*)日?"));
0969:     wregex re2(ESCYEAR);
0970:     wsmatch mt;
0971: 
0972:     wstring wdest = L"";
0973:     wstring suffix = L"";
0974:     for (bool ismatch = regex_search(wsourmtre); ismatch != FALSE;
0975:             ismatch = regex_search(mt[0].secondmt.suffix().secondmtre)) {
0976:         if (flag) {
0977:             wdest += (wstring)mt.prefix() + 
0978:                 regex_replace(this->wareki2seireki(mtoption), re2L"");
0979:         } else {
0980:             wdest += (wstring)mt.prefix() + this->wareki2seireki(mtoption);
0981:         }
0982:         suffix = (wstring)mt.suffix();        //未変換の部分文字列
0983:     }
0984:     wdest += suffix;
0985: 
0986:     return wdest;
0987: }

0933: /**
0934:  * 和暦→西暦変換(漢数字対応)
0935:  * @param wsmatch mt 年月日パターンにマッチしたテキスト
0936:  * @return wstring 西暦
0937: */
0938: wstring pahooNormalizeText::wareki2seireki(wsmatch mtconst charoption=NULL) {
0939:     wstring yearmonthday;
0940: 
0941:     if (mt[2].str() == _SW("")) {
0942:         year = L"1";
0943:     } else if (mt[2].str() != L"") {
0944:         year = this->kan2num(mt[2].str(), 0);
0945:     }
0946:     if (mt[3].str() != L"") {
0947:         month = this->kan2num(mt[3].str(), 0);
0948:     } else {
0949:         month = _SW("0");
0950:     }
0951:     if (mt[4].str() != L"") {
0952:         day = this->kan2num(mt[4].str(), 0);
0953:     } else {
0954:         day = _SW("0");
0955:     }
0956: 
0957:     return this->era2ad(mt[1].str(), stoi(year), stoi(month), stoi(day), option);
0958: }

西暦への統一は、seirekiwareki2seireki の 2 つのメソッドで処理する。

解説:和暦に統一

0904: /**
0905:  * 和暦に統一
0906:  * @param wstring sour オリジナル・テキスト
0907:  * @param char*   option 変換オプション
0908:  * @param bool    flag   TRUE=エスケープ文字を消去/FALSE=消去しない
0909:  * @return wstring 変換後テキスト
0910: */
0911: wstring pahooNormalizeText::wareki(wstring wsourconst charoption=NULLbool flag=TRUE) {
0912:     wregex re(_SW("([^0-90-9〇一二三四五六七八九十\百千万あ-ん、-?!-¥]{0,4})\\s*([0-90-9元〇一二三四五六七八九十\百千万]+)年\\s*([0-90-9〇一二三四五六七八九十\]*)月?([0-90-9〇一二三四五六七八九十\]*)日?"));
0913:     wregex re2(ESCYEAR);
0914:     wsmatch mt;
0915: 
0916:     wstring wdest = L"";
0917:     wstring suffix = L"";
0918:     for (bool ismatch = regex_search(wsourmtre); ismatch != FALSE;
0919:             ismatch = regex_search(mt[0].secondmt.suffix().secondmtre)) {
0920:         if (flag) {
0921:             wdest += (wstring)mt.prefix() + 
0922:                 regex_replace(this->seireki2wareki(mtoption), re2L"");
0923:         } else {
0924:             wdest += (wstring)mt.prefix() + this->seireki2wareki(mtoption);
0925:         }
0926:         suffix = (wstring)mt.suffix();        //未変換の部分文字列
0927:     }
0928:     wdest += suffix;
0929: 
0930:     return wdest;
0931: }

0873: /**
0874:  * 西暦→和暦変換(漢数字対応)
0875:  * @param wsmatch mt 年月日パターンにマッチしたテキスト
0876:  * @return wstring 和暦
0877: */
0878: wstring pahooNormalizeText::seireki2wareki(wsmatch mtconst charoption=NULL) {
0879:     wstring yearmonthday;
0880: 
0881:     if (mt[2].str() != L"") {
0882:         if (mt[2].str() == _SW("")) {
0883:             year = L"1";
0884:         } else {
0885:             year = this->kan2num(mt[2].str(), 0);
0886:         }
0887:     } else {
0888:         year = _SW("0");
0889:     }
0890:     if (mt[3].str() != L"") {
0891:         month = this->kan2num(mt[3].str(), 0);
0892:     } else {
0893:         month = _SW("0");
0894:     }
0895:     if (mt[4].str() != L"") {
0896:         day = this->kan2num(mt[4].str(), 0);
0897:     } else {
0898:         day = _SW("0");
0899:     }
0900: 
0901:     return this->ad2era(mt[1].str(), stoi(year), stoi(month), stoi(day), option);
0902: }

西暦への統一は、warekiseireki2wareki の 2 つのメソッドで処理する。

解説:西暦(和暦)混合変換

1020: /**
1021:  * 西暦(和暦)混合変換
1022:  * @param wstring sour   オリジナル・テキスト
1023:  * @param char*   option 変換オプション
1024:  * @param bool    flag   TRUE=エスケープ文字を消去/FALSE=消去しない
1025:  * @return wstring 変換後テキスト
1026: */
1027: wstring pahooNormalizeText::mixture(wstring wsourconst charoption=NULLbool flag=TRUE) {
1028:     wregex re(_SW("([^0-90-9〇一二三四五六七八九十\百千万あ-ん、-?!-¥]{0,4})([0-9元]+)年"));
1029:     wregex re2(ESCYEAR);
1030:     wsmatch mt;
1031:     static const char opt[] = { OPTION_ERA_HAN0x00 };
1032: 
1033:     wstring wstr = this->wareki(wsouroptFALSE);       //和暦(半角数字)に統一
1034: 
1035:     wstring wdest  = L"";
1036:     wstring suffix = L"";
1037:     for (bool ismatch = regex_search(wstrmtre); ismatch != FALSE;
1038:             ismatch = regex_search(mt[0].secondmt.suffix().secondmtre)) {
1039:         if (flag) {
1040:             wdest += (wstring)mt.prefix() +
1041:                 regex_replace(this->seireki2mix(mtoption), re2L"");
1042:         } else {
1043:             wdest += (wstring)mt.prefix() + this->seireki2mix(mtoption);
1044:         }
1045:         suffix = (wstring)mt.suffix();        //未変換の部分文字列
1046:     }
1047:     wdest += suffix;
1048: 
1049:     return wdest;
1050: }

0989: /**
0990:  * 和暦→西暦(和暦)変換
0991:  * @param wsmatch mt     年月日パターンにマッチしたテキスト
0992:  * @param char*   option 変換オプション
0993:  * @return wstring 西暦
0994: */
0995: wstring pahooNormalizeText::seireki2mix(wsmatch mtconst charoption=NULL) {
0996:     wstring wdest = L"";
0997:     wstring wstr  = L"";
0998:     wstring ad = this->wareki2seireki(mtoption);
0999: 
1000:     if (strchr(optionOPTION_ERA_ZEN) != NULL) {
1001:         wstr = this->han2zen(mt[2].str(), _decimal);
1002:     } else if (strchr(optionOPTION_ERA_KAN1) != NULL) {
1003:         wstr = this->num2kanSimple(mt[2].str());
1004:     } else if (strchr(optionOPTION_ERA_KAN2) != NULL) {
1005:         wstr = this->num2kan(mt[2].str());
1006:     } else {
1007:         wstr = mt[2].str();
1008:     }
1009:     wstring wareki = mt[1].str() + wstr + _SW("");
1010: 
1011:     if (ad != wareki) {
1012:         wdest = ad + _SW("") + wareki + _SW("");
1013:     } else {
1014:         wdest = ad;
1015:     }
1016: 
1017:     return wdest;
1018: }

西暦(和暦)混合変換は、mixture に入り、まず、wareki メソッドを使って和暦に統一する。続いて、seireki2mix を使って西暦(和暦)に変換する。

解説:西暦⇔元号変換表

0001: //西暦⇔元号変換表
0002: //
0003: //書式 'yyyymmdd' => '元号'  yyyymmddは当該元号の開始日(西暦年月日)
0004: 
0005: //飛鳥時代
0006: '06450717' => '大化',
0007: '06500322' => '白雉',
0008: '06541124' => '',         //空白期間
0009: '06860814' => '朱鳥',
0010: '06861001' => '',         //空白期間

定数 TABLEERA_FNAME で示されるファイルが西暦⇔元号変換表である。テキスト・ファイル(シフト JIS)で、元号と、当該元号の開始日(西暦年月日)を列挙している。
変換表はアプリケーション起動時に readTableEra メソッドを使って読み込む。ファイルの内容を書き換えれば、再コンパイルすることなく変換ルールを変更できる。

同梱する変換表では、南北朝時代は南朝の元号を採用している。参考として、北朝の元号をコメントアウトしてあり、南朝と入れ換えることができる。

本アプリケーションでは旧暦/新暦の変換計算を行っていない。このため、月日は西暦という前提である。「明治 5 年 12 月 2 日」は旧暦なので、「1872 年 12 月 31 日」と変換すべきだが、「1872 年 12 月 2 日」に変換する。
そこで、変換条件として「新暦以降」(1873 年 1 月 1 日以降)を指定できるようにしている。。「新暦以降」を指定して西暦変換すると、明治 5 年 12 月 2 日は変換しないが、明治 6 年 1 月 1 日は 1873 年 1 月 1 日に変換する。
新暦から旧暦への変換については、「旧暦の計算 - PHP で 3 ヶ月カレンダーを作る」を参考にしていただきたい。

解説:定数など

0036: // 定数など ==================================================================
0037: #define MAKER     "pahoo.org"                //作成者
0038: #define APPNAME     "nengowin"                //アプリケーション名
0039: #define APPNAMEJP "和暦・西暦年号変換"    //アプリケーション名(日本語)
0040: #define APPVERSION "1.0"                    //バージョン
0041: #define APPYEAR     "2020"                    //作成年
0042: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-19-01.shtm"  //参考サイト
0043: 
0044: //ヘルプ・ファイル
0045: #define HELPFILE ".\\etc\\help.chm"
0046: 
0047: //デフォルト保存ファイル名
0048: #define SAVEFILE "nengowin.txt"
0049: 
0050: //西暦⇔元号変換ファイル名
0051: #define TABLEERA_FNAME "etc/era.txt"
0052: 
0053: //オプション保存ファイル名:変更不可
0054: #define OPTIONFILE "option.txt"
0055: 
0056: //エラー・メッセージ格納用:変更不可
0057: string ErrorMessage;
0058: 
0059: //現在のインターフェイス
0060: HINSTANCE hInst;
0061: 
0062: //親ウィンドウ
0063: HWND hParent;
0064: 
0065: //pahooNormalizeTextオブジェクト
0066: pahooNormalizeTextpNT;
0067: 
0068: //char*バッファサイズ
0069: #define SIZE_BUFF 512
0070: 
0071: //変換元テキスト(初期値)
0072: #define DEF_SOUR "慶応四年一月三日、鳥羽・伏見の戦いが起きる。10月23日、明治天皇が即位。改元は1868年1月25日に遡って適用された。明治2年5月17日、五稜郭が開放され、戊辰戦争は終わる。"

GUI および CUI版のプログラムは、"nengowin.cpp" に記述しており、マクロ定数 CMDAPP が定義されている場合には CUI版が、そうでない場合には GUI版がビルドできるようにしてある。
とくに注意記載が無い限り、定数は自由に変更できる。

解説:変換オプション取得・設定

0378: /**
0379:  * 変換オプションを設定する
0380:  * @param HWND hDlg   ウィンドウ・ハンドラ
0381:  * @param string opt   正規化オプション
0382:  * @return なし
0383:  */
0384: void setOption(HWND hDlgstring opt) {
0385:     for (int i = 0; i < (int)opt.length(); i++) {
0386:         charch = (char*)opt.substr(i, 1).c_str();
0387:         switch (*ch) {
0388:         //変換方式
0389:         case OPTION_MODE_AD:
0390:             SendMessage(GetDlgItem(hDlgIDC_RADIO_MODE_AD),
0391:                             BM_SETCHECKBST_CHECKED,   0);
0392:             SendMessage(GetDlgItem(hDlgIDC_RADIO_MODE_ERA),
0393:                             BM_SETCHECKBST_UNCHECKED, 0);
0394:             SendMessage(GetDlgItem(hDlgIDC_RADIO_MODE_ADERA),
0395:                             BM_SETCHECKBST_UNCHECKED, 0);
0396:             break;
0397:         case OPTION_MODE_ERA:
0398:             SendMessage(GetDlgItem(hDlgIDC_RADIO_MODE_AD),
0399:                             BM_SETCHECKBST_UNCHECKED, 0);
0400:             SendMessage(GetDlgItem(hDlgIDC_RADIO_MODE_ERA),
0401:                             BM_SETCHECKBST_CHECKED,   0);
0402:             SendMessage(GetDlgItem(hDlgIDC_RADIO_MODE_ADERA),
0403:                             BM_SETCHECKBST_UNCHECKED, 0);
0404:             break;
0405:         case OPTION_MODE_ADERA:
0406:             SendMessage(GetDlgItem(hDlgIDC_RADIO_MODE_AD),
0407:                             BM_SETCHECKBST_UNCHECKED, 0);
0408:             SendMessage(GetDlgItem(hDlgIDC_RADIO_MODE_ERA),
0409:                             BM_SETCHECKBST_UNCHECKED, 0);
0410:             SendMessage(GetDlgItem(hDlgIDC_RADIO_MODE_ADERA),
0411:                             BM_SETCHECKBST_CHECKED,   0);
0412:             break;
0413:         //変換範囲
0414:         case OPTION_SCOPE_1:
0415:             SendMessage(GetDlgItem(hDlgIDC_RADIO_SCOPE_1),
0416:                             BM_SETCHECKBST_CHECKED,   0);
0417:             SendMessage(GetDlgItem(hDlgIDC_RADIO_SCOPE_2),
0418:                             BM_SETCHECKBST_UNCHECKED, 0);
0419:             SendMessage(GetDlgItem(hDlgIDC_RADIO_SCOPE_0),
0420:                             BM_SETCHECKBST_UNCHECKED, 0);
0421:             break;
0422:         case OPTION_SCOPE_2:
0423:             SendMessage(GetDlgItem(hDlgIDC_RADIO_SCOPE_1),
0424:                             BM_SETCHECKBST_UNCHECKED, 0);
0425:             SendMessage(GetDlgItem(hDlgIDC_RADIO_SCOPE_2),
0426:                             BM_SETCHECKBST_CHECKED,   0);
0427:             SendMessage(GetDlgItem(hDlgIDC_RADIO_SCOPE_0),
0428:                             BM_SETCHECKBST_UNCHECKED, 0);
0429:             break;
0430:         case OPTION_SCOPE_0:
0431:             SendMessage(GetDlgItem(hDlgIDC_RADIO_SCOPE_1),
0432:                             BM_SETCHECKBST_UNCHECKED, 0);
0433:             SendMessage(GetDlgItem(hDlgIDC_RADIO_SCOPE_2),
0434:                             BM_SETCHECKBST_UNCHECKED, 0);
0435:             SendMessage(GetDlgItem(hDlgIDC_RADIO_SCOPE_0),
0436:                             BM_SETCHECKBST_CHECKED,   0);
0437:             break;
0438:         //数字変換(西暦)
0439:         case OPTION_AD_HAN:
0440:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_HAN),
0441:                             BM_SETCHECKBST_CHECKED,   0);
0442:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_ZEN),
0443:                             BM_SETCHECKBST_UNCHECKED, 0);
0444:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_KAN1),
0445:                             BM_SETCHECKBST_UNCHECKED, 0);
0446:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_KAN2),
0447:                             BM_SETCHECKBST_UNCHECKED, 0);
0448:             break;
0449:         case OPTION_AD_ZEN:
0450:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_HAN),
0451:                             BM_SETCHECKBST_UNCHECKED, 0);
0452:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_ZEN),
0453:                             BM_SETCHECKBST_CHECKED,   0);
0454:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_KAN1),
0455:                             BM_SETCHECKBST_UNCHECKED, 0);
0456:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_KAN2),
0457:                             BM_SETCHECKBST_UNCHECKED, 0);
0458:             break;
0459:         case OPTION_AD_KAN1:
0460:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_HAN),
0461:                             BM_SETCHECKBST_UNCHECKED, 0);
0462:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_ZEN),
0463:                             BM_SETCHECKBST_UNCHECKED, 0);
0464:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_KAN1),
0465:                             BM_SETCHECKBST_CHECKED,   0);
0466:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_KAN2),
0467:                             BM_SETCHECKBST_UNCHECKED, 0);
0468:             break;
0469:         case OPTION_AD_KAN2:
0470:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_HAN),
0471:                             BM_SETCHECKBST_UNCHECKED, 0);
0472:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_ZEN),
0473:                             BM_SETCHECKBST_UNCHECKED, 0);
0474:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_KAN1),
0475:                             BM_SETCHECKBST_UNCHECKED, 0);
0476:             SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_KAN2),
0477:                             BM_SETCHECKBST_CHECKED,   0);
0478:             break;
0479:         //数字変換(和暦)
0480:         case OPTION_ERA_HAN:
0481:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_HAN),
0482:                             BM_SETCHECKBST_CHECKED,   0);
0483:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_ZEN),
0484:                             BM_SETCHECKBST_UNCHECKED, 0);
0485:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_KAN1),
0486:                             BM_SETCHECKBST_UNCHECKED, 0);
0487:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_KAN2),
0488:                             BM_SETCHECKBST_UNCHECKED, 0);
0489:             break;
0490:         case OPTION_ERA_ZEN:
0491:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_HAN),
0492:                             BM_SETCHECKBST_UNCHECKED, 0);
0493:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_ZEN),
0494:                             BM_SETCHECKBST_CHECKED,   0);
0495:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_KAN1),
0496:                             BM_SETCHECKBST_UNCHECKED, 0);
0497:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_KAN2),
0498:                             BM_SETCHECKBST_UNCHECKED, 0);
0499:             break;
0500:         case OPTION_ERA_KAN1:
0501:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_HAN),
0502:                             BM_SETCHECKBST_UNCHECKED, 0);
0503:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_ZEN),
0504:                             BM_SETCHECKBST_UNCHECKED, 0);
0505:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_KAN1),
0506:                             BM_SETCHECKBST_CHECKED,   0);
0507:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_KAN2),
0508:                             BM_SETCHECKBST_UNCHECKED, 0);
0509:             break;
0510:         case OPTION_ERA_KAN2:
0511:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_HAN),
0512:                             BM_SETCHECKBST_UNCHECKED, 0);
0513:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_ZEN),
0514:                             BM_SETCHECKBST_UNCHECKED, 0);
0515:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_KAN1),
0516:                             BM_SETCHECKBST_UNCHECKED, 0);
0517:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_KAN2),
0518:                             BM_SETCHECKBST_CHECKED,   0);
0519:             break;
0520:         default:
0521:             break;
0522:         }
0523:     }
0524: }

0526: /**
0527:  * 正規化オプションを取得する
0528:  * @param HWND hDlg   ウィンドウ・ハンドラ
0529:  * @return string 正規化オプション
0530:  */
0531: string getOption(HWND hDlg) {
0532:     string opt = "";
0533:     //変換方式
0534:     if (SendMessage(GetDlgItem(hDlgIDC_RADIO_MODE_AD), BM_GETCHECK, 0, 0)) {
0535:         opt += OPTION_MODE_AD;
0536:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_MODE_ERA), BM_GETCHECK, 0, 0)) {
0537:         opt += OPTION_MODE_ERA;
0538:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_MODE_ADERA), BM_GETCHECK, 0, 0)) {
0539:         opt += OPTION_MODE_ADERA;
0540:     }
0541:     //変換範囲
0542:     if (SendMessage(GetDlgItem(hDlgIDC_RADIO_SCOPE_1), BM_GETCHECK, 0, 0)) {
0543:         opt += OPTION_SCOPE_1;
0544:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_SCOPE_2), BM_GETCHECK, 0, 0)) {
0545:         opt += OPTION_SCOPE_2;
0546:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_SCOPE_0), BM_GETCHECK, 0, 0)) {
0547:         opt += OPTION_SCOPE_0;
0548:     }
0549:     //数字変換(西暦)
0550:     if (SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_HAN), BM_GETCHECK, 0, 0)) {
0551:         opt += OPTION_AD_HAN;
0552:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_ZEN), BM_GETCHECK, 0, 0)) {
0553:         opt += OPTION_AD_ZEN;
0554:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_KAN1), BM_GETCHECK, 0, 0)) {
0555:         opt += OPTION_AD_KAN1;
0556:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_AD_KAN2), BM_GETCHECK, 0, 0)) {
0557:         opt += OPTION_AD_KAN2;
0558:     }
0559:     //数字変換(和暦)
0560:     if (SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_HAN), BM_GETCHECK, 0, 0)) {
0561:         opt += OPTION_ERA_HAN;
0562:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_ZEN), BM_GETCHECK, 0, 0)) {
0563:         opt += OPTION_ERA_ZEN;
0564:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_KAN1), BM_GETCHECK, 0, 0)) {
0565:         opt += OPTION_ERA_KAN1;
0566:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_ERA_KAN2), BM_GETCHECK, 0, 0)) {
0567:         opt += OPTION_ERA_KAN2;
0568:     }
0569: 
0570:     return opt;
0571: }

GUI版は、変換オプションをラジオボタンに設定したり取り出すために、関数 setOptiongetOption の 2種類の関数を用意した。

解説:変換オプション読込・保存

0124: /**
0125:  * AppDataのパスを取得
0126:  * @param char* appname アプリケーション名
0127:  * @return TCHAR* パス
0128:  */
0129: TCHARgetMyPath(const charappname) {
0130:     static TCHAR myPath[MAX_PATH] = "";
0131: 
0132:     if (strlen(myPath) == 0) {
0133:         if (SHGetSpecialFolderPath(NULLmyPathCSIDL_APPDATA, 0)) {
0134:             TCHAR *ptmp = _tcsrchr(myPath_T('\\'));
0135:             if (ptmp != NULL) {
0136:                 ptmp = _tcsinc(ptmp);
0137:                 *ptmp = _T('\0');
0138:             }
0139:             strcat(myPath_T("Roaming"));
0140:             CreateDirectory((LPCTSTR)myPathNULL);
0141:             strcat(myPath_T("\\pahoo.org"));
0142:             CreateDirectory((LPCTSTR)myPathNULL);
0143:             strcat(myPath_T("\\"));
0144:             strcat(myPath_T(appname));
0145:             CreateDirectory((LPCTSTR)myPathNULL);
0146:             strcat(myPath_T("\\"));
0147:         } else {
0148:         }
0149:     }
0150:     return myPath;
0151: }

0153: /**
0154:  * オプションの読み込み
0155:  * @param なし
0156:  * @return string オプション
0157:  */
0158: string loadOption(void) {
0159:     string option = OPTION_INIT;
0160: 
0161:     string fname = (string)getMyPath(APPNAME) + OPTIONFILE;
0162:     ifstream ifs(fname);
0163:     if (! ifs) {
0164:         ErrorMessage = (string)fname + " の読み込みに失敗しました";
0165:         return option;
0166:     }
0167:     getline(ifsoption);
0168:     ifs.close();
0169: 
0170:     return option;
0171: 
0172: }

0174: /**
0175:  * オプションの保存
0176:  * @param string option オプション
0177:  * @return なし
0178:  */
0179: void saveOption(string option) {
0180:     string fname = (string)getMyPath(APPNAME) + OPTIONFILE;
0181:     ofstream ofs(fname);
0182:     ofs << option;
0183: 
0184:     if (ofs.bad()) {
0185:         ErrorMessage = (string)fname + " の書き込みに失敗しました";
0186:         cout << ErrorMessage << endl;
0187:     }
0188:     ofs.close();
0189: }

プログラム起動時に loadOption 関数によって、変換オプションを読み込む。また、終了時に saveOption 関数によって、変換オプションを保存する。変数 option の値をテキスト・ファイルとして読み書きしている。
保存場所は、getMyPath 関数によって、ユーザーの AppData の下にアプリケーションフォルダを作って保存する。
この処理は、GUI/CUI 共通だ。

CUI用メインプログラム

0749: /**
0750:  * CUI用メインプログラム
0751:  * @param int argc
0752:  * @paramm char* argv[]
0753:  * @return int リターンコード
0754:  */
0755: int main(int argccharargv[]) {
0756:     //オプション読み込み
0757:     string lopt = loadOption();
0758: 
0759:     //pahooNormalizeTextオブジェクト
0760:     pNT = new pahooNormalizeText();
0761:     //西暦⇔元号変換表読み込み
0762:     pNT->readTableEra(TABLEERA_FNAME);
0763: 
0764:     //コマンドライン・オプションの定義
0765:     options_description options("コマンドライン・オプション");
0766:     options.add_options()
0767:         ("option,o", value<std::string>()->default_value(lopt), "変換オプション")
0768:         ("sour,s", value<std::string>(), "入力テキスト")
0769:         ("dest,d", value<std::string>(), "出力テキスト")
0770:         ("help,h", "ヘルプ")
0771:         ("version,v", "バージョン情報")
0772:         ;
0773:     //コマンドライン・オプションの取得
0774:     variables_map vm;
0775:     try {
0776:         store(parse_command_line(argcargvoptions), vm);
0777:     } catch(const boost::program_options::error_with_option_namee) {
0778:         ErrorMessage =  e.what();
0779:         cerr << ErrorMessage << endl;
0780:         return 1;
0781:     }
0782:     notify(vm);
0783: 
0784:     //正規化オプション
0785:     auto opt = vm["option"].as<string>();
0786: 
0787:     wstring wdest = L"";
0788:     //ヘルプ情報
0789:     if (vm.count("help")) {
0790:         wdest = _SW(Help);
0791:     //バージョン情報
0792:     } else if (vm.count("version")) {
0793:         wdest = _SW(Version);
0794: 
0795:     //変換実行
0796:     } else {
0797:         //入力ファイル
0798:         wstring wsour = _SW(DEF_SOUR);
0799:         if (vm.count("sour")) {
0800:             auto infile = vm["sour"].as<string>();
0801:             ifstream ifs(infile.c_str());
0802:             if (ifs.fail()) {
0803:                 ErrorMessage = infile + " が見つかりません";
0804:                 cerr << ErrorMessage << endl;
0805:                 return 1;
0806:             }
0807:             string sour = "";
0808:             string ss;
0809:             while (getline(ifsss)) {
0810:                 sour += ss + "\r";
0811:             }
0812:             wsour = _SW(sour);
0813:         //標準入力から
0814:         } else {
0815:             string sour;
0816:             cin >> sour;
0817:             if (sour.length() > 0) {
0818:                 wsour = _SW(sour);
0819:             }
0820:         }
0821:         wdest = pNT->convNengo(wsouropt.c_str());
0822:     }
0823: 
0824:     //出力ファイル
0825:     if (vm.count("dest")) {
0826:         //改行コード置換
0827:         wregex re(_SW("\r"));
0828:         wdest = regex_replace(wdestre_SW("\n"));
0829:         auto outfile = vm["dest"].as<string>();
0830:         ofstream ofs(outfile.c_str());
0831:         ofs << _WS(wdest);
0832:         if (ofs.bad()) {
0833:             ErrorMessage = outfile + " への書き込みに失敗しました";
0834:             cerr << ErrorMessage << endl;
0835:             return 1;
0836:         }
0837:         ofs.close();
0838:     //標準出力へ
0839:     } else {
0840:         //改行コード置換
0841:         wregex re(_SW("\r"));
0842:         wdest = regex_replace(wdestre_SW("\n"));
0843:         cout << _WS(wdest);
0844:     }
0845: 
0846:     //オブジェクト解放
0847:     delete pNT;
0848: 
0849:     //オプション保存
0850:     saveOption(opt);
0851: 
0852:     return 0;
0853: }

CUI版は、昔ながらの main 関数ではじまる。
GUI版の WinMain 関数はマルチスレッド対応で、すべてのスレッドが終わる前にコマンドラインに戻ってきてしまう。また、標準入出力のパイプ処理への対応も面倒であるため、CUI版は実行ファイルを分けることにした。

コマンドラインオプションの解釈は、Boost C++ライブラリoptions を利用した。
その他の関数、ヘルプファイルやインストーラー作成方法については、これまでの連載で説明してきたとおりである。

参考サイト

(この項おわり)
header