C++ でテキストの正規化

(1/1)
>C++でテキストの正規化
日本語テキストに混在する全角・半角文字を統一したり、漢数字を算用数字に変換したり、その逆の変換を一気に行うことができる正規化アプリケーションを作る。正規化したテキストは、クリップボードにコピーしたり、テキストファイルに保存することができる。
また、ソースを共通化してコマンドライン版アプリケーションを作り、バッチや他のプログラムから流し込んだテキストを正規化することができるようにする。
PHPで日本語テキストを正規化(Windowsアプリ版)」で作ったPHPプログラムをC++に移植したものである。

(2022年1月3日)「一律」「何千万」など文字化け対策を品詞を用いて実現.
(2021年12月4日)アプリケーション終了時のウィンドウ位置を保存するようにした.
(2021年11月6日)「一律」「何千万」など文字化け対策
(2021年2月3日)不足していた記号類を追加。

目次

サンプル・プログラム

圧縮ファイルの内容
normalizetextwin.msiインストーラ
bin/normalizetextwin.exe実行プログラム本体(GUI版)
bin/nmtxt.exe実行プログラム本体(CUI版)
bin/option.txt正規化オプションを保存するファイル
bin/libgcc_s_seh-1.dll
bin/libiconv-2.dll
bin/libmecab-2.dll
bin/libstdc++-6.dll
bin/libwinpthread-1.dll
実行時に必要になるDLL
bin/etc/help.chmヘルプ・ファイル
bin/etc/char.bin
bin/etc/dicrc
bin/etc/matrix.bin
bin/etc/sys.dic
bin/etc/unk.dic
bin/etc/user_wiki.dic
MeCabが参照する辞書ファイル等
sour/normalizetextwin.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テキスト正規化クラス(ヘッダ)

使用ライブラリ

漢数字と固有名詞(例:『千と千尋の神隠し』)を識別するために、形態素解析エンジン「MeCab (めかぶ) 」を利用する。導入方法は後述する。
また、CUI版でコマンダイン・オプションを操作する場合などに、オープンソースのライブラリ Boost C++ライブラリが必要になる。Boost C++ライブラリの導入方法等については、「C++ 開発環境の準備」をご覧いただきたい。

リソースの準備

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

Eclipse に戻り、ソース・プログラム "normalizetextwin.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"" に設定する。

MeCabの導入

MeCab については「PHPで MeCabのユーザー辞書を作成する」で紹介している。
簡単に振り返っておくと、入力されたテキストを日本語の品詞に分解する形態素解析エンジンと呼ばれるプログラムである。
なぜテキストの正規化に形態素解析エンジンが必要かというと、たとえば「千と千尋の神隠しという映画を見た」というテキストをそのまま正規化してしまうと、「千」を数字と解釈し、「1000と1000尋の神隠しという映画を見た」という変換結果になってしまうためである。
テキスト中にある固有名詞は数字変換の対象としないように除外するため、形態素解析エンジンを利用する。形態素解析エンジンであれば MeCab でなくても構わないのだが、C++から利用可能なDLLファイルが利用できることから、ここでは MeCab を採用する。

MeCab を64ビット環境でビルドすると、"libmecab-2.dll" が得られる。これを適当なディレクトリに配置し、リンクするようにする。

MeCab のユーザー辞書であるが、前述の通り、固有名詞を識別するのが目的であるので、Wikipediaの見出し語をユーザー辞書として用意することにした。Wikipediaユーザー辞書の作るプログラムは、前述の「PHPで MeCabのユーザー辞書を作成する」で紹介している。

プログラムの流れ

>C++でテキストの正規化
プログラムの流れは上図の通りである。
変換ボタンがクリックされたら、toHankaku メソッドを使って半角か可能な文字を全て半角にする。その後、正規化オプションの値に応じて全角化、漢数字か化を行ってゆく。

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

0029: //正規化オプション
0030: #define OPTION_SPC_TRIM1   't'       //行頭・行末の空白文字を除く
0031: #define OPTION_SPC_TRIM2   'T'       //全角文字と隣り合う空白文字を除く
0032: #define OPTION_NUM_HAN     'n'       //数字を半角に統一
0033: #define OPTION_NUM_ZEN     'N'       //数字を全角に統一
0034: #define OPTION_NUM_KAN     'K'       //数字を漢字に統一
0035: #define OPTION_NUM_KAN2        'k'       //数字を漢字(単純)に統一
0036: #define OPTION_ALP_HAN     'a'       //英字を半角に統一
0037: #define OPTION_ALP_ZEN     'A'       //英字を全角に統一
0038: #define OPTION_YAK_HAN     'y'       //記号を半角に統一
0039: #define OPTION_YAK_ZEN     'Y'       //記号を全角に統一
0040: #define OPTION_KANA_HAN        'h'       //カタカナを半角に統一
0041: #define OPTION_KANA_ZEN        'H'       //カタカナを全角に統一
0042: #define OPTION_SPEC_HAN        's'       //特殊文字を半角に統一
0043: #define OPTION_SPEC_ZEN        'S'       //特殊文字を全角に統一
0044: #define OPTION_NUM_SCALE   'F'       //数字を位取り記法にする
0045: #define OPTION_NUM_NOSCALE     'f'       //数字を位取り記法にしない
0046: 
0047: //正規化オプションの初期値
0048: #define OPTION_INIT            "anYSHF"

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

解説:MeCabの呼び出し

0114: private:
0115: MeCab::Tagger *tagger = MeCab::createTagger("--dicdir=etc --userdic=etc/user_wiki.dic --node-format=%M¥¥t%f[0],%f[1],%f[2],%f[3],%f[4],%f[5],%f[6],%f[7],%f[8]¥¥n --unk-format=%M¥¥t%f[0],%f[1],%f[2],%f[3],%f[4],%f[5]¥¥n");

