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為縮排字符串,默認為”\t”,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內部無內存管理,可能也會導致內存碎片以及輕微的訪問、操作較慢一些)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM