OpenCppCoverageの文字化けを直す(2)

# 0. 前回のあらすじ

自作の C プログラムのテストにカバレッジ測定が必要となった。オープンソースの OpenCppCoverage を使いたいが，HTML レポートを出力すると日本語が文字化けしてしまうらしい。文字化けを修正するためにもまず自力でソースコードからバイナリをビルドできるようにした。そして前回は文字化けの原因究明まで行った。

https://qiita.com/TETSURO1999/items/f8a6ab40ec1e51fc8d96?1

# 1. やりたいことの整理

1. バージョンを 0.9.9.0 から 0.9.9.1 に変える。
2. コマンドプロンプト実行時の日本語パス名の文字化け対策
3. ソースコード中のタブ幅をコマンドラインオプションから指定できるようにする。
4. HTML レポートの日本語文字化け対策
   - ソースコードに BOM がある場合はそれを優先する。
   - BOM が無い場合，コマンドラインオプションで指定したコードページが適用される。
     デフォルトのコードページは CP_ACP（ANSI コードページ）とする。

# 2. 具体的な変更内容
## 2.1 バージョンの変更

HTML レポートに OpenCppCoverage のバージョンが記載されるので，世間一般に出回っているものとは違うものを使ったことを示すため（誠に勝手ながら）バージョンを書き換えることにした。Visual Studio での変更方法が分からんのでテキストエディタで以下のように直接プロパティシートを書き換えた。

```diff:PropertySheets\Default.props
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup />
  <ItemDefinitionGroup>
    <ClCompile>
      <AdditionalIncludeDirectories>$(SolutionDir);$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
-     <PreprocessorDefinitions>OPENCPPCOVERAGE_VERSION="0.9.9.0";_CRT_SECURE_NO_WARNINGS;PROJECT_DIR=R"|($(ProjectDir))|";SOLUTION_DIR=R"|($(SolutionDir))|";TARGET_FILE_NAME=R"|($(TargetFileName))|";OUT_DIR=R"|($(OutDir))|";NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+     <PreprocessorDefinitions>OPENCPPCOVERAGE_VERSION="0.9.9.1";_CRT_SECURE_NO_WARNINGS;PROJECT_DIR=R"|($(ProjectDir))|";SOLUTION_DIR=R"|($(SolutionDir))|";TARGET_FILE_NAME=R"|($(TargetFileName))|";OUT_DIR=R"|($(OutDir))|";NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <TreatWarningAsError>true</TreatWarningAsError>
      <UseFullPaths>true</UseFullPaths>
      <ConformanceMode>true</ConformanceMode>
      <LanguageStandard>stdcpp17</LanguageStandard>
    </ClCompile>
  </ItemDefinitionGroup>
  <ItemGroup />
</Project>
```
## 2.2. コマンドプロンプト実行時の日本語パス名の文字化け対策

OpenCppCoverage をコマンドプロンプトから使用すると，オプション設定や処理過程のログを表示してくれるのだが，困ったことにソースコードのパス名などに日本語を使うとログが文字化けしてしまう。

そもそもの話として，ソースコードのファイル名に日本語を使うな！というのはある。もちろん筆者はソースコードのファイル名に日本語を使ったこともないし見たこともないが，フォルダ名に日本語が使われていることは時々あるのだ。そうするとコンソールに出力されるログの文字が化けてしまう。ただ，ファイルに出力されるログファイルのほうは問題ないし，コマンドプロンプトのコードページをUTF-8 に切り替える（`CHCP 65001` と打つ）ことによっても解決する。<font color=magenta><b>だが，直したい！</b></font>

ログ出力の管理に Boost.Log を使っていることが分かったときは恐怖したが，幸いなことに Boost c++ ライブラリ本体ではなく，OpenCppCoverage 側の修正，それも僅かな修正で済んだ。

まず，コンソール出力のロケールを UTF-8 に変更していたので止めさせた。