MeCab はクラス化されており、createTagger メソッドで呼び出す。引数は、MeCab.exe のオプションとほぼ同じである。
ここで、前述のWikipediaユーザー辞書 "user_wiki.dic" などを指定する。

解説:半角数字を漢数字に変換

0177: /**
0178:  * 半角数字を漢数字に変換する
0179:  * @param   wstring instr 半角数字
0180:  *                          小数、負数に対応;指数表記には未対応
0181:  *                          カンマは削除
0182:  * @return  wstring 漢数字
0183: */
0184: wstring pahooNormalizeText::num2kanji(wstring instr) {
0185:     static wchar_t kantbl1[] =
0186:         { L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7',
0187:             L'8', L'9', L'.', L'-' };
0188:     static wchar_t kantbl2[] =
0189:         { 0x00000x4E000x4E8C0x4E090x56DB0x4E940x516D,    //一〜九
0190:             0x4E030x516B0x4E5D0xFF0E0xFF0D };             //.−
0191:     static wchar_t kantbl3[] = { 0x00000x53410x767E0x5343 };   //十百千
0192:     static wchar_t kantbl4[] = { 0x00000x4E070x51040x51460x4EAC };   //万億兆京
0193: 
0194:     wstring outstr = L"";;
0195:     wstring ws2;
0196:     wchar_t wch1wch2;
0197:     int m = (int)instr.length() / 4;
0198:     //一、万、億、兆‥‥の繰り返し
0199:     for (int i = 0; i <= mi++) {
0200:         ws2 = L"";
0201:         //一、十、百、千の繰り返し
0202:         for (int j = 0; j < 4; j++) {
0203:             int pos = instr.length() - i * 4 - j - 1;
0204:             if (pos >= 0) {
0205:                 wchar_twch  = (wchar_t*)instr.substr(pos, 1).c_str();
0206:                 if (*wch == L',')  continue;        //カンマは無視
0207:                 for (int k = 0; k < (int)(sizeof(kantbl1) / sizeof(kantbl1[0])); k++) {
0208:                     //漢数字 or 半角数字のまま
0209:                     wch1 = 0x0000;
0210:                     if (*wch == kantbl1[k]) {
0211:                         wch1 = kantbl2[k];
0212:                         break;
0213:                     }
0214:                 }
0215:                 wch2 = 0x0000;;
0216:                 if ((j >= 0) && (j <= 3)) {
0217:                     wch2 = kantbl3[j];
0218:                 }
0219: 
0220:                 //冒頭が「一」の場合の処理
0221:                 if (wch1 != 0x0000) {
0222:                     if ((wch1 == 0x4E00) && (wch2 != 0x0000)) {
0223:                         ws2 = (wstring){wch2} + ws2;
0224:                     } else if (wch2 != 0x0000) {
0225:                         ws2 = (wstring){wch1} + (wstring){wch2} + ws2;
0226:                     } else {
0227:                         ws2 = (wstring){wch1} + ws2;
0228:                     }
0229:                 }
0230:             }
0231:         }
0232:         if (ws2 != L"") {
0233:             if (kantbl4[i] == 0x0000) {
0234:                 outstr = ws2 + outstr;
0235:             } else {
0236:                 outstr = ws2 + (wstring){kantbl4[i]} + outstr;
0237:             }
0238:         }
0239:     }
0240: 
0241:     return outstr;
0242: }

半角数字を漢数字に変換するメソッドは num2kanji である。
右から左へ向かって逆順に処理し、4桁ごとに、万、億、兆、京の単位を付けてゆく。各々の4桁の中で、十、百、千を付ける。冒頭が「一」の場合は「一百」にならないよう配慮する。

解説:解説:半角数字を漢数字(単純)に変換

0307: /**
0308:  * 半角数字を漢数字に変換する(単純変換)
0309:  * @param   wstring wsour 半角数字を含む文字列
0310:  * @return  wstring 漢数字
0311: */
0312: wstring pahooNormalizeText::num2kanSimple(wstring wsour) {
0313:     static wchar_t kantbl1[] =
0314:         { L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7',
0315:             L'8', L'9', L'.', L'-' };
0316:     static wchar_t kantbl2[] =
0317:         { 0x30070x4E000x4E8C0x4E090x56DB0x4E940x516D,    //一〜九
0318:             0x4E030x516B0x4E5D0xFF0E0xFF0D };             //.−
0319: 
0320:     //左から1文字ずつ処理
0321:     wstring wdest = L"";
0322:     wstring wstr;
0323:     wchar_twch;
0324:     bool flag;
0325:     for (int pos = 0; pos < (int)wsour.length(); pos++) {
0326:         flag = FALSE;
0327:         wstr = wsour.substr(pos, 1);
0328:         wch  = (wchar_t*)wstr.c_str();
0329:         for (int k = 0; k < (int)(sizeof(kantbl1) / sizeof(kantbl1[0])); k++) {
0330:             if (*wch == kantbl1[k]) {
0331:                 wdest += (wstring){kantbl2[k]};
0332:                 flag = TRUE;
0333:                 break;
0334:             }
0335:         }
0336:         if (! flag) {
0337:             wdest += *wch;
0338:         }
0339:     }
0340:     return wdest;
0341: }

半角数字を漢数字(単純)に変換するメソッドは num2kanSimple である。2020年(令和2年)を「二〇二〇年」のように変換する。
左から右へ向かって、算用数字にマッチする文字があれば漢数字に置換する。

解説:半角数字を位取り記法に変換

