文章目录
- 1.个人关于解析XML的建议
- 2.expat安装
- 3.expat函数介绍
- 3.1 创建一个XML分析器-----XML_Parser* XML_ParserCreate(const XML_Char *encodingName)
- 3.2 设置文本 buffer 块 handler----XML_SetCharacterDataHandler(XML_Parser parser,XML_CharacterDataHandler handler)
- 3.3 XML_SetUserData(XML_Parser parser, void *p)
- 3.4 设置处理标记开始和结束的处理函数-----XML_SetElementHandler()
- 3.5 接续buffer内的数据----XML_Parse(XML_Parser parser, const char *s, int len, int isFinal)
- 3.6 释放解析器使用的内存--XML_ParserFree()
- 3.7 分析给出的缓冲区XML数据-----XML_ParserReset(XML_Parser parser, const XML_Char *encodingName)函数
- 3.8 获得一个大小为len的缓冲区来读取一块数据----void * XMLCALL XML_GetBuffer(XML_Parser p, int len);
- 4.expat测试代码
expat: 官方文档
1.个人关于解析XML的建议
- 先说结论:目前的xml解析器一般有两种解析的形式.一种是DOM模型的,一种是SAX2模型的.DOM模型是讲xml文件中的结构解析成一棵树,然后再进行各种操作;而SAX2模型是类似与事件处理的方式从头到位解析xml文件.两种方式各有优劣,不过在嵌入式设备上用DOM模型的解析器似乎太耗内存,所以一般都用SAX2的解析器。
1.还是看项目,如果需要面向对象的,不适用expat
2.但是如果需要边解析边修改值,可以用expat
- 其他:boost库的依赖太多,tinyxml每次都需要new一个节点出来,有内存泄露,而且树形结构不仅耗费内存,时间也长
2.expat安装
1、下载expat(expat-2.2.9)库:http://distfiles.macports.org/expat/
2、tar jxvf expat-2.4.1.tar.bz2
cd expat-2.4.1/
/configure
3、./configure --without-tests --without-examples --prefix= /home/yun/project/lib/bluez/expat-2.2.9/install --host= arm-none-linux-gnueabi
4、cd xmlwf
5、make clean
6、make
7、返回上一级目录
8、make
9、make install
3.expat函数介绍
- expat库要包含的头文件是expat.h,如果是集成开发环境,如eclipse,需要包含动态库或者静态库(libexpat.a,libexpat.so)的路径
- Expat XML Parser 支持设置多种不同的处理器。但是要使用它们,你只需要学习四个功能,即可满足 80%的需要。 它们是:
XML_ParserCreate ------Create a new parser object.
XML_SetElementHandler ------ Set handlers for start and end tags.
XML_SetCharacterDataHandler ------Set handler for text.
XML_Parse ------ Pass a buffer full of document to the parser
3.1 创建一个XML分析器-----XML_Parser* XML_ParserCreate(const XML_Char *encodingName)
- 参数:一般为NULL
- 返回值:函数返回一个XML_Parser类型指针,我们就当他是一个句柄吧,类似于Windows里的内核对象,一般需要保存在一个全局的指针里。
- 流程:
1.先创建一个句柄
2.再调用XML_SetElementHandler(XML_Parser parser,
XML_StartElementHandler start,
XML_EndElementHandler end),
这二个回调分别是对应于解析<>和</>, 下面分别详细介绍这个2个回调函数。
- 第一个回调函数:typedef void (XMLCALL *XML_StartElementHandler) (void *userData,
const XML_Char *name,
const XML_Char **atts);
①第一个参数userData, 可以由函数XML_SetUserData(XML_Parser parser, void *p)设置
②举例说明第二个和第三个参数:
<feed version=“2.0” ctxt-id=“9212” template-id=“default” feed-type=“ftti”>
那么StartElementHandler回调返回的name就是标签"feed", **atts是一个指针数组,分别指向标签的一组属性,atts[0]就是"version", atts[1]就是"2.0", 以此类推
- 这时候必然有个对应的</feed>,第二个回调函数
typedef void (XMLCALL *XML_EndElementHandler) (void *userData,
const XML_Char *name);
就是处理标签结束的,name就是"feed”了,这个回调一般是用户设置自己的状态机的。
3.2 设置文本 buffer 块 handler----XML_SetCharacterDataHandler(XML_Parser parser,XML_CharacterDataHandler handler)
- 函数作用:这个函数是设置处理一个<>和</>之间的字段的回调。
- 回调原型:
typedef void (XMLCALL *XML_CharacterDataHandler) (void *userData,
const XML_Char *s,
int len);
- 参数说明:
Buffer的指针,如果你单步DEBUG后,你会发现expat用的就是你传入的那块Buffer(这块Buffer下面讲解),比如解析下面的XML:
<title>天气</title>
<summary>28日08时至29日08时,陕西中南部、山西西南部、河南中南部、湖北北部、四川中
东部、重庆西部和北部、贵州西部等地的部分地区有大雨或暴雨,河南南部、湖北北部等地局部
有大暴雨。【点击“更多”查询其他城市天气】</summary>
第二个参数buffer的指针指向的内容是:(只是没有了<title>)
天气</title>
<summary>28日08时至29日08时,陕西中南部、山西西南部、河南中南部、湖北北部、四川中
东部、重庆西部和北部、贵州西部等地的部分地区有大雨或暴雨,河南南部、湖北北部等地局部
有大暴雨。【点击“更多”查询其他城市天气】</summary>
所以要根据第三个参数len来确定正确的数据。
- 补充注意点:一次返回的可能并不是完整的CharData,比如这个charData的Len大于你的Buffer大小,那这是会连续调用2次XML_CharacterDataHandler,我们需要将2次结果拼接起来,以得到正确结果,因此我们的状态机一定要考虑到这点
3.3 XML_SetUserData(XML_Parser parser, void *p)
3.4 设置处理标记开始和结束的处理函数-----XML_SetElementHandler()
XML_SetElementHandler(XML_Parser p,
XML_StartElementHandler start,
XML_EndElementHandler end);
typedef void
(*XML_StartElementHandler)(void *userData,
const XML_Char *name,
const XML_Char **atts);
typedef void
(*XML_EndElementHandler)(void *userData,
const XML_Char *name);
前面已经介绍过
3.5 接续buffer内的数据----XML_Parse(XML_Parser parser, const char *s, int len, int isFinal)
- 参数说明
①第二个参数是用户指定的Buffer指针
②第三个是这块Buffer中实际内容的字节数,比如要解析的XML文件太大,但内存比较吃紧,Buffer比较小,则可以循环读取文件,然后丢给Parser
③最后参数代表是否这块Buffer已经结束,在文件读取结束前,isFinal参数为FALSE,反之为TRUE。
3.6 释放解析器使用的内存–XML_ParserFree()
你的应用程序将释放user_data使用的所有内存-
3.7 分析给出的缓冲区XML数据-----XML_ParserReset(XML_Parser parser, const XML_Char *encodingName)函数
在某些时候,如果你不确定前后2次XML是否一样的情况下,比如网络上投递的XML,在一次解析后最好调用一次本函数,否则会出现意料之外的结果。比如前后两次XML完全一样,可这你并不知情,那么XML_Parse()会返回失败。
3.8 获得一个大小为len的缓冲区来读取一块数据----void * XMLCALL XML_GetBuffer(XML_Parser p, int len);
如果expat不能为buffer分配足够的空间,它将返回一个空字符。这将不得不再每次调用XML_ParseBuffer函数之前先调用这个函数。一个通常的用法像这样子
for (;;) {
int bytes_read;
void *buff = XML_GetBuffer(p, BUFF_SIZE);
if (buff == NULL) { /* handle error */ } //expat不能为buffer分配足够的空间
bytes_read = read(docfd, buff, BUFF_SIZE);
if (bytes_read < 0) { /* handle error */ }
if (! XML_ParseBuffer(p, bytes_read, bytes_read == 0)) { /* handle parse error */ }
if (bytes_read == 0) break;
}
4.expat测试代码
#include <stdio.h>
#include "expat.h"
#pragma warning(disable:4996)
#define XML_FMT_INT_MOD "l"
static void XMLCALL startElement(void *userData, const char *name, const char **atts)
{
int i;
int *depthPtr = (int *)userData;
for (i = 0; i < *depthPtr; i++)
printf(" ");
printf(name);
*depthPtr += 1;
for(i=0;atts[i]!=0;i+=2)
{
printf(" %s=%s",atts[i],atts[i+1]);
}
printf("\n");
}
static void XMLCALL endElement(void *userData, const char *name)
{
int *depthPtr = (int *)userData;
*depthPtr -= 1;
}
int main(int argc, char *argv[])
{
char buf[BUFSIZ]; //创建8192,也就是2页的缓冲区
XML_Parser parser = XML_ParserCreate(NULL);//创建XML分析器
int done; int depth = 0;
XML_SetUserData(parser, &depth);
XML_SetElementHandler(parser, startElement, endElement);
FILE* pFile= argc<2 ?stdin : fopen(argv[1],"rb");//没参数就打印到屏幕,有参数就以流的方式打开文件,以读二进制的方式打开
do
{ int len = (int)fread(buf, 1, sizeof(buf), pFile);
done = len < sizeof(buf);
if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR)
{
fprintf(stderr,"%s at line %" XML_FMT_INT_MOD "u\n",
XML_ErrorString(XML_GetErrorCode(parser)),
XML_GetCurrentLineNumber(parser));
return 1;
}
}
while (!done);
XML_ParserFree(parser);//释放解析器使用的内存
fclose(pFile);
return 0;
}