加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

expat XML解析器

发布时间:2020-12-15 23:20:17 所属栏目:百科 来源:网络整理
导读:工作中用到了EXPAT,为了以后查询方便,把网上搜索到的内容综合整理如下。 win32 plat下的 c/c++下使用expat。 expat是基于sax来进行xml解析而不是dom解析。因此,在expat中设置了很多的回调来处理。 在 win32下使用,可以 http://sourceforge.net/projects/

工作中用到了EXPAT,为了以后查询方便,把网上搜索到的内容综合整理如下。

win32 plat下的c/c++下使用expat。
expat是基于sax来进行xml解析而不是dom解析。因此,在expat中设置了很多的回调来处理。

win32下使用,可以http://sourceforge.net/projects/expat/下载,里面有win32版本,下载下来的是一个安装包,直接安装,
安装之后,在安装目录例如C:Expat-2.0.0下有Docslibs source 等几个目录,其中libs目录下放了4个文件,分别是libexpat
的Ansi和Unicode的dll和对应lib。在source下,有example tests lib等目录,其中lib目录下是expat的源代码(头文件和实现文件)
以及dsp文件。里面有编译生成动态链接库的工程文件expat.dsp以及编译成为静态库的expat_static.dsp。expat.dsp编译出来的动态区
名称为libexpat.dll(libexpat.lib);静态库工程expat_static.dsp编译出来的静态库名称为:libexpatMT.lib。
在sourceexample目录下有参考代码。
其中看看elements 这个example就ok了。
对于一个简单的xml文件,我们
main(int argc,char *argv[])
{
XML_Parser p = XML_ParserCreate(NULL);

XML_SetCharacterDataHandler(p,charhandler);
XML_SetElementHandler(p,start,end);

for (;;) {
int done;
int len;

len = fread(Buff,1,BUFFSIZE,stdin);
if (ferror(stdin)) {
fprintf(stderr,"Read errorn");
exit(-1);
}
done = feof(stdin);

if (XML_Parse(p,Buff,len,done) ==XML_STATUS_ERROR) {
fprintf(stderr,"Parse error at line%" XML_FMT_INT_MOD "u:n%sn",
XML_GetCurrentLineNumber(p),
XML_ErrorString(XML_GetErrorCode(p)));
exit(-1);
}
printf( " depth = %d n",Depth);
if (done)
break;
}
return 0;
}
其中
XML_SetElementHandler设置回调,处理element节点;XML_SetCharacterDataHandler设置回调用于处理text节点
一般来说有了这两个我们就可以处理了,例如下面的一个xml文件
<?xml version="1.0"?>
<xmlRoot price="">
<YEAR Now="2005">

<QUARTER1>2005</QUARTER1>

</YEAR>
</xmlRoot>

处理到xmlRoot节点的时候,会调用XML_SetElementHandler设置的回调函数,我们可以从回调函数中获取节点名称,节点的
属性列表,包括各个属性名称和对应的属性值。这里就可以获取到一个属性price,值为空。
继续下面的处理,当处理到QIARTER1时,会调用XML_SetCharacterDataHandler设置的回调函数获取text节点值。
char buf[100]={0};
static void XMLCALL charhandler(void *userData,const XML_Char *s,int len)
{
if(len!=0)
{
memcpy(buf,s,len);
buf[len] = '';
rintf("%s ",buf);
}
}
注意,这里的s不是以结束的。

C++中使用Expat解析XML

本文介绍expat 解析xml的基本方法,如果你希望用最轻量的解析器,请选择TinyXML,它更简单。

使用expat的原因很多,主要还是因为expat更灵活。习惯了TinyXML,一开始不太习惯expat,分析一下,其实很容易上手的。

1.回调函数

以下案例解析xml文件中的elment,attribute和text。expat使用回调方式返回xml数据,解析器解析到一个element及其内部属性后,将调用事先设置好的函数,同样,当element结束和text结束后,也会分别调用对应的函数。

2.如何处理数据之间的包含关系

典型的方式是定义三个函数分别处理elment开始(含属性)、element结束和文本内容。回调函数的第一个参数是自定义的,通常用于存储XML文档的上下文信息,用XML_SetUserData可以设置这个参数,下例中传递一个整数指针,以便在每次回调时能知道该元素是第几层元素。

