acl 之 xml 流解析器

  如今 XML 解析器比較多,其實本沒有必要在ACL中添加新的XML解析庫,象JAVA、PHP、C#的開發者大都習慣於使用XML數據,由於他們有比較好用的XML解析庫,而C/C++的程序員可能使用非XML數據的情形比較多,數據格式也各式各樣。固然,若是C/C++程序員使用XML數據也有一些成熟的XML解析庫,最豐富的解析庫之一如libxml2,比較小型的如tinyxml等,這些庫功能都比較豐富,但對流的支持可能有些侷限性,象libxml2,接口使用起來還比較複雜,也不太容易掌握。通過再三考慮,決定在ACL中添加XML解析庫,但願能知足以下功能:node

  一、接口豐富而簡單(看似是矛盾的,呵呵)程序員

  二、很好地支持流的處理(能夠支持同步網絡流及異步網絡流)編程

  三、能夠比較容易進行查詢、添加、刪除、遍歷等操做,最好能象JS同樣進行操做。數組

  通過幾個週末努力,終於算是完成了XML解析庫並添加進ACL中。爲了很好支持異步流,該XML庫採用了有限狀態機的方式(效率雖可能不是最好,但也不會差),下面對主要的編程接口進行介紹。網絡

 

1、API 介紹框架

一、XML容器對象的建立、XML解析樹的生成以及XML容器對象的釋放異步

 

/**
* 建立一個 xml 容器對象
* @return {ACL_XML*} 新建立的 xml 對象
*/
ACL_API ACL_XML *acl_xml_alloc(void);

  當進行XML解析前,首先必須調用 acl_xml_alloc 建立一個XML容易對象。函數

 

/**
 * 解析 xml 數據, 並持續地自動生成 xml 結點樹
 * @param xml {ACL_XML*} xml 對象
 * @param data {const char*} 以 '\0' 結尾的數據字符串, 能夠是完整的 xml 數據;
 *  也能夠是不完整的 xml 數據, 容許循環調用此函數, 將不完整數據持續地輸入
 */
ACL_API void acl_xml_parse(ACL_XML *xml, const char *data);

  將 xml 源數據輸入,經過 acl_xml_parser進行解析,由於該函數支持數據狀態緩衝(採用有限狀態機的好處),因此能夠很是容易地支持流數據。this

 

/**
 * 釋放一個 xml 對象, 同時釋放該對象裏容納的全部 xml 結點
 * @param xml {ACL_XML*} xml 對象
 */
ACL_API int acl_xml_free(ACL_XML *xml);

  最後須要調用 acl_xml_free 來釋放 XML 容易對象。spa

 

二、XML數據結點的查詢

  XML數據結點的查詢很是方便,有點相似於JAVASCRIPT中的方式(這也是做者的本意,若是象libxml2那樣估計別人用起來就會比較麻煩)。在ACL的XML庫裏提供了很是豐富的查詢方式,以下:

/**
 * 從 xml 對象中得到全部的與所給標籤名相同的 xml 結點的集合
 * @param xml {ACL_XML*} xml 對象
 * @param tag {const char*} 標籤名稱
 * @return {ACL_ARRAY*} 符合條件的 xml 結點集合, 存於 動態數組中, 若返回 NULL 則
 *  表示沒有符合條件的 xml 結點
 */
ACL_API ACL_ARRAY *acl_xml_getElementsByTagName(ACL_XML *xml, const char *tag);

 

/**
 * 從 xml 對象中得到全部給定屬性名及屬性值的 xml 結點元素集合
 * @param xml {ACL_XML*} xml 對象
 * @param name {const char*} 屬性名
 * @param value {const char*} 屬性值
 * @return {ACL_ARRAY*} 符合條件的 xml 結點集合, 存於 動態數組中, 若返回 NULL 則
 *  表示沒有符合條件的 xml 結點
 */
ACL_API ACL_ARRAY *acl_xml_getElementsByAttr(ACL_XML *xml,
	const char *name, const char *value);

 

/**
 * 從 xml 對象中得到全部的與給定屬性名 name 的屬性值相同的 xml 結點元素集合
 * @param xml {ACL_XML*} xml 對象
 * @param value {const char*} 屬性名爲 name 的屬性值
 * @return {ACL_ARRAY*} 符合條件的 xml 結點集合, 存於 動態數組中, 若返回 NULL 則
 *  表示沒有符合條件的 xml 結點
 */
