やったこと
C++でXMLを操作する機会があったのでboostのBoost Property Tree Libraryを試してみました。
環境
- VS2022 C++17
- boost 1.86.0
まずはReadしてみる
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>.属性名
で取得できる(削除もできる)
コード
#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;
}
}
結果
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を使う方法
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
でもできるけどセパレータを考慮する必要がある
コード
#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;
}
}
結果
orange
次にWriteしてみる
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
を渡すことでインデントやエンコーディングを指定できる
コード
#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 version="1.0" encoding="utf-8"?>
<root>
<sample2>
<child0>banana</child0>
<child1 color="blue">apple</child1>
<child3 color="yellow">lemon</child3>
</sample2>
</root>
結果(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も扱えるようなので機会があれば使ってみたいです。