0%

Boost操作xml&json常用操作

  1. 预设
  2. 关于ptree的数据结构
  3. 写XML
    1. 示例
    2. 单个节点:指定路径写入
    3. 多个子节点:列表写入
    4. 写入标签属性
    5. 写入注释
  4. XML的输入输出
    1. 文件
    2. 字符流
    3. 格式化与去格式化
    4. 打印ptree
  5. 读XML
    1. 判断节点是否存在
    2. 单个节点读取
    3. 子节点遍历
  6. 读写JSON
    1. 示例
    2. 写数组(列表)
    3. 读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">
<!--Root Node-->
<node id="single-node">
Single Node
<!--Single Node Comment-->
<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-list>
<!--Node List 1-->
<node id="node-1">
<sub>Node 1</sub>
</node>
<!--Node List 2-->
<node id="node-2">
<sub>Node 2</sub>
</node>
<!--Node List 3-->
<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.get_child("root.node").data() = "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 获取路径pathT类型的值,当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">
<!--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 List 1-->
<node id="node-1">
<sub>Node 1</sub>
</node>
<!--Node List 2-->
<node id="node-2">
<sub>Node 2</sub>
</node>
<!--Node List 3-->
<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);,则什么也不会写入。

由于属性的唯一性,不推荐使用addadd_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);
// 或者:auto settings = boost::property_tree::xml_writer_settings<std::string>(' ', 4);

// 输出到文件
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">
<!--Root Node-->
<node id="single-node">
Single Node
<!--Single Node Comment-->
<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-list>
<!--Node List 1-->
<node id="node-1">
<sub>Node 1</sub>
</node>
<!--Node List 2-->
<node id="node-2">
<sub>Node 2</sub>
</node>
<!--Node List 3-->
<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"))
{
// opt.get() 获取节点引用
// .data() 获取节点数据
std::cout << opt.get().data() << std::endl;
}
else
{
std::cout << "节点不存在" << std::endl;
}

单个节点读取

获取数据:

1
2
3
// 通过路径索引到位
tree.get<std::string>("root.node.<xmlattr>.id"); // single-node
tree.get<std::string>("root.node"); // Single Node

获取节点:

1
2
3
4
// 获取子节点后再获取内部数据
tree.get_child("root.node").get<std::string>("<xmlattr>.id"); // single-node
tree.get_child("root.node").data(); // Single Node
tree.get_child("root.node").get<std::string>("<xmlcomment>"); // Single Node Comment

读取节点内部的数据(也可以将属性和注释作为子节点读取):

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提供了一个迭代器,用来遍历子节点,这些子节点除了数据、子子节点,还包括属性和注释。迭代器中有两个参数,分别为firstsecond,前者表示子节点的标签名,也可能是<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">
<!--Root Node-->
<node id="single-node">
Single Node
<!--Single Node Comment-->
<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-list>
<!--Node List 1-->
<node id="node-1">
<sub>Node 1</sub>
</node>
<!--Node List 2-->
<node id="node-2">
<sub>Node 2</sub>
</node>
<!--Node List 3-->
<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. 每一项数据只能是字符串;
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;
{
// Node 1
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);
}
{
// Node 2
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;
// 1. 单个数据
list.push_back(std::make_pair("", ptree("Item 1")));
// 2. 键值对
{
ptree node;
node.put<std::string>("name", "Item 2");
node.put<int>("data", 123);
list.push_back(std::make_pair("", node));
}
// 3. 数字
{
ptree node;
node.put<double>("", 17325.321);
list.push_back(std::make_pair("", node));
}
// 4. 子列表
{
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; // node-1

ptree& node2 = (++itr)->second;
std::cout << node2.get<std::string>("name") << std::endl; // node-2