ACL_API ACL_ARRAY *acl_xml_getElementsByName(ACL_XML *xml, const char *value);
 

  以上三個函數的返回結果都是一個數組對象(ACL中數組對象庫的使用請參考ACL中的 acl_array.h),這些數組元素的類型爲ACL_XML_NODE,提取這些數組元素的方式以下:

 

void test(const char *xml_data)
{
  ACL_XML *xml = acl_xml_create();
  ACL_ARRAY *a;
  ACL_ITER iter;

  acl_xml_parse(xml, xml_data);

  a = acl_xml_getElementsByTagName(xml, "user");
  if (a) {
    acl_foreach(iter, a) {
      ACL_XML_NODE *node = (ACL_XML_NODE*) iter.data;
      printf("tagname: %s\n", acl_vstring_str(node->ltag));
    }
    /* 釋放數組對象 */
    acl_xml_free_array(a);
  }

  acl_xml_free(xml);
}

   最後,得注意使用 acl_xml_free_array 來釋放數組結果集。

 

  另外,還有一些函數用來得到單個結果,以下:

/**
 * 從 xml 對象中得到指定 id 值的 xml 結點元素的某個屬性對象
 * @param xml {ACL_XML*} xml 對象
 * @param id {const char*} id 值
 * @return {ACL_XML_ATTR*} 某 xml 結點的某個屬性對象, 若返回 NULL 則表示
 *  沒有符合條件的屬性
 */
ACL_API ACL_XML_ATTR *acl_xml_getAttrById(ACL_XML *xml, const char *id);

/**
 * 從 xml 對象中得到指定 id 值的 xml 結點元素的某個屬性值
 * @param xml {ACL_XML*} xml 對象
 * @param id {const char*} id 值
 * @return {const char*} 某 xml 結點的某個屬性值, 若返回 NULL 則表示沒有符合
 *  條件的屬性
 */
ACL_API const char *acl_xml_getAttrValueById(ACL_XML *xml, const char *id);

/**
 * 從 xml 對象中得到指定 id 值的 xml 結點元素
 * @param xml {ACL_XML*} xml 對象
 * @param id {const char*} id 值
 * @return {ACL_XML_NODE*} xml 結點元素, 若返回 NULL 則表示沒有符合
 *  條件的 xml 結點
 */
ACL_API ACL_XML_NODE *acl_xml_getElementById(ACL_XML *xml, const char *id);

/**
 * 從 xml 結點中得到指定屬性名的屬性對象
 * @param node {ACL_XML_NODE*} xml 結點
 * @param name {const char*} 屬性名稱
 * @return {ACL_XML_ATTR*} 屬性對象, 爲空表示不存在
 */
ACL_API ACL_XML_ATTR *acl_xml_getElementAttr(ACL_XML_NODE *node, const char *name);

/**
 * 從 xml 結點中得到指定屬性名的屬性值
 * @param node {ACL_XML_NODE*} xml 結點
 * @param name {const char*} 屬性名稱
 * @return {const char*} 屬性值, 爲空表示不存在
 */
ACL_API const char *acl_xml_getElementAttrVal(ACL_XML_NODE *node, const char *name);

  這些查詢接口也很是相似於JAVASCRIPT的查詢方式。由於查詢結果具備惟一性,因此僅返回一個結果。

 

三、XML樹結點的遍歷

  XML樹結點的遍歷遵循ACL框架庫中定義的統一遍歷方式,因此遍歷XML樹也很是容易,示例以下:

ACL_XML *xml;
ACL_XML_NODE *node;
ACL_ITER iter;

/*......*/
/* 假設 XML 解析樹已經建立完畢 */

/* 遍歷整個XML容器對象的全部數據結點 */
acl_foreach(iter, xml) {
  node = (ACL_XML_NODE*) iter.data;
  printf("tagname: %s, text: %s\n", acl_vstring_str(node->ltag),
    acl_vstring_str(node->text));
}

/* 遍歷某個XML結點的下一級子結點 */
node = acl_xml_getElementById(xml, "id_test");
if (node) {
  acl_foreach(iter, node) {
    ACL_XML_NODE *node2 = (ACL_XML_NODE*) iter.data;
    printf("tagname: %s, text: %s\n", acl_vstring_str(node->ltag),
        acl_vstring_str(node->text));
  }
}

 