```diff:Tools\Log.cpp より変更箇所を抜粋
void InitConsoleAndFileLog(const std::filesystem::path& logPath)
{		
	boost::log::add_common_attributes();
	auto fileSink = logging::add_file_log(logPath.wstring(),
		boost::log::keywords::format =
		expr::stream
		<< "[" << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S")
		<< "] [" << logging::trivial::severity
		<< "] " << expr::message);
	auto consoleSink = logging::add_console_log(std::clog,
		boost::log::keywords::format =
		expr::stream
		<< "[" << logging::trivial::severity
		<< "] " << expr::message
		);
	// Set correct endocing for special char
	auto loc = boost::locale::generator()("en_US.UTF-8");
	fileSink->imbue(loc);
-	consoleSink->imbue(loc);
}
```

次に `main()` の冒頭にロケール設定を追加した。これだけで解決できた。

```diff:OpenCppCoverage\main.cpp より変更箇所を抜粋
int main(int argc, const char* argv[])
{
+	std::locale::global(std::locale(""));
	Tools::CreateMiniDumpOnUnHandledException();
	try
	{
		OpenCppCoverage::OpenCppCoverage openCppCoverage;
		return openCppCoverage.Run(argc, argv, &std::wcerr);
	}
	catch (const std::exception& e)
	{
		std::cerr << "Error: " << e.what() << std::endl;
	}
	catch (...)
	{
		std::cerr << "Unknown error" << std::endl;
	}
	return OpenCppCoverage::FailureExitCode;
}
```

:::note
PowerShell とか Windows Terminal では動作未確認である。
:::

## 2.3. ソースコードのタブ幅指定 <font color=magenta>※コレはかなり大変!!</font>

筆者はスペース幅４文字のハードタブ派であり，自分の書いたソースコードの大半はそうなっているのだが，HTML レポート出力させると８文字幅になっている。これを４文字幅に変更するには技術的にはさほど難しくはなく，HTML ファイルあるいは同時に出力されるスタイルシートにタブ幅指定を追加すれば良いだけだ。

まず HTML ファイルのテンプレートを次のように書き換えた。あとは指定したタブ幅に応じてテンプレートの `TAB_STOPS` を書き変えれば良い。

```diff:Exporter\Html\Template\SourceTemplate.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8"/>
        <title>{{TITLE}}</title>
        <link href="../../third-party/google-code-prettify/prettify-CppCoverage.css" type="text/css" rel="stylesheet" />
        <script type="text/javascript" src="../../third-party/google-code-prettify/prettify.js"></script>
    </head>
    <body onload="{{BODY_ON_LOAD}}">
        <h4>{{SOURCE_WARNING_MESSAGE}}</h4>
-       <pre class="prettyprint lang-cpp linenums">{{CODE}}</pre>
+       <pre class="prettyprint lang-cpp linenums" style="tab-size: {{TAB_STOPS}};">{{CODE}}</pre>
        <hr />
        <table width="100%">
            <thead>
                <tr>
                    <th align="center">
                        <small>Generated by</small>
                        <a href="{{OCC_PROJECT_LINK}}">
                            <strong>OpenCppCoverage (Version: {{OCC_VERSION}})</strong>
                        </a>
                    </th>
                </tr>
            </thead>
        </table>
    </body>
</html>
```

コマンドラインオプションでタブ幅を変更できるよう，以下のコードを追加した。なお，説明の都合上，後で説明する HTML 出力の文字化け対策用のオプションも一緒に追加した。

```diff:CppCoverage\ProgramOptions.hpp より変更箇所を抜粋
	static const std::string ExcludedLineRegexOption;
	static const std::string SubstitutePdbSourcePathOption;
+	static const std::string EncodingOption;
+	static const std::string EncodingShortOption;
+	static const std::string TabStopsOption;
+	static const std::string TabStopsShortOption;
	explicit ProgramOptions(const std::vector<std::unique_ptr<IOptionParser>>&);
```

