- 预设
- 关于ptree的数据结构
- 写XML
- 示例
- 单个节点:指定路径写入
- 多个子节点:列表写入
- 写入标签属性
- 写入注释
- XML的输入输出
- 文件
- 字符流
- 格式化与去格式化
- 打印ptree
- 读XML
- 判断节点是否存在
- 单个节点读取
- 子节点遍历
- 读写JSON
- 示例
- 写数组(列表)
- 读JSON
预设
通过测试的Boost版本:
版本号 |
编译器 |
平台 |
IDE |
语言 |
1.57.0 |
v100/v140/v142 |
x86/x64 |
VS2010/VS2019 |
C/C++ |
1.75.0 |
v100/v140/v142 |
x86/x64 |
VS2010/VS2019 |
C/C++ |
关于ptree的数据结构
一个ptree表示一棵树,其自身是不具名的,只能对其中的数据、节点进行操作。因此,它可以保存的数据类型包括:数据字符串、一个节点、多个节点(列表)。在JSON和XML中都有结构树的概念,因此boost中使用ptree同时表示JSON和XML数据。
表示XML时,ptree还可以存放注释、节点属性等,分别由<xmlcomment>
和<xmlattr>
索引。
写XML
示例
要得到的xml文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?xml version="1.0" encoding="utf-8"?> <root version="v1.0"> <node id="single-node"> Single Node <sub type="string">Hello World</sub> <list> <item id="1">1</item> <item id="2">2</item> <item id="3">3</item> </list> </node> <node-list> <node id="node-1"> <sub>Node 1</sub> </node> <node id="node-2"> <sub>Node 2</sub> </node> <node id="node-3"> <sub>Node 3</sub> </node> </node-list> </root>
|
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #include <iostream> #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/xml_parser.hpp>
int main() { using boost::property_tree::ptree; using boost::property_tree::write_xml;
try { ptree tree;
tree.put<std::string>("root.<xmlattr>.version", "v1.0"); tree.put<std::string>("root.<xmlcomment>", "Root Node"); tree.put<std::string>("root.node.<xmlattr>.id", "single-node"); tree.put<std::string>("root.node.<xmlcomment>", "Single Node Comment"); tree.put<std::string>("root.node", "Single Node"); tree.put<std::string>("root.node.sub.<xmlattr>.type", "string"); tree.put<std::string>("root.node.sub", "Hello World"); for (int i = 0; i < 3; i++) { ptree& item = tree.add<int>("root.node.list.item", i + 1); item.put<int>("<xmlattr>.id", i + 1); }
ptree nodeList; for (int i = 0; i < 3; i++) { ptree node; node.put<std::string>("<xmlattr>.id", std::string("node-") + std::to_string(i + 1)); node.put<std::string>("sub", std::string("Node ") + std::to_string(i + 1));
ptree comment; comment.put<std::string>("", std::string("Node List ") + std::to_string(i + 1));
nodeList.add_child("<xmlcomment>", comment); nodeList.add_child("node", node); } tree.add<std::string>("root.<xmlcomment>", "Node List"); tree.add_child("root.node-list", nodeList);
write_xml("output.xml", tree, std::locale(), boost::property_tree::xml_writer_settings<std::string>('\t', 1)); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return -1; } return 0; }
|
boost::property_tree::ptree
几个函数的用法解释:
函数名 |
作用 |
注释 |
put<T>(path, T value) -> ptree& |
在指定路径path 放置由类型T 指定的值,若path 为空字符串,则用法类似于data() |
T不能为ptree。若路径存在则替换 |
put_child(path, ptree) -> ptree& |
在指定路径path 放置子节点ptree ,子节点的名称为path 中由. 分隔的最后一个字符串,且不允许path 为空 |
若路径存在则替换 |
add<T>(path, T value) -> ptree& |
在指定路径path 添加值,path 不允许为空 |
T不能为ptree。无论路径是否存在,都执行插入操作 |
add_child(path, ptree) -> ptree& |
在指定路径path 添加子节点,子节点的名称为path 中由. 分隔的最后一个字符串,path 不允许为空 |
无论路径是否存在,都执行插入操作 |
get<T>(path) -> T |
获取路径path 的T 类型的值,当path 为空字符串时,等同于get_value() |
T不能为ptree |
get_value<T>() -> T |
获取当前节点中保存的T 类型的值 |
|
get_child(path) -> ptree& |
获取路径path 对应的子节点 |
|
data() -> std::string& |
获取节点的数据(不带标签的字符串内容) |
单个节点:指定路径写入
如果是在某个路径下放置一个数据项,例如字符串、整型浮点型的数字,可以直接调用put()
函数:
1 2 3 4
| tree.put<std::string>("root.<xmlattr>.version", "v1.0"); tree.put<std::string>("root.<xmlcomment>", "Root Node"); tree.put<std::string>("root.node", "Single Node");
|
如果子节点较为复杂,可以先构建好子节点的内容,再调用put_child()
放置:
1 2 3 4 5 6
| ptree node; node.put<std::string>("<xmlattr>.id", std::string("node-") + std::to_string(1)); node.put<std::string>("<xmlcomment>", std::string("Node ") + std::to_string(1)); node.put<std::string>("sub", std::string("Node ") + std::to_string(1));
tree.put_child("root.node", node);
|
1 2 3 4 5 6
| <root> <node id="node-1"> <sub>Node 1</sub> </node> </root>
|
多个子节点:列表写入
由于ptree不具名的特点,因此其中可以保存多个子节点,使用add()
和add_child
进行操作。
add()
的用法如下所示,用来在指定位置添加数据,其返回值是插入的最后一个节点的实例引用
1 2 3 4 5
| for (int i = 0; i < 3; i++) { ptree& item = tree.add<int>("root.node.list.item", i + 1); item.put<int>("<xmlattr>.id", i + 1); }
|
1 2 3 4 5 6 7 8 9
| <root> <node> <list> <item id="1">1</item> <item id="2">2</item> <item id="3">3</item> </list> </node> </root>
|
add_child()
的用法如下所示,用来在指定位置添加子节点,返回值为插入后的最后一个节点的实例引用
1 2 3 4 5 6 7 8 9 10 11 12 13
| ptree nodeList; for (int i = 0; i < 3; i++) { ptree node; node.put<std::string>("<xmlattr>.id", std::string("node-") + std::to_string(i + 1)); node.put<std::string>("sub", std::string("Node ") + std::to_string(i + 1));
ptree comment(std::string("Node List ") + std::to_string(i + 1));
nodeList.add_child("<xmlcomment>", comment); nodeList.add_child("node", node); } tree.add_child("root.node-list", nodeList);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <root> <node-list> <node id="node-1"> <sub>Node 1</sub> </node> <node id="node-2"> <sub>Node 2</sub> </node> <node id="node-3"> <sub>Node 3</sub> </node> </node-list> </root>
|
写入标签属性
写入属性通过<xmlattr>
来索引:
1 2
| ptree node; node.put<int>("<xmlattr>.id", 1);
|
可以使用put_child
,不过设置的值只能是子节点的文本内容,子节点包含的标签不被考虑:
1 2 3
| ptree node, sub; sub.data() = "1"; node.put_child("<xmlattr>.id", sub);
|
如果不使用.
来指定属性,比如node.put<int>("<xmlattr>", 1);
,则什么也不会写入。
由于属性的唯一性,不推荐使用add
和add_child
。(用也没事,只不过输出的XML文件会报错)
写入注释
注释比标签属性更纯粹,通过<xmlcomment>
写入:
1 2
| ptree node; node.put<std::string>("<xmlcomment>", "Hello");
|
写入的注释位置是在节点的内部,其本身就是个文本字符串,加个点指定个属性就啥也写不了("<xmlcomment>.id"
)
同标签属性,写入注释也可以使用put_child
,也是获取子节点文本内容进行写入。
可以使用add()
和add_child()
,在节点中写入多个注释。
XML的输入输出
文件
1 2 3 4 5 6 7 8 9
| using boost::property_tree::ptree; using boost::property_tree::write_xml; using boost::property_tree::read_xml;
ptree tree;
read_xml("input.xml", tree);
write_xml("output.xml", tree);
|
字符流
1 2 3 4 5 6 7 8 9 10 11 12
| using boost::property_tree::ptree; using boost::property_tree::write_xml; using boost::property_tree::read_xml;
ptree tree;
std::stringstream ss; ss.str("<root>Root Node</root>");
read_xml(ss, tree);
write_xml(ss, tree);
|
格式化与去格式化
读入时,去除无用的空格和换行符
1 2 3 4 5
| int flags = boost::property_tree::xml_parser::trim_whitespace;
read_xml("input.xml", tree, flags);
read_xml(ss, tree, flags);
|
输出时,加入缩进
1 2 3 4 5 6 7
| auto settings = boost::property_tree::xml_writer_settings<std::string>('\t', 1);
write_xml("output.xml", tree, std::locale(), settings);
write_xml(ss, tree, settings);
|
打印ptree
1 2 3 4 5 6 7
| void print(const boost::property_tree::ptree& tree) { auto settings = boost::property_tree::xml_parser::xml_writer_settings<std::string>('\t', 1); std::stringstream ss; boost::property_tree::write_xml(ss, tree, settings); std::cout << ss.str() << std::endl; }
|
读XML
本节内容使用的XML文件如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?xml version="1.0" encoding="utf-8"?> <root version="v1.0"> <node id="single-node"> Single Node <sub type="string">Hello World</sub> <list> <item id="1">1</item> <item id="2">2</item> <item id="3">3</item> </list> </node> <node-list> <node id="node-1"> <sub>Node 1</sub> </node> <node id="node-2"> <sub>Node 2</sub> </node> <node id="node-3"> <sub>Node 3</sub> </node> </node-list> </root>
|
首先将该XML文件读入到ptree中,然后读取不同数据。
1 2 3 4 5
| using boost::property_tree::ptree; using boost::property_tree::read_xml;
ptree tree; read_xml("input.xml", tree, boost::property_tree::xml_parser::trim_whitespace);
|
判断节点是否存在
使用get_child_optional(path)
判断节点是否存在
1 2 3 4 5 6 7 8 9 10
| if (auto opt = tree.get_child_optional("root.node.sub1")) { std::cout << opt.get().data() << std::endl; } else { std::cout << "节点不存在" << std::endl; }
|
单个节点读取
获取数据:
1 2 3
| tree.get<std::string>("root.node.<xmlattr>.id"); tree.get<std::string>("root.node");
|
获取节点:
1 2 3 4
| tree.get_child("root.node").get<std::string>("<xmlattr>.id"); tree.get_child("root.node").data(); tree.get_child("root.node").get<std::string>("<xmlcomment>");
|
读取节点内部的数据(也可以将属性和注释作为子节点读取):
1 2 3 4
| tree.get_child("root.node.sub").get_value<std::string>(); tree.get_child("root.node.sub").get<std::string>(""); tree.get_child("root.node.sub").get_child("").data();
|
子节点遍历
ptree提供了一个迭代器,用来遍历子节点,这些子节点除了数据、子子节点,还包括属性和注释。迭代器中有两个参数,分别为first
和second
,前者表示子节点的标签名,也可能是<xmlattr>
和<xmlcomment>
,后者表示子节点,是一个ptree
。当迭代器遇到标签属性<xmlattr>
时,可以通过遍历第二个参数second
来获取每一个标签属性的名称和数据;当迭代器遇到注释<xmlcomment>
时,第二个参数的.data()
返回的字符串即是注释的内容;当迭代器遇到子节点时,第一个参数first
是子节点的标签名,第二个参数second
则是子节点本身。
1 2 3 4 5 6 7 8 9
| for (auto itr = list.begin(); itr != list.end(); itr++) { ptree& node = itr->second; }
BOOST_FOREACH(auto & itr, list) { ptree& node = itr.second; }
|
使用迭代器遍历子节点的方式如下方代码所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ptree* node = nullptr; node = &tree.get_child("root.node"); for (auto itr = node->begin(); itr != node->end(); itr++) { if (itr->first.compare("<xmlattr>") == 0) { std::cout << "属性:" << std::endl; for (auto itr1 = itr->second.begin(); itr1 != itr->second.end(); itr1++) { std::cout << "-> " << itr1->first << ": " << itr1->second.data() << std::endl; } } else if (itr->first.compare("<xmlcomment>") == 0) { std::cout << "注释:" << itr->second.data() << std::endl; } else { std::cout << "子节点:" << itr->first << std::endl; } }
|
root.node
的节点数据及上方代码的输出如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13
| <root version="v1.0"> <node id="single-node"> Single Node <sub type="string">Hello World</sub> <list> <item id="1">1</item> <item id="2">2</item> <item id="3">3</item> </list> </node> </root>
|
1 2 3 4 5
| 属性: -> id: single-node 注释:Single Node Comment 子节点:sub 子节点:list
|
遍历root.node-list
,数据和代码输出如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <root> <node-list> <node id="node-1"> <sub>Node 1</sub> </node> <node id="node-2"> <sub>Node 2</sub> </node> <node id="node-3"> <sub>Node 3</sub> </node> </node-list> </root>
|
1 2 3 4 5 6
| 注释:Node List 1 子节点:node 注释:Node List 2 子节点:node 注释:Node List 3 子节点:node
|
读写JSON
使用boost读写JSON相较于其他处理JSON的C++库(如 jsoncpp),要来的繁琐一点,功能性也不如。但如果不想额外引入,仅使用一个boost,也不是完全不能用。
JSON与XML的数据格式有所类似也有所区别,本节通过一个JSON示例来演示JSON读写。
示例
以下这个JSON文件,使用boost无法构建输出出来,原因有二:
- 无法输出根节点为列表;
- 每一项数据只能是字符串;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| [ { "name": "node-1", "list": [ "Item-1", { "name": "Item-2", "data": 123 }, 17325.321 ] }, { "name": "node-2", "data": 0.22222 } ]
|
因此,boost只能输出如下格式的JSON:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| { "node1": { "name": "node-1", "list": [ "Item 1", { "name": "Item 2", "data": "123" }, "17325.321" ] }, "node2": { "name": "node-2", "data": "0.22222" } }
|
实现的代码如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include <iostream> #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/json_parser.hpp>
int main() { using boost::property_tree::ptree; using boost::property_tree::write_json;
try { ptree root; { ptree node; node.put<std::string>("name", "node-1");
ptree list; list.push_back(std::make_pair("", ptree("Item 1"))); { ptree node; node.put<std::string>("name", "Item 2"); node.put<int>("data", 123); list.push_back(std::make_pair("", node)); } { ptree node; node.put<double>("", 17325.321); list.push_back(std::make_pair("", node)); } node.put_child("list", list); root.put_child("node1", node); } { ptree node; node.put<std::string>("name", "node-2"); node.put<double>("data", 0.22222); root.put_child("node2", node); }
write_json("output1.json", root); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return -1; } return 0; }
|
写数组(列表)
使用boost写json时,无法将根节点作为列表写入,但可以在子节点写入列表,需要用到ptree::push_back()
和std::make_pair()
两个函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| ptree root; { ptree list; list.push_back(std::make_pair("", ptree("Item 1"))); { ptree node; node.put<std::string>("name", "Item 2"); node.put<int>("data", 123); list.push_back(std::make_pair("", node)); } { ptree node; node.put<double>("", 17325.321); list.push_back(std::make_pair("", node)); } { ptree list1; for (int i = 0; i < 3; i++) { list1.push_back(std::make_pair("", ptree(std::string("List Item ") + std::to_string(i)))); } list.push_back(std::make_pair("", list1)); } root.put_child("list", list); }
|
得到root
的输出如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { "list": [ "Item 1", { "name": "Item 2", "data": "123" }, "17325.321", [ "List Item 0", "List Item 1", "List Item 2" ] ] }
|
读JSON
虽然boost无法输出根节点为列表的JSON,但可以读入该类型的JSON,就以如下JSON文件的读入为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| [ { "name": "node-1", "list": [ "Item-1", { "name": "Item-2", "data": 123 }, 17325.321 ] }, { "name": "node-2", "data": 0.22222 } ]
|
读入后,打印的结果为:
1 2 3 4 5 6 7
| ptree root; read_json("input.json", root);
std::stringstream ss; write_json(ss, root); std::cout << ss.str() << std::endl;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| { "": { "name": "node-1", "list": [ "Item-1", { "name": "Item-2", "data": "123" }, "17325.321" ] }, "": { "name": "node-2", "data": "0.22222" } }
|
读取两个node
节点的代码如下所示。对于列表,使用迭代器进行循环迭代;对于具名字段,通过get()
函数获取数据。
1 2 3 4 5 6 7
| auto itr = root.begin();
ptree& node1 = itr->second; std::cout << node1.get<std::string>("name") << std::endl;
ptree& node2 = (++itr)->second; std::cout << node2.get<std::string>("name") << std::endl;
|