四、其它函數

/**
 * 從 xml 結點刪除某個屬性對象, 若是該屬性爲 id 屬性, 則同時會從 xml->id_table 中刪除
 * @param node {ACL_XML_NODE*} xml 結點
 * @param name {const char*} 屬性名稱
 * @return {int} 0 表示刪除成功, -1: 表示刪除失敗(有多是該屬性不存在)
 */
ACL_API int acl_xml_removeElementAttr(ACL_XML_NODE *node, const char *name);

/**
 * 給 xml 結點添加屬性, 若是該屬性名已存在, 則用新的屬性值替換其屬性值, 不然
 * 建立並添加新的屬性對象
 * @param node {ACL_XML_NODE*} xml 結點
 * @param name {const char*} 屬性名稱
 * @param value {const char*} 屬性值
 * @return {ACL_XML_ATTR*} 返回該屬性對象(有多是原來的, 也有多是新的)
 */
ACL_API ACL_XML_ATTR *acl_xml_addElementAttr(ACL_XML_NODE *node,
        const char *name, const char *value);

/**
 * 將 xml 對象轉儲於指定流中
 * @param xml {ACL_XML*} xml 對象
 * @param fp {ACL_VSTREAM*} 流對象
 */
ACL_API void acl_xml_dump(ACL_XML *xml, ACL_VSTREAM *fp);

 

   固然,還有更多的函數未列出,以上僅多是用戶在使用XML庫過程當中經常使用的函數接口。

 

2、舉例

    下面舉一個完整的例子以結束本文。

#include "lib_acl.h"

#define STR	acl_vstring_str