コマンドラインオプションの管理には Boost C++ ライブラリの Boost.program_options を使用しており，オプション名（短縮形を含む）および説明文を追記した。

```diff:CppCoverage\ProgramOptions.cpp より変更箇所を抜粋
	for (const auto& optionParser : optionParsers)
		optionParser->AddOption(options);
+	options.add_options()
+		((ProgramOptions::EncodingOption + "," + ProgramOptions::EncodingShortOption).c_str(),
+		po::value<int>()->default_value(0),
+		"Encoding of imported source files.\n  0:AP_ACP, 932:SHIFT_JIS, 51932:EUC-JP\n  1200:UTF16LE, 1201:UTF16BE, 65001:UTF8")
+		((ProgramOptions::TabStopsOption + "," + ProgramOptions::TabStopsShortOption).c_str(),
+		po::value<int>()->default_value(8),
+		"Tabstops of imported source files.");
}

/* 中略 */

	const std::string ProgramOptions::SubstitutePdbSourcePathOption = "substitute_pdb_source_path";
	const std::string ProgramOptions::StopOnAssertOption = "stop_on_assert";
+	const std::string ProgramOptions::EncodingOption = "encoding";
+	const std::string ProgramOptions::EncodingShortOption = "e";
+	const std::string ProgramOptions::TabStopsOption = "tabstops";
+	const std::string ProgramOptions::TabStopsShortOption = "t";
```

上記の `ProgramOptions` はあくまで Boost.program_options 用のクラスであり，本プログラムのオプション管理クラス `Options` にも追加した。Setter/Getter を介してアクセスする作法のようなので，それに合わせた。

```diff:CppCoverage\Options.hpp より変更箇所を抜粋
	void SetLogLevel(LogLevel);
	LogLevel GetLogLevel() const;
+	void SetCodePage(int);
+	int GetCodePage() const;
+	void SetTabStops(int);
+	int GetTabStops() const;
	void EnablePlugingMode();
	bool IsPlugingModeEnabled() const;

/* 中略 */

	LogLevel logLevel_;
+	int codePage_;
+	int tabStops_;
	bool isPluginModeEnabled_;
```

```diff:CppCoverage\Options.cpp より変更箇所を抜粋
	//-------------------------------------------------------------------------
	LogLevel Options::GetLogLevel() const
	{
		return logLevel_;
	}
+	//-------------------------------------------------------------------------
+	void Options::SetCodePage(int codePage)
+	{
+		codePage_ = codePage;
+	}
+	//-------------------------------------------------------------------------
+	int Options::GetCodePage() const
+	{
+		return codePage_;
+	}
+	//-------------------------------------------------------------------------
+	void Options::SetTabStops(int tabStops)
+	{
+		tabStops_ = tabStops;
+	}
+	//-------------------------------------------------------------------------
+	int Options::GetTabStops() const
+	{
+		return tabStops_;
+	}
	//-------------------------------------------------------------------------
	void Options::EnablePlugingMode()
	{
		isPluginModeEnabled_ = true;
	}
```

Boost.program_options によるコマンドラインオプションの解析結果を本プログラムのオプション管理クラス `Options` にも反映させた。

```diff:CppCoverage\OptionsParser.cpp より変更箇所を抜粋
	auto modulePatterns =
	    GetPatterns(variablesMap,
	                ProgramOptions::SelectedModulesOption,
	                ProgramOptions::ExcludedModulesOption);
	auto sourcePatterns =
	    GetPatterns(variablesMap,
	                ProgramOptions::SelectedSourcesOption,
	                ProgramOptions::ExcludedSourcesOption);
+	int codePage = variablesMap.GetValue<int>(ProgramOptions::EncodingOption);
+	int tabStops = variablesMap.GetValue<int>(ProgramOptions::TabStopsOption);
	auto optionalStartInfo = GetStartInfo(variablesMap);

/* 中略 */

	if (isVerbose)
		options.SetLogLevel(LogLevel::Verbose);
	if (isQuiet)
		options.SetLogLevel(LogLevel::Quiet);
+	options.SetCodePage(codePage);
+	options.SetTabStops(tabStops);
	if (variablesMap.IsOptionSelected(ProgramOptions::CoverChildrenOption))
		options.EnableCoverChildrenMode();
```

