添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

最近我正在开始研究消息队列, MQueue ,今天在阅读我以前的代码 DBPool SocketPool 的时候,看到我以前在 DBPool SocketPool 的池的构造函数当中使用 boost property_tree 来解析 xml json 的代码非常冗长,今天花了一天的时间把解析xml和json的接口实现了,名字为 parse_xml.cpp parse_json.cpp ,放到 MQueue 里面了,并同步更新到了 DBPool SocketPool

老规矩,还是先讲解一下 boost property_tree 。我看的是 《Boost程序库完全开发指南 深入准标准库》

boost::property_tree

property_tree 是一个保存了多个属性值的树形数据结构,可以使用类似路径的简单方式访问任意节点的属性,而且每个节点都可以用类似STL的风格遍历子节点。 property_tree 可以处理 xml , json , ini , info 四种格式的文本数据。

使用 property_tree 之前要包含头文件

1
#include <boost/property_tree/ptree.hpp>

解析xml的接口

property_tree 本身并没有实现 xml 解析器,使用的是 rapidxml 这个开源项目,它比市面上能够找到的大部分 xml 解析器都要快。这个 rapidxml 非常有名,以至于 rapidjson 这个开源 json 解析库连名字也是借鉴它的,但是 boost 并没有整合 rapidjson 。但是我还是非常推荐 rapidjson 的,毕竟是 miloyip 的作品。

在解析 xml 之前需要包含头文件

1
#include <boost/property_tree/xml_parser.hpp>

boost::property_tree::read_xml

1
2
3
template<typename Ptree> 
void read_xml(std::basic_istream< typename Ptree::key_type::value_type > & stream,
Ptree & pt, int flags = 0);

这个接口的使用方法是这样:

1
2
3
sting configPath_ = "../config/config.xml";
boost::property_tree::ptree pt;
boost::property_tree::read_xml(configPath_, pt);

然后就把 ../config/config.xml 的这个路径下的 config.xml 读入到了 pt 这个数据结构当中了。

get

1
2
3
4
5
6
<root>
<child>
<a>“1”</a>
<b>2</b>
</child>
</root>

直接使用 get 即可获取 xml 的标签值,比如

1
2
pt.get<string>("root.child.a");
pt.get<int>("root.child.b");

property 使用点号 . 来作为路径分隔符号。 root.child.a 就是获取 root 标签下 child 标签的 a 标签的值。
string 是指定将获取的值的类型。

get_child

get_child 用来获取子节点的对象。
比如说在

1
2
3
4
5
6
<root>
<child>
<a>“1”</a>
<b>2</b>
</child>
</root>

当中,当我需要遍历 child 下所有的标签的时候(这在我不知道 child 标签下有几个子标签的时候经常用),我就不能单纯地使用 get 了。

1
2
3
4
auto child = pt_->get_child("root.child");
for (auto pos = child.begin(); pos!= child.end(); ++pos) {
cout << pos->first << "---" << pos->second.data();
}

child.begin() 得到的是 boost::property_tree::ptree::iterator 类型的对象,是个迭代器。
注意节点的数据结构是一个 pair , pair first 是节点的标签名, second 是节点自身。那么对 root.child 进行遍历就得到了 child 的子节点,子节点的 first 是标签名,在这里也就是 a b second 是子节点自身,对 second 调用 data() 方法就能访问值。

attribute

xml json 有个最大的不同点,就是 xml attribute ,而 json 有数组的概念。所以 parse_xml.cpp parse_json.cpp 对外接口并不一样。

property_tree 将所有的 xml 节点转换为属性树对应的节点,每一个节点的类型都是 value_type 。节点的 attribute 保存在改节点的 <xmlattr> 下级节点,注释保存在 <xmlcomment> 下级节点。这样说可能比较难以理解,我们来看个例子:

1
2
3
4
5
6
7
8
<root>
<!-- root comment -->
<child>
<!-- child comment -->
<a hello="style">“1”</a>
<b>2</b>
</child>
</root>
1
2
3
4
read_xml("conf.xml", pt);
pt.get<string>("root.<xmlcomment>"); //root comment
pt.get<string>("root.child.<xmlcomment>"); //child comment
pt.get<string>("root.child.a.<xmlattr>.hello"); //style

请注意 root.child.a.<xmlattr>.hello ,不是直接使用 <xmlattr> 就可以了,因为一个标签可以有很多个 attribute ,所以还是需要指定 attribute 的名字。

boost::property_tree::write_xml