0244: /**
0245:  * 半角数字を位取り記法に変換する
0246:  * @param   wstring instr 半角数字(小数,負数,指数表記は未対応)
0247:  * @return  wstring 位取り記法
0248: */
0249: wstring pahooNormalizeText::num2scale(wstring instr) {
0250:     static wchar_t kantbl[] = { 0x00000x4E070x51040x51460x4EAC };    //万億兆京
0251:     //余計な'0'を除く
0252:     wregex re(_SW("0+([1-9]+)|0+([1-9]+)"));
0253: 
0254:     //桁あふれ
0255:     if (instr.length() > 20) {
0256:         return instr;
0257:     }
0258: 
0259:     //右から1文字ずつ処理
0260:     wstring outstr = L"";
0261:     wstring ws = L"";
0262:     int i = 0;
0263:     bool flag = FALSE;
0264:     for (int pos = instr.length() - 1; pos >= 0; pos--) {
0265:         if (flag) {
0266:             outstr = (wstring){kantbl[(int)(i / 4)]} + outstr;
0267:             flag = FALSE;
0268:         }
0269:         wstring ss = instr.substr(pos, 1);
0270:         ws = ss + ws;
0271:         i++;
0272:         if (i % 4 == 0) {
0273:             if ((ws == L"0000") || (ws == _SW("0000"))) {
0274:                 outstr = (wstring){kantbl[(int)(i / 4)]};
0275:             } else {
0276:                 outstr = regex_replace(wsreL"$1") + outstr;
0277:                 flag = TRUE;
0278:             }
0279:             ws = L"";
0280:         }
0281:     }
0282:     outstr = ws + outstr;
0283: 
0284:     return outstr;
0285: }

「三百億」を算用数字に変換すると「30000000000」となってしまい、視認性が悪くなる。そこで、算用数字に変換した場合でも「300億」になるよう、半角数字を位取り記法に変換するメソッド num2scale を用意した。
前述の num2kanji と同じように右から左へ向かって逆順に処理するが、変換するのは万、億、兆、京の単位だけである。

解説:漢数字を半角数字に変換

0363: /**
0364:  * 漢数字を半角数字に変換する
0365:  * @param   wstring kanji 漢数字
0366:  * @param   int mode 出力書式/1=3桁カンマ区切り,2=漢字混じり, それ以外=ベタ打ち
0367:  * @return  wstring 半角数字
0368: */
0369: wstring pahooNormalizeText::kan2num(wstring kanjiint mode) {
0370:     wstring dest = L"";
0371:     wsmatch mt1;
0372: 
0373:     //全角=半角対応
0374:     const wstring kan_num1 = _SW("0〇○1一壱2二弐3三参4四5五6六7七8八9九");
0375:     const wstring kan_num2 = _SW("000111222333445566778899");
0376: 
0377:     //位取り
0378:     const wstring kan_deci_sub = _SW("十¥百千");
0379:     const wstring kan_deci = _SW("万億兆京");
0380: 
0381:     //半角数字が混在していたら何もしない
0382:     wregex re1(_SW("[0-9]+"));
0383:     if (regex_search(kanjimt1re1)) {
0384:         return kanji;
0385:     }
0386: 
0387:     //右側から解釈していく
0388:     size_t ll = kanji.length();
0389:     wstring a = L"";
0390:     long long int deci = 1;
0391:     long long int deci_sub = 1;
0392:     long long int m = 0;
0393:     long long int n = 0;
0394: 
0395:     for (int pos = ll - 1; pos >= 0; pos--) {
0396:         wstring c = kanji.substr(pos, 1);
0397:         size_t ps1 = kan_num1.find(c);
0398:         size_t ps2 = kan_deci_sub.find(c);
0399:         size_t ps3 = kan_deci.find(c);
0400:         if (ps1 != wstring::npos) {
0401:             a = kan_num2.substr(ps1, 1) + a;
0402:         } else if (ps2 != wstring::npos) {
0403:             if (a != L"") {
0404:                 m = m + stol(a) * deci_sub;
0405:             } else if (deci_sub != 1) {
0406:                 m = m + deci_sub;
0407:             }
0408:             a = L"";
0409:             deci_sub = pow(10, ps2 + 1);
0410:         } else if (ps3 != wstring::npos) {
0411:             if (a != L"") {
0412:                 m = m + stol(a) * deci_sub;
0413:             } else if (deci_sub != 1) {
0414:                 m = m + deci_sub;
0415:             }
0416:             n = m * (long long int)deci + n;
0417:             m = 0;
0418:             a = L"";
0419:             deci_sub = 1;
0420:             deci = (long long int)pow(10000, ps3 + 1);
0421:         }
0422:     }
0423: 
0424:     wstring ss = L"";
0425:     wregex re2(_SW("^(0+)"));
0426:     if (regex_search(amt1re2)) {
0427:         ss = mt1[1].str();
0428:     }
0429:     if (a != L"") {
0430:         m = m + stol(a) * deci_sub;
0431:     } else if (deci_sub != 1) {
0432:         m = m + deci_sub;
0433:     }
0434:     n = m * deci + n;
0435: 
0436:     return to_wstring(n);
0437: }

漢数字を算用数字に変換するメソッド kan2num である。単位としては「京」まで対応している。
前述の num2kanji と同じように右から左へ向かって逆順に処理するが、long long型の整数として扱い、最後にテキストに変換する。

解説:日本語テキストを半角に統一