本来，カバレッジの測定結果を HTML 形式で出力する `HtmlExporter` クラスのみにオプション情報を伝えれば良いのだが，出力形式を選択できるようになっており（複数選択も可），いったん抽象クラス `IExporter` を継承する形で各形式のクラスが作られている。このため，大元の抽象クラスにオプション情報を伝えるメソッド `Config()` を追加した。

```diff:Exporter\Html\IExporter.hpp より変更箇所を抜粋
public:
	IExporter() = default;
	virtual std::filesystem::path GetDefaultPath(const std::wstring& prefix) const = 0;
	virtual void Export(const Plugin::CoverageData&, const std::filesystem::path& output) = 0;
+	virtual void Config(const CppCoverage::Options& options) = 0;
```

レポート出力（エクスポート）する直前にオプション情報を伝えるメソッド `Config()` を呼び出す。

```diff:OpenCppCoverage\OpenCppCoverage.cpp より変更箇所を抜粋
	const auto& exporter = exporters.at(exportType);
	auto output =
	    (parameter)
	        ? fs::path{*parameter}
	        : exporter->GetDefaultPath(defaultPathPrefix);
+	exporter->Config(options);
	exporter->Export(coverage, output);
```
バイナリ形式出力クラス `BinaryExporter` では `Config()` の中身は空である。

```diff:Exporter\Binary\BinaryExporter.hpp より変更箇所を抜粋
public:
	BinaryExporter() = default;
	std::filesystem::path GetDefaultPath(const std::wstring& prefix) const override;
	void Export(const Plugin::CoverageData&, const std::filesystem::path& output) override;
+	void Config(const CppCoverage::Options& options) override;
```

```diff:Exporter\Binary\BinaryExporter.cpp より変更箇所を抜粋
+	void BinaryExporter::Config(const CppCoverage::Options& options)
+	{
+	}
```

同様に Cobertura 形式出力クラス `CoberturaExporter` も `Config()` の中身はない。

```diff:Exporter\CoberturaExporter.hpp より変更箇所を抜粋
public:
	CoberturaExporter() ;
	std::filesystem::path GetDefaultPath(const std::wstring& runningCommandFilename) const override;
	void Export(const Plugin::CoverageData&, const std::filesystem::path& output) override;
	void Export(const Plugin::CoverageData&, std::wostream&) const;
+	void Config(const CppCoverage::Options& options) override;
```

```diff:Exporter\CoberturaExporter.cpp より変更箇所を抜粋
+	void CoberturaExporter::Config(const CppCoverage::Options& options)
+	{
+	}
```

HTML 形式出力クラス `HtmlExporter` だけは `Config()` を真面目に実装する必要があり，コードページとタブ幅の設定値を受け取り，さらに `HtmlExporter` のメンバである `fileCoverageExporter_` と `exporter_` に引き渡す。この頃にはすっかり面倒になっていて Setter/Getter 使わず，public メンバにして直接書き込んだ。

```diff:Exporter\Html\HtmlExporter.hpp より変更箇所を抜粋
public:
	explicit HtmlExporter(const std::filesystem::path& templateFolder);
	std::filesystem::path GetDefaultPath(const std::wstring& prefix) const override;
	void Export(const Plugin::CoverageData&, const std::filesystem::path& outputFolder) override;
+	void Config(const CppCoverage::Options& options) override;
```

