zoukankan      html  css  js  c++  java
  • Pugixml一种快速解析XML文件的开源解析库

    Pugixml是一个轻量级的C++ XML开源解析库,DOM形式的解析器、接口和丰富的遍历和修改操作,快速的解析,此外支持XPath1.0实现数据查询,支持unicode编码;

      使用Pugixml可通过直接在项目中包含其几个文件或者编译为动态库dll、静态库lib的形式供其他项目使用、比较方便,如果需要推荐编译为静态库或文件包含即可;

      Pugixml项目中提供了文档手册、快速使用指南,可参考文档说明和smaples中的示例代码尝试快速上手使用,以及源码分析;

      搭建好环境、工程(具体可参照文档、手册),我们以smaples中的load_file.cpp文件作为分析的出发点,运行程序,观察执行结果,当然需要当前路径下的tree.xml文件,否则会加载失败;可以预先查看tree.xml文件内容,该文件作为经典例子来学习,内容基本涵盖了整个解析器可实现的解析功能,继续调试跟踪;

      首先pugi::xml_document作为文档类也作为DOM树的根节点类,其继承于xml_node节点类;在Pugixml中xml_node节点类作为操作节点的轻量级基础类,基本上大多数操作基于此类;xml_node节点类实现的操作接口比较多,但是成员变量仅有一个_root,该变量类型为节点结构,作为当前节点的根;继续跟踪节点结构定义;

    xml_node_struct:节点结构

        header:目前还不知道含义,根据初始化可推测为指向分配的内存页首地址;

        name:节点名称;

        value:节点的值;

        parent:父节点;

        first_child:第一个子节点;

        prev_sibling_c:上一个兄弟节点;

        next_sibling:下一个兄弟节点;

        first_attribute:节点的第一个属性;

    xml_attribute_struct:节点的属性结构

        header:指向内存地址首地址;

        name:节点属性名称;

        value:节点属性的值;

        prev_attribute_c:上一个兄弟属性;

        next_attribute:下一个兄弟属性;

    事实上xml_node节点类的成员_root标识当前节点的(作为当前节点的根节点),所以xml_document的_root则为整个DOM树的根节点;xml_node节点类的其他成员暂不分析后面会分析到;

    xml_memory_page:内存页

        allocator:内存分配器对象;

        prev:上一个内存页;

        next:下一个内存页;

        busy_size:正使用的内存页大小;

        freed_size:空闲的内存页大小;

    xml_allocator:内存分配器(提供了分配和释放内存的操作接口)

        _root:内存页根节点;

        _busy_size:已使用内存大小;

    xml_document_struct:文档结构类(继承于xml_node_struct、xml_allocator)

        buffer:文档结构缓冲区;

        extra_buffers:额外的缓冲区;

      

    xml_extra_buffer:额外缓冲区

        buffer:缓冲区;

        next:下一个额外缓冲区;

    xml_document类,可以发现继承了xml_node类操作还增加了一些加载和保存相关的操作接口,以及create、destory、reset,这几个函数结合_buffer、_memory主要用来预分配、初始化页内存分配、对齐或释放操作;

    create: 内部操作;

    1.检验哨兵页_memory是否够用以保证分配页起始位置仍在_memory范围内;

    2.对齐分配页起始内存位置(按照xml_memory_page_alignment长度对齐);

    3.将在_memory中的得到的起始内存页位置初始化得到内存页page;

    4.将page的正使用的内存页大小busy_size设置为xml_memory_page_size(32768个字节);

    5.在_memory的page页结构后new重分配xml_document_struct大小的空间作为_root根节点,并将_root的上一个兄弟节点指向自身,_root节点作为page的内存分配器;

    6.再次检验page后重分配xml_document_struct大小的空间是否超过_memory范围;

            destroy:内部操作

                释放_buffer缓冲空间、_root下的额外缓冲空间以及_root的兄弟缓冲空间;

            reset:内部调用create、destroy,另外一个重载版本支持拷贝另一个xml_document来重新初始化DOM树;

        基本上我们已确定的当前内存布局方式:         

            以_memory作为基础,在_memory上建立page(root_page)、_root布局和位置定位,确定page与_root间的关系,此外_root扮演着内存分配器的功能负责分配额外缓冲区xml_extra_buffer以及分配页缓冲区xml_memory_page;而xml_exrta_buffer维护一个额外缓冲区链表xml_extra_buffer交由_root管理, xml_memory_page维护自己的页缓冲区链表交由page管理,不过xml_document只保存了_root,但_root

    成员已保存了page的首地址,可以追寻到page;总结:目前已经存在上述的两套链表;

        文件加载和解析:

    load或load_XXX:加载XML文件或文件内容,先暂时直接分析load_file接口,load_file()-->impl::load_file_impl()-->load_buffer_impl()-->impl::xml_parser::parse();以下将依次按照函数调用顺序进行分析:load_file:提供重载版本,参数path为xml文件路径,options为解析选项,默认解析模式为parse_default,即在DOM树种元素、PCDATA、CDATA块被扩展,结束换行符标准化、属性值按照CDATA块方式进行标准化处理;若选项为parse_full,则解析所有包含parse_default以及pi数据、注释数据、声明数据等;参数encoding为编码方式,默认为自动识别,pugixml提供了可支持的多种编码方式xml_encoding如:UTF8、Little-endian UTF16、Big-endian UTF16、Little-endian UTF32、Big-endian UTF32等;impl::load_file_impl:加载文件实现接口,增加参数_root,文件描述符,_buffer保存文件内容缓冲区;函数内部通过get_file_size获取文件大小并通过impl::xml_memory::allocate分配足够容纳所有文件内容的缓冲区(该分配器内部默认调用malloc和free,用户可通过set_memory_management_functions修改其为自己的内存分配器),通过get_buffer_encoding获取缓冲区内容真实的编码方式,最后通过调用zero_terminate_buffer修正buffer结束终止符号;load_buffer_impl:内部调用impl::convert_buffer实现编码格式的转化,具体的转化过程暂时跳过,不作分析(不过内部重新申请了一片新的转化后的缓冲区内容,并将早期的_buffer通过impl::xml_memory::deallocate释放掉了),_buffer指向了转化后的文件内容缓冲区,此外doc->buffer亦保存该新的缓冲区地址;impl::xml_parser::parse:解析文件内容缓冲区,内部通过xml_parser解析器调用parse_tree解析文件内容,完成DOM树构建(事实上每个节点都一个_root成员表示以自己为根节点时可以遍历其兄弟节点和子节点,故xml_xml_document的_root作为根节点可以遍历整个树的信息() ),此外每个节点或属性均通过内存分配器在堆上分配的(不过不用担心这些节点已在xml_memory_page和xml_extra_buffer中管理和分配,其已尽可能减少内存分配和内存碎片),最后说明所有节点的前一个兄弟节点若不存在时则指向自己,下一个兄弟节点不存在时指向空,所有的属性的前一个兄弟属性若不存在时也指向自己,下一个兄弟属性不存在时指向空;具体的解析过程不再去分析,比较繁琐,有时间可以去细化深入分析;

    再次分析load/load_xxx加载接口,目前load接口提供了三个重载版本,分别支持std::basic_istream流、XML文件内容格式的字符串;无论哪种最终转化为调用接口load_buffer_impl()实现加载解析;load_string、load_buffer支持加载其他内存缓冲区或xml文件内容字符串;此外还有load_buffer_inplace、load_buffer_inplace_own,前者需要用户提供内容缓冲区且保证整个DOM树生存期内仍然存活,与其他加载方式不同,此接口下DOM内部不再拷贝副本;load_buffer_inplace_own也由用户提供内容缓冲区,但是DOM会接管该缓冲区,外部不再允许释放(!DOM会释放,意味着该缓冲区必须在堆上创建,建议不使用该接口);

    文件保存:

    save/save_file:保存XML内容至数据流或文件中;现在暂时分析save_file接口;save_file()-->impl::save_file_impl()-->save()-->impl::node_output();

    以下将依次按照函数调用顺序进行分析:

    save_file:参数path_为保存xml文件路径名,indent为缩排字符串,默认为” ”,flags为输出格式选项,默认值format_default(节点缩进依赖于其在DOM树的深度,以及一个默认的声明),参数encoding为输出文件的编码方式,默认为自动encoding_auto(即默认的编码);impl::save_file_impl:保存文件实现,增加参数文档对象doc,文件描述符,内部通过创建一个xml_writer_file对象,并将该对象传入文档对象doc的save方法

    实现文件保存;

    save:保存文件操作,内部通过传入的xml_writer_file对象创建impl::xml_buffered_writer实例,该实例对象进行真正的写文件操作对象(内含一成员buffer当保存大于该容量阈值才进行文件flush,以减少写文件的次数),默认将声明写入文件,此后调用impl::node_output遍历DOM树节点并按照缩进格式和节点的当前树深度写入指定文件;

    再次分析save接口,重载三个版本,分别支持输出到xml_writer、

    std::basic_ostream,用户可以继承并重写xml_writer的write函数增加自己的操作,也可以输出到ostream流中;

        节点添加、修改、删除:

            pugixml提供了丰富的操作接口,具体可查看xml_node节点类相关接口,此外最重要的是内存布局、分配的问题,所有节点均在分配页中进行管理,基本上不用单独申请或释放任何节点,释放时调用destroy时会释放掉所有的那些节点或属性依附的分配页即可;

        节点的遍历和查找:

            pugixml提供了基本的遍历方式即通过child、next_sibling、next_attribute等可以较为方便的实现节点或属性的遍历;也提供了xml_node_iterator、xml_attribute_iterator节点迭代器和属性迭代器(事实上是对基本遍历方式的简单封装);此外提供了谓语查找,用户可以提供自己的查找函数(只需要提供仿函数或函数对象即可)主要用在find_child、find_node、find_attribute这几个接口;xml_document类提供了一个接口traverse,其可支持用户自定义的遍历“步行者”,通过继承xml_tree_walker类并实现for_each回调函数接口,该接口将遍历所有节点,返回值为true则继续爬行,否则停止爬行,此外用户也可以实现其begin与end接口,这两个接口分别在for_each执行前后,可增加必要的初始化操作或收尾操作,返回false则退出;该类中成员_depth为当前节点的爬行深度,可通过depth()接口获取;

    额外说明:

    值得说明的是pugiconfig.hpp配置文件,里面定义了许多宏,如:

    PUGIXML_WCHAR_MODE:开启unicode支持;

    PUGIXML_COMPACT:支持紧凑型内存管理布局;

    PUGIXML_NO_XPATH:不需要xpath支持,可减少pugixml编译为库的体积;

    PUGIXML_NO_STL:不使用STL支持,对于不想使用STL环境下比较有用;

    PUGIXML_NO_EXCEPTIONS:不允许抛出异常;

    PUGIXML_API:编译导出为DLL;

    PUGIXML_MEMORY_PAGE_SIZE:内存页大小;

    PUGIXML_HAS_LONG_LONG:支持long long数据类型;

    PUGIXML_HEADER_ONLY:只包含头文件,这样头文件内部会自己包含cpp文件;

      可根据需要使用或修改相应的宏;

        总结:

      pugixml提供了丰富的节点操作和遍历接口并以DOM形式构建,此外内存管理提供用户内存分配器以及支持对齐或紧凑型内存布局、文件解析并支持多种编码方式xml文件和编码格式的相互转化,支unicode,默认为utf-8格式;(事实上xml文件加载或文件内存缓冲区加载的时候,可以提前释放xml_document类成员_buffer的空间,因后面一直没有用到,且释放空间可供进程重新申请使用该释放的空间);接口使用简便、操作解析也比较快速,内存管理相对tinyxml内存碎片也会少很多且指针访问比较集中(tinyxml内部无内存管理,可能也会导致内存碎片以及轻微的访问、操作较慢一些)。

  • 相关阅读:
    Parameter Binding in ASP.NET Web API
    Which HTTP methods match up to which CRUD methods?
    ErrorHandling in asp.net web api
    HttpStatusCode
    Autofac Getting Started(默认的构造函数注入)
    Autofac Controlling Scope and Lifetime
    luvit 被忽视的lua 高性能框架(仿nodejs)
    undefined与null的区别
    VsCode中使用Emmet神器快速编写HTML代码
    字符串匹配---KMP算法
  • 原文地址:https://www.cnblogs.com/haomiao/p/5041065.html
Copyright © 2011-2022 走看看