static void parse_xml(int once)
{
	ACL_XML *xml = acl_xml_alloc();
	const char *data =
		"<?xml version=\"1.0\"?>\r\n"
		"<?xml-stylesheet type=\"text/xsl\"\r\n"
		"\thref=\"http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl\"?>\r\n"
		"\t<!DOCTYPE refentry PUBLIC \"-//OASIS//DTD DocBook XML V4.1.2//EN\"\r\n"
		"\t\"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd\" [\r\n"
		"	<!ENTITY xmllint \"<command>xmllint</command>\">\r\n"
		"]>\r\n"
		"<root name='root' id='root_id_1'>\r\n"
		"	<user name='user1' value='zsx1' id='id1'> user zsx1 </user>\r\n"
		"	<user name='user2' value='zsx2' id='id2'> user zsx2 \r\n"
		"		<age year='1972'>my age</age>\r\n"
		"	</user>\r\n"
		"	<user name='user3' value='zsx3' id='id3'> user zsx3 </user>\r\n"
		"</root>\r\n"
		"<root name='root' id='root_id_2'>\r\n"
		"	<user name='user1' value='zsx1' id='id1'> user zsx1 </user>\r\n"
		"	<user name='user2' value='zsx2' id='id2'> user zsx2 \r\n"
		"		<!-- date should be the date of the latest change or the release version -->\r\n"
		"		<age year='1972'>my age</age>\r\n"
		"	</user>\r\n"
		"	<user name='user3' value='zsx3' id='id3'> user zsx3 </user>\r\n"
		"</root>\r\n"
		"<root name = 'root2' id = 'root_id_3'>\r\n"
		"	<user name = 'user2_1' value = 'zsx2_1' id = 'id2_1'> user zsx2_1 </user>\r\n"
		"	<user name = 'user2_2' value = 'zsx2_2' id = 'id2_2'> user zsx2_2 </user>\r\n"
		"	<user name = 'user2_3' value = 'zsx2_3' id = 'id2_3'> user zsx2_3 \r\n"
		"		<age year = '1978' month = '12' day = '11'> bao bao </age>\r\n"
		"	</user>\r\n"
		"	<!-- still a bit buggy output, will talk to docbook-xsl upstream to fix this -->\r\n"
		"	<!-- <releaseinfo>This is release 0.5 of the xmllint Manual.</releaseinfo> -->\r\n"
		"	<!-- <edition>0.5</edition> -->\r\n"
		"	<user name = 'user2_2' value = 'zsx2_2' id = 'id2_4'> user zsx2_2 </user>\r\n"
		"</root>\r\n";
	const char *ptr;
	ACL_ITER iter1;
	int   i, total, left;
	ACL_ARRAY *a;
	ACL_XML_NODE *pnode;

	ptr = data;
	if (once) {
		/* 一次性地分析完整 xml 數據 */
		acl_xml_parse(xml, ptr);
	} else {
		/* 每次僅輸入一個字節來分析 xml 數據 */
		while (*ptr != 0) {
			char  ch2[2];

			ch2[0] = *ptr;
			ch2[1] = 0;
			acl_xml_parse(xml, ch2);
			ptr++;
		}
	}

	if (acl_xml_is_complete(xml, "root")) {
		printf(">> Yes, the xml complete\n");
	} else {
		printf(">> No, the xml not complete\n");
	}

	total = xml->node_cnt;

	/* 遍歷根結點的一級子結點 */
	acl_foreach(iter1, xml->root) {
		ACL_ITER iter2;

		ACL_XML_NODE *node = (ACL_XML_NODE*) iter1.data;
		printf("tag> %s, text: %s\n", STR(node->ltag), STR(node->text));

		/* 遍歷一級子結點的二級子結點 */
		acl_foreach(iter2, node) {
			ACL_ITER iter3;
			ACL_XML_NODE *node2 = (ACL_XML_NODE*) iter2.data;

			printf("\ttag> %s, text: %s\n", STR(node2->ltag), STR(node2->text));

			/* 遍歷二級子結點的屬性 */
			acl_foreach(iter3, node2->attr_list) {
				ACL_XML_ATTR *attr = (ACL_XML_ATTR*) iter3.data;
				printf("\t\tattr> %s: %s\n", STR(attr->name), STR(attr->value));
			}
		}
	}

	printf("----------------------------------------------------\n");

	/* 從根結點開始遍歷 xml 對象的全部結點 */

	acl_foreach(iter1, xml) {
		ACL_ITER iter2;
		ACL_XML_NODE *node = (ACL_XML_NODE*) iter1.data;

		for (i = 1; i < node->depth; i++) {
			printf("\t");
		}

		printf("tag> %s, text: %s\n", STR(node->ltag), STR(node->text));

		/* 遍歷 xml 結點的屬性 */
		acl_foreach(iter2, node->attr_list) {
			ACL_XML_ATTR *attr = (ACL_XML_ATTR*) iter2.data;

			for (i = 1; i < node->depth; i++) {
				printf("\t");
			}

			printf("\tattr> %s: %s\n", STR(attr->name), STR(attr->value));
		}
	}

	/* 根據標籤名得到 xml 結點集合 */

	printf("--------- acl_xml_getElementsByTagName ----------\n");
	a = acl_xml_getElementsByTagName(xml, "user");
	if (a) {
		/* 遍歷結果集 */
		acl_foreach(iter1, a) {
			ACL_XML_NODE *node = (ACL_XML_NODE*) iter1.data;
			printf("tag> %s, text: %s\n", STR(node->ltag), STR(node->text));
		}
		/* 釋放數組對象 */
		acl_xml_free_array(a);
	}


	/* 查詢屬性名爲 name, 屬性值爲 user2_1 的全部 xml 結點的集合 */

	printf("--------- acl_xml_getElementsByName ------------\n");
	a = acl_xml_getElementsByName(xml, "user2_1");
	if (a) {
		/* 遍歷結果集 */
		acl_foreach(iter1, a) {
			ACL_XML_NODE *node = (ACL_XML_NODE*) iter1.data;
			printf("tag> %s, text: %s\n", STR(node->ltag), STR(node->text));
		}
		/* 釋放數組對象 */
		acl_xml_free_array(a);
	}

	/* 查詢屬性名爲 id, 屬性值爲 id2_2 的全部 xml 結點集合 */
	printf("----------- acl_xml_getElementById -------------\n");
	pnode = acl_xml_getElementById(xml, "id2_2");
	if (pnode) {
		printf("tag> %s, text: %s\n", STR(pnode->ltag), STR(pnode->text));
		/* 遍歷該 xml 結點的屬性 */
		acl_foreach(iter1, pnode->attr_list) {
			ACL_XML_ATTR *attr = (ACL_XML_ATTR*) iter1.data;
			printf("\tattr_name: %s, attr_value: %s\n",
				STR(attr->name), STR(attr->value));
		}

		pnode = acl_xml_node_next(pnode);
		printf("----------------- the id2_2's next node is ---------------------\n");
		if (pnode) {
			printf("-------------- walk node -------------------\n");
			/* 遍歷該 xml 結點的屬性 */
			acl_foreach(iter1, pnode->attr_list) {
				ACL_XML_ATTR *attr = (ACL_XML_ATTR*) iter1.data;
				printf("\tattr_name: %s, attr_value: %s\n",
						STR(attr->name), STR(attr->value));
			}

		} else {
			printf("-------------- null node -------------------\n");
		}
	}

	pnode = acl_xml_getElementById(xml, "id2_3");
	if (pnode) {
		int   ndel = 0, node_cnt;

		/* 刪除該結點及其子結點 */
		printf(">>>before delete %s, total: %d\n", STR(pnode->ltag), xml->node_cnt);
		ndel = acl_xml_node_delete(pnode);
		node_cnt = xml->node_cnt;
		printf(">>>after delete id2_3(%d deleted), total: %d\n", ndel, node_cnt);
	}

	acl_foreach(iter1, xml) {
		ACL_XML_NODE *node = (ACL_XML_NODE*) iter1.data;
		printf(">>tag: %s\n", STR(node->ltag));
	}

	pnode = acl_xml_getElementById(xml, "id2_3");
	if (pnode) {
		printf("-------------- walk %s node -------------------\n", STR(pnode->ltag));
		/* 遍歷該 xml 結點的屬性 */
		acl_foreach(iter1, pnode->attr_list) {
			ACL_XML_ATTR *attr = (ACL_XML_ATTR*) iter1.data;
			printf("\tattr_name: %s, attr_value: %s\n",
					STR(attr->name), STR(attr->value));
		}
	} else {
		printf("---- the id2_3 be deleted----\n");
	}

	/* 釋放 xml 對象 */
	left = acl_xml_free(xml);
	printf("free all node ok, total(%d), left is: %d\n", total, left);
}

