开源xml解析器.docx
《开源xml解析器.docx》由会员分享,可在线阅读,更多相关《开源xml解析器.docx(47页珍藏版)》请在冰豆网上搜索。
开源xml解析器
Expat
Expat是什么?
Expat是一个用C语言开发的、用来解析XML文档的开发库,它最初是开源的、Mozilla项目下的一个XML解析器。
关于作者
这个库的开发者是JamesClark,还开发了很多我们所熟知的工具包:
groff、Jade、XP(aJavaXMLparserpackage)、andXT(aJavaXSLengine)。
ExpatXMLParser概述
Expat是一个面向流的解析器。
您注册的解析器回调(或handler)功能,然后开始搜索它的文档。
当解析器识别该文件的指定的位置,它会调用该部分相应的处理程序(如果您已经注册的一个)。
该文件被输送到解析器,会被分割成多个片断,并分段装到内存中。
因此expat可以解析那些巨大的文件。
如何使用它们
ExpatXMLParser支持设置多种不同的处理器。
但是要使用它们,你只需要学习四个功能,即可满足80%的需要。
它们是:
XML_ParserCreateCreateanewparserobject.
XML_SetElementHandlerSethandlersforstartandendtags.
XML_SetCharacterDataHandlerSethandlerfortext.
XML_ParsePassabufferfullofdocumenttotheparser
开源的XMLParserexpat
文章分类:
C++编程
expat是使用C所写的XML解释器,采用流的方式来解析XML文件,并且基于事件通知型来调用分析到的数据,并不需要把所有XML文件全部加载到内存里,这样可以分析非常大的XML文件。
由于expat库是由XML的主要负责人JamesClark来实现的,因此它是符合W3C的XML标准的。
正因为源码全部是纯C所写,因此,非常容易移植,尤其是适用于嵌入式平台,我在往联芯的手机平台上移植时,几乎没改任何东西。
不过,优点也带来了缺点,因为是采用流的方式解析XML,所以不会像TinyXML那样在一块内存中生成基于DOM的树。
虽然这样解析起来略显麻烦,但是基于回调的机制,在我看来还是蛮方便的。
下面就说使用方法:
首先是用XML_ParserCreate(constXML_Char*encodingName),参数一般为NULL,函数返回一个XML_Parser类型指针,我们就当他是一个句柄吧,类似于Windows里的内核对象,一般需要保存在一个全局的指针里。
然后调用XML_SetElementHandler(XML_Parserparser,
XML_StartElementHandlerstart,
XML_EndElementHandlerend)
第一个参数是那个Parser句柄,第二个和第三个参数则是整个Parser的核心,类型为CallBack的函数,不了解CallBack函数的,我在这里简单说下,函数调用一般分为两种,一种是主调,即编写代码者自己调用的函数,还一种称为Callback函数,编码者写好,但他自己却不主动调用,而是在某些条件下(编码者并不清楚具体时间和流程),由其他函数调用,比如设备驱动,操作系统提供了一组某个设备的函数指针,比如LCD屏驱动,由一组画点,画线,画块等函数组成,当更换LCD时,只需要把操作系统开放的函数指针,指向你提供的接口即可,操作系统再需要时,会自动调用你的驱动函数,这就是回调函数一个典型的例子。
这二个回调分别是对应于解析<>和>,下面分别详细介绍这个2个回调函数。
typedefvoid(XMLCALL*XML_StartElementHandler)(
void*userData,
constXML_Char*name,
constXML_Char**atts);
其中第一个参数userData,可以由函数
XML_SetUserData(XML_Parserparser,void*p)
设置,参数就不用说了吧?
后面两个参数,我用个具体的列子说明下,这样更好理解:
比如有个标准XML,某个标签属性如下:
那么StartElementHandler回调返回的name就是标签"feed",**atts是一个指针数组,分别指向标签的一组属性,atts[0]就是"version",atts[1]就是"2.0",以此类推。
应该很清楚了吧?
呵呵。
这时候必然有个对应的:
typedefvoid(XMLCALL*XML_EndElementHandler)(
void*userData,
constXML_Char*name);
就是处理标签结束的,name就是"feed”了,这个回调一般是用户设置自己的状态机的。
最后一个函数就是
XML_SetCharacterDataHandler(
XML_Parserparser,
XML_CharacterDataHandlerhandler)
这个函数是设置处理一个<>和>之间的字段的回调。
回调原型如下:
typedefvoid(XMLCALL*XML_CharacterDataHandler)(
void*userData,
constXML_Char*s,
intlen);
其中第二个参数是一块Buffer的指针,如果你单步DEBUG后,你会发现expat用的就是你传入的那块Buffer(这块Buffer下面讲解),比如:
天气28日08时至29日08时,陕西中南部、山西西南部、河南中南部、湖北北部、四川中东部、重庆西部和北部、贵州西部等地的部分地区有大雨或暴雨,河南南部、湖北北部等地局部有大暴雨。
【点击“更多”查询其他城市天气】
假设目前解析到天气这个charData,如果你看那个指针的所有内容的话,实际上是这样的:
天气
28日08时至29日08时,陕西中南部、山西西南部、河南中南部、湖北北部、四川中东部、重庆西部和北部、贵州西部等地的部分地区有大雨或暴雨,河南南部、湖北北部等地局部有大暴雨。
【点击“更多”查询其他城市天气】
所以要根据第三个参数len来确定正确的数据。
但这里有个非常隐晦的问题,如果不知道的话,会带来很大麻烦,下面说。
最后就是parse,调用
XML_Parse(XML_Parserparser,constchar*s,intlen,intisFinal)
第二个参数是用户指定的Buffer指针,第三个是这块Buffer中实际内容的字节数,最后参数代表是否这块Buffer已经结束。
比如要解析的XML文件太大,但内存比较吃紧,Buffer比较小,则可以循环读取文件,然后丢给Parser, 在文件读取结束前,isFinal参数为FALSE,反之为TRUE。
这里的Buffer如果太小则会造成上面提到那个隐晦的问题,
XML_CharacterDataHandler一次返回的可能并不是完整的CharData,比如这个charData的Len大于你的Buffer大小,那这是会连续调用2次XML_CharacterDataHandler,我们需要将2次结果拼接起来,以得到正确结果,因此我们的状态机一定要考虑到这点。
顺便说下
XML_ParserReset(XML_Parserparser,constXML_Char*encodingName)函数,在某些时候,如果你不确定前后2次XML是否一样的情况下,比如网络上投递的XML,在一次解析后最好调用一次本函数,否则会出现意料之外的结果。
比如前后两次XML完全一样,可这你并不知情,那么XML_Parse()会返回失败。
XML解析-libxml库函数解释
libxml
(一)
绪论
Libxml是一个实现读、创建及操纵XML数据功能的C语言库。
这个指南提供例子代码并给出它基本功能的解释。
在这个项目的主页上有Libxml及更多关于它可用的资料。
包含有完整的API文档。
这个指南并不能替代这些完整的文档,但是阐明功能需要使用库来完成基本操作。
这个指南基于一个简单的XML应用,它使用我写的一篇文章生成,它包含有元数据和文章的主体。
本指南中的例子代码示范如何做到:
•解析文档
•取得指定元素的文本
•添加一个元素及它的内容
•添加一个属性
•取得一个属性的值
例子的完整代码包含在附录中
数据类型
Libxml定义了许多数据类型,我们将反复碰到它们,它隐藏了杂乱的来源以致你不必处理它除非你有特定的需要。
xmlChar替代char,使用UTF-8编码的一字节字符串。
如果你的数据使用其它编码,它必须被转换到UTF-8才能使用libxml的函数。
在libxml编码支持WEB页面有更多关于编码的有用信息。
XmlDoc包含由解析文档建立的树结构,xmlDocPtr是指向这个结构的指针。
xmlNodePtrandxmlNode包含单一结点的结构xmlNodePtr是指向这个结构的指针,它被用于遍历文档树。
解析文档
解析文档时仅仅需要文件名并只调用一个函数,并有错误检查。
完整代码:
附录C,Keyword例程代码
①xmlDocPtrdoc;
②xmlNodePtrcur;
③doc=xmlParseFile(docname);
④if(doc==NULL){
fprintf(stderr,"Documentnotparsedsuccessfully.\n");
return;
}
⑤cur=xmlDocGetRootElement(doc);
⑥if(cur==NULL){
fprintf(stderr,"emptydocument\n");
xmlFreeDoc(doc);
return;
}
⑦if(xmlStrcmp(cur->name,(constxmlChar*)"story")){
fprintf(stderr,"documentofthewrongtype,rootnode!
=story");
xmlFreeDoc(doc);
return;
}
①定义解析文档指针。
②定义结点指针(你需要它为了在各个结点间移动)。
④检查解析文档是否成功,如果不成功,libxml将指一个注册的错误并停止。
注释
一个常见错误是不适当的编码。
XML标准文档除了用UTF-8或UTF-16外还可用其它编码保存。
如果文档是这样,libxml将自动地为你转换到UTF-8。
更多关于XML编码信息包含在XML标准中。
⑤取得文档根元素
⑥检查确认当前文档中包含内容。
⑦在这个例子中,我们需要确认文档是正确的类型。
“Story”是在这个指南中使用文档的根类型。
取得元素内容
你找到在文档树中你要查找的元素后可以取得它的内容。
在这个例子中我们查找“story”元素。
进程将在冗长的树中查找我们感兴趣的元素。
我们假定你已经有了一个名为doc的xmlDocPtr和一个名为cur的xmlNodPtr。
①cur=cur->xmlChildrenNode;
②while(cur!
=NULL){
if((!
xmlStrcmp(cur->name,(constxmlChar*)"storyinfo"))){
parseStory(doc,cur);
}
cur=cur->next;
}
①取得cur的第一个子结点,cur指向文档的根,即“story”元素。
②这个循环迭代通过“story”的子元素查找“storyinfo”。
这是一个包含有我们将查找的“keywords”的元素。
它使用了libxml字符串比较函数xmlStrcmp。
如果相符,它调用函数parseStory。
voidparseStory(xmlDocPtrdoc,xmlNodePtrcur){
xmlChar*key;
①cur=cur->xmlChildrenNode;
②while(cur!
=NULL){
if((!
xmlStrcmp(cur->name,(constxmlChar*)"keyword"))){
③key=xmlNodeListGetString(doc,cur->xmlChildrenNode,1);
printf("keyword:
%s\n",key);
xmlFree(key);
}
cur=cur->next;
}
return;
}
①再次取得第一个子结点。
②像上面那个循环一样,我们能够迭代,查找我们感兴趣的叫做“keyword”的元素。
③当我们找到元素“keyword”时,我们需要打印它包含在XML中的记录的内容,文本被包含于元素的子结点中,因此我们借助了cur->xmlChildrenNode,为了取得文本,我们使用函数xmlNodeListGetString,它有一个文档指针参数,在这个例子中,我们仅仅打印它。
注释
因为xmlNodeListGetString为它返回的字符串分配内存,你必须使用xmlFree释放它。
使用XPath取得元素内容
除了一步步遍历文档树查找元素外,Libxml2包含支持使用Xpath表达式取得指定结点集。
完整的XpathAPI文档在这里。
Xpath允许通过路径文档搜索匹配指定条件的结点。
在下面的例子中,我们搜索文档中所有的“keyword”元素。
注释
下面是Xpath完整的讨论。
它详细的使用资料,请查阅Xpath规范。
这个例子完整的代码参见附录D,XPath例程代码。
UsingXPathrequiressettingupanxmlXPathContextandthensupplyingtheXPathexpressionandthecontexttothexmlXPathEvalExpression
function.
ThefunctionreturnsanxmlXPathObjectPtr,whichincludesthesetofnodessatisfyingtheXPathexpression.
使用XPath需要安装xmlXPathContext才支持XPath表达式及xmlXPathEvalExpression函数,这个函数返回一个xmlXPathObjectPtr,它包含有
XPath表达式的结点集。
xmlXPathObjectPtr
getnodeset(xmlDocPtrdoc,xmlChar*xpath){
①xmlXPathContextPtrcontext;
xmlXPathObjectPtrresult;
②context=xmlXPathNewContext(doc);
③result=xmlXPathEvalExpression(xpath,context);
④if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
printf("Noresult\n");
returnNULL;
}
xmlXPathFreeContext(context);
returnresult;
}
①首先定义变量
②初始化变量context
③应用XPath表达式
④检查结果
由函数返回的xmlPathObjectPtr包含一个结点集和其它需要被迭代及操作的信息。
在这个例子中我们的函数返回xmlXPathObjectPtr,我们使用它打印我们文档中keyword结点的内容。
这个结点集对象包含在集合(nodeNr)中的元素数目及一个结点(nodeTab)数组。
①for(i=0;inodeNr;i++){
②keyword=xmlNodeListGetString(doc,
nodeset->nodeTab[i]->xmlChildrenNode,printf("keyword:
%s\n",keyword);
xmlFree(keyword);
}
①变量nodeset->Nr持有结点集中元素的数量。
我们使用它遍历数组。
②打印每个结点包含的内容。
注释
Notethatweareprintingthechildnodeofthenodethatisreturned,becausethecontentsofthekeywordelementareachildtextnode.注意我们打印的是结点的子结点的返回值,因为keyword元素的内容是一个子文本结点。
写(插入)元素
写元素内容使用上面许多一样的步骤—解析文档并遍历树。
我们先解析文档然后遍历树查找我们想插入元素的位置。
在这个例子中,我们再一次查找“storyinfo”元素并插入一个keyword。
然后我们装文件写入磁盘。
完整代码:
附录E,添加keyword例程
本例中主要的不同在于parseStory
void
parseStory(xmlDocPtrdoc,xmlNodePtrcur,char*keyword){
①xmlNewTextChild(cur,NULL,"keyword",keyword);
return;
}
①XmlNewTextChild函数添加一个当前结点的新的子元素到树中
一旦结点被添加,我们应当写文档到文件中。
你是否想给元素指定一个命名空间?
你能添加它,在我们的例子中,命名空间是NULL。
xmlSaveFormatFile(docname,doc,1);
第一个参数是写入文件的名,你注意到和我们刚刚读入的文件名是一样的。
在这个例子中,我们仅仅覆盖原来的文件。
第二个参数是一个xmlDoc结构指针,第三个参数设定为1,保证在输出上写入。
libxml
(二)
写属性
写属性类似于给一个新元素写文本。
在这个例子中,我们将添加一个reference结点URI属性到我们的文档中。
完整代码:
附录F,添加属性例程代码。
reference是story元素的一个子结点,所以找到并插入新元素及其属性是简单的。
一旦我们在parseDoc进行了错误检查,我们将在正确的位置加放我们的新元素。
但进行之前我们需要定义一个此前我们不见过的数据类型。
xmlAttrPtrnewattr;
我们也需要xmlNodePtr:
xmlNodePtrnewnode;
剩下的parseDoc则和前面一样,检查根结点是否为story。
如果是的,那我们知道我们将在指定的位置添加我们的元素。
①newnode=xmlNewTextChild(cur,NULL,"reference",NULL);
②newattr=xmlNewProp(newnode,"uri",uri);
①使用xmlNewTextChild函数添国一个新结点到当前结点位置。
一旦结点被添加,文件应像前面的例子将我们添加的元素及文本内容写入磁盘。
取得属性
取得属性值类似于前面我们取得一个结点的文本内容。
在这个例子中,我们将取出我们在前一部分添加的URI的值。
完整代码:
附录G,取得属性值例程代码。
这个例子的初始步骤和前面是类似的:
解析文档,查找你感兴趣的元素,然后进入一个函数完成指定的请求任务。
在这个例子中,我们调用getReference。
voidgetReference(xmlDocPtrdoc,xmlNodePtrcur){
xmlChar*uri;
cur=cur->xmlChildrenNode;
while(cur!
=NULL){
if((!
xmlStrcmp(cur->name,(constxmlChar*)"reference"))){
①uri=xmlGetProp(cur,"uri");
printf("uri:
%s\n",uri);
xmlFree(uri);
}
cur=cur->next;
}
return;
}
①关键函数是xmlGetProp,它返回一个包含属性值的xmlChar。
在本例中,我们仅仅打印它。
注释
如果你使用DTD定义属性的固定值或缺省值,这个函数也将取得它。
编码转换
数据编码兼容问题是程序员新建普通的XML或特定XML时最常见的困难。
稍后的讨论来思考设计你的应用程序将帮助你避免这个困难。
实际上,libxml能以UTF-8格式保存和操纵多种数据
你的程序使用其它的数据格式,比如常见的ISO-8859-1编码,必须使用libxml函数转换到UTF-8。
如果你想你的程序以除UTF-8外的其它编码方式输出也必须做转换。
如果能有效地转换数据Libxml将使用转换器。
无转换器时,仅仅UTF-8、UTF-16和ISO-8859-1能够被作为外部格式使用。
有转换器时,它能将从其它格式与UTF-8互换的任何格式均可使用。
当前转换器支持大约150种不同的编码格式之间的相互转换。
实际支持的格式数量正在被实现。
每一个实现在的转换器尽可能的支持每一种格式。
警告:
一个常见错误是在内部数据不同的部分使用不同的编码格式。
最常见的情况是一个用以ISO-8859-1作为内部数据格式,结合libxml部分使用UTF-8格式。
结果是一个应用程序要面对不同地内部数据格式。
一部分代码执行后,它或其它部分代码将使用曲解的数据。
这个例子构造一个简单的文档,然后添加在命令行提供的内容到根元素并使用适当的编码将结果输出到标准输出设备上。
在这个例子中,我们使用ISO-8859-1编码。
在命令输入的内容将被从ISO-8859-1转换到UTF-8。
完整代码:
附件H,编码转换例程代码。
包含在例子中的转换函数使用libxml的xmlFindCharEncodingHandler函数。
①xmlCharEncodingHandlerPtrhandler;
②size=(int)strlen(in)+1;
out_size=size*2-1;
out=malloc((size_t)out_size);
…
③handle