1
2
3
4
template<typename Ptree> 
void write_xml(std::basic_ostream< typename Ptree::key_type::value_type > & stream,
const Ptree & pt,
const xml_writer_settings< typename Ptree::key_type::value_type > & settings = xml_writer_settings< typename Ptree::key_type::value_type >());

这个接口的使用方法是这样:

1
2
sting configPath_ = "../config/config.xml";
boost::property_tree::write_xml(configPath_, pt);

pt 这个数据结构当中的信息写入 ../config/config.xml 当中。可以看到 property_tree 既可以读取数据,又可以写入数据,它的操作具有对称性。

put

get 方法对称的就是 put 方法。
比如:

1
2
3
4
5
6
<root>
<child>
<a></a>
<b></b>
</child>
</root>

调用了 pt.put("key", "testput") 之后:

1
2
3
4
5
6
7
<root>
<child>
<a></a>
<b></b>
</child>
</root>
<key>"testput"</key>

用法非常简单,其中 key xml 的路径,可以随便替换。

add_child

get_child 相对的就是 add_child 函数了。比如我需要一口气插入很多标签,那么就要用到 add_child 了。

1
2
3
4
5
6
<root>
<child>
<a>"1"</a>
<b>2</b>
</child>
</root>
1
2
3
4
5
6
read_xml("config.xml", pt);
ptree child;
child.put("a", "3");
child.put("b", "4");
pt.add_child("root.newchild", child);
write_xml("config.xml", pt);

之后,就变成了:

1
2
3
4
5
6
7
8
9
10
<root>
<child>
<a>"1"</a>
<b>2</b>
</child>
<newchild>
<a>"3"</a>
<b>"4"</b>
</newchild>
</root>

attribute

根据上面说的 attribute 其实就是下级标签 <xmlattr> 之后,直接使用 put 方法就能够把 attribute 写入 xml 文本当中去。
比如说执行:

1
pt.put("error.<xmlattr>.id" "DB_ERROR_EXECUTE")

很简单地就能得到:

1
<error id="DB_ERROR_EXECUTE"/>

解析json接口

上面已经提到过了, json xml 的最大不同就在于 json 的数组。

get数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{ 
"root":
{
"child":
[
{
"a": "1",
"b": "2"
},
{
"a": "1",
"b": "2"
}
]
}
}

可以看到在这个 json 当中, root.child 是一个数组。
获取数组当中的值就需要使用 value_type 类型:

1
2
3
4
5
6
7
8
for (ptree::value_type &v : child.get_child("")) {
//v 就是child数组当中的元素
auto nextchild = v.second.get_child("");
//nextchild 就是元素"a",“b”的集合
for (auto pos = nextchild.begin(); pos!= nextchild.end(); ++pos) {
cout << pos->first << "---" << pos->second.data() << endl;
}
}

要回答这个问题,还是要回到 property_tree 这个结构当中去,在 property_tree 的定义式当中,每一个节点的类型都是 value_type ,它是一个 pair ,节点的名字就是 first ,节点自身就是 second 。那么在这里, child 也是一个 value_type ,它调用 get_child("") 就得到了 child 这个数组的所有成员,当然都是 value_type 型的了。

put数组

要想put一个数组,就要是用 push_back 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ptree temp;
temp.put("a", "3");
temp.put("b", "4");
ptree temp_second;
temp_second.put("a", "3");
temp_second.put("b", "4");

ptree myTree;
// 构造一个数组就是要用push_back方法
myTree.push_back(make_pair("", temp);
myTree.push_back(make_pair("", temp_second);

read_xml("config.json", pt);
pt.add_child("root.newchild", myTree);
write_xml("config.json", pt);

因为一个节点的数据类型就是 value_type 类型, value_type 类型不是简单的 pair ,否则的话也不可能调用 push_back ,它是经过了特殊处理。

执行结果:

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
{ 
"root":
{
"child":
[
{
"a": "1",
"b": "2"
},
{
"a": "1",
"b": "2"
}
],
"newchild":
[
{
"a": "3",
"b": "4"
},
{
"a": "3",
"b": "4"
}
],
}
}
C++ C1000k C10k IO多路复用 IPC MessageQueue SharedMemory SocketObj SocketPoll UNPv1 bash boost classFactory debug epoll g++ git gitlab hexo json libevent log logobj property_tree protobuf rapidjson rapidxml regex select shell singleton socket xml 三次握手 历程 反射 四次挥手 并发服务器 正则表达式 编译 连接