0476: /**
0477:  * 日本語テキストを半角に統一
0478:  * @param   wstring sour 変換元テキスト
0479:  * @param   bool    trim 行頭・行末の空白を除くかどうか
0480:  * @return  string  変換後テキスト
0481: */
0482: wstring pahooNormalizeText::toHankaku(wstring wsourbool trim) {
0483:     regex sep{"¥¥t|,"};
0484:     wsmatch mt1mt2mt3;
0485:     //数字パターン
0486:     wregex pat_kannum(_SW("^[^数]*[01234567890123456789○〇一二三四五六七八九十¥百千万億兆京]+$"));
0487:     //MeCabの品詞パターン
0488:     wregex re1(_SW("[数幾]"));
0489:     //月の漢数字
0490:     wregex re2(_SW("([一二三四五六七八九十¥]+)(月)"));
0491:     //名詞接続の場合はそのまま
0492:     wregex re3(_SW("名詞接続"));
0493:     //行頭空白パターン
0494:     wregex re4(_SW("^[  ¥¥t¥¥n¥¥r]+"));
0495:     //行末空白パターン
0496:     wregex re5(_SW("[  ¥¥t¥¥n¥¥r]+$"));
0497:     //無変換パターン・その1
0498:     wregex re71(_SW("副詞可+"));
0499:     wregex re72(_SW("接尾|格助詞"));
0500:     //無変換パターン・その2
0501:     wregex re8(_SW("^[千万億兆]+$"));
0502:     //無変換パターン・その3
0503:     wregex re9(_SW("一生"));
0504: 
0505:     //カンマ置換
0506:     wregex re6(_SW(","));
0507:     wsour = regex_replace(wsourre6_SW(""));
0508: 
0509:     //形態素に分解
0510:     string input = _WS(wsour);
0511:     const char *words = tagger->parse(input.c_str());
0512: 
0513:     //変換処理
0514:     bool flag = FALSE;
0515:     bool adverb = FALSE;
0516:     wstring dest = L"";
0517:     wstring numstr = L"";
0518:     wstring surfacepos;
0519: 
0520:     //1行ずつ読み込む
0521:     string ss0;
0522:     stringstream ss;
0523:     ss << words;
0524:     while(ss && getline(ssss0)) {
0525:         int cnt = 0;
0526:         for (std::cregex_token_iterator end,
0527:             ite{ss0.c_str(), ss0.c_str() + strlen(ss0.c_str()), sep, -1};
0528:             ite != end; ++ite) {
0529:             if (cnt == 0)       surface = _SW((*ite).str().c_str());
0530:             else if (cnt == 2)  pos = _SW((*ite).str().c_str());
0531:             cnt++;
0532:         }
0533:         cout << _WS(surface) << " : " <<  _WS(pos) << endl;
0534: 
0535:         //最後
0536:         if (surface == _SW("EOS")) {
0537:             break;
0538:         //無変換
0539:         } else if (regex_search(surfacemt1re9)) {
0540:             dest += surface;
0541:         //月の処理
0542:         } else if (regex_search(surfacemt1re2)) {
0543:             dest += this->kan2num(mt1[1].str(), 2) + mt1[2].str();
0544:         } else if (flag == FALSE) {
0545:             //副詞可能な数字
0546:             if (regex_search(posmt2re71)) {
0547:                 numstr = surface;
0548:                 flag = TRUE;
0549:                 adverb = TRUE;
0550:             //漢数字の1文字目
0551:              } else if (regex_search(surfacemt1pat_kannum) && regex_search(posmt2re1)) {
0552:                 numstr = surface;
0553:                 flag = TRUE;
0554:                 adverb = FALSE;
0555:             //数字ではない
0556:             } else {
0557:                 dest += surface;
0558:             }
0559:         } else {
0560:             //無変換(副詞可能+接尾)
0561:             if (adverb && regex_search(posmt2re72)) {
0562:                 dest += (numstr + surface);
0563:                 numstr = L"";
0564:                 flag = FALSE;
0565:                 adverb = FALSE;
0566:             //漢数字の2文字目以降
0567:             } else if (regex_search(surfacemt1pat_kannum)) {
0568:                 numstr += surface;
0569:                 flag = TRUE;
0570:             //数字以外
0571:             } else {
0572:                 //名詞接続の場合はそのまま
0573:                 if (regex_search(posmt2re3)) {
0574:                     dest += (numstr + surface);
0575:                 //無変換パターン
0576:                 } else if (regex_search(numstrmt1re8)) {
0577:                     dest += (numstr + surface);
0578:                 //副詞可能の場合
0579:                 } else if (adverb) {
0580:                     dest += (this->kanword2num(numstr) + surface);
0581:                 //ここまでの漢数字を半角数字に
0582:                 } else {
0583:                     dest += (this->kan2num(numstr, 2) + surface);
0584:                 }
0585:                 numstr = L"";
0586:                 flag = FALSE;
0587:                 adverb = FALSE;
0588:             }
0589:         }
0590:     }
0591: 
0592:     //末尾処理
0593:     if (flag == TRUE) {
0594:         if (adverb == TRUE) {
0595:             dest += numstr;
0596:         } else {
0597:             dest += this->kan2num(numstr, 2);
0598:         }
0599:     }
0600: 
0601:     //行頭・行末空白処理
0602:     if (trim) {
0603:         wstring wss = regex_replace(destre4L"");
0604:         wss = regex_replace(wssre5L"");
0605:         dest = wss + L"¥¥n";
0606:     }
0607: 
0608:     return wconvString(destLCMAP_HALFWIDTH);
0609: }

正規化処理の前段では、すべてのテキストを半角に変換する。そのためのメソッドが toHankaku である。
MeCab による形態素解析を実行するメソッド parse によってテキストを形態素の分解、個々の形態素に対して、kan2num を実行するかどうかを判断する。
最後に、Win32APIを呼び出す wconvString を使って、変換可能な全ての文字を半角にする。

