文档章节

acl 之 xml 流解析器

郑树新
 郑树新
发布于 2014/09/03 12:11
字数 3052
阅读 66
收藏 0

  现在 XML 解析器比较多,其实本没有必要在ACL中添加新的XML解析库,象JAVA、PHP、C#的开发者大都习惯于使用XML数据,因为他们有比较好用的XML解析库,而C/C++的程序员可能使用非XML数据的情形比较多,数据格式也各式各样。当然,如果C/C++程序员使用XML数据也有一些成熟的XML解析库,最丰富的解析库之一如libxml2,比较小型的如tinyxml等,这些库功能都比较丰富,但对流的支持可能有些局限性,象libxml2,接口使用起来还比较复杂,也不太容易掌握。经过再三考虑,决定在ACL中添加XML解析库,希望能满足如下功能:

  1、接口丰富而简单(看似是矛盾的,呵呵)

  2、很好地支持流的处理(可以支持同步网络流及异步网络流)

  3、可以比较容易进行查询、添加、删除、遍历等操作,最好能象JS一样进行操作。

  经过几个周末努力,终于算是完成了XML解析库并添加进ACL中。为了很好支持异步流,该XML库采用了有限状态机的方式(效率虽可能不是最好,但也不会差),下面对主要的编程接口进行介绍。

 

一、API 介绍

1、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进行解析,因为该函数支持数据状态缓冲(采用有限状态机的好处),所以可以非常容易地支持流数据。

 

/**
 * 释放一个 xml 对象, 同时释放该对象里容纳的所有 xml 结点
 * @param xml {ACL_XML*} xml 对象
 */
ACL_API int acl_xml_free(ACL_XML *xml);

  最后需要调用 acl_xml_free 来释放 XML 容易对象。

 

2、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的查询方式。因为查询结果具有唯一性,所以仅返回一个结果。

 

3、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));
  }
}

 

4、其它函数

/**
 * 从 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库过程中常用的函数接口。

 

二、举例

    下面举一个完整的例子以结束本文。

#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

© 著作权归作者所有

郑树新

郑树新

粉丝 104
博文 87
码字总数 161171
作品 2
昌平
程序员
私信 提问
C语言网络框架库--acl

acl 框架库是一个 C 库,主要包含:服务器开发框架、同步/异步网络通讯、常用数据结构、进程池/线程池、流式 xml/json 解析器、http/ping 应用协议等内容; acl 包括以下丰富的常用函数库: ...

郑树新
2012/06/03
21.4K
3
acl 3.1.4 发布,跨平台网络通信与服务器编程框架

acl 3.1.4 版本发布了,acl 是 one advanced C/C++ library 的简称,主要包括网络通信库以及服务器框架库等功能,支持 Linux/Windows/Solaris/FreeBsd/MacOS 平台;整个 acl 项目主要包含三个...

郑树新
2015/12/20
2.9K
6
支持 LINUX/WIN32 平台的网络通讯及服务器框架

acl 框架库是一个 C 库,主要包含:服务器开发框架、同步/异步网络通讯、常用数据结构、进程池/线程池、流式 xml/json 解析器、http/ping 应用协议等内容; acl_cpp 是基于 acl 库的 C++ 库,...

郑树新
2012/06/03
35
0
acl -- 网络及服务器编程框架库 3.0.15 版本发布

acl 3.0.15 版本 (项目主页:https://sourceforge.net/projects/acl/,技术文章主页:http://zsxxsz.iteye.com/)) 发布了,acl 是 one Advanced C/C++ library 的简称,主要包括网络通信库以...

郑树新
2014/01/25
1K
2
acl 网络通信服务器框架 3.0.22 版本发布

acl 3.0.22 版本发布了,acl 是 one advanced C/C++ library 的简称,主要包括网络通信库以及服务器框架库等功能,支持 Linux/Windows/Solaris/FreeBsd/MacOS 平台;整个 acl 项目主要包含三...

郑树新
2014/12/16
2.5K
9

没有更多内容

加载失败,请刷新页面

加载更多

Jenkins系列_插件安装及报错处理

进入Jenkins之后我们可以进行插件的安装,插件管理位于以下模块: 发现上面报了一堆错误,是因为插件的依赖没有安装好,那么这一节,就先把这些错误解决掉吧。解决完成后,也就基本会使用插件...

shzwork
今天
2
0
mysql mysql的所有查询语句和聚合函数(整理一下,忘记了可以随时看看)

查询所有字段 select * from 表名; 查询自定字段 select 字段名 from 表名; 查询指定数据 select * from 表名 where 条件; 带关键字IN的查询 select * from 表名 where 条件 [not] in(元素...

edison_kwok
昨天
9
0
解决多线程并行加载缓存问题(利用guava实现)

依赖 com.google.guava:guava:20.0 import com.google.common.cache.Cache;import com.google.common.cache.CacheBuilder;import java.util.concurrent.ExecutionException;import j......

暗中观察
昨天
3
0
利用VisualVM 内存查看

准备工作,建几个测试类。等下就是要查看这几个类里面的属性 package visualvm;public class MultiObject { private String str; private int i; MultiObject(String str...

冷基
昨天
2
0
组装一台工作游戏两用机

一、配置清单如下: 分类 项目 价格(元) 主板 华硕(ASUS)TUF Z370-PLUS GAMING II 电竞特工 Z370二代 支持9代CPU 1049 CPU 英特尔(Intel) i7 8700K 酷睿六核 盒装CPU处理器 2640 风扇 九...

mbzhong
昨天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部