```diff:Exporter\Html\HtmlExporter.cpp より変更箇所を抜粋
+	void HtmlExporter::Config(const cov::Options& options)
+	{
+		fileCoverageExporter_.codePage = options.GetCodePage();
+		exporter_.tabStops = options.GetTabStops();
+	}
```

で，受け取ったタブ幅の情報を HTML ファイルのテンプレートに書き込む。

```diff:Exporter\Html\TemplateHtmlExporter.hpp より変更箇所を抜粋
	static const std::string OCCVersion;
	static const std::string ActualProjectLink;
+	static const std::string TabStops;
+	int tabStops;
```

```diff:Exporter\Html\TemplateHtmlExporter.cpp より変更箇所を抜粋
	const std::string TemplateHtmlExporter::OCCVersion = "OCC_VERSION";
	const std::string TemplateHtmlExporter::ActualProjectLink = "https://github.com/OpenCppCoverage/OpenCppCoverage/releases";
+	const std::string TemplateHtmlExporter::TabStops = "TAB_STOPS";

/* 中略 */

void TemplateHtmlExporter::GenerateSourceTemplate(
	const std::wstring& title,
	const std::wstring& codeContent,
	bool enableCodePrettify,
	const fs::path& output) const
{
	auto titleStr = ToString(title);
	ctemplate::TemplateDictionary dictionary(titleStr);
	std::string bodyLoad = BodyOnLoadFct;
	std::string warning = "";
	if (!enableCodePrettify)
	{
		bodyLoad = "";
		warning = SyntaxHighlightingDisabledMsg;
	}
	dictionary.SetValue(OCCVersion, OPENCPPCOVERAGE_VERSION);
+	dictionary.SetValue(TabStops, std::to_string(tabStops));
	WriteTemplate(dictionary, fileTemplatePath_, output);
}
```

とタブ幅の変更はひたすら面倒なだけで，C++ 入門者レベルの筆者でもなんとか実装することができた。

## 2.4. HTML レポートの日本語文字化け対策

ここでの注意事項（要するに苦労したこと）を以下に示す。

- BOM を判別するため，いったん `char` 型の配列で読み込んだ後 `wchar_t` 型の配列に変換し，さらに `std::wstring` に変換する。このとき文字列の末尾を示すヌル文字 `\0` が必要なので，最初にファイルを読み込んだ際に末尾にヌル文字 `\0` を２個追加する。Unicode／UnicodeBig Endian の場合に備えて２個とした。その後，さらにストリーム型 `std::wistringstream` に変換する。これは既存のコードの変更量を抑えるため。
- 当初 C++ らしく C++ 11 で導入された `std::codecvt` を使用するつもりだったが，C++ 17 から非推奨とのことなので，わざわざ Windows API の `MultiByteToWideChar()` を使うハメになった。しかし，`MultiByteToWideChar()` は Unicode もしくは Unicode BigEndian から変換できないことが発覚して少し慌てた。そもそも Unicode は変換不要であり，Unicode BigEndian もバイトオーダを入れ替えるだけで良い。しかし，`MultiByteToWideChar()` で変換できると他のコードページの処理と統合できるのでシンプルになる。だが，どうやらできないようである。このためバイトオーダを入れ替える処理をわざわざ書くハメになった。CRT の `swab()` を使う手もあったが，いまさら CRT を使うのって抵抗あるよね。
- BOM を判別するためファイルをバイナリ形式で読んでいる。このため Unicode に変換した後，キャリッジリターン `\r` を取り除いている。これをしないと `std::getline()` で行を読み出すとき，行の末尾にキャリッジリターン `\r` が付いてしまう。この結果，HTML レポートにも `\r` が付いてしまうのだ。

変更箇所を以下に示す。既存のコードをできるだけ流用して変更箇所を少なくするため，指定されたファイル名の内容を読み取り，文字コードの変換を行って `std::wistreamstring` 型で返す関数 `GetStringStream()` を新規作成し，これを呼び出すようにした。