解説:日本語テキストを全角に変換

0635: /**
0636:  * 半角→全角変換に変換
0637:  * @param   wstring sour 変換元テキスト
0638:  * @param   bool (*func) 該当文字判定関数
0639:  * @return  wstring  変換後テキスト
0640: */
0641: wstring pahooNormalizeText::han2zen(const wstring wsourbool (*func)(wchar_t wch)) {
0642:     wstring wss = L"";
0643:     wstring wdest = L"";
0644:     bool flag = FALSE;
0645: 
0646:     //先頭から1文字ずつ
0647:     for (size_t i = 0; i < wsour.length(); i++) {
0648:         wchar_twch = (wchar_t*)wsour.substr(i, 1).c_str();
0649:         //該当文字ならwss0へ
0650:         if (func(*wch)) {
0651:             wss += (wstring){*wch};
0652:             flag = TRUE;
0653:         //半角→全角変換
0654:         } else if (flag) {
0655:             wdest += wconvString(wssLCMAP_FULLWIDTH);
0656:             wdest += wsour.substr(i, 1);
0657:             flag = FALSE;
0658:             wss = L"";
0659:         } else {
0660:             wdest += wsour.substr(i, 1);
0661:         }
0662:     }
0663:     //最後の1文字が該当文字
0664:     if (flag) {
0665:         wdest += wconvString(wssLCMAP_FULLWIDTH);
0666:     }
0667: 
0668:     return wdest;
0669: }

前述の toHankaku で半角になったテキストから、正規化オプションによって該当する文字だけ全角に変換するメソッドが han2zen である。
文字種変換は、Win32APIを呼び出す wconvString を利用している。

解説:日本語テキストを正規化する

0671: /**
0672:  * 日本語テキストを正規化する
0673:  * @param   wstring sour   漢数字混じりテキスト
0674:  * @param   char*   option 変換オプション
0675:  * @param   bool    trim   行頭・行末の空白を除くかどうか
0676:  * @return  wstring 変換後テキスト
0677: */
0678: wstring pahooNormalizeText::normalizeText(wstring wsourconst charoptionbool trim) {
0679:     wstring wdest = wsour;
0680: 
0681:     //いったん半角に
0682:     wdest = this->toHankaku(wdesttrim);
0683: 
0684:     //全角文字と隣り合う空白文字を除く
0685:     wregex re1(_SW("[  ¥¥t]+([^!-‾].)"));
0686:     wdest = regex_replace(wdestre1L"$1");
0687: 
0688:     //英字:半角→全角
0689:     if (strchr(optionOPTION_ALP_ZEN) != NULL) {
0690:         wdest = this->han2zen(wdest_alphabet);
0691:     }
0692:     //数字:位取り記法
0693:     if (strchr(optionOPTION_NUM_SCALE) != NULL) {
0694:         wdest = this->bignum2scale(wdest);
0695:     }
0696:     //数字:半角→全角
0697:     if (strchr(optionOPTION_NUM_ZEN) != NULL) {
0698:         wdest = this->han2zen(wdest_decimal);
0699:     //数字:半角→漢数字
0700:     } else if (strchr(optionOPTION_NUM_KAN) != NULL) {
0701:         wdest = this->num2kan(wdest);
0702:     //数字:半角→漢数字(単純)
0703:     } else if (strchr(optionOPTION_NUM_KAN2) != NULL) {
0704:         wdest = this->num2kanSimple(wdest);
0705:     }
0706:     //記号:半角→全角
0707:     if (strchr(optionOPTION_YAK_ZEN) != NULL) {
0708:         wdest = this->han2zen(wdest_yakumono);
0709:     }
0710:     //カタカナ:半角→全角
0711:     if (strchr(optionOPTION_KANA_ZEN) != NULL) {
0712:         wdest = this->han2zen(wdest_katakana);
0713:     }
0714:     //小数点の全角を半角に変換
0715:     wdest = this->hanfloat(wdest);
0716: 
0717:     //半角文字と隣り合う全角空白は半角空白へ
0718:     wregex re2(_SW("[ ]+([!-‾].)"));
0719:     wdest = regex_replace(wdestre2L" $1");
0720: 
0721:     return wdest;
0722: }

これまで紹介してきたメソッドを利用し、正規化オプションにもとづいて日本語テキストを正規化するメソッドが normalizeText である。

解説:定数など

0038: // 定数など ==================================================================
0039: #define MAKER      "pahoo.org"               //作成者
0040: #define APPNAME        "normalizetextwin"        //アプリケーション名
0041: #define APPNAMEJP  "テキストの正規化"        //アプリケーション名(日本語)
0042: #define APPVERSION "1.4"                 //バージョン
0043: #define APPYEAR        "2020-2021"               //作成年
0044: #define REFERENCE  "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-18-01.shtm" //参考サイト
0045: 
0046: //ヘルプ・ファイル
0047: #define HELPFILE   ".¥¥etc¥¥help.chm"
0048: 
0049: //デフォルト保存ファイル名
0050: #define SAVEFILE   "normalizetextwin.txt"
0051: 
0052: //オプション保存ファイル名:変更不可
0053: #define OPTIONFILE "option.txt"
0054: 
0055: //エラー・メッセージ格納用:変更不可
0056: string ErrorMessage;
0057: 
0058: //現在のインターフェイス
0059: HINSTANCE hInst;
0060: 
0061: //アプリケーション・ウィンドウ
0062: HWND hParent;
0063: 
0064: //アプリケーション・ウィンドウ位置
0065: unsigned hParent_XhParent_Y;
0066: 
0067: //pahooNormalizeTextオブジェクト
0068: pahooNormalizeTextpNT;
0069: 
0070: //char*バッファサイズ
0071: #define SIZE_BUFF  5120
0072: 
0073: //変換元テキスト(初期値)
0074: #define DEF_SOUR   "『千と千尋の神隠し』(せんとちひろのかみかくし)は、スタジオジブリ制作の長編アニメーション映画。監督は宮崎駿。二〇〇一年七月二十¥日に日本公開。興行収入は三百億円を超え、日本歴代興行収入第一位を達成した。英語のタイトルは『Spirited Away』。¥n千尋という名の十¥歳の少女が、引っ越し先へ向かう途中に立ち入ったトンネルから、神々の世界へ迷い込んでしまう物語。"

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

