boost.property_tree可以用來解析xml和json文件,我主要用它來解析xml文件,它內(nèi)部封裝了號稱最快的xml解析器rapid_xml,其解析效率還是很好的。但是在使用過程中卻發(fā)現(xiàn)各種不好用,歸納一下不好用的地方有這些:
獲取不存在的節(jié)點時就拋出異常
獲取屬性值時,要排除屬性和注釋節(jié)點,如果沒注意這一點就會拋出異常,讓人摸不著頭腦。
內(nèi)存模型有點怪。
默認不支持中文的解析。解析中文會亂碼。
ptree獲取子節(jié)點
獲取子節(jié)點接口原型為get_child(node_path),這個node_path從當前路徑開始的全路徑,父路徑和子路徑之間通過“.”連接,如“root.sub.child”。需要注意的是get_child獲取的是第一個子節(jié)點,如果我們要獲取子節(jié)點列表,則要用路徑“root.sub”,這個路徑可以獲取child的列表。如果獲取節(jié)點的路徑不存在則會拋出異常,這時,如果不希望拋出異常則可以用get_xxx_optional接口,該接口返回一個optional的結(jié)果出來,由外面判斷是否獲取到結(jié)果托福答案 www.jamo123.com
//ptree的optional接口
auto item = root.get_child_optional("Root.Scenes");
該接口返回的是一個optional,外面還要判斷該節(jié)點是否存在,optional對象通過bool操作符來判斷該對象是否是無效值,通過指針訪問
符"*"來訪問該對象的實際內(nèi)容。建議用optional接口訪問xml節(jié)點。
//ptree的optional接口
auto item = root.get_child_optional("Root.Scenes");
if(item)
cout<<"該節(jié)點存在"<
ptree的內(nèi)存模型
ptree維護了一個pair的子節(jié)點列表,first指向的是該節(jié)點的TagName,second指向的才是ptree節(jié)點,因此在遍歷ptree子節(jié)點時要注意迭代器的含義。
for (auto& data : root)
{
for (auto& item : data.second) //列表元素為pair,要用second繼續(xù)遍歷
{
cout<
}
}
需要注意的是ptree.first可能是屬性("")也可能是注釋(""),只有非注釋類型的節(jié)點才能使用獲取屬性值、子節(jié)點等常用接口。
ptree獲取屬性值
通過get(attr_name)可以獲取屬性的值,如果想獲取屬性的整形值的話,可以用get("Id"),返回一個整數(shù)值。有一點要注意如果ptree.first為""時,是沒有屬性值的,可以通過data()來獲取注釋內(nèi)容。如果這個ptree.first不為時需要在屬性名稱前面加".",即get(".Id")才能正確獲取屬性值。可以看到獲取屬性值還是比較繁瑣的,在后面要介紹的幫助類中可以簡化屬性值的獲取。如果要獲取節(jié)點的值則用get_value()接口,該接口用來獲取節(jié)點的值,如節(jié)點:2通過get_value()就可以獲取值"2"。
解析中文的問題
ptree只能解析窄字符的xml文件,如果xml文件中含有unicode如中文字符,解析出來就是亂碼。解析unicode要用wptree,該類的接口均支持寬字符并且接口和ptree保持一致。要支持中文解析僅僅wptree還不夠,還需要一個unicode轉(zhuǎn)換器的幫助,該轉(zhuǎn)換器可以實現(xiàn)寬字符和窄字符的轉(zhuǎn)換,寬窄的互相轉(zhuǎn)換函數(shù)有很多實現(xiàn),不過c++11中有更簡單統(tǒng)一的方式實現(xiàn)寬窄字符的轉(zhuǎn)換。
c++11中寬窄字符的轉(zhuǎn)換:
std::wstring_convert> conv
(newstd::codecvt("CHS"));
//寬字符轉(zhuǎn)為窄字符
string str = conv.to_bytes(L"你好");
//窄字符轉(zhuǎn)為寬字符
string wstr = conv.from_bytes(str);
boost.property_tree在解析含中文的xml文件時,需要先將該文件轉(zhuǎn)換一下。
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); //先轉(zhuǎn)換一下
//用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);
f.imbue(utf8Locale); //先轉(zhuǎn)換一下
//用wptree去解析
property_tree::read_xml(f, ptree);
}
用c++11就不需要再引入boost的libboost_program_options庫了,很簡單。
property_tree的幫助類
property_tree的幫助類解決了前面提到的問題:
用c++11解決中文解析問題
簡化屬性的獲取
增加一些操作接口,比如一些查找接口
避免拋出異常,全部返回optional對象
隔離了底層繁瑣的操作接口,提供統(tǒng)一、簡潔的高層接口,使用更加方便。
下面來看看這個幫助類是如何實現(xiàn)的吧:
#include
#include
using namespace boost;
using namespace boost::property_tree;
#include
#include
#include
#include
using namespace std;
const wstring XMLATTR = L"";
const wstring XMLCOMMENT = L"";
const wstring XMLATTR_DOT = L".";
const wstring XMLCOMMENT_DOT = L".";
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);
f.imbue(utf8Locale); //先轉(zhuǎn)換一下
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);
}
//獲取子節(jié)點列表
auto Descendants(const wptree& root, const wstring& key)->decltype(root.get_child_optional(key))
{
return root.get_child_optional(key);
}
//根據(jù)子節(jié)點屬性獲取子節(jié)點列表
template
vector GetChildsByAttr(const wptree& parant, const wstring& tagName, const wstring& attrName, const T& attrVal)
{
vector v;
for (auto& child : parant)
{
if (child.first != tagName)
continue;
auto attr = Attribute(child, attrName);
if (attr&&*attr == attrVal)
v.push_back(child.second);
}
return v;
}
//獲取節(jié)點的某個屬性值
template
optional Attribute(const wptree& node, const wstring& attrName)
{
return node.get_optional(XMLATTR_DOT + attrName);
}
//獲取節(jié)點的某個屬性值,默認為string
optional Attribute(const wptree& node, const wstring& attrName)
{
return Attribute(node, attrName);
}
//獲取value_type的某個屬性值
template
optional Attribute(const wptree::value_type& pair, const wstring& attrName)
{
if (pair.first == XMLATTR)
return pair.second.get_optional(attrName);
else if (pair.first == XMLCOMMENT)
return optional();
else
return pair.second.get_optional(XMLATTR_DOT + attrName);
}
//獲取value_type的某個屬性值,默認為string
optional Attribute(const wptree::value_type& pair, const wstring& attrName)
{
return Attribute(pair, attrName);
}
//根據(jù)某個屬性生成一個的multimap
template>
multimap MakeMapByAttr(const wptree& root, const wstring& key, const wstring& attrName, F predict = [](wstring& str){return true; })
{
multimap 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;
std::wstring_convert m_conv;
};
View Code
測試文件test.xml和測試代碼:
void Test()
{
wptree pt; pt.get_value()
ConfigParser parser;
parser.Init(L"test1.xml", pt); //解決中文問題,要轉(zhuǎn)換為unicode解析
auto scenes = parser.Descendants(pt, L"Root.Scenes"); //返回的是optional
if (!scenes)
return;
for (auto& scene : *scenes)
{
auto s = parser.Attribute(scene, L"Name"); //獲取Name屬性,返回的是optional
if (s)
{
wcout << *s << endl;
}
auto dataList = parser.Descendants(scene.second, L"DataSource"); //獲取第一個子節(jié)點
if (!dataList)
continue;
for (auto& data : *dataList)
{
for (auto& item : data.second)
{
auto id = parser.Attribute(item, L"Id");
auto fileName = parser.Attribute(item, L"FileName");
if (id)
{
wcout << *id << L" " << *fileName << endl; //打印id和filename
}
}
}
}
}
測試結(jié)果:
可以看到通過幫助類,無需使用原生接口就可以很方便的實現(xiàn)節(jié)點的訪問與操作。使用者不必關(guān)注內(nèi)部細節(jié),根據(jù)統(tǒng)一而簡潔的接口就可以操作xml文件了。
一點題外話,基于這個幫助類再結(jié)合linq to object可以輕松的實現(xiàn)linq to xml:
//獲取子節(jié)點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;
});