acl_cpp 編程之 xml 流式解析與建立

      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

      更多文章:http://zsxxsz.iteye.com/

      我的微博:http://weibo.com/zsxxsz

      QQ 羣:242722074

相關文章
相關標籤/搜索