解説:正規化オプション取得・設定

0470: /**
0471:  * 正規化オプションを設定する
0472:  * @param   HWND hDlg   ウィンドウ・ハンドラ
0473:  * @param   string opt  正規化オプション
0474:  * @return  なし
0475:  */
0476: void setOption(HWND hDlgstring opt) {
0477:     for (int i = 0; i < (int)opt.length(); i++) {
0478:         charch = (char*)opt.substr(i, 1).c_str();
0479:         switch (*ch) {
0480:         case OPTION_ALP_HAN:
0481:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ALP_HAN),
0482:                             BM_SETCHECKBST_CHECKED,   0);
0483:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ALP_ZEN),
0484:                             BM_SETCHECKBST_UNCHECKED, 0);
0485:             break;
0486:         case OPTION_ALP_ZEN:
0487:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ALP_HAN),
0488:                             BM_SETCHECKBST_UNCHECKED, 0);
0489:             SendMessage(GetDlgItem(hDlgIDC_RADIO_ALP_ZEN),
0490:                             BM_SETCHECKBST_CHECKED,   0);
0491:             break;
0492:         case OPTION_NUM_HAN:
0493:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_HAN),
0494:                             BM_SETCHECKBST_CHECKED,   0);
0495:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_ZEN),
0496:                             BM_SETCHECKBST_UNCHECKED, 0);
0497:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_KAN),
0498:                             BM_SETCHECKBST_UNCHECKED, 0);
0499:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_KAN2),
0500:                             BM_SETCHECKBST_UNCHECKED, 0);
0501:             break;
0502:         case OPTION_NUM_ZEN:
0503:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_HAN),
0504:                             BM_SETCHECKBST_UNCHECKED, 0);
0505:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_ZEN),
0506:                             BM_SETCHECKBST_CHECKED,   0);
0507:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_KAN),
0508:                             BM_SETCHECKBST_UNCHECKED, 0);
0509:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_KAN2),
0510:                             BM_SETCHECKBST_UNCHECKED, 0);
0511:             break;
0512:         case OPTION_NUM_KAN:
0513:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_HAN),
0514:                             BM_SETCHECKBST_UNCHECKED, 0);
0515:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_ZEN),
0516:                             BM_SETCHECKBST_UNCHECKED, 0);
0517:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_KAN),
0518:                             BM_SETCHECKBST_CHECKED,   0);
0519:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_KAN2),
0520:                             BM_SETCHECKBST_UNCHECKED, 0);
0521:             break;
0522:         case OPTION_NUM_KAN2:
0523:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_HAN),
0524:                             BM_SETCHECKBST_UNCHECKED, 0);
0525:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_ZEN),
0526:                             BM_SETCHECKBST_UNCHECKED, 0);
0527:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_KAN),
0528:                             BM_SETCHECKBST_UNCHECKED, 0);
0529:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_KAN2),
0530:                             BM_SETCHECKBST_CHECKED,   0);
0531:             break;
0532:         case OPTION_NUM_SCALE:
0533:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_SCALE),
0534:                             BM_SETCHECKBST_CHECKED,   0);
0535:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_NOSCALE),
0536:                             BM_SETCHECKBST_UNCHECKED, 0);
0537:             break;
0538:         case OPTION_NUM_NOSCALE:
0539:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_SCALE),
0540:                             BM_SETCHECKBST_UNCHECKED, 0);
0541:             SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_NOSCALE),
0542:                             BM_SETCHECKBST_CHECKED,   0);
0543:             break;
0544:         case OPTION_YAK_HAN:
0545:             SendMessage(GetDlgItem(hDlgIDC_RADIO_YAK_HAN),
0546:                             BM_SETCHECKBST_CHECKED,   0);
0547:             SendMessage(GetDlgItem(hDlgIDC_RADIO_YAK_ZEN),
0548:                             BM_SETCHECKBST_UNCHECKED, 0);
0549:             break;
0550:         case OPTION_YAK_ZEN:
0551:             SendMessage(GetDlgItem(hDlgIDC_RADIO_YAK_HAN),
0552:                             BM_SETCHECKBST_UNCHECKED, 0);
0553:             SendMessage(GetDlgItem(hDlgIDC_RADIO_YAK_ZEN),
0554:                             BM_SETCHECKBST_CHECKED,   0);
0555:             break;
0556:         case OPTION_KANA_HAN:
0557:             SendMessage(GetDlgItem(hDlgIDC_RADIO_KANA_HAN),
0558:                             BM_SETCHECKBST_CHECKED,   0);
0559:             SendMessage(GetDlgItem(hDlgIDC_RADIO_KANA_ZEN),
0560:                             BM_SETCHECKBST_UNCHECKED, 0);
0561:             break;
0562:         case OPTION_KANA_ZEN:
0563:             SendMessage(GetDlgItem(hDlgIDC_RADIO_KANA_HAN),
0564:                             BM_SETCHECKBST_UNCHECKED, 0);
0565:             SendMessage(GetDlgItem(hDlgIDC_RADIO_KANA_ZEN),
0566:                             BM_SETCHECKBST_CHECKED,   0);
0567:             break;
0568:         case OPTION_SPEC_HAN:
0569:             SendMessage(GetDlgItem(hDlgIDC_RADIO_SPEC_HAN),
0570:                             BM_SETCHECKBST_CHECKED,   0);
0571:             SendMessage(GetDlgItem(hDlgIDC_RADIO_SPEC_ZEN),
0572:                             BM_SETCHECKBST_UNCHECKED, 0);
0573:             break;
0574:         case OPTION_SPEC_ZEN:
0575:             SendMessage(GetDlgItem(hDlgIDC_RADIO_SPEC_HAN),
0576:                             BM_SETCHECKBST_UNCHECKED, 0);
0577:             SendMessage(GetDlgItem(hDlgIDC_RADIO_SPEC_ZEN),
0578:                             BM_SETCHECKBST_CHECKED,   0);
0579:             break;
0580:         default:
0581:             break;
0582:         }
0583:     }
0584: }