```diff:Exporter\Html\HtmlFileCoverageExporter.cpp より変更箇所を抜粋
	bool HtmlFileCoverageExporter::Export(
		const Plugin::FileCoverage& fileCoverage,
		std::wostream& output) const
	{
		auto filePath = fileCoverage.GetPath();
-		std::wifstream ifs{filePath.string()};
-		if (!ifs)
-			THROW(L"Cannot open file : " + filePath.wstring());
+		auto ifs = GetStringStream(filePath.wstring(), codePage);
		std::wstring line;
		const Plugin::LineCoverage* previousLineCoverage = nullptr;
		int styleChangesCount = 0;
		int lineCount = 0;
		for (int i = 1; std::getline(ifs, line); ++i)
		{
			auto lineCoverage = fileCoverage[i];
			line = boost::spirit::classic::xml::encode(line);
			if (AddLineCoverageColor(output, line, lineCoverage, previousLineCoverage))
				++styleChangesCount;
			++lineCount;
			previousLineCoverage = lineCoverage;
		}
		AddEndStyleIfNeeded(output, previousLineCoverage);
		output.flush();
		return MustEnableCodePrettify(lineCount, styleChangesCount);
	}
```

新規作成した `GetStringStream()` の内容を以下に示す。

```c++:Exporter\Html\HtmlFileCoverageExporter.cpp より新規追加箇所を抜粋
std::wistringstream GetStringStream(std::wstring filename, int codepage)
{
	const int CodePageDefault   = 0;
	const int CodePageUnicode   = 1200;
	const int CodePageBigEndian = 1201;
	const int CodePageUTF8      = 65001;
	// ファイルをバイナリ形式で一括読み込む
	std::ifstream ifs(filename, std::ios_base::in | std::ios_base::binary);
	if (ifs.fail())
		THROW(L"Cannot open file : " + filename);
	int	size = (int)ifs.seekg(0, std::ios::end).tellg();
	ifs.seekg(0, std::ios::beg);
	char* buf = new char[size + 2];
	ifs.read(buf, size);
	ifs.close();
	// 終端文字を書き込む
	buf[size++] = '\0';
	buf[size++] = '\0';
	// BOM チェック
	int start, encode;
	if (size >= 3 && buf[0] == -17 && buf[1] == -69 && buf[2] == -65) {
		start = 3; encode = CodePageUTF8;
	} else if (buf[0] == -1 && buf[1] == -2) {
		start = 2; encode = CodePageUnicode;
	} else if (buf[0] == -2 && buf[1] == -1) {
		start = 2; encode = CodePageBigEndian;
	} else {
		start = 0; encode = codepage;
	}
	// 文字コード変換
	int wsize;
	wchar_t* wbuf;
	if (encode == CodePageUnicode) {
		wsize = (size - start) / 2;
		wbuf = new wchar_t[wsize];
		char* p = (char*)wbuf;
		for (int i = start; i < size; i += 2) {
			*p++ = buf[i];
			*p++ = buf[i + 1];
		}
	} else if (encode == CodePageBigEndian) {
		wsize = (size - start) / 2;
		wbuf = new wchar_t[wsize];
		char* p = (char*)wbuf;
		for (int i = start; i < size; i += 2) {
			*p++ = buf[i + 1];
			*p++ = buf[i];
		}
	} else {
		wsize = MultiByteToWideChar(encode, 0, &buf[start], -1, nullptr, 0);
		if (wsize == 0)
			THROW(L"Error in MultiByteToWideChar.");
		wbuf = new wchar_t[wsize];
		if (!MultiByteToWideChar(encode, 0, &buf[start], -1, wbuf, wsize))
			THROW(L"Error in MultiByteToWideChar.");
	}
	// キャリッジリターン \r を削除する
	wchar_t* wp = wbuf;
	for (int i = 0; i < wsize; i++)
		if (wbuf[i] != L'\r') *wp++ = wbuf[i];
	std::wistringstream wis(wbuf);
	delete[] wbuf;
	delete[] buf;
	return wis;
}
```
いやあ・・・オレのコードって C++ らしくないなあ・・・と思った。