该参数也可以是一个栈对象的地址,开始一个元素时,将新元素对应的数据压入堆栈,处理下一级元素时,新元素是栈顶元素在子元素,然后处理完了继续把该元素压入堆栈,继续下一级新的子元素。当元素结束后,需要出栈,以便解析下个兄弟元素程时能取到父节点。

好啦,基本应用还是很简单的,实际上Expat的API函数不多。

3.如何处理属性

属性通过ElementHandler回调函数传入,这里有一个char** atts就是属性,这是一个字符指针数组,如果有N个属性,数组大小就是2*N+1,最后一个素组元素为空指针,奇数指针对应属性名称,偶数指针对应属性值(字符串格式)。可以在一个循环中处理多个属性,当遇到空指针时,表示没有更多属性了。

好啦,先看sample吧:

#include <stdio.h>
#include "expat.h"


#pragmawarning(disable:4996)


#defineXML_FMT_INT_MOD "l"

staticvoidXMLCALL 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");
}

staticvoidXMLCALL endElement(void *userData,const char *name)
{
int *depthPtr = (int *)userData;
*depthPtr -= 1;
}

intmain(intargc, char *argv[])
{
char buf[BUFSIZ]; XML_Parserparser = XML_ParserCreate(NULL);

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,done) == XML_STATUS_ERROR)
{
fprintf(stderr,"%s at line %"XML_FMT_INT_MOD "un",
XML_ErrorString(XML_GetErrorCode(parser)),
XML_GetCurrentLineNumber(parser));
return 1;
}
}
while (!done);
XML_ParserFree(parser);
fclose(pFile);
return 0;
}

4.其他ElementHanlder

expat还可以设置CData,Comment的handler,另外一些函数本人还没使用过,涉及到更多的xml标准的知识,如果需要,可以参考官方的手册。

参考:

http://www.xml.com/pub/a/1999/09/expat/index.html

要了解如何使用expat XML解析器之前,先来仔细地分析一下怎么样使用expat库的小例子,看看具体调用了那些接口函数,是否会很复杂的呢?它的例子程序如下:
#001
#013
#014

下面包括输出文件和库文件头。
#015 #include<stdio.h>
#016 #include"xmlparse.h"
#017

定义缓冲区的大小。
#018 #defineBUFFSIZE 8192
#019

创建一个缓冲区。
#020 charBuff[BUFFSIZE];
#021
#022 int Depth;
#023

下面定义一个XML元素开始处理的函数。
#024 void
#025 start(void*data,const char *el,const char **attr) {
#026 int i;
#027
#028 for (i = 0; i < Depth; i++)
#029 printf("");
#030
#031 printf("%s",el);
#032
#033 for (i = 0; attr[i]; i += 2) {
#034 printf("%s='%s'",attr[i],attr[i + 1]);
#035 }
#036
#037 printf("n");
#038 Depth++;
#039 }
#040

下面定义一个XML元素结束调用的函数。
#041 void
#042 end(void*data,const char *el) {
#043 Depth--;
#044 }
#045

程序入口点。
#046 void
#047 main(int argc,char **argv) {

创建一个XML分析器。
#048 XML_Parser p =XML_ParserCreate(NULL);

下面判断是否创建XML分析器失败。
#049 if (! p) {
#050fprintf(stderr,"Couldn't allocate memory for parsern");
#051 exit(-1);
#052 }
#053

下面设置每个XML元素出现和结束的处理函数。这里设置start为元素开始处理函数,end元素结束处理函数。
#054XML_SetElementHandler(p,end);
#055

循环分析所有XML文件。
#056 for (;;) {
#057 int done;
#058 int len;
#059

调用函数fread从文件里读取数据到缓冲区Buff里。
#060 len =fread(Buff,stdin);

读取文件出错就退出。
#061 if(ferror(stdin)) {
#062fprintf(stderr,"Read errorn");
#063 exit(-1);
#064 }

判断是否读取文件到结束。
#065 done =feof(stdin);
#066

