boost.property_tree能够用来解析xml和json文件,我主要用它来解析xml文件,它内部封装了号称最快的xml解析器rapid_xml,其解析效率仍是很好的。可是在使用过程当中却发现各类很差用,概括一下很差用的地方有这些:node
获取子节点接口原型为get_child(node_path),这个node_path从当前路径开始的全路径,父路径和子路径之间经过“.”链接,如“root.sub.child”。须要注意的是get_child获取的是第一个子节点,若是咱们要获取子节点列表,则要用路径“root.sub”,这个路径能够获取child的列表。若是获取节点的路径不存在则会抛出异常,这时,若是不但愿抛出异常则能够用get_xxx_optional接口,该接口返回一个optional<T>的结果出来,由外面判断是否获取到结果了。c++
//ptree的optional接口 auto item = root.get_child_optional("Root.Scenes");
该接口返回的是一个optional<ptree>,外面还要判断该节点是否存在,optional对象经过bool操做符来判断该对象是不是无效值,经过指针访问 json
符"*"来访问该对象的实际内容。建议用optional接口访问xml节点。api
//ptree的optional接口 auto item = root.get_child_optional("Root.Scenes"); if(item) cout<<"该节点存在"<<endl;
ptree维护了一个pair<string, ptree>的子节点列表,first指向的是该节点的TagName,second指向的才是ptree节点,所以在遍历ptree子节点时要注意迭代器的含义。ide
for (auto& data : root) { for (auto& item : data.second) //列表元素为pair<string, ptree>,要用second继续遍历 { cout<<item.first<<endl; } }
须要注意的是ptree.first多是属性("<xmlattr>")也多是注释("<xmlcomment>"),只有非注释类型的节点才能使用获取属性值、子节点等经常使用接口。函数
经过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文件的格式是utf-8格式的,若是xml文件中含有unicode如中文字符,解析出来就是乱码。解析unicode要用wptree,该类的接口均支持宽字符而且接口和ptree保持一致。要支持中文解析仅仅wptree还不够,还须要一个unicode转换器的帮助,该转换器能够实现宽字符和窄字符的转换,宽窄的互相转换函数有不少实现,不过c++11中有更简单统一的方式实现宽窄字符的转换。spa
c++11中宽窄字符的转换:指针
std::wstring_convert<std::codecvt<wchar_t,char,std::mbstate_t>> conv (newstd::codecvt<wchar_t,char,std::mbstate_t>("CHS")); //宽字符转为窄字符 string str = conv.to_bytes(L"你好"); //窄字符转为宽字符 string wstr = conv.from_bytes(str);
boost.property_tree在解析含中文的xml文件时,须要先将该文件转换一下。c++11
boost解决方法:
#include "boost/program_options/detail/utf8_codecvt_facet.hpp" void ParseChn() { std::wifstream f(fileName); std::locale utf8Locale(std::locale(), new boost::program_options::detail::utf8_codecvt_facet()); f.imbue(utf8Locale); //先转换一下 //用wptree去解析 property_tree::wptree ptree; property_tree::read_xml(f, ptree); }
这种方法有个缺点就是要引入boost的libboost_program_options库,该库有二十多M,仅仅是为了解决一个中文问题,却要搞得这么麻烦,有点得不偿失。好在c++11提供更简单的方式,用c++11能够这样:
void Init(const wstring& fileName, wptree& ptree) { std::wifstream f(fileName); std::locale utf8Locale(std::locale(), new std::codecvt_utf8<wchar_t>); f.imbue(utf8Locale); //先转换一下 //用wptree去解析 property_tree::read_xml(f, ptree); }
用c++11就不须要再引入boost的libboost_program_options库了,很简单。
另一种方法就是,仍然用ptree和string,只是在取出string字符串后,作一个转换为unicode的转换,就能获得中文字符串了。例如:
auto child = item.second.get_child("Scenes.Scene"); auto oname = child.get_optional<string>("<xmlattr>.Name"); //oname内部存了一个unicode字符串,须要将其转换为宽字符串获得中文 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter; std::wstring wide = converter.from_bytes(*oname); //宽字符串转为窄字符串 //std::string narrow = converter.to_bytes(L"foo");
property_tree的帮助类解决了前面提到的问题:
下面来看看这个帮助类是如何实现的吧:
#include<boost/property_tree/ptree.hpp> #include<boost/property_tree/xml_parser.hpp> using namespace boost; using namespace boost::property_tree; #include <map> #include <vector> #include <codecvt> #include <locale> using namespace std; const wstring XMLATTR = L"<xmlattr>"; const wstring XMLCOMMENT = L"<xmlcomment>"; const wstring XMLATTR_DOT = L"<xmlattr>."; const wstring XMLCOMMENT_DOT = L"<xmlcomment>."; class ConfigParser { public: ConfigParser() : m_conv(new code_type("CHS")) { } ~ConfigParser() { } void Init(const wstring& fileName, wptree& ptree) { std::wifstream f(fileName); std::locale utf8Locale(std::locale(), new std::codecvt_utf8<wchar_t>); f.imbue(utf8Locale); //先转换一下 wcout.imbue(std::locale("chs")); //初始化cout为中文输出格式 //用wptree去解析 property_tree::read_xml(f, ptree); } // convert UTF-8 string to wstring std::wstring to_wstr(const std::string& str) { return m_conv.from_bytes(str); } // convert wstring to UTF-8 string std::string to_str(const std::wstring& str) { return m_conv.to_bytes(str); } //获取子节点列表 auto Descendants(const wptree& root, const wstring& key)->decltype(root.get_child_optional(key)) { return root.get_child_optional(key); } //根据子节点属性获取子节点列表 template<typename T> vector<wptree> GetChildsByAttr(const wptree& parant, const wstring& tagName, const wstring& attrName, const T& attrVal) { vector<wptree> v; for (auto& child : parant) { if (child.first != tagName) continue; auto attr = Attribute<T>(child, attrName); if (attr&&*attr == attrVal) v.push_back(child.second); } return v; } //获取节点的某个属性值 template<typename R> optional<R> Attribute(const wptree& node, const wstring& attrName) { return node.get_optional<R>(XMLATTR_DOT + attrName); } //获取节点的某个属性值,默认为string optional<wstring> Attribute(const wptree& node, const wstring& attrName) { return Attribute<wstring>(node, attrName); } //获取value_type的某个属性值 template<typename R> optional<R> Attribute(const wptree::value_type& pair, const wstring& attrName) { if (pair.first == XMLATTR) return pair.second.get_optional<R>(attrName); else if (pair.first == XMLCOMMENT) return optional<R>(); else return pair.second.get_optional<R>(XMLATTR_DOT + attrName); } //获取value_type的某个属性值,默认为string optional<wstring> Attribute(const wptree::value_type& pair, const wstring& attrName) { return Attribute<wstring>(pair, attrName); } //根据某个属性生成一个<string, ptree>的multimap template<class F = std::function<bool(wstring&)>> multimap<wstring, wptree> MakeMapByAttr(const wptree& root, const wstring& key, const wstring& attrName, F predict = [](wstring& str){return true; }) { multimap<wstring, wptree> resultMap; auto list = Descendants(root, key); if (!list) return resultMap; for (auto& item : *list) { auto attr = Attribute(item, attrName); if (attr&&predict(*attr)) resultMap.insert(std::make_pair(*attr, item.second)); } return resultMap; } private: using code_type = std::codecvt<wchar_t, char, std::mbstate_t>; std::wstring_convert<code_type> m_conv; };
测试文件test.xml和测试代码:
<?xml version="1.0" encoding="UTF-8"?> <Root Id="123456"> <Scenes> <!--注释说明1--> <Scene Name="测试1"> <!--注释说明11--> <DataSource> <!--注释说明111--> <Data> <!--注释说明111--> <Item Id="1" FileName="测试文件1" /> </Data> <Data> <Item Id="2" FileName="测试文件2" /> <Item Id="3" FileName="测试文件3" /> </Data> </DataSource> </Scene> <!--注释说明1--> <Scene Name="测试2"> <DataSource> <Data> <Item Id="4" FileName="测试文件4" /> </Data> <Data> <Item Id="5" FileName="测试文件5" /> </Data> </DataSource> </Scene> </Scenes> </Root>
void Test() { wptree pt; ConfigParser parser; parser.Init(L"test1.xml", pt); //解决中文问题,要转换为unicode解析 auto scenes = parser.Descendants(pt, L"Root.Scenes"); //返回的是optional<wptree> if (!scenes) return; for (auto& scene : *scenes) { auto s = parser.Attribute(scene, L"Name"); //获取Name属性,返回的是optional<wstring> if (s) { wcout << *s << endl; } auto dataList = parser.Descendants(scene.second, L"DataSource"); //获取第一个子节点 if (!dataList) continue; for (auto& data : *dataList) { for (auto& item : data.second) { auto id = parser.Attribute<int>(item, L"Id"); auto fileName = parser.Attribute(item, L"FileName"); if (id) { wcout << *id << L" " << *fileName << endl; //打印id和filename } } } } }
测试结果:
能够看到经过帮助类,无需使用原生接口就能够很方便的实现节点的访问与操做。使用者没必要关注内部细节,根据统一而简洁的接口就能够操做xml文件了。
一点题外话,基于这个帮助类再结合linq to object能够轻松的实现linq to xml:
//获取子节点SubNode的属性ID的值为0x10000D的项并打印出该项的Type属性 from(node.Descendants("Root.SubNode")).where([](XNode& node) { auto s = node.Attribute("ID"); return s&&*s == "0x10000D"; }).for_each([](XNode& node) { auto s = node.Attribute("Type"); if (s) cout << *s << endl; });
若是你以为这篇文章对你有用,能够点一下推荐,谢谢。
c++11 boost技术交流群:296561497,欢迎你们来交流技术。