boost.property_tree可以用来解析xml和json文件,我主要用它来解析xml文件,它内部封装了号称最快的xml解析器rapid_xml,其解析效率还是很好的。但是在使用过程中却发现各种不好用,归纳一下不好用的地方有这些:获取不存在的节点时就抛出异常 获取属性值时,要排除属性和注释节点,如果没注意这一点就会抛出异常,让人摸不着头脑。 内存模型有点怪。 默认不支持中文的解析。解析中文会乱码。
ptree获取子节点
获取子节点接口原型为get_child(node_path),这个node_path从当前路径开始的全路径,父路径和子路径之间通过“.”连接,如“root.sub.child”。需要注意的是get_child获取的是第一个子节点,如果我们要获取子节点列表,则要用路径“root.sub”,这个路径可以获取child的列表。如果获取节点的路径不存在则会抛出异常,这时,如果不希望抛出异常则可以用get_xxx_optional接口,该接口返回一个optional<T>的结果出来,由外面判断是否获取到结果了。
1.
//ptree的optional接口
2.
auto item = root.get_child_optional(
'Root.Scenes'
);
该接口返回的是一个optional<ptree>,外面还要判断该节点是否存在,optional对象通过bool操作符来判断该对象是否是无效值,通过指针访问
符'*'来访问该对象的实际内容。建议用optional接口访问xml节点。
1.
//ptree的optional接口
2.
auto item = root.get_child_optional(
'Root.Scenes'
);
3.
if
(item)
4.
cout<<
'该节点存在'
<<endl;
ptree的内存模型
ptree维护了一个pair<string, ptree>的子节点列表,first指向的是该节点的TagName,second指向的才是ptree节点,因此在遍历ptree子节点时要注意迭代器的含义。
1.
for
(auto& data : root)
2.
{
3.
for
(auto& item : data.second)
//列表元素为pair<string, ptree>,要用second继续遍历
4.
{
5.
cout<<item.first<<endl;
6.
}
7.
}
需要注意的是ptree.first可能是属性('<xmlattr>')也可能是注释('<xmlcomment>'),只有非注释类型的节点才能使用获取属性值、子节点等常用接口。
ptree获取属性值
通过get<T>(attr_name)可以获取属性的值,如果想获取属性的整形值的话,可以用get<int>('Id'),返回一个整数值。有一点要注意如果ptree.first为'<xmlcomment>'时,是没有属性值的,可以通过data()来获取注释内容。如果这个ptree.first不为<xmlattr>时需要在属性名称前面加'<xmlcomment>.',即get<int>('<xmlcomment>.Id')才能正确获取属性值。可以看到获取属性值还是比较繁琐的,在后面要介绍的帮助类中可以简化属性值的获取。如果要获取节点的值则用get_value()接口,该接口用来获取节点的值,如节点:<Field>2</Field>通过get_value()就可以获取值'2'。
解析中文的问题
ptree只能解析窄字符的xml文件,如果xml文件中含有unicode如中文字符,解析出来就是乱码。解析unicode要用wptree,该类的接口均支持宽字符并且接口和ptree保持一致。要支持中文解析仅仅wptree还不够,还需要一个unicode转换器的帮助,该转换器可以实现宽字符和窄字符的转换,宽窄的互相转换函数有很多实现,不过c++11中有更简单统一的方式实现款窄字符的转换。
c++11中宽窄字符的转换:
1.
std::wstring_convert<std::codecvt<wchar_t,
char
,std::mbstate_t>> conv
2.
3.
(newstd::codecvt<wchar_t,
char
,std::mbstate_t>(
'CHS'
));
4.
//宽字符转为窄字符
5.
string str = conv.to_bytes(L
'你好'
);
6.
//窄字符转为宽字符
7.
string wstr = conv.from_bytes(str);
boost.property_tree在解析含中文的xml文件时,需要先将该文件转换一下。
boost解决方法:
01.
#include
'boost/program_options/detail/utf8_codecvt_facet.hpp'
02.
void
ParseChn()
03.
{
04.
std::wifstream f(fileName);
05.
std::locale utf8Locale(std::locale(),
new
boost::program_options::detail::utf8_codecvt_facet());
06.
f.imbue(utf8Locale);
//先转换一下
07.
08.
//用wptree去解析
09.
property_tree::wptree ptree;
10.
property_tree::read_xml(f, ptree);
11.
}
这种方法有个缺点就是要引入boost的libboost_program_options库,该库有二十多M,仅仅是为了解决一个中文问题,却要搞得这么麻烦,有点得不偿失。好在c++11提供更简单的方式,用c++11可以这样:
01.
void
Init(
const
wstring& fileName, wptree& ptree)
02.
{
03.
std::wifstream f(fileName);
04.
std::locale utf8Locale(std::locale(),
new
std::codecvt_utf8<wchar_t>);
05.
f.imbue(utf8Locale);
//先转换一下
06.
07.
//用wptree去解析
08.
property_tree::read_xml(f, ptree);
09.
}
用c++11就不需要再引入boost的libboost_program_options库了,很简单。
property_tree的帮助类
property_tree的帮助类解决了前面提到的问题:
用c++11解决中文解析问题 简化属性的获取 增加一些操作接口,比如一些查找接口 避免抛出异常,全部返回optional<T>对象 隔离了底层繁琐的操作接口,提供统一、简洁的高层接口,使用更加方便。
下面来看看这个帮助类是如何实现的吧:
001.
#include<boost/property_tree/ptree.hpp>
002.
#include<boost/property_tree/xml_parser.hpp>
003.
using namespace boost;
004.
using namespace boost::property_tree;
005.
006.
#include <map>
007.
#include <vector>
008.
#include <codecvt>
009.
#include <locale>
010.
using namespace std;
011.
012.
const
wstring XMLATTR = L
'<xmlattr>'
;
013.
const
wstring XMLCOMMENT = L
'<xmlcomment>'
;
014.
const
wstring XMLATTR_DOT = L
'<xmlattr>.'
;
015.
const
wstring XMLCOMMENT_DOT = L
'<xmlcomment>.'
;
016.
017.
class
ConfigParser
018.
{
019.
public
:
020.
021.
ConfigParser() : m_conv(
new
code_type(
'CHS'
))
022.
{
023.
024.
}
025.
026.
~ConfigParser()
027.
{
028.
}
029.
030.
void
Init(
const
wstring& fileName, wptree& ptree)
031.
{
032.
std::wifstream f(fileName);
033.
std::locale utf8Locale(std::locale(),
new
std::codecvt_utf8<wchar_t>);
034.
f.imbue(utf8Locale);
//先转换一下
035.
wcout.imbue(std::locale(
'chs'
));
//初始化cout为中文输出格式
036.
037.
//用wptree去解析
038.
property_tree::read_xml(f, ptree);
039.
}
040.
041.
// convert UTF-8 string to wstring
042.
std::wstring to_wstr(
const
std::string& str)
043.
{
044.
return
m_conv.from_bytes(str);
045.
}
046.
047.
// convert wstring to UTF-8 string
048.
std::string to_str(
const
std::wstring& str)
049.
{
050.
return
m_conv.to_bytes(str);
051.
}
052.
053.
//获取子节点列表
054.
auto Descendants(
const
wptree& root,
const
wstring& key)->decltype(root.get_child_optional(key))
055.
{
056.
return
root.get_child_optional(key);
057.
}
058.
059.
//根据子节点属性获取子节点列表
060.
template<typename T>
061.
vector<wptree> GetChildsByAttr(
const
wptree& parant,
const
wstring& tagName,
const
wstring& attrName,
const
T& attrVal)
062.
{
063.
vector<wptree> v;
064.
065.
for
(auto& child : parant)
066.
{
067.
if
(child.first != tagName)
068.
continue
;
069.
070.
auto attr = Attribute<T>(child, attrName);
071.
072.
if
(attr&&*attr == attrVal)
073.
v.push_back(child.second);
074.
}
075.
076.
return
v;
077.
}
078.
079.
//获取节点的某个属性值
080.
template<typename R>
081.
optional<R> Attribute(
const
wptree& node,
const
wstring& attrName)
082.
{
083.
return
node.get_optional<R>(XMLATTR_DOT + attrName);
084.
}
085.
086.
//获取节点的某个属性值,默认为string
087.
optional<wstring> Attribute(
const
wptree& node,
const
wstring& attrName)
088.
{
089.
return
Attribute<wstring>(node, attrName);
090.
}
091.
092.
//获取value_type的某个属性值
093.
template<typename R>
094.
optional<R> Attribute(
const
wptree::value_type& pair,
const
wstring& attrName)
095.
{
096.
if
(pair.first == XMLATTR)
097.
return
pair.second.get_optional<R>(attrName);
098.
else
if
(pair.first == XMLCOMMENT)
099.
return
optional<R>();
100.
else
101.
return
pair.second.get_optional<R>(XMLATTR_DOT + attrName);
102.
}
103.
104.
//获取value_type的某个属性值,默认为string
105.
optional<wstring> Attribute(
const
wptree::value_type& pair,
const
wstring& attrName)
106.
{
107.
return
Attribute<wstring>(pair, attrName);
108.
}
109.
110.
//根据某个属性生成一个<string, ptree>的multimap
111.
template<
class
F = std::function<bool(wstring&)>>
112.
multimap<wstring, wptree> MakeMapByAttr(
const
wptree& root,
const
wstring& key,
const
wstring& attrName, F predict = [](wstring& str){
return
true
; })
113.
{
114.
multimap<wstring, wptree> resultMap;
115.
auto list = Descendants(root, key);
116.
if
(!list)
117.
return
resultMap;
118.
119.
for
(auto& item : *list)
120.
{
121.
auto attr = Attribute(item, attrName);
122.
if
(attr&&predict(*attr))
123.
resultMap.insert(std::make_pair(*attr, item.second));
124.
}
125.
126.
return
resultMap;
127.
}
128.
129.
private
:
130.
using code_type = std::codecvt<wchar_t,
char
, std::mbstate_t>;
131.
std::wstring_convert<code_type> m_conv;
132.
};
测试文件test.xml和测试代码:
01.
<?xml version=
'1.0'
encoding=
'UTF-8'
?>
02.
<Root Id=
'123456'
>
03.
<Scenes>
04.
<!--注释说明
1
-->
05.
<Scene Name=
'测试1'
>
06.
<!--注释说明
11
-->
07.
<DataSource>
08.
<!--注释说明
111
-->
09.
<Data>
10.
<!--注释说明
111
-->
11.
<Item Id=
'1'
FileName=
'测试文件1'
/>
12.
</Data>
13.
<Data>
14.
<Item Id=
'2'
FileName=
'测试文件2'
/>
15.
<Item Id=
'3'
FileName=
'测试文件3'
/>
16.
</Data>
17.
</DataSource>
18.
</Scene>
19.
<!--注释说明
1
-->
20.
<Scene Name=
'测试2'
>
21.
<DataSource>
22.
<Data>
23.
<Item Id=
'4'
FileName=
'测试文件4'
/>
24.
</Data>
25.
<Data>
26.
<Item Id=
'5'
FileName=
'测试文件5'
/>
27.
</Data>
28.
</DataSource>
29.
</Scene>
30.
</Scenes>
31.
</Root>
01.
void
Test()
02.
{
03.
wptree pt; pt.get_value()
04.
ConfigParser parser;
05.
parser.Init(L
'test1.xml'
, pt);
//解决中文问题,要转换为unicode解析
06.
07.
auto scenes = parser.Descendants(pt, L
'Root.Scenes'
);
//返回的是optional<wptree>
08.
if
(!scenes)
09.
return
;
10.
11.
for
(auto& scene : *scenes)
12.
{
13.
auto s = parser.Attribute(scene, L
'Name'
);
//获取Name属性,返回的是optional<wstring>
14.
if
(s)
15.
{
16.
wcout << *s << endl;
17.
}
18.
19.
auto dataList = parser.Descendants(scene.second, L
'DataSource'
);
//获取第一个子节点
20.
if
(!dataList)
21.
continue
;
22.
23.
for
(auto& data : *dataList)
24.
{
25.
for
(auto& item : data.second)
26.
{
27.
auto id = parser.Attribute<
int
>(item, L
'Id'
);
28.
auto fileName = parser.Attribute(item, L
'FileName'
);
29.
30.
if
(id)
31.
{
32.
wcout << *id << L
' '
<< *fileName << endl;
//打印id和filename
33.
}
34.
}
35.
}
36.
}
37.
}
测试结果:
可以看到通过帮助类,无需使用原生接口就可以很方便的实现节点的访问与操作。使用者不必关注内部细节,根据统一而简洁的接口就可以操作xml文件了。
一点题外话,基于这个帮助类再结合linq to object可以轻松的实现linq to xml:
01.
//获取子节点SubNode的属性ID的值为0x10000D的项并打印出该项的Type属性
02.
from(node.Descendants(
'Root.SubNode'
)).where([](XNode& node)
03.
{
04.
auto s = node.Attribute(
'ID'
);
05.
return
s&&*s ==
'0x10000D'
;
06.
}).for_each([](XNode& node)
07.
{
08.
auto s = node.Attribute(
'Type'
);
09.
if
(s)
10.
cout << *s << endl;
11.
});