expat XML解析器

工作中用到了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。
 在source\example目录下有参考代码。
 其中看看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 error\n");
      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%s\n",
             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] = '\0';
  rintf("%s " ,buf);  
 } 
}
   注意,这里的s不是以\0结束的。

 

 

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

 

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 parser\n");
#051 exit(-1);
#052 }
#053 
 
下面设置每个XML元素出现和结束的处理函数。这里设置start为元素开始处理函数,end元素结束处理函数。
#054XML_SetElementHandler(p, start, end);
#055 
 
循环分析所有XML文件。
#056 for (;;) {
#057 int done;
#058 int len;
#059 
 
调用函数fread从文件里读取数据到缓冲区Buff里。
#060 len =fread(Buff, 1, BUFFSIZE, stdin);
 
读取文件出错就退出。
#061 if(ferror(stdin)) {
#062fprintf(stderr, "Read error\n");
#063 exit(-1);
#064 }
 
判断是否读取文件到结束。
#065 done =feof(stdin);
#066 
 
调用库函数XML_Parse来分析缓冲区Buff里的XML数据。
#067 if (!XML_Parse(p, Buff, len, done)) {
#068fprintf(stderr, "Parse error at line %d:\n%s\n",
#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()会返回失败。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值