开源 XmlParser expat 使用介绍

1. 概述

expat 是使用 C 所写的 XML 解析器,采用流导向的方式(stream-oriented)解析 XML 文件,我们需要首先向解析器注册 callback 函数,在传入文档解析时,对于解析器能够识别的文档部分,它会调用适当的 callback 动作进行解析(在已注册的前提下)。

被解析文档是分块传给解析器的,因此在你全部传入文档之前就已经开始解析了。expat 可以分析非常大的 XML 文件,因为不需要将整份 XML 文件全部加载到内存里。

由于 expat 库是由 XML 的主要创始人 James Clark 来开发实现的,因此它符合 W3C 的 XML 标准。在 Android 的 framwrorks 框架中大量使用了 expat 来解析 XML。本文只介绍 expat 库的基本使用方法,更多信息可以访问下方的官方网址。

XML expat introduce

2. 使用方法简介

2.1 解析器创建
XML_Parser XML_ParserCreate(const XML_Char*encoding)

首先是用 XML_ParserCreate 创建一个新的解析器,参数一般为 NULL,函数返回一个 XML_Parser 类型指针,我们可以将它看做是一个解析器句柄。

2.2 为开始和结束标签设置 handler
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);

解析器创建之后,由于流导向需要先设置 callback,XML_SetElementHandler 函数用于设置开始和结束标签的 handlers。

第一个参数是那个 Parser 句柄,第二个和第三个参数则是整个 Parser 的核心,类型为 CallBack 的函数,这两个回调分别对应于解析到 <> 和 </>,下面分别详细解介绍这两个回调函数:

typedef void
(*XML_StartElementHandler)(void *userData,
                           const XML_Char *name,
                           const XML_Char **atts);

其中第一个参数 userData,可以由函数 XML_SetUserData(XML_Parser parser, void *p) 设置。

后面两个参数,用一个详细的例子来说明更好理解。

比如有一个 XML 文档,某个标签属性如下:

 <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
(*XML_EndElementHandler)(void *userData,
                         const XML_Char *name);

这个函数用于处理标签结束,name 就是 feed 了,这个回调一般用于用户设置自己的状态机。

2.3 设置文本 buffer 块 handler
XML_SetCharacterDataHandler(XML_Parser p,
                            XML_CharacterDataHandler charhndl)

这个函数是设置处理一个<>和</>之间的字段的回调,回调函数原型如下:

typedef void
(*XML_CharacterDataHandler)(void *userData,
                            const XML_Char *s,
                            int len);

其中第二个参数是一块 buffer 的指针,如果你单步 DEBUG 后,你会发现 expat 使用的就是你传入的那块 buffer(parse 函数传入),比如:

<title>天气</title>
<summary>28日08时至29日08时,陕西中南部、山西西南部、河南中南部、湖北北部、四川中
东部、重庆西部和北部、贵州西部等地的部分地区有大雨或暴雨,河南南部、湖北北部等地局部
有大暴雨。【点击“更多”查询其他城市天气】</summary>

假设目前解析到天气这个 charData,如果你看那个指针的所有内容的话,实际上是这样的:

天气</title>
<summary>28日08时至29日08时,陕西中南部、山西西南部、河南中南部、湖北北部、四川中
东部、重庆西部和北部、贵州西部等地的部分地区有大雨或暴雨,河南南部、湖北北部等地局部
有大暴雨。【点击“更多”查询其他城市天气】</summary>

所以要根据第三个参数 len 来确定正确的数据。

2.4 传入字符开始解析 Parse
int XML_Parse(XML_Parser parser, const char *s, int len, int isFinal)

XML_Parse 函数用于实时传入字符解析 Parse,第二个参数是用户指定的 buffer指针,第三个是这块 buffer 中实际内容的字节数,最后参数代表是否这块 buffer 已经结束。比如要解析的 XML 文件太大,但内存比较吃紧,buffer 比较小,则可以循环读取文件,然后丢给 Parser, 在文件读取结束前,isFinal 参数为FALSE,反之为 TRUE。

这里的 buffer 如果太小则会造成上面提到那个隐晦的问题:

XML_CharacterDataHandler 一次返回的可能并不是完整的 CharData,比如这个charData的 Len 大于你的 buffer 大小,那这是会连续调用 2 次 XML_CharacterDataHandler,我们需要将 2 次结果拼接起来,以得到正确结果,因此我们的状态机一定要考虑到这点。

XML_ParseBuffer 是 expat 支持的另一种将字符传入解析器的方式:

int XML_ParseBuffer(XML_Parser p, int len, int isFinal)

void *XML_GetBuffer(XML_Parser p, int len)

XML_ParseBuffer 函数类似于 XML_Parse 函数,不同的是 XML_ParseBuffer 使用的 buffer 由 XML_GetBuffer 函数提供,这样能避免应用程序两次复制输入。

用法示例:

 void *buff = ::XML_GetBuffer(parser, BUFF_SIZE);
 if (buff == nullptr) {
     ALOGE("failed in call to XML_GetBuffer()");
     mParsingStatus = UNKNOWN_ERROR;
     break;
 }

 int bytes_read = ::fread(buff, 1, BUFF_SIZE, file);
 if (bytes_read < 0) {
     ALOGE("failed in call to read");
     mParsingStatus = ERROR_IO;
     break;
 }

 XML_Status status = ::XML_ParseBuffer(parser, bytes_read, bytes_read == 0);
 if (status != XML_STATUS_OK) {
     ALOGE("malformed (%s)", ::XML_ErrorString(::XML_GetErrorCode(parser)));
     mParsingStatus = ERROR_MALFORMED;
     break;
 }
2.5 XML_ParserReset
XML_ParserReset(XML_Parser parser, const XML_Char *encodingName)

在某些时候,如果你不确定前后 2 次 XML 是否一样的情况下,比如网络上投递的XML,在一次解析后最好调用一次本函数,否则会出现意料之外的结果。比如前后两次 XML 完全一样,可这你并不知情,那么 XML_Parse 函数会返回失败。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值