xml解析总结(主分析expat)

涂玉韵
2023-12-01


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;
}

 类似资料: