C++とboostでXMLを操作してみる

# やったこと
C++でXMLを操作する機会があったのでboostのBoost Property Tree Libraryを試してみました。

# 環境
* VS2022 C++17
* boost 1.86.0

# まずはReadしてみる
XMLはこんな感です。
```xml:sample0.xml
<?xml version="1.0" encoding="utf-8"?>
<root>
  <sample0>
    <child0 color="yellow" id="0">banana</child0>
    <child1 color="red" id="1">apple</child1>
    <child2 color="" id="2">
      <item>melon</item>
      <item>water melon</item>
    </child2>
  </sample0>
</root>
```

コードは以下の通り。
* 存在しない要素や属性をgetしてしまうと例外が発生する
* ```get_optional()```のように～optionalが付く関数を使うと、getできなかった場合に```boost::optional```の無効値が返される
* 属性を取得したい場合は```<xmlattr>.属性名```で取得できる（削除もできる）

```cpp:コード
#include <iostream>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>

using namespace boost::property_tree;
using namespace boost::property_tree::xml_parser;

int main()
{
	// XMLファイルを読み込む
	ptree pt;
	read_xml("sample0.xml", pt, trim_whitespace);

    // <sample0>の子要素を取得     
	if (auto opt = pt.get_child_optional("root.sample0"))
	{	
		for (auto& child : opt.value())
		{
            // 要素名 取得
			std::cout << child.first.c_str() << std::endl;	

			// 属性color 取得
			if (auto opt = child.second.get_optional<std::string>("<xmlattr>.color"))			
			{
				std::cout << "\t" << opt.value() << std::endl;
			}
			else
			{
				// 指定した属性がなかったとき
				std::cout << "\t" << "属性なし" << std::endl;
			}

			// 属性id 取得
			if (auto opt = child.second.get_optional<std::string>("<xmlattr>.id"))
			{
				std::cout << "\t" << opt.value() << std::endl;
			}
            // elseは省略   

			// 要素の値
			if (auto opt = child.second.get_value_optional<std::string>())
			{
				std::cout << "\t" << opt.value() << std::endl;
			}
			
			// さらに子要素を取得			
			if (auto opt = child.second.get_child_optional(""))
			{
				opt.value().erase("<xmlattr>");		// 属性情報を削除
				for (auto& item : opt.value())
				{
					std::cout << "\t" << item.second.get_value<std::string>() << std::endl;
				}
			}
		}
	}
	else
	{
		std::cout << "<root>.<sample0>が存在しない." << std::endl;
	}
}
```

```txt:結果
child0
         color = yellow
         id = 0
         value = banana
child1
         color = red
         id = 1
         value = apple
child2
         color =
         id = 2
         value =
        melon
        water melon
```

# property_tree::pathを使う方法

```xml:sample1.xml
<?xml version="1.0" encoding="utf-8"?>
<root>
  <sample1>
    <child0>
      <child1>
        <child2>orange</child2>
      </child1>
    </child0>
  </sample1>
</root>
```

* パスを連結する場合は```property_tree::path```を使うとちょっと便利
* ```std::string```でもできるけどセパレータを考慮する必要がある

```cpp:コード
#include <iostream>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>

using namespace boost::property_tree;
using namespace boost::property_tree::xml_parser;

int main()
{	
	ptree pt;
	read_xml("sample1.xml", pt, trim_whitespace);

    // property_tree::pathを使う
	path p1 = "root.sample1.child0.child1"; 
	path p2 = "child2";
	path p3 = p1 /= p2;

	// これでもできるけど...
	//std::string p1 = "root.sample1.child0.child1";
	//std::string p2 = "child2";
	//std::string p3 = p1 + "." + p2;
 
	if (auto opt = pt.get_optional<std::string>(p3))
	{
		std::cout << opt.value() << std::endl;
	}
}
```

```txt:結果
orange
```

# 次にWriteしてみる
XMLはこんな感です。

```xml:sample2.xml
<?xml version="1.0" encoding="utf-8"?>
<root>
  <sample2>
    <child0>melon</child0>
    <child1 color="red">apple</child1>
    <child2/>
  </sample2>
</root>
```

* ```put```が変更、```add```が追加
* Writeするときに、```xml_writer_make_settings```を渡すことでインデントやエンコーディングを指定できる 

```cpp:コード
#include <iostream>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>

using namespace boost::property_tree;
using namespace boost::property_tree::xml_parser;

int main()
{
	ptree pt;
	read_xml("sample2.xml", pt, trim_whitespace);	

	// 要素の値を変更
	pt.put("root.sample2.child0", "banana");
	
	// 要素の属性を変更
	pt.put("root.sample2.child1.<xmlattr>.color", "blue");
	
	if (auto opt = pt.get_child_optional("root.sample2"))
	{
		// 要素の削除
		opt.value().erase("child2");
	}
	
	if (auto opt = pt.get_child_optional("root.sample2"))
	{
		// 要素の追加
		 ptree& child = opt.value().add("child3", "lemon");

		 // 属性の追加
		 child.put("<xmlattr>.color", "yellow");
	}
 
	const int indent = 2;
	write_xml<ptree>("sample2.xml", pt, std::locale(),
		xml_writer_make_settings<std::string>(' ', indent));}

    // xml_writer_make_settingsを渡さないと...
	// write_xml<ptree>("sample2.xml", pt);

  
```

```xml:結果
<?xml version="1.0" encoding="utf-8"?>
<root>
  <sample2>
    <child0>banana</child0>
    <child1 color="blue">apple</child1>
    <child3 color="yellow">lemon</child3>
  </sample2>
</root>
```

```xml:結果（xml_writer_make_settingsなし）
<?xml version="1.0" encoding="utf-8"?>
<root><sample2><child0>banana</child0><child1 color="blue">apple</child1><child3 color="yellow">lemon</child3></sample2></root>
```


# まとめ
昔（どの位昔かは秘密）はもっと苦労してRead/Writeしていたような気がしますが、とても簡単になりましたね。戻り値でboost::optionalが受け取れるのも使いやすいです。Boost Property Tree Libraryは、iniやjsonも扱えるようなので機会があれば使ってみたいです。

# 参考
[Boost逆引きリファレンス  XMLの読み込み／書き込み](https://boostjp.github.io/tips/xml.html)