zoukankan      html  css  js  c++  java
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决

      boost.property_tree可以用来解析xml和json文件,我主要用它来解析xml文件,它内部封装了号称最快的xml解析器rapid_xml,其解析效率还是很好的。但是在使用过程中却发现各种不好用,归纳一下不好用的地方有这些:

    1. 获取不存在的节点时就抛出异常
    2. 获取属性值时,要排除属性和注释节点,如果没注意这一点就会抛出异常,让人摸不着头脑。
    3. 内存模型有点怪。
    4. 默认不支持中文的解析。解析中文会乱码。

    ptree获取子节点

      获取子节点接口原型为get_child(node_path),这个node_path从当前路径开始的全路径,父路径和子路径之间通过“.”连接,如“root.sub.child”。需要注意的是get_child获取的是第一个子节点,如果我们要获取子节点列表,则要用路径“root.sub”,这个路径可以获取child的列表。如果获取节点的路径不存在则会抛出异常,这时,如果不希望抛出异常则可以用get_xxx_optional接口,该接口返回一个optional<T>的结果出来,由外面判断是否获取到结果了。

    //ptree的optional接口
    auto item = root.get_child_optional("Root.Scenes");

      该接口返回的是一个optional<ptree>,外面还要判断该节点是否存在,optional对象通过bool操作符来判断该对象是否是无效值,通过指针访问 

    符"*"来访问该对象的实际内容。建议用optional接口访问xml节点。

    //ptree的optional接口
    auto item = root.get_child_optional("Root.Scenes");
    if(item)
        cout<<"该节点存在"<<endl;

    ptree的内存模型

      ptree维护了一个pair<string, ptree>的子节点列表,first指向的是该节点的TagName,second指向的才是ptree节点,因此在遍历ptree子节点时要注意迭代器的含义。

    for (auto& data : root)
    {
        for (auto& item : data.second) //列表元素为pair<string, ptree>,要用second继续遍历
        {
            cout<<item.first<<endl;
        }
    }

      需要注意的是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文件的格式是utf-8格式的,如果xml文件中含有unicode如中文字符,解析出来就是乱码。解析unicode要用wptree,该类的接口均支持宽字符并且接口和ptree保持一致。要支持中文解析仅仅wptree还不够,还需要一个unicode转换器的帮助,该转换器可以实现宽字符和窄字符的转换,宽窄的互相转换函数有很多实现,不过c++11中有更简单统一的方式实现宽窄字符的转换。

    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文件时,需要先将该文件转换一下。

      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的帮助类

      property_tree的帮助类解决了前面提到的问题:

    1. 用c++11解决中文解析问题
    2. 简化属性的获取
    3. 增加一些操作接口,比如一些查找接口
    4. 避免抛出异常,全部返回optional<T>对象
    5. 隔离了底层繁琐的操作接口,提供统一、简洁的高层接口,使用更加方便。

      下面来看看这个帮助类是如何实现的吧:

    #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;
    };
    View Code

      测试文件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,欢迎大家来交流技术。

  • 相关阅读:
    Django ORM 进行查询操作和性能优化
    Python PIL 长文本换行,二维码,图片合成
    python 常用的资料链接
    人生苦短,我用Python
    windows下搭建Python virtualenvvirtualenvwrapper虚拟环境。
    Python 文件上传base64图片
    python实现中文转换url编码的方法
    同时装了Python3和Python2,怎么用pip?
    统计当天下单量
    Django ORM 级联删除
  • 原文地址:https://www.cnblogs.com/qicosmos/p/3555668.html
Copyright © 2011-2022 走看看