0586: /**
0587:  *正規化オプションを取得する
0588:  * @param   HWND hDlg   ウィンドウ・ハンドラ
0589:  * @return  string 正規化オプション
0590:  */
0591: string getOption(HWND hDlg) {
0592:     string opt = "";
0593:     if (SendMessage(GetDlgItem(hDlgIDC_RADIO_ALP_HAN), BM_GETCHECK, 0, 0)) {
0594:         opt += OPTION_ALP_HAN;
0595:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_ALP_ZEN), BM_GETCHECK, 0, 0)) {
0596:         opt += OPTION_ALP_ZEN;
0597:     }
0598:     if (SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_HAN), BM_GETCHECK, 0, 0)) {
0599:         opt += OPTION_NUM_HAN;
0600:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_ZEN), BM_GETCHECK, 0, 0)) {
0601:         opt += OPTION_NUM_ZEN;
0602:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_KAN), BM_GETCHECK, 0, 0)) {
0603:         opt += OPTION_NUM_KAN;
0604:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_KAN2), BM_GETCHECK, 0, 0)) {
0605:         opt += OPTION_NUM_KAN2;
0606:     }
0607:     if (SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_SCALE), BM_GETCHECK, 0, 0)) {
0608:         opt += OPTION_NUM_SCALE;
0609:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_NUM_NOSCALE), BM_GETCHECK, 0, 0)) {
0610:         opt += OPTION_NUM_NOSCALE;
0611:     }
0612:     if (SendMessage(GetDlgItem(hDlgIDC_RADIO_YAK_HAN), BM_GETCHECK, 0, 0)) {
0613:         opt += OPTION_YAK_HAN;
0614:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_YAK_ZEN), BM_GETCHECK, 0, 0)) {
0615:         opt += OPTION_YAK_ZEN;
0616:     }
0617:     if (SendMessage(GetDlgItem(hDlgIDC_RADIO_KANA_HAN), BM_GETCHECK, 0, 0)) {
0618:         opt += OPTION_KANA_HAN;
0619:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_KANA_ZEN), BM_GETCHECK, 0, 0)) {
0620:         opt += OPTION_KANA_ZEN;
0621:     }
0622:     if (SendMessage(GetDlgItem(hDlgIDC_RADIO_SPEC_HAN), BM_GETCHECK, 0, 0)) {
0623:         opt += OPTION_SPEC_HAN;
0624:     } else if (SendMessage(GetDlgItem(hDlgIDC_RADIO_SPEC_ZEN), BM_GETCHECK, 0, 0)) {
0625:         opt += OPTION_SPEC_ZEN;
0626:     }
0627: 
0628:     return opt;
0629: }

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

解説:正規化オプション読込・保存

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

0157: /**
0158:  * オプションの読み込み
0159:  * @param   なし
0160:  * @return  string オプション
0161:  */
0162: string loadOption(void) {
0163:     hParent_X = 0;
0164:     hParent_Y = 0;
0165:     string option = OPTION_INIT;
0166:     ptree pt;
0167: 
0168:     //XMLファイル読み込み
0169:     try {
0170:         xml_parser::read_xml(getMyPath(APPNAME) + APPNAME + ".xml", pt);
0171: 
0172:         //XML解釈
0173:         try {
0174:             //形式チェック
0175:             if (optional<string>str = pt.get_optional<string>("parameter")) {
0176:             } else {
0177:                 return OPTION_INIT;
0178:             }
0179:             //パラメータ読み込み
0180:             for (auto it : pt.get_child("parameter")) {
0181:                 string typeit.second.get_optional<string>("<xmlattr>.type").value();
0182:                 if (type == "option") {
0183:                     option =(string)it.second.data();
0184:                 } else if (type == "wx") {
0185:                     hParent_X = (unsigned)stoi(it.second.data());
0186:                 } else if (type == "wy") {
0187:                     hParent_Y = (unsigned)stoi(it.second.data());
0188:                 }
0189:             }
0190:         //解釈失敗したら初期値設定
0191:         } catch (xml_parser_errore) {
0192:             return OPTION_INIT;
0193:         }
0194:     //読み込み失敗したら初期値設定
0195:     } catch (xml_parser_errore) {
0196:         return OPTION_INIT;
0197:     }
0198: 
0199:     //アプリケーション・ウィンドウの位置(デスクトップ範囲外なら原点移動)
0200:     HWND hDesktop = GetDesktopWindow();
0201:     WINDOWINFO windowInfo;
0202:     windowInfo.cbSize = sizeof(WINDOWINFO);
0203:     GetWindowInfo(hDesktop, &windowInfo);
0204:     if (hParent_X >= (unsigned)windowInfo.rcWindow.right) {
0205:         hParent_X = 0;
0206:     }
0207:     if (hParent_Y >= (unsigned)windowInfo.rcWindow.bottom) {
0208:         hParent_Y = 0;
0209:     }
0210: 
0211:     cout << "option=" << option << endl;
0212: 
0213:     return option;
0214: }

