1、开源xml解析器ExpatExpat 是什么?Expat 是一个用C语言开发的、用来解析XML文档的开发库,它最初是开源的、Mozilla 项目下的一个XML解析器。 关于作者这个库的开发者是James Clark, 还开发了很多我们所熟知的工具包:groff、 Jade、 XP (a Java XML parser package)、 and XT (a Java XSL engine)。 Expat XML Parser 概述Expat是一个面向流的解析器。您注册的解析器回调(或handler)功能,然后开始搜索它的文档。当解析器识别该文件的指定的位置,它会调用该部分相应的处理程序(如果您
2、已经注册的一个)。该文件被输送到解析器,会被分割成多个片断,并分段装到内存中。因此expat可以解析那些巨大的文件。 如何使用它们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
3、 buffer full of document to the parser开源的XML Parser expat文章分类:C+编程 expat是使用C所写的XML解释器,采用流的方式来解析XML文件,并且基于事件通知型来调用分析到的数据,并不需要把所有XML文件全部加载到内存里,这样可以分析非常大的XML文件。由于 expat库是由XML的主要负责人James Clark来实现的,因此它是符合W3C的XML标准的。 正因为源码全部是纯C所写,因此,非常容易移植,尤其是适用于嵌入式平台,我在往联芯的手机平台上移植时,几乎没改任何东西。 不过,优点也带来了缺点,因为是采用流的方式解析XML,所以
4、不会像TinyXML那样在一块内存中生成基于DOM的树。 虽然这样解析起来略显麻烦,但是基于回调的机制,在我看来还是蛮方便的。下面就说使用方法: 首先是用XML_ParserCreate(const XML_Char *encodingName),参数一般为NULL,函数返回一个XML_Parser类型指针,我们就当他是一个句柄吧,类似于Windows里的内核对象,一般需要保存在一个全局的指针里。 然后调用XML_SetElementHandler(XML_Parser parser, XML_StartElementHandler start, XML_EndElementHandler e
5、nd) 第一个参数是那个Parser句柄,第二个和第三个参数则是整个Parser的核心,类型为CallBack的函数,不了解CallBack函数的,我在这里简单说下,函数调用一般分为两种,一种是主调,即编写代码者自己调用的函数,还一种称为Callback函数,编码者写好,但他自己却不主动调用,而是在某些条件下(编码者并不清楚具体时间和流程),由其他函数调用,比如设备驱动,操作系统提供了一组某个设备的函数指针,比如LCD屏驱动,由一组画点,画线,画块等函数组成,当更换LCD时,只需要把操作系统开放的函数指针,指向你提供的接口即可,操作系统再需要时,会自动调用你的驱动函数,这就是回调函数一个典型的
6、例子。 这二个回调分别是对应于解析和, 下面分别详细介绍这个2个回调函数。 typedef void (XMLCALL *XML_StartElementHandler) (void *userData, const XML_Char *name,const XML_Char *atts); 其中第一个参数userData, 可以由函数XML_SetUserData(XML_Parser parser, void *p)设置,参数就不用说了吧? 后面两个参数,我用个具体的列子说明下,这样更好理解: 比如有个标准XML,某个标签属性如下: 那么StartElementHandler回调返回的na
7、me就是标签feed, *atts是一个指针数组,分别指向标签的一组属性,atts0就是version, atts1就是2.0, 以此类推。应该很清楚了吧?呵呵。这时候必然有个对应的: typedef void (XMLCALL *XML_EndElementHandler) (void *userData, const XML_Char *name); 就是处理标签结束的,name就是feed”了,这个回调一般是用户设置自己的状态机的。最后一个函数就是XML_SetCharacterDataHandler(XML_Parser parser,XML_CharacterDataHandler
8、handler) 这个函数是设置处理一个和之间的字段的回调。回调原型如下: typedef void (XMLCALL *XML_CharacterDataHandler) (void *userData,const XML_Char *s,int len); 其中第二个参数是一块Buffer的指针,如果你单步DEBUG后,你会发现expat用的就是你传入的那块Buffer(这块Buffer下面讲解),比如: 天气 28日08时至29日08时,陕西中南部、山西西南部、河南中南部、湖北北部、四川中东部、重庆西部和北部、贵州西部等地的部分地区有大雨或暴雨,河南南部、湖北北部等地局部有大暴雨。【点击
9、“更多”查询其他城市天气】 假设目前解析到天气这个charData, 如果你看那个指针的所有内容的话,实际上是这样的: 天气 28日08时至29日08时,陕西中南部、山西西南部、河南中南部、湖北北部、四川中东部、重庆西部和北部、贵州西部等地的部分地区有大雨或暴雨,河南南部、湖北北部等地局部有大暴雨。【点击“更多”查询其他城市天气】 所以要根据第三个参数len来确定正确的数据。 但这里有个非常隐晦的问题,如果不知道的话,会带来很大麻烦,下面说。 最后就是parse,调用 XML_Parse(XML_Parser parser, const char *s, int len, int isFina
10、l) 第二个参数是用户指定的Buffer指针, 第三个是这块Buffer中实际内容的字节数,最后参数代表是否这块Buffer已经结束。比如要解析的XML文件太大,但内存比较吃紧,Buffer比较小,则可以循环读取文件,然后丢给Parser, 在文件读取结束前,isFinal参数为FALSE,反之为TRUE。 这里的Buffer如果太小则会造成上面提到那个隐晦的问题, XML_CharacterDataHandler一次返回的可能并不是完整的CharData,比如这个charData的Len大于你的 Buffer大小,那这是会连续调用2次XML_CharacterDataHandler,我们需要
11、将2次结果拼接起来,以得到正确结果,因此我们的状态机一定要考虑到这点。 顺便说下XML_ParserReset(XML_Parser parser, const XML_Char *encodingName)函数,在某些时候,如果你不确定前后2次XML是否一样的情况下,比如网络上投递的XML,在一次解析后最好调用一次本函数,否则会出现意料之外的结果。比如前后两次XML完全一样,可这你并不知情,那么XML_Parse()会返回失败。 XML解析libxml库函数解释libxml(一)绪论 Libxml是一个实现读、创建及操纵XML数据功能的C语言库。这个指南提供例子代码并给出它基本功能的解释。在
12、这个项目的主页上有Libxml及更多关于它可用的资料。包含有完整的API文档。这个指南并不能替代这些完整的文档,但是阐明功能需要使用库来完成基本操作。 这个指南基于一个简单的XML应用,它使用我写的一篇文章生成,它包含有元数据和文章的主体。本指南中的例子代码示范如何做到: 解析文档 取得指定元素的文本 添加一个元素及它的内容 添加一个属性 取得一个属性的值 例子的完整代码包含在附录中 数据类型 Libxml定义了许多数据类型,我们将反复碰到它们,它隐藏了杂乱的来源以致你不必处理它除非你有特定的需要。xmlChar替代char,使用UTF-8编码的一字节字符串。如果你的数据使用其它编码,它必须被
13、转换到UTF-8才能使用libxml的函数。在libxml编码支持WEB页面有更多关于编码的有用信息。 XmlDoc包含由解析文档建立的树结构,xmlDocPtr是指向这个结构的指针。xmlNodePtr and xmlNode包含单一结点的结构xmlNodePtr是指向这个结构的指针,它被用于遍历文档树。 解析文档 解析文档时仅仅需要文件名并只调用一个函数,并有错误检查。完整代码:附录C, Keyword例程代码 xmlDocPtr doc; xmlNodePtr cur; doc = xmlParseFile(docname); if (doc = NULL ) fprintf(stder
14、r,Document not parsed successfully. n); return; cur = xmlDocGetRootElement(doc); if (cur = NULL) fprintf(stderr,empty documentn); xmlFreeDoc(doc); return; if (xmlStrcmp(cur-name, (const xmlChar *) story) fprintf(stderr,document of the wrong type, root node != story); xmlFreeDoc(doc); return; 定义解析文档指
15、针。 定义结点指针(你需要它为了在各个结点间移动)。 检查解析文档是否成功,如果不成功,libxml将指一个注册的错误并停止。 注释 一个常见错误是不适当的编码。XML标准文档除了用UTF-8或UTF-16外还可用其它编码保存。如果文档是这样,libxml将自动地为你转换到UTF-8。更多关于XML编码信息包含在XML标准中。 取得文档根元素 检查确认当前文档中包含内容。 在这个例子中,我们需要确认文档是正确的类型。“Story”是在这个指南中使用文档的根类型。 取得元素内容 你找到在文档树中你要查找的元素后可以取得它的内容。在这个例子中我们查找“story”元素。进程将在冗长的树中查找我们感
16、兴趣的元素。我们假定你已经有了一个名为doc的xmlDocPtr和一个名为cur的xmlNodPtr。 cur = cur-xmlChildrenNode; while (cur != NULL) if (!xmlStrcmp(cur-name, (const xmlChar *)storyinfo) parseStory (doc, cur); cur = cur-next; 取得cur的第一个子结点,cur指向文档的根,即“story”元素。 这个循环迭代通过“story”的子元素查找“storyinfo”。这是一个包含有我们将查找的“keywords”的元素。它使用了libxml字符串比
17、较函数xmlStrcmp。如果相符,它调用函数parseStory。 void parseStory (xmlDocPtr doc, xmlNodePtr cur) xmlChar *key; cur = cur-xmlChildrenNode; while (cur != NULL) if (!xmlStrcmp(cur-name, (const xmlChar *)keyword) key = xmlNodeListGetString(doc, cur-xmlChildrenNode, 1); printf(keyword: %sn, key); xmlFree(key); cur = c
18、ur-next; return; 再次取得第一个子结点。 像上面那个循环一样,我们能够迭代,查找我们感兴趣的叫做“keyword”的元素。 当我们找到元素“keyword”时,我们需要打印它包含在XML中的记录的内容,文本被包含于元素的子结点中,因此我们借助了cur-xmlChildrenNode,为了取得文本,我们使用函数xmlNodeListGetString,它有一个文档指针参数,在这个例子中,我们仅仅打印它。 注释 因为xmlNodeListGetString为它返回的字符串分配内存,你必须使用xmlFree释放它。 使用XPath取得元素内容除了一步步遍历文档树查找元素外,Libxm
19、l2包含支持使用Xpath表达式取得指定结点集。完整的Xpath API文档在这里。Xpath允许通过路径文档搜索匹配指定条件的结点。在下面的例子中,我们搜索文档中所有的“keyword”元素。 注释 下面是Xpath完整的讨论。它详细的使用资料,请查阅Xpath规范。 这个例子完整的代码参见附录D,XPath例程代码。 Using XPath requires setting up an xmlXPathContext and then supplying the XPath expression and the context to the xmlXPathEvalExpression f
20、unction. The function returns an xmlXPathObjectPtr, which includes the set of nodes satisfying the XPath expression. 使用XPath需要安装xmlXPathContext才支持XPath表达式及xmlXPathEvalExpression函数,这个函数返回一个xmlXPathObjectPtr,它包含有XPath表达式的结点集。 xmlXPathObjectPtr getnodeset (xmlDocPtr doc, xmlChar *xpath) xmlXPathContext
21、Ptr context; xmlXPathObjectPtr result; context = xmlXPathNewContext(doc); result = xmlXPathEvalExpression(xpath, context); if(xmlXPathNodeSetIsEmpty(result-nodesetval) printf(No resultn); return NULL; xmlXPathFreeContext(context); return result; 首先定义变量 初始化变量context 应用XPath表达式 检查结果 由函数返回的xmlPathObjec
22、tPtr包含一个结点集和其它需要被迭代及操作的信息。在这个例子中我们的函数返回xmlXPathObjectPtr,我们使用它打印我们文档中keyword结点的内容。这个结点集对象包含在集合(nodeNr)中的元素数目及一个结点(nodeTab)数组。 for (i=0; i nodeNr; i+) keyword = xmlNodeListGetString(doc, nodeset-nodeTabi-xmlChildrenNode, printf(keyword: %sn, keyword); xmlFree(keyword); 变量nodeset-Nr持有结点集中元素的数量。我们使用它遍历
23、数组。 打印每个结点包含的内容。 注释 Note that we are printing the child node of the node that is returned, because the contents of the keyword element are a child text node.注意我们打印的是结点的子结点的返回值,因为keyword元素的内容是一个子文本结点。写(插入)元素 写元素内容使用上面许多一样的步骤解析文档并遍历树。我们先解析文档然后遍历树查找我们想插入元素的位置。在这个例子中,我们再一次查找“storyinfo”元素并插入一个keyword。然后我
24、们装文件写入磁盘。完整代码:附录E,添加keyword例程 本例中主要的不同在于parseStory void parseStory (xmlDocPtr doc, xmlNodePtr cur, char *keyword) xmlNewTextChild (cur, NULL, keyword, keyword); return; XmlNewTextChild函数添加一个当前结点的新的子元素到树中 一旦结点被添加,我们应当写文档到文件中。你是否想给元素指定一个命名空间?你能添加它,在我们的例子中,命名空间是NULL。 xmlSaveFormatFile (docname, doc, 1)
25、; 第一个参数是写入文件的名,你注意到和我们刚刚读入的文件名是一样的。在这个例子中,我们仅仅覆盖原来的文件。第二个参数是一个xmlDoc结构指针,第三个参数设定为1,保证在输出上写入。 libxml(二)写属性 写属性类似于给一个新元素写文本。在这个例子中,我们将添加一个reference结点URI属性到我们的文档中。完整代码:附录F,添加属性例程代码。reference是story元素的一个子结点,所以找到并插入新元素及其属性是简单的。一旦我们在parseDoc进行了错误检查,我们将在正确的位置加放我们的新元素。但进行之前我们需要定义一个此前我们不见过的数据类型。 xmlAttrPtr ne
26、wattr; 我们也需要xmlNodePtr: xmlNodePtr newnode; 剩下的parseDoc则和前面一样,检查根结点是否为story。如果是的,那我们知道我们将在指定的位置添加我们的元素。 newnode = xmlNewTextChild (cur, NULL, reference, NULL); newattr = xmlNewProp (newnode, uri, uri); 使用xmlNewTextChild函数添国一个新结点到当前结点位置。 一旦结点被添加,文件应像前面的例子将我们添加的元素及文本内容写入磁盘。 取得属性 取得属性值类似于前面我们取得一个结点的文本内
27、容。在这个例子中,我们将取出我们在前一部分添加的URI的值。完整代码:附录G,取得属性值例程代码。 这个例子的初始步骤和前面是类似的:解析文档,查找你感兴趣的元素,然后进入一个函数完成指定的请求任务。在这个例子中,我们调用getReference。 void getReference (xmlDocPtr doc, xmlNodePtr cur) xmlChar *uri; cur = cur-xmlChildrenNode; while (cur != NULL) if (!xmlStrcmp(cur-name, (const xmlChar *)reference) uri = xmlGe
28、tProp(cur, uri); printf(uri: %sn, uri); xmlFree(uri); cur = cur-next; return; 关键函数是xmlGetProp,它返回一个包含属性值的xmlChar。在本例中,我们仅仅打印它。 注释 如果你使用DTD定义属性的固定值或缺省值,这个函数也将取得它。 编码转换 数据编码兼容问题是程序员新建普通的XML或特定XML时最常见的困难。稍后的讨论来思考设计你的应用程序将帮助你避免这个困难。实际上,libxml能以UTF-8格式保存和操纵多种数据 你的程序使用其它的数据格式,比如常见的ISO-8859-1编码,必须使用libxml函
29、数转换到UTF-8。如果你想你的程序以除UTF-8外的其它编码方式输出也必须做转换。 如果能有效地转换数据Libxml将使用转换器。无转换器时,仅仅UTF-8、UTF-16和ISO-8859-1能够被作为外部格式使用。有转换器时,它能将从其它格式与UTF-8互换的任何格式均可使用。当前转换器支持大约150种不同的编码格式之间的相互转换。实际支持的格式数量正在被实现。每一个实现在的转换器尽可能的支持每一种格式。 警告:一个常见错误是在内部数据不同的部分使用不同的编码格式。最常见的情况是一个用以ISO-8859-1作为内部数据格式,结合libxml部分使用UTF-8格式。结果是一个应用程序要面对不
30、同地内部数据格式。一部分代码执行后,它或其它部分代码将使用曲解的数据。 这个例子构造一个简单的文档,然后添加在命令行提供的内容到根元素并使用适当的编码将结果输出到标准输出设备上。在这个例子中,我们使用ISO-8859-1编码。在命令输入的内容将被从ISO-8859-1转换到UTF-8。完整代码:附件H,编码转换例程代码。 包含在例子中的转换函数使用libxml的xmlFindCharEncodingHandler函数。 xmlCharEncodingHandlerPtr handler; size = (int)strlen(in)+1; out_size = size*2-1; out = malloc(size_t)out_size); handle
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1