xml 數據格式作爲當今WEB開發的重要數據格式之一,應用很是普及,在文章 <acl 之 xml 流解析器> 中, 專門講述了 acl 庫中是如何實現流式 xml 數據解析的,在 acl_cpp 庫中利用 c++ 語言特色對 acl 中的 xml 流式解析進行了進一步封裝,從而更加方便用戶使用,其中主要涉及到兩個類:xml 類和 xml_node 類,如今分別就這兩個類的函數功能作一簡單介紹。node
1、解析過程當中的用法c++
一、 xml 類中的主要方法以下:數組
/** * 以流式方式循環調用本函數添加 XML 數據,也能夠一次性添加 * 完整的 XML 數據,若是是重複使用該 XML 解析器解析多個 XML * 對象,則應該在解析下一個 XML 對象前調用 reset() 方法來清 * 除上一次的解析結果 * @param data {const char*} xml 數據 */ void update(const char* data);
/** * 從 XML 對象中取得某個標籤名的全部結點集合 * @param tag {const char*} 標籤名(不區分大小寫) * @return {const std::vector<xml_node*>&} 返回結果集的對象引用, * 若是查詢結果爲空,則該集合爲空,即:empty() == true * 注:返回的數組中的 xml_node 結點數據能夠修改,但不能刪除該結點, * 由於該庫內部有自動刪除的機制 */ const std::vector<xml_node*>& getElementsByTagName(const char* tag) const; /** * 從 xml 對象中得到全部的與給定多級標籤名相同的 xml 結點的集合 * @param tags {const char*} 多級標籤名,由 '/' 分隔各級標籤名,如針對 xml 數據: * <root> <first> <second> <third name="test1"> text1 </third> </second> </first> ... * <root> <first> <second> <third name="test2"> text2 </third> </second> </first> ... * <root> <first> <second> <third name="test3"> text3 </third> </second> </first> ... * 能夠經過多級標籤名:root/first/second/third 一次性查出全部符合條件的結點 * @return {const std::vector<xml_node*>&} 符合條件的 xml 結點集合, * 若是查詢結果爲空,則該集合爲空,即:empty() == true * 注:返回的數組中的 xml_node 結點數據能夠修改,但不能刪除該結點, * 由於該庫內部有自動刪除的機制 */ const std::vector<xml_node*>& getElementsByTags(const char* tags) const; /** * 從 xml 對象中得到全部的與給定屬性名 name 的屬性值相同的 xml 結點元素集合 * @param name {const char*} 屬性名爲 name 的屬性值 * @return {const std::vector<xml_node*>&} 返回結果集的對象引用, * 若是查詢結果爲空,則該集合爲空,即:empty() == true * 注:返回的數組中的 xml_node 結點數據能夠修改,但不能刪除該結點, * 由於該庫內部有自動刪除的機制 */ const std::vector<xml_node*>& getElementsByName(const char* value) const; /** * 從 xml 對象中得到全部給定屬性名及屬性值的 xml 結點元素集合 * @param name {const char*} 屬性名 * @param value {const char*} 屬性值 * @return {const std::vector<xml_node*>&} 返回結果集的對象引用, * 若是查詢結果爲空,則該集合爲空,即:empty() == true */ const std::vector<xml_node*>& getElementsByAttr(const char* name, const char* value) const; /** * 從 xml 對象中得到指定 id 值的 xml 結點元素 * @param id {const char*} id 值 * @return {const xml_node*} xml 結點元素, 若返回 NULL 則表示沒有符合 * 條件的 xml 結點, 返回值不須要釋放 */ const xml_node* getElementById(const char* id) const;
/** * 開始遍歷該 xml 對象並得到第一個結點 * @return {xml_node*} 返回空表示該 xml 對象爲空結點 * 注:返回的結點對象用戶不能手工釋放,由於該對象被 * 內部庫自動釋放 */ xml_node* first_node(void); /** * 遍歷該 xml 對象的下一個 xml 結點 * @return {xml_node*} 返回空表示遍歷完畢 * 注:返回的結點對象用戶不能手工釋放,由於該對象被 * 內部庫自動釋放 */ xml_node* next_node(void);
二、xml_node 類中的主要方法網絡
/** * 取得本 XML 結點的標籤名 * @return {const char*} 返回 XML 結點標籤名,若是返回空,則說明 * 不存在標籤?xxxx,以防萬一,調用者須要判斷返回值 */ const char* tag_name(void) const; /** * 若是該 XML 結點的 ID 號屬性不存在,則返回空指針 * @return {const char*} 當 ID 屬性存在時返回對應的值,不然返回空 */ const char* id(void) const; /** * 返回該 XML 結點的正文內容 * @return {const char*} 返回空說明沒有正文內容 */ const char* text(void) const; /** * 返回該 XML 結點的某個屬性值 * @param name {const char*} 屬性名 * @return {const char*} 屬性值,若是返回空則說明該屬性不存在 */ const char* attr_value(const char* name) const; /** * 遍歷結點的全部屬性時,須要調用此函數來得到第一個屬性對象 * @return {const xml_attr*} 返回第一個屬性對象,若爲空,則表示 * 該結點沒有屬性 */ const xml_attr* first_attr(void) const; /** * 遍歷結點的全部屬性時,調用本函數得到下一個屬性對象 * @return {const xml_attr*} 返回下一下屬性對象,若爲空,則表示 * 遍歷完畢 */ const xml_attr* next_attr(void) const;
/** * 得到本結點的父級結點對象的引用 * @return {xml_node&} */ xml_node& get_parent(void) const; /** * 得到本結點的第一個子結點,須要遍歷子結點時必須首先調用此函數 * @return {xml_node*} 返回空表示沒有子結點 */ xml_node* first_child(void); /** * 得到本結點的下一個子結點 * @return {xml_node*} 返回空表示遍歷過程結束 */ xml_node* next_child(void); /** * 返回該 xml 結點的下一級子結點的個數 * @return {int} 永遠 >= 0 */ int children_count(void) const;
上面列出的函數接口比較多,還有一些未列出,用戶在用時難免會被這麼多接口搞暈,下面就寫一個簡單的例子說明如何使用這兩個類。app
#include <vector> #include "xml.hpp" static void test1(void) { const char *data = "<?xml version=\"1.0\"?>\r\n" "<?xml-stylesheet type=\"text/xsl\"\r\n" " href=\"http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl\"?>\r\n" "<!DOCTYPE refentry PUBLIC \"-//OASIS//DTD DocBook XML V4.1.2//EN\"\r\n" " \"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd\" [\r\n" " <!ENTITY xmllint \"<command>xmllint</command>\">\r\n" "]>\r\n" "<root>test\r\n" " <!-- <edition> - <!--0.5--> - </edition> -->\r\n" " <user name = user_name>zsx\r\n" " <age>38</age>\r\n" " </user>\r\n" "</root>\r\n" "<!-- <edition><!-- 0.5 --></edition> -->\r\n" "<!-- <edition>0.5</edition> -->\r\n" "<!-- <edition> -- 0.5 -- </edition> -->\r\n" "<root name='root' id='root_id'>test</root>\r\n"; acl::xml xml; // xml 解析器對象定義 xml.update(data); // 將 xml 數據輸入並進行解析 // 根據 xml 標籤名得到全部相應的 xml 結點對象 const std::vector<acl::xml_node*>& elements = xml.getElementsByTagName("user"); if (!elements.empty()) { // 遍歷查詢結果集 std::vector<acl::xml_node*>::const_iterator cit = elements.begin(); for (; cit != elements.end(); cit++) { acl::xml_node *node = *cit; printf("tagname: %s, text: %s\n", node->tag_name() ? node->tag_name() : "", node->text() ? node->text() : ""); // 遍歷一個結點的全部屬性 const acl::xml_attr* attr = (*cit)->first_attr(); // 取得結點的第一個屬性 while (attr) { printf("test1: %s=%s\r\n", attr->get_name(), attr->get_value()); attr = (*cit)->next_attr(); // 取得結點的下一個屬性 } } } }
上面的例子中是一次性將 xml 數據傳給 acl::xml 解析器進行解析的,固然也能夠採用以下的方法:函數
const char* ptr; char buf[2]; ptr = data; while (*ptr) { buf[0] = *ptr++; buf[1] = 0; xml.update(buf); }
每次傳給xml解析器一個字節的解析效率比較低,這只是展現 acl_cpp 中的 xml 的流式解析器的特色,這對於網絡通訊中尤爲是 HTTP 數據流中針對 xml 數據流的解析比較有幫助。ui
另外,xml 解析器還給出一個用於遍歷全部 xml 結點對象的函數:first_node 和 next_node,經過這兩個函數能夠得到一個完整的 xml 樹的全部結點,示例以下:.net
acl::xml xml; ... acl::xml_node* node = xml.first_node(); // 取得第一個 xml 結點 while (node) { printf("tag: %s\r\n", node->tag_name()); node = xml.next_node(); // 取得下一個 xml 結點 }
不只 xml 樹對象有遍歷的功能函數,xml_node 結點對象也有遍歷其下一級子結點的功能函數,示例以下:指針
acl::xml xml; ... acl::xml_node* node = xml.first_node(); //取得 xml 對象的第一個xml_node 結點 if (node) { acl::xml_node* child = node->first_child(); // 取得該 xml_node 結點的第一個第一級子結點 while (child) { printf("child tag: %s\r\n", child->tag_name()); child = node->next_child(); // 取得該 xml_node 結點的下一下第一級子結點 } }
2、生成 xml 字符串的用法code
爲了便於生成 xml 對象,acl_cpp 的 xml 模塊增長了相應的函數接口用於生成 xml 數據流,下面介紹如何生成 xml 數據流。
一、在 xml 類中相關函數接口:
/** * 建立一個 xml_node 結點對象 * @param tag {const char*} 標籤名 * @param text {const char*} 文本字符串 * @return {xml_node*} 新產生的 xml_node 對象不須要用戶手工釋放,由於在 * xml 對象被釋放時這些結點會自動被釋放,固然用戶也能夠在不用時調用 * reset 來釋放這些 xml_node 結點對象 */ xml_node& create_node(const char* tag, const char* text = NULL); /** * 得到根結點對象 * @return {xml_node&} */ xml_node& get_root();
在 xml 解析器中,有一個虛擬的 xml 根結點,這個結點自己不存任何 xml 數據,但全部的 xml_node 結點都屬於這個根結點的子結點。
二、在 xml_node 類中相關函數接口:
/** * 添加 XML 結點屬性 * @param name {const char*} 屬性名 * @param value {const char*} 屬性值 * @return {xml_node&} */ xml_node& add_attr(const char* name, const char* value); /** * 設置 xml 結點的文本內容 * @param str {const char*} 字符串內容 * @return {xml_node&} */ xml_node& set_text(const char* str);
/** * 給本 xml 結點添加 xml_node 子結點對象 * @param child {xml_node*} 子結點對象 * @return {xml_node&} return_child 爲 true 返回子結點的引用, * 不然返回本 xml 結點引用 */ xml_node& add_child(xml_node* child, bool return_child = false); /** * 給本 xml 結點添加 xml_node 子結點對象 * @param child {xml_node&} 子結點對象 * @return {xml_node&} return_child 爲 true 返回子結點的引用, * 不然返回本 xml 結點引用 */ xml_node& add_child(xml_node& child, bool return_child = false); /** * 給本 xml 結點添加 xml_node 子結點對象 * @param tag {const char* tag} 子結點對象的標籤名 * @return {xml_node&} return_child 爲 true 返回子結點的引用, * @param str {const char*} 文本字符串 * 不然返回本 xml 結點引用 */ xml_node& add_child(const char* tag, bool return_child = false, const char* str = NULL);
下面舉幾個簡單的例子來講明如何生成 xml 數據流:
acl::xml xml;
acl::xml_node& root = xml.get_root(); // 得到 xml 的根結點 acl::xml_node* node1, *node2, *node11; // 建立一個 xml_node 結點 node1 = &xml.create_node("test1"); // 給 node1 結點添加屬性值 (*node1).add_attr("name1_1", "value1_1") .add_attr("name1_2", "value1_2") .add_attr("name1_3", "value1_3"); // 將 node1 作爲 xml 根結點的第一個子結點 root.add_child(node1); // 建立一個 xml_node 結點 node11 = &xml.create_node("test11"); // 給 node11 結點添加屬性值 (*node11).add_attr("name11_1", "value11_1") .add_attr("name11_2", "value11_2") .add_attr("name11_3", "value11_3"); // 將 node11 作爲 node1 根結點的第一個子結點 node1.add_child(node11); // 建立一個 xml_node 結點 node2 = &xml.create_node("test2"); // 給 node2 結點添加屬性值 (*node2).add_attr("name2_1", "value2_1") .add_attr("name2_2", "value2_2") .add_attr("name2_3", "value2_3"); // 將 node2 作爲 xml 根結點的第二個子結點 root.add_child(node2); acl::string buf("<?xml version=\"1.0\"?>"); xml.build_xml(buf); // 生成 xml 數據流,注:在 函數 build_xml 內部對於緩衝區 buf 的處理方式是 append 模式,即若是在 buf 裏有數據,build_xml 只是在 buf 原來的數據尾部追加數據而已 printf("xml: %s\r\n", buf.c_str()); // 打印生成的 xml 數據
其實,上面的示例還有一個更加簡潔的寫法,以下:
acl::xml_node& root = xml.get_root(); // 得到 xml 的根結點 acl::xml_node* node1, *node2, *node11; // 建立一個 xml_node 結點 xml.get_root() .add_child("test1", true) // 因第二個參數爲 true,因此 add_child 函數返回新建立子結點的引用 .add_attr("name1_1", "value1_1") // 給 test1 結點添加屬性 .add_attr("name1_2", "value1_2") .add_attr("name1_3", "value1_3"); .add_child("test11", true) // 給 test1 結點添加一個標籤值爲 test11 的子結點 .add_attr("name11_1", "value11_1") // 給 test11 子結點添加屬性 .add_attr("name11_2", "value11_2") .add_attr("name11_3", "value11_3"); .get_parent() // 返回 test11 結點的父結點的引用,即返回 test1 結點 .get_parent() // 返回 test1 結點的引用即返回 xml 的 root 結點 .add_child("test2", true) // 給 xml 根結點添加 test2 子結點 .add_attr("name2_1", "value2_1") // 給 test2 子結點添加屬性 .add_attr("name2_2", "value2_2") .add_attr("name2_3", "value2_3");
能夠看出,第二種寫法更加簡潔有效,同時邏輯關係更爲清晰,有種一鼓作氣的感受,呵呵。固然,讀者能夠根據本身的習慣使用其中任意一種寫法。另外,你們仔 細查看 xml_node 類的聲明可能會看出,該類的構造函數和析構函數是私有的,這意味着用戶不能使用 new 或delete 來手工建立和銷燬 xml_node 類對象,同時不能如 acl::xml_node node 這樣定義對象,這就說,xml_node 對象只能是由 acl::xml 類對象或 acl::xml_node 類對象來建立,同時對全部 xml_node 類對象的銷燬都是在 acl::xml 類對象內部自動完成的,即當 xml 對象銷燬時,這些內部動態建立的 xml_node 結點會被自動銷燬;若是用戶想在 acl::xml 類對象銷燬以前提早銷燬全部的 acl::xml_node 類對象,則用戶能夠手工調用 acl::xml類中的 reset() 方法來達到此目的。
使用 xml 的例子在:samples/xml 目錄下
acl_cpp 下載:http://sourceforge.net/projects/acl/
原文地址:http://zsxxsz.iteye.com/blog/1506643
QQ 羣:242722074