使用boost::property_tree解析xml与json (一):概述
最近我正在开始研究消息队列,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 | template<typename Ptree> |
这个接口的使用方法是这样:
1 | sting configPath_ = "../config/config.xml"; |
然后就把../config/config.xml
的这个路径下的config.xml
读入到了pt
这个数据结构当中了。
get
1 | <root> |
直接使用get
即可获取xml
的标签值,比如
1 | pt.get<string>("root.child.a"); |
property
使用点号.
来作为路径分隔符号。root.child.a
就是获取root
标签下child
标签的a
标签的值。string
是指定将获取的值的类型。
get_child
get_child
用来获取子节点的对象。
比如说在
1 | <root> |
当中,当我需要遍历child
下所有的标签的时候(这在我不知道child
标签下有几个子标签的时候经常用),我就不能单纯地使用get
了。
1 | auto child = pt_->get_child("root.child"); |
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 | <root> |
那么
1 | read_xml("conf.xml", pt); |
请注意root.child.a.<xmlattr>.hello
,不是直接使用<xmlattr>
就可以了,因为一个标签可以有很多个attribute
,所以还是需要指定attribute
的名字。
boost::property_tree::write_xml
1 | template<typename Ptree> |
这个接口的使用方法是这样:
1 | sting configPath_ = "../config/config.xml"; |
把pt
这个数据结构当中的信息写入../config/config.xml
当中。可以看到property_tree
既可以读取数据,又可以写入数据,它的操作具有对称性。
put
和get
方法对称的就是put
方法。
比如:
1 | <root> |
调用了pt.put("key", "testput")
之后:
1 | <root> |
用法非常简单,其中key
是xml
的路径,可以随便替换。
add_child
和get_child
相对的就是add_child
函数了。比如我需要一口气插入很多标签,那么就要用到add_child
了。
1 | <root> |
在执行了
1 | read_xml("config.xml", pt); |
之后,就变成了:
1 | <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 | { |
可以看到在这个json
当中,root.child
是一个数组。
获取数组当中的值就需要使用value_type
类型:
1 | for (ptree::value_type &v : child.get_child("")) { |
要回答这个问题,还是要回到property_tree
这个结构当中去,在property_tree
的定义式当中,每一个节点的类型都是value_type
,它是一个pair
,节点的名字就是first
,节点自身就是second
。那么在这里,child
也是一个value_type
,它调用get_child("")
就得到了child
这个数组的所有成员,当然都是value_type
型的了。
put数组
要想put一个数组,就要是用push_back
方法。
1 | ptree temp; |
因为一个节点的数据类型就是value_type
类型,value_type
类型不是简单的pair
,否则的话也不可能调用push_back
,它是经过了特殊处理。
执行结果:
1 | { |