# 3. 実行結果

OpenCppCoverage をコマンドプロンプトから引数なしで実行すると下記のようにヘルプメッセージが表示される。ご覧の通り，バージョン 0.9.9.1 となっており，最後の２つのオプションが筆者が独自に追加したものである。

```
You must specify a program to execute or use --input_coverage
OpenCppCoverage Version: 0.9.9.1

Usage: [options] -- program_to_run optional_arguments:

Command line only:
  -v [ --verbose ]                  Verbose mode.
  -q [ --quiet ]                    Quiet mode.
  -h [ --help ]                     Show help message.
  --config_file arg                 Filename of a configuration file.

Command line and configuration file:
  --modules arg (=*)                The pattern that module's paths should
                                    match. Can have multiple occurrences.
  --excluded_modules arg            The pattern that module's paths should NOT
                                    match. Can have multiple occurrences.
  --sources arg (=*)                The pattern that source's paths should
                                    match. Can have multiple occurrences.
  --excluded_sources arg            The pattern that source's paths should NOT
                                    match. Can have multiple occurrences.
  --input_coverage arg              A output path of export_type=binary. This
                                    coverage data will be merged with the
                                    current one. Can have multiple occurrences.
  --working_dir arg                 The program working directory.
  --cover_children                  Enable code coverage for children
                                    processes.
  --no_aggregate_by_file            Do not aggregate coverage for same file
                                    path.
  --stop_on_assert                  Do not continue after DebugBreak() or
                                    assert().
  --unified_diff arg                Format: <unifiedDiffPath>?<rootFolder>
                                    <unifiedDiffPath> path of the unified diff
                                    file. Git users can use git diff output.
                                    <rootFolder> (optional) root folder for
                                    paths in the diff file.
                                    See documentation for limitations.
  --continue_after_cpp_exception    Try to continue after throwing a C++
                                    exception.
  --optimized_build                 Enable heuristics to support optimized
                                    build. See documentation for restrictions.
  --excluded_line_regex arg         Exclude all lines match the regular
                                    expression. Regular expression must match
                                    the whole line.
  --substitute_pdb_source_path arg  Substitute the starting path defined in the
                                    pdb by a local path.
                                    Format: <pdbStartPath>?<localPath>. Can
                                    have multiple occurrences.
  --export_type arg (=html)         Format: <exportType>[:<parameter>].
                                    <exportType> can be:
                                       html: output folder (optional)
                                       cobertura: output file (optional)
                                       binary: output file (optional)
                                       TestCoverageSharedLib: output file
                                    (optional)
                                    Example: html:MyFolder\MySubFolder
                                    This flag can have multiple occurrences.
  -e [ --encoding ] arg (=0)        Encoding of imported source files.
                                      0:AP_ACP, 932:SHIFT_JIS, 51932:EUC-JP
                                      1200:UTF16LE, 1201:UTF16BE, 65001:UTF8
  -t [ --tabstops ] arg (=8)        Tabstops of imported source files.
```

続いて実行中の様子。ソースコードのフォルダ名に日本語が使われているが，文字化けは起こらない。