static void parse_xml_file(const char *filepath, int once)
{
	char *data = acl_vstream_loadfile(filepath);
	ACL_VSTREAM *fp;
	char *ptr;
	ACL_XML *xml;
	struct timeval  begin, end;

	if (data == NULL)
		return;

	gettimeofday(&begin, NULL);

	/* 建立 xml 對象 */
	xml = acl_xml_alloc();

	ptr = data;

	if (once) {
		/* 一次性地分析完整 xml 數據 */
		acl_xml_parse(xml, ptr);
	} else {
		/* 每次僅輸入一個字節來分析 xml 數據 */
		while (*ptr) {
			char  ch2[2];

			ch2[0] = *ptr;
			ch2[1] = 0;
			acl_xml_parse(xml, ch2);
			ptr++;
		}
	}

	gettimeofday(&end, NULL);

	printf("------ok, time: %ld seconds, %ld microseconds -------\r\n",
		end.tv_sec - begin.tv_sec, end.tv_usec - begin.tv_usec);


	fp = acl_vstream_fopen("dump.txt", O_RDWR | O_CREAT | O_TRUNC, 0600, 4096);

	/* 將 xml 對象轉儲至指定流中 */
	acl_xml_dump(xml, fp);

	acl_vstream_fclose(fp);
	acl_xml_free(xml);
	acl_myfree(data);
}

static void usage(const char *procname)
{
	printf("usage: %s -h[help] -f {xml_file} -s[parse once]\n", procname);
}

int main(int argc, char *argv[])
{
	int   ch, once = 0;
	char  filepath[256];

	acl_init();
	snprintf(filepath, sizeof(filepath), "xmlcatalog_man.xml");

	while ((ch = getopt(argc, argv, "hf:s")) > 0) {
		switch (ch) {
		case 'h':
			usage(argv[0]);
			return (0);
		case 'f':
			snprintf(filepath, sizeof(filepath), "%s", optarg);
			break;
		case 's':
			once = 1;
			break;
		default:
			break;
		}
	}

	parse_xml(once);
	parse_xml_file(filepath, once);

#ifdef	ACL_MS_WINDOWS
	printf("ok, enter any key to exit ...\n");
	getchar();
#endif
	return 0;
}
 
我的微博:http://weibo.com/zsxxsz
相關文章
相關標籤/搜索