调用库函数XML_Parse来分析缓冲区Buff里的XML数据。
#067 if (!XML_Parse(p,done)) {
#068fprintf(stderr,"Parse error at line %d:n%sn",
#069XML_GetCurrentLineNumber(p),
#070XML_ErrorString(XML_GetErrorCode(p)));
#071 exit(-1);
#072 }
#073

如果分析文件到结尾位置,或者出错,就可以退出循环处理。
#074 if (done)
#075 break;
#076 }
#077 }
#078
#079
#080

通过上面调用库函数XML_ParserCreate、XML_SetElementHandler、XML_Parse等三个函数就完成了XML的分析过程,这样使用起来真是太简单了,看到expat库的威力无穷。

----------------------------------------------------------------------------------------------------------------

expat是使用C所写的XML解释器,采用流的方式来解析XML文件,并且基于事件通知型来调用分析到的数据,并不需要把所有XML文件全部加载到内存里,这样可以分析非常大的XML文件。由于expat库是由XML的主要负责人James Clark来实现的,因此它是符合W3C的XML标准的。

---------------------------以上为转载-------------------------------------

正因为源码全部是纯C所写,因此,非常容易移植,尤其是适用于嵌入式平台,我在往联芯的手机平台上移植时,几乎没改任何东西。

不过,优点也带来了缺点,因为是采用流的方式解析XML,所以不会像TinyXML那样在一块内存中生成基于DOM的树。

虽然这样解析起来略显麻烦,但是基于回调的机制,在我看来还是蛮方便的。

下面就说使用方法:

首先是用XML_ParserCreate(const XML_Char *encodingName),参数一般为NULL,函数返回一个XML_Parser类型指针,我们就当他是一个句柄吧,类似于Windows里的内核对象,一般需要保存在一个全局的指针里。



然后调用XML_SetElementHandler(XML_Parser parser,
XML_StartElementHandler start,
XML_EndElementHandlerend)



第一个参数是那个Parser句柄,第二个和第三个参数则是整个Parser的核心,类型为CallBack的函数,不了解CallBack函数的,我在这里简单说下,函数调用一般分为两种,一种是主调,即编写代码者,自己调用的函数,还一种成为Callback函数,编码者写好,但他自己却不主动调用,而是在某些条件下(编码者并不清楚具体时间和流程),由其他函数调用,比如简单的,如设备驱动,操作系统提供了一组某个设备的函数指针,比如LCD屏驱动,由一组画点,画线,画块等函数组成,当更换LCD时,只需要把操作系统开放的函数指针,指向你提供的接口即可,操作系统再需要时,会自动调用你的驱动函数,这就是回调函数一个典型的例子。

这二个回调分别是对应于解析<>和</>, 下面分别详细介绍这个2个回调函数。

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

其中第一个参数userData,可以由函数XML_SetUserData(XML_Parserparser,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 (XMLCALL *XML_EndElementHandler) (void*userData,
const XML_Char *name);

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

最后一个函数就是XML_SetCharacterDataHandler(XML_Parser parser,XML_CharacterDataHandlerhandler)

这个函数是设置处理一个<>和</>之间的字段的回调。

回调原型如下:

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

其中第二个参数是一块Buffer的指针,如果你单步DEBUG后,你会发现expat用的就是你传入的那块Buffer(这块Buffer下面讲解),比如:

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

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

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

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

但这里有个非常隐晦的问题,如果不知道的话,会带来很大麻烦,下面说。

最后就是parse,调用

XML_Parse(XML_Parser parser,const char *s,int len,intisFinal)

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

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

XML_CharacterDataHandler一次返回的可能并不是完整的CharData,比如这个charData的Len大于你的Buffer大小,那这是会连续调用2次XML_CharacterDataHandler,我们需要将2次结果拼接起来,以得到正确结果,因此我们的状态机一定要考虑到这点。 顺便说下XML_ParserReset(XML_Parser parser,const XML_Char *encodingName)函数,在某些时候,如果你不确定前后2次XML是否一样的情况下,比如网络上投递的XML,在一次解析后最好调用一次本函数,否则会出现意料之外的结果。比如前后两次XML完全一样,可这你并不知情,那么XML_Parse()会返回失败。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读