pugixml 1.10快速入門指南
網站https://pugixml.org存儲庫https://github.com/zeux/pugixml
目錄
介紹
pugixml是一個輕量級的C ++ XML處理庫。它由具有豐富遍歷/修改功能的類DOM接口,非常快的XML解析器(從XML文件/緩沖區構造DOM樹)以及用於復雜數據驅動的樹查詢的XPath 1.0實現組成。還提供完全的Unicode支持,具有兩種Unicode接口變體以及不同Unicode編碼之間的轉換(在解析/保存期間自動發生)。該庫具有極高的可移植性,易於集成和使用。pugixml從2006年開始開發和維護,擁有許多用戶。所有代碼均根據MIT許可進行分發,從而使其完全免費在開源和專有應用程序中使用。
pugixml支持非常快速,便捷和內存有效的XML文檔處理。但是,由於pugixml具有DOM解析器,因此它無法處理內存中不適合的XML文檔。解析器也是非驗證解析器,因此如果您需要DTD / Schema驗證,則該庫不適合您。
這是pugixml的快速入門指南,其目的是使您能夠快速開始使用該庫。許多重要的庫功能或者根本沒有描述,或者只是簡短地提及。有關更完整的信息,請閱讀完整的手冊。
安裝
您可以下載最新的源分發作為存檔:
pugixml-1.10.zip(Windows行尾)/ pugixml-1.10.tar.gz(Unix行尾)
該發行版包含庫源代碼,文檔(您正在閱讀的指南和手冊)以及一些代碼示例。下載發行版后,通過從壓縮檔案中提取所有文件來安裝pugixml。
完整的pugixml源代碼包含三個文件-一個源文件pugixml.cpp
和兩個頭文件pugixml.hpp
和pugiconfig.hpp
。pugixml.hpp
是要使用pugixml類/函數需要包括的主要標頭。本指南的其余部分假定該文件pugixml.hpp
位於項目的當前目錄中或項目的包含目錄之一中,以便#include "pugixml.hpp"
可以找到標頭。但是,您也可以使用相對路徑(即#include "../libs/pugixml/src/pugixml.hpp"
)或包含目錄相對路徑(即#include <xml/thirdparty/pugixml/src/pugixml.hpp>
)。
生成pugixml的最簡單方法是編譯源文件pugixml.cpp
,以及現有的庫/可執行文件。此過程取決於構建應用程序的方法。例如,如果您使用的是Microsoft Visual Studio [ 1 ],Apple Xcode,Code :: Blocks或任何其他IDE,則只需將其添加pugixml.cpp
到您的項目中。還有其他可用的構建方法,包括將pugixml構建為獨立的靜態/共享庫。閱讀手冊以獲取更多信息。
文件物件模型
pugixml以類似於DOM的方式存儲XML數據:整個XML文檔(文檔結構和元素數據)都以樹的形式存儲在內存中。可以從字符流(文件,字符串,C ++ I / O流)加載樹,然后通過特殊的API或XPath表達式遍歷。整個樹是可變的:節點結構和節點/屬性數據都可以隨時更改。最后,文檔轉換的結果可以保存到字符流(文件,C ++ I / O流或自定義傳輸)中。
樹的根是文檔本身,它對應於C ++ type xml_document
。文檔具有一個或多個子節點,它們對應於C ++ type xml_node
。節點具有不同的類型;根據類型,節點可以具有子節點的集合,與C ++類型相對應的屬性的集合xml_attribute
以及一些其他數據(即名稱)。
最常見的節點類型是:
-
文檔節點(
node_document
)-這是樹的根,由幾個子節點組成。該節點對應於xml_document
class;注意,xml_document
是一個子類的xml_node
,所以整個節點接口也同樣有效。 -
元素/標簽節點(
node_element
)-這是最常見的節點類型,代表XML元素。元素節點具有名稱,屬性的集合和子節點的集合(兩者均可以為空)。該屬性是一個簡單的名稱/值對。 -
node_pcdata
純字符數據節點()表示XML中的純文本。PCDATA節點具有一個值,但沒有名稱或子代/屬性。請注意,純字符數據不是元素節點的一部分,而是具有自己的節點;例如,一個元素節點可以有幾個子PCDATA節點。
盡管有幾種類型的節點,有表示樹只有三個C ++類型(xml_document
,xml_node
,xml_attribute
); 某些操作xml_node
僅對某些節點類型有效。他們在下面描述。
注意 |
所有的pugixml類和函數都位於pugi 名稱空間中。您必須使用顯式名稱限定(即pugi::xml_node ),或通過using 指令(即using pugi::xml_node; 或using namespace pugi; )獲得對相關符號的訪問權限。 |
xml_document
是整個文檔結構的所有者;破壞文檔會破壞整個樹。的界面xml_document
由加載功能,保存功能和的整個界面組成xml_node
,可用於檢查和/或修改文檔。請注意,雖然xml_document
是的子類xml_node
,但xml_node
不是多態類型。存在繼承只是為了簡化用法。
xml_node
是文檔節點的句柄;它可以指向文檔中的任何節點,包括文檔本身。所有類型的節點都有一個通用接口。請注意,這xml_node
僅是實際節點的句柄,而不是節點本身-您可以有多個xml_node
指向同一基礎對象的句柄。銷毀xml_node
手柄不會破壞該節點,也不會將其從樹中刪除。
有一個特殊的xml_node
類型值,稱為空節點或空節點。它不對應於任何文檔中的任何節點,因此類似於空指針。但是,所有操作都在空節點上定義。通常,這些操作不執行任何操作,並且返回空節點/屬性或空字符串作為結果。這對於鏈接呼叫很有用;也就是說,你可以得到像這樣一個節點的祖父母:node.parent().parent()
; 如果節點是空節點或沒有父節點,則第一次parent()
調用返回空節點;parent()
然后,第二個調用也返回空節點,因此您不必兩次檢查錯誤。您可以通過隱式boolean cast:if (node) { … }
或來測試句柄是否為空if (!node) { … }
。
xml_attribute
是XML屬性的句柄;它具有與相同的語義xml_node
,即可以有多個xml_attribute
指向同一基礎對象的句柄,並且有一個特殊的null屬性值,該值傳播到函數結果。
配置pugixml時,接口和內部表示形式有兩種選擇:您可以選擇UTF-8(也稱為char)接口或UTF-16 / 32(也稱為wchar_t)接口。選擇是通過PUGIXML_WCHAR_MODE
定義來控制的;您可以通過pugiconfig.hpp
或通過預處理器選項進行設置。所有與字符串一起使用的樹函數都可以與C樣式的以null終止的字符串或所選字符類型的STL字符串一起使用。閱讀手冊以獲取有關Unicode接口的更多信息。
載入文件
pugixml提供了幾種從不同位置加載XML數據的功能-文件,C ++ iostream,內存緩沖區。所有函數都使用非常快速的非驗證解析器。此解析器不完全符合W3C規范-它可以加載任何有效的XML文檔,但不執行某些格式正確的檢查。盡管已做出很大的努力來拒絕無效的XML文檔,但是由於性能原因,未執行某些驗證。XML數據始終在解析之前轉換為內部字符格式。pugixml支持所有流行的Unicode編碼(UTF-8,UTF-16(大和小尾數),UTF-32(大和小尾數);自然支持UCS-2,因為它是UTF-16的嚴格子集)並處理所有自動編碼轉化。
XML數據最常見的來源是文件。pugixml提供了一個單獨的功能,用於從文件加載XML文檔。此函數接受文件路徑作為其第一個參數,還接受兩個可選參數,它們指定解析選項和輸入數據編碼,這在手冊中進行了描述。
這是一個從文件(samples / load_file.cpp)加載XML文檔的示例:
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file("tree.xml");
std::cout << "Load result: " << result.description() << ", mesh name: "
<< doc.child("mesh").attribute("name").value() << std::endl;
load_file
以及其他加載功能,將銷毀現有文檔樹,然后嘗試從指定文件加載新樹。操作的結果在一個xml_parse_result
對象中返回;該對象包含操作狀態和相關信息(即,如果解析失敗,則在輸入文件中最后成功解析的位置)。
解析結果對象可以隱式轉換到bool
; 如果你不想徹底處理解析錯誤,你可以檢查負載函數的返回值,如果它是一個bool
:if (doc.load_file("file.xml")) { … } else { … }
。否則,您可以使用status
成員獲取解析狀態,或者使用description()
成員函數以字符串形式獲取狀態。
這是處理加載錯誤的示例(samples / load_error_handling.cpp):
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_string(source);
if (result)
{
std::cout << "XML [" << source << "] parsed without errors, attr value: [" <<
doc.child("node").attribute("attr").value() << "]\n\n";
}
else
{
std::cout << "XML [" << source << "] parsed with errors, attr value: [" <<
doc.child("node").attribute("attr").value() << "]\n";
std::cout << "Error description: " << result.description() << "\n";
std::cout << "Error offset: " << result.offset << " (error at [..." << (source +
result.offset) << "]\n\n";
}
有時,應從文件以外的其他來源加載XML數據,例如HTTP URL。您也可能希望使用非標准功能從文件加載XML數據,即使用虛擬文件系統工具或從gzip壓縮文件加載XML。這些情況要么需要從內存中加載文檔,在這種情況下,您應該准備一個包含所有XML數據的連續內存塊,然后將其傳遞到緩沖區加載函數之一,或者從C ++ IOstream加載文檔,在這種情況下,您應該提供一個對象實現std::istream
或std::wistream
接口。
從內存加載文檔有不同的功能。他們將傳遞的緩沖區視為不變的(load_buffer
),調用方擁有的可變緩沖區(load_buffer_inplace
)或所有權屬於pugixml(load_buffer_inplace_own
)的可變緩沖區。還有一個簡單的幫助程序功能,xml_document::load
用於需要從以空字符結尾的字符串加載XML文檔的情況。
這是一個使用以下函數之一從內存中加載XML文檔的示例(sample / load_memory.cpp);閱讀示例代碼以獲取更多示例:
const char source[] = "<mesh name='sphere'><bounds>0 0 1 1</bounds></mesh>";
size_t size = sizeof(source);
// You can use load_buffer_inplace to load document from mutable memory block;
// the block's lifetime must exceed that of document
char* buffer = new char[size];
memcpy(buffer, source, size);
// The block can be allocated by any method; the block is modified during parsing
pugi::xml_parse_result result = doc.load_buffer_inplace(buffer, size);
// You have to destroy the block yourself after the document is no longer used
delete[] buffer;
這是一個使用流(samples / load_stream.cpp)從文件中加載XML文檔的簡單示例;閱讀示例代碼以獲取涉及廣泛流和語言環境的更復雜示例:
std::ifstream stream("weekly-utf-8.xml");
pugi::xml_parse_result result = doc.load(stream);
訪問文檔數據
pugixml具有廣泛的接口,可從文檔中獲取各種類型的數據並遍歷文檔。您可以使用各種訪問器來獲取節點/屬性數據,可以通過訪問器或迭代器遍歷子節點/屬性列表,可以對xml_tree_walker
對象進行深度優先遍歷,還可以將XPath用於復雜的數據驅動查詢。
您可以通過name()
訪問器獲取節點或屬性名稱,並通過value()
訪問器獲取值。請注意,兩個函數都絕不會返回空指針-它們要么返回包含相關內容的字符串,要么返回空字符串(如果名稱/值不存在或句柄為空)。另外,有兩個值得注意的東西可以讀取值:
-
通常將數據存儲為某個節點的文本內容,即
<node><description>This is a node</description></node>
。在這種情況下,<description>
節點沒有值,而是具有類型node_pcdata
為value的子級"This is a node"
。pugixml提供child_value()
和text()
輔助功能解析這樣的數據。 -
在許多情況下,屬性值的類型不是字符串-即,盡管屬性值在XML中表示為字符串,但屬性可能始終包含應視為整數的值。pugixml提供了幾個將屬性值轉換為其他類型的訪問器。
這是使用這些功能的示例(sample / traverse_base.cpp):
for (pugi::xml_node tool = tools.child("Tool"); tool; tool = tool.next_sibling("Tool"))
{
std::cout << "Tool " << tool.attribute("Filename").value();
std::cout << ": AllowRemote " << tool.attribute("AllowRemote").as_bool();
std::cout << ", Timeout " << tool.attribute("Timeout").as_int();
std::cout << ", Description '" << tool.child_value("Description") << "'\n";
}
由於許多文檔遍歷都是通過找到具有正確名稱的節點/屬性組成的,因此有一些特殊功能。例如,child("Tool")
返回名稱為的第一個節點,"Tool"
如果沒有這樣的節點,則返回null句柄。這是使用此類函數的示例(samples / traverse_base.cpp):
std::cout << "Tool for *.dae generation: " << tools.find_child_by_attribute("Tool", "OutputFileMasks", "*.dae")
.attribute("Filename").value() << "\n";
for (pugi::xml_node tool = tools.child("Tool"); tool; tool = tool.next_sibling("Tool"))
{
std::cout << "Tool " << tool.attribute("Filename").value() << "\n";
}
子節點列表和屬性列表只是簡單的雙向鏈接列表。盡管您可以使用previous_sibling
/ next_sibling
和其他此類函數進行迭代,但pugixml還提供了節點和屬性迭代器,因此您可以將節點視為其他節點或屬性的容器。所有迭代器都是雙向的,並支持所有常規迭代器操作。如果將迭代器指向的節點/屬性對象從樹中刪除,則迭代器無效。添加節點/屬性不會使任何迭代器無效。
這是使用迭代器進行文檔遍歷的示例(samples / traverse_iter.cpp):
for (pugi::xml_node_iterator it = tools.begin(); it != tools.end(); ++it)
{
std::cout << "Tool:";
for (pugi::xml_attribute_iterator ait = it->attributes_begin(); ait != it->attributes_end(); ++ait)
{
std::cout << " " << ait->name() << "=" << ait->value();
}
std::cout << std::endl;
}
如果您的C ++編譯器支持基於范圍的for循環(這是C ++ 11功能,在撰寫本文時,Microsoft Visual Studio 11 Beta,GCC 4.6和Clang 3.0都支持它),則可以使用它來枚舉節點/屬性。提供了額外的幫助者來支持這一點;請注意,它們還與Boost Foreach以及其他C ++ 11之前的foreach功能兼容。
這是一個使用C ++ 11基於范圍的for循環進行文檔遍歷的示例(samples / traverse_rangefor.cpp):
for (pugi::xml_node tool: tools.children("Tool"))
{
std::cout << "Tool:";
for (pugi::xml_attribute attr: tool.attributes())
{
std::cout << " " << attr.name() << "=" << attr.value();
}
for (pugi::xml_node child: tool.children())
{
std::cout << ", child " << child.name();
}
std::cout << std::endl;
}
上述方法允許遍歷某個節點的直接子代;如果要進行深度樹遍歷,則必須通過遞歸函數或某些等效方法進行。但是,pugixml為子樹的深度優先遍歷提供了一個幫助器。為了使用它,你必須實現xml_tree_walker
接口和調用traverse
功能。
這是一個使用xml_tree_walker(samples / traverse_walker.cpp)遍歷樹層次結構的示例:
struct simple_walker: pugi::xml_tree_walker
{
virtual bool for_each(pugi::xml_node& node)
{
for (int i = 0; i < depth(); ++i) std::cout << " "; // indentation
std::cout << node_types[node.type()] << ": name='" << node.name() << "', value='" << node.value() << "'\n";
return true; // continue traversal
}
};
simple_walker walker;
doc.traverse(walker);
最后,對於復雜的查詢,通常需要更高級別的DSL。pugixml為此類查詢提供了XPath 1.0語言的實現。可以在手冊中找到XPath使用的完整說明,但是這里有一些示例:
pugi::xpath_node_set tools = doc.select_nodes("/Profile/Tools/Tool[@AllowRemote='true' and @DeriveCaptionFrom='lastparam']");
std::cout << "Tools:\n";
for (pugi::xpath_node_set::const_iterator it = tools.begin(); it != tools.end(); ++it)
{
pugi::xpath_node node = *it;
std::cout << node.node().attribute("Filename").value() << "\n";
}
pugi::xpath_node build_tool = doc.select_node("//Tool[contains(Description, 'build system')]");
if (build_tool)
std::cout << "Build tool: " << build_tool.node().attribute("Filename").value() << "\n";
警告 |
XPath函數將xpath_exception 對象拋出錯誤;上面的示例沒有捕獲這些異常。 |
修改文件資料
pugixml中的文檔是完全可變的:您可以完全更改文檔結構並修改節點/屬性的數據。所有功能本身都負責內存管理和結構完整性,因此它們總是產生結構上有效的樹-但是,可以創建無效的XML樹(例如,通過添加兩個具有相同名稱的屬性或設置屬性/節點)名稱為空/無效字符串)。樹修改針對性能和內存消耗進行了優化,因此,如果您有足夠的內存,則可以使用pugixml從頭開始創建文檔,然后將它們保存到文件/流中,而不必依賴容易出錯的手動文本編寫,而不會產生太多開銷。
更改節點/屬性數據或結構的所有成員函數都是非恆定的,因此不能在恆定的句柄上調用。但是,您可以通過簡單的賦值輕松地將常量句柄轉換為非常量句柄:void foo(const pugi::xml_node& n) { pugi::xml_node nc = n; }
,因此這里的const-correctness主要提供其他文檔。
如前所述,節點可以具有名稱和值,它們都是字符串。根據節點類型,名稱或值可能不存在。您可以使用set_name
和set_value
成員函數進行設置。類似的功能可用於屬性。但是,set_value
對於字符串以外的其他一些類型,該函數將重載,例如浮點數。另外,可以使用賦值運算符設置屬性值。這是設置節點/屬性名稱和值(samples / modify_base.cpp)的示例:
pugi::xml_node node = doc.child("node");
// change node name
std::cout << node.set_name("notnode");
std::cout << ", new node name: " << node.name() << std::endl;
// change comment text
std::cout << doc.last_child().set_value("useless comment");
std::cout << ", new comment text: " << doc.last_child().value() << std::endl;
// we can't change value of the element or name of the comment
std::cout << node.set_value("1") << ", " << doc.last_child().set_name("2") << std::endl;
pugi::xml_attribute attr = node.attribute("id");
// change attribute name/value
std::cout << attr.set_name("key") << ", " << attr.set_value("345");
std::cout << ", new attribute: " << attr.name() << "=" << attr.value() << std::endl;
// we can use numbers or booleans
attr.set_value(1.234);
std::cout << "new attribute value: " << attr.value() << std::endl;
// we can also use assignment operators for more concise code
attr = true;
std::cout << "final attribute value: " << attr.value() << std::endl;
沒有文檔樹就沒有節點和屬性,因此如果不將它們添加到某些文檔中就無法創建它們。可以在節點/屬性列表的末尾或其他某個節點之前/之后創建一個節點或屬性。所有插入函數在成功時都將句柄返回到新創建的對象,而在失敗時將其返回空句柄。即使操作失敗(例如,如果您嘗試將子節點添加到PCDATA節點),文檔仍保持一致狀態,但不會添加請求的節點/屬性。
警告 |
attribute() 和child() 函數不會在樹上添加屬性或節點,因此,如果代碼node.attribute("id") = 123; 沒有node name屬性,像like這樣的代碼將不會執行任何操作"id" 。通過添加必要的屬性/節點來確保您正在使用它們。 |
這是向文檔中添加新屬性/節點的示例(samples / modify_add.cpp):
// add node with some name
pugi::xml_node node = doc.append_child("node");
// add description node with text child
pugi::xml_node descr = node.append_child("description");
descr.append_child(pugi::node_pcdata).set_value("Simple node");
// add param node before the description
pugi::xml_node param = node.insert_child_before("param", descr);
// add attributes to param node
param.append_attribute("name") = "version";
param.append_attribute("value") = 1.1;
param.insert_attribute_after("type", param.attribute("name")) = "float";
如果您不希望文檔包含某些節點或屬性,則可以使用remove_attribute
和remove_child
函數將其刪除。刪除屬性或節點會使指向同一基礎對象的所有句柄無效,並使指向同一對象的所有迭代器也無效。刪除節點還會使所有其屬性或子節點列表的后端迭代器無效。小心確保刪除屬性/節點后不存在或不使用所有此類句柄和迭代器。
這是從文檔(samples / modify_remove.cpp)中刪除屬性/節點的示例:
// remove description node with the whole subtree
pugi::xml_node node = doc.child("node");
node.remove_child("description");
// remove id attribute
pugi::xml_node param = node.child("param");
param.remove_attribute("value");
// we can also remove nodes/attributes by handles
pugi::xml_attribute id = param.attribute("name");
param.remove_attribute(id);
保存文件
通常在創建新文檔或加載現有文檔並對其進行處理之后,有必要將結果保存回文件中。將整個文檔或子樹輸出到某個流有時也很有用;用例包括調試打印,通過網絡或其他面向文本的介質進行序列化等。pugixml提供了多種功能,可將文檔的任何子樹輸出到文件,流或其他通用傳輸接口;這些功能允許自定義輸出格式,並執行必要的編碼轉換。
在寫入目標之前,根據節點類型正確格式化節點/屬性數據;所有特殊的XML符號(例如<和&)均已正確轉義。為了防止遺忘的節點/屬性名稱,將空節點/屬性名稱打印為":anonymous"
。對於格式正確的輸出,請確保所有節點和屬性名稱均設置為有意義的值。
如果要將整個文檔保存到文件中,則可以使用該save_file
函數,該函數true
成功返回。這是將XML文檔保存到文件(samples / save_file.cpp)的簡單示例:
// save document to file
std::cout << "Saving result: " << doc.save_file("save_file_output.xml") << std::endl;
為了增強互操作性,pugixml提供了用於將文檔保存到實現C ++ std::ostream
接口的任何對象的功能。這使您可以將文檔保存到任何標准C ++流(即文件流)或任何第三方兼容的實現(即Boost Iostreams)。最值得注意的是,這可以輕松進行調試輸出,因為您可以將std::cout
流用作保存目標。有兩種功能,一種用於狹窄字符流,另一種用於處理寬字符流。
這是將XML文檔保存到標准輸出(samples / save_stream.cpp)的簡單示例:
// save document to standard output
std::cout << "Document:\n";
doc.save(std::cout);
以上所有保存功能均通過編寫器接口實現。這是一個具有單個功能的簡單界面,在輸出過程中以文檔數據塊作為輸入多次被調用。為了通過某些自定義傳輸(例如套接字)輸出文檔,您應該創建一個實現xml_writer_file
接口的對象並將其傳遞給xml_document::save
函數。
這是用於將文檔數據保存到STL字符串(sample / save_custom_writer.cpp)的定制編寫器的簡單示例;閱讀示例代碼以獲取更復雜的示例:
struct xml_string_writer: pugi::xml_writer
{
std::string result;
virtual void write(const void* data, size_t size)
{
result.append(static_cast<const char*>(data), size);
}
};
前面描述的功能將整個文檔保存到目標位置時,很容易保存單個子樹。除了調用之外xml_document::save
,只需xml_node::print
在目標節點上調用函數即可。您可以通過這種方式將節點內容保存到C ++ IOstream對象或自定義編寫器。保存子樹與保存整個文檔略有不同。閱讀手冊以獲取更多信息。
反饋
如果您認為自己在pugixml中發現了錯誤,請通過問題提交表單提交問題。確保包括相關信息,以便可以重現該錯誤:pugixml的版本,編譯器版本和目標體系結構,使用pugixml並顯示該bug的代碼等。功能請求和貢獻也可以作為問題提交。
如果由於隱私或其他問題而無法提交問題,可以直接通過電子郵件與pugixml作者聯系:arseny.kapoulkine@gmail.com。
執照
pugixml庫是根據MIT許可證分發的:
版權所有(c)2006-2019 Arseny Kapoulkine
特此免費授予任何
獲得本軟件和相關文檔
文件(以下簡稱“軟件”)副本的任何人,以
不受限制地(包括但不限於)使用,
復制,修改,合並,發布,分發,再許可和/或出售
軟件副本的權利,並允許
提供軟件的人員這樣做,但須滿足以下
條件:
上述版權聲明和該許可通知應
包含在本軟件的所有副本或大部分內容中。
該軟件按“原樣”提供,沒有任何形式的保證,
明確或隱含的,包括但
不限於對
適銷性,特定目的的適用性和非侵權性的保證。在任何情況下,作者或版權
持有者都不對任何索賠,損壞或其他責任,
無論是在合同的ACTION,侵權或其他原因,
FROM,OUT的或在軟件或使用或連接
其他交易軟件。
這意味着您可以在開源和專有應用程序中自由使用pugixml。如果您在產品中使用pugixml,則向產品分發中添加這樣的確認就足夠了:
該軟件基於pugixml庫(https://pugixml.org)。
pugixml是Arseny Kapoulkine版權所有(C)2006-2019。