0216: /**
0217:  * オプションの保存
0218:  * @param   string option オプション
0219:  * @return  なし
0220:  */
0221: void saveOption(string option) {
0222:     //アプリケーション・ウィンドウの位置取得
0223:     WINDOWINFO windowInfo;
0224:     windowInfo.cbSize = sizeof(WINDOWINFO);
0225:     GetWindowInfo(hParent, &windowInfo);
0226:     hParent_X = (unsigned)windowInfo.rcWindow.left;
0227:     hParent_Y = (unsigned)windowInfo.rcWindow.top;
0228: 
0229:     //XMLファイルへ書き込む
0230:     ptree pt;
0231:     ptreechild1 = pt.add("parameter.param", option);
0232:     child1.add("<xmlattr>.type", "option");
0233:     ptreechild2 = pt.add("parameter.param", hParent_X);
0234:     child2.add("<xmlattr>.type", "wx");
0235:     ptreechild3 = pt.add("parameter.param", hParent_Y);
0236:     child3.add("<xmlattr>.type", "wy");
0237: 
0238:     cout << "option=" << option << endl;
0239: 
0240:     const int indent = 4;
0241:     write_xml(getMyPath(APPNAME) + APPNAME + ".xml", ptstd::locale(),
0242:         xml_writer_make_settings<std::string>(' ', indent));
0243: }

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

CUI用メインプログラム

0809: /**
0810:  * CUI用メインプログラム
0811:  * @param   int argc
0812:  * @paramm  char* argv[]
0813:  * @return  int リターンコード
0814:  */
0815: int main(int argccharargv[]) {
0816:     //オプション読み込み
0817:     string lopt = loadOption();
0818: 
0819:     //pahooNormalizeTextオブジェクト
0820:     pNT = new pahooNormalizeText();
0821: 
0822:     //コマンドライン・オプションの定義
0823:     options_description options("コマンドライン・オプション");
0824:     options.add_options()
0825:         ("option,o", value<std::string>()->default_value(lopt), "正規化オプション")
0826:         ("sour,s", value<std::string>(), "入力テキスト")
0827:         ("dest,d", value<std::string>(), "出力テキスト")
0828:         ("help,h", "ヘルプ")
0829:         ("version,v", "バージョン情報")
0830:         ;
0831:     //コマンドライン・オプションの取得
0832:     variables_map vm;
0833:     try {
0834:         store(parse_command_line(argcargvoptions), vm);
0835:     } catch(const boost::program_options::error_with_option_namee) {
0836:         ErrorMessage =  e.what();
0837:         cerr << ErrorMessage << endl;
0838:         return 1;
0839:     }
0840:     notify(vm);
0841: 
0842:     //正規化オプション
0843:     auto opt = vm["option"].as<string>();
0844: 
0845:     wstring wsour = L"";
0846:     wstring wdest = L"";
0847:     //ヘルプ情報
0848:     if (vm.count("help")) {
0849:         wdest = _SW(Help);
0850:     //バージョン情報
0851:     } else if (vm.count("version")) {
0852:         wdest = _SW(Version);
0853: 
0854:     //正規化実行
0855:     } else {
0856:         //入力ファイル
0857:         wsour = _SW(DEF_SOUR);
0858:         if (vm.count("sour")) {
0859:             auto infile = vm["sour"].as<string>();
0860:             ifstream ifs(infile.c_str());
0861:             if (ifs.fail()) {
0862:                 ErrorMessage = infile + " が見つかりません";
0863:                 cerr << ErrorMessage << endl;
0864:                 return 1;
0865:             }
0866:             string sour = "";
0867:             string ss;
0868:             while (getline(ifsss)) {
0869:                 sour += ss + "¥r";
0870:             }
0871:             wsour = _SW(sour);
0872:         //標準入力から
0873:         } else {
0874:             string sour;
0875:             cin >> sour;
0876:             if (sour.length() > 0) {
0877:                 wsour = _SW(sour);
0878:             }
0879:         }
0880:         wdest = pNT->normalizeText(wsouropt.c_str(), FALSE);
0881:     }
0882: 
0883:     //出力ファイル
0884:     if (vm.count("dest")) {
0885:         //改行コード置換
0886:         wregex re(_SW("¥r"));
0887:         wdest = regex_replace(wdestre_SW("¥n"));
0888:         auto outfile = vm["dest"].as<string>();
0889:         ofstream ofs(outfile.c_str());
0890:         ofs << _WS(wdest);
0891:         if (ofs.bad()) {
0892:             ErrorMessage = outfile + " への書き込みに失敗しました";
0893:             cerr << ErrorMessage << endl;
0894:             return 1;
0895:         }
0896:         ofs.close();
0897:     //標準出力へ
0898:     } else {
0899:         //改行コード置換
0900:         wregex re(_SW("¥r"));
0901:         wdest = regex_replace(wdestre_SW("¥n"));
0902:         cout << _WS(wdest);
0903:     }
0904: 
0905:     //オブジェクト解放
0906:     delete pNT;
0907: 
0908:     //オプション保存
0909:     saveOption(lopt);
0910: 
0911:     return 0;
0912: }

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

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

参考サイト

(この項おわり)
header