```
[info] Start Program:
Path:"TESTPRIME.EXE"
Arguments:634715672521
Working directory: not set.
Modules: Selected: * Excluded:
Sources: Selected: D:\Work\素因数分解のテスト Excluded:
Log Level: Normal
Cover Children: 0
Aggregate by file: 1
Continue after C++ exception: 0
Optimized build support: 0
Export: html
Input coverage:
Unified diff:
Excluded line regular expressions:
Substitute pdb source paths:
[info] Module: D:\Work\素因数分解のテスト\TESTPRIME.exe is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\ntdll.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\SysWOW64\ntdll.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\wow64.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\wow64win.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\System32\wow64cpu.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\SysWOW64\kernel32.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\SysWOW64\KernelBase.dll is selected because it matches selected pattern: *
整数 634715672521 は合成数です。
7,11887,7627969 を素因数に持ちます。
[info] Module: C:\Windows\SysWOW64\kernel.appcore.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\SysWOW64\msvcrt.dll is selected because it matches selected pattern: *
[info] Module: C:\Windows\SysWOW64\rpcrt4.dll is selected because it matches selected pattern: *
[info] ----------------------------------------------------
[info] Coverage generated in Folder D:\Work\素因数分解のテスト\CoverageReport-2023-09-24-08h46m07s
[info] ----------------------------------------------------
[info] The code coverage report is not what you expect? See the FAQ https://github.com/OpenCppCoverage/OpenCppCoverage/wiki/FAQ.
[error] Your program stop with error code: 1
```

HTML レポートを以下に示す。バージョン名が変更されたことが明示されている。ちなみに下記のソースコードは Shift_JIS，ハードタブ４文字幅で記述されている。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3490655/c3124942-5d0e-52fa-26e8-3fe68807364c.png">

# 4. おまけ

テストしているときに気づいたこと。

今回，テスト対象となるソースコードの文字コードとして Shift JIS，UTF-8 BOM 無し，UTF-8 BOM 付きなどを試した。このうち UTF-8 BOM 無しをコンパイルするとそのままではエラーになるが，コンパイルオプションで使用言語として UTF-8 を指定する，具体的には `/source-charset:utf-8` を追加することでコンパイルエラーを回避できる。

で，あれば前回の記事では日本語環境下で文字化けする一部のソースコードをわざわざ Western（Windows 1252）で開いて UTF-8 で保存し直すという変更作業を施したが，コンパイルオプションで使用言語を指定できるのであれば，このような変更作業は不要である。そのことに気づき，前回の記事に修正を加えた。

なお本プログラムのライセンスは GPLv3 なのでソースコード一式を公開する。
[https://drive.google.com/file/d/1_Do5eoiJEY-Kf9uk9sIgN4vR6VWcPS9Z/view?usp=sharing](https://drive.google.com/file/d/1_Do5eoiJEY-Kf9uk9sIgN4vR6VWcPS9Z/view?usp=sharing)

:::note warn
2023-09-24 リリーズ版は Unicode／Unicode BigEndian 時のバイトオーダ交換処理に不具合があります。2023-09-25 リリース版以降ではこの不具合は修正されています。
:::

# 5. 参考情報

- [Boost勉強会#9つくばでの発表スライド - slideshare](https://www.slideshare.net/nyaocat/boost-jp9-programoptions)
- [boost::program_optionsでは短いオプション名の指定に注意 - Qiita](https://qiita.com/yakigac/items/f686e8cec21b3d3c7b8a)
- [C++ Boostによるコマンドライン引数処理 - トライフィールズ ](https://www.trifields.jp/cpp-boost-program-options-950)
- [C++でShift-JIS, UTF-8, UTF-16 BOM有無とエンディアンを考慮してファイルの読み込みをする - はてなブログ](https://kagasu.hatenablog.com/entry/2017/05/02/120156)
- [VC++でマルチバイト文字列とワイド文字列を簡単に変換するライブラリを作ってみた - Qiita](https://qiita.com/Sugar_8/items/e35aae130fbc92fe4103)
- [C++(Visual Studio)でUTF-8を扱うための試行錯誤のメモ - Qiita](https://qiita.com/BlueSilverCat/items/1e5af7fdaeaf8fd2289d)
- [utf8 のファイルの読み込み - Qiita](https://qiita.com/AsladaGSX/items/af50066dbbfd59991af9)
- [C++のstreamでbom skipする方法をまた忘れないうちに書き留める - Qiita](https://qiita.com/yumetodo/items/3744afa94ab029f4e1d5)