`
chinamming
  • 浏览: 141599 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

libxml2剖析(3):使用教程

 
阅读更多
本文整理自官方使用教程http://xmlsoft.org/tutorial/index.html

示例文档story.xml如下:

  1. <?xmlversion="1.0"?>
  2. <story>
  3. <storyinfo>
  4. <author>JohnFleck</author>
  5. <datewritten>June2,2002</datewritten>
  6. <keyword>examplekeyword</keyword>
  7. </storyinfo>
  8. <body>
  9. <headline>Thisistheheadline</headline>
  10. <para>Thisisthebodytext.</para>
  11. </body>
  12. </story>
1、解析xml文档
解析文档时只需要文档名和一个函数调用,再加上错误处理。下面代码查找keyword节点并打印节点下的文本内容,如下:
  1. #include<stdio.h>
  2. #include<string.h>
  3. #include<stdlib.h>
  4. #include<libxml/xmlmemory.h>
  5. #include<libxml/parser.h>
  6. /*解析storyinfo节点,打印keyword节点的内容*/
  7. voidparseStory(xmlDocPtrdoc,xmlNodePtrcur){
  8. xmlChar*key;
  9. cur=cur->xmlChildrenNode;
  10. while(cur!=NULL){
  11. /*找到keyword子节点*/
  12. if(!xmlStrcmp(cur->name,(constxmlChar*)"keyword")){
  13. key=xmlNodeListGetString(doc,cur->xmlChildrenNode,1);
  14. printf("keyword:%s\n",key);
  15. xmlFree(key);
  16. }
  17. cur=cur->next;/*下一个子节点*/
  18. }
  19. return;
  20. }
  21. /*解析文档*/
  22. staticvoidparseDoc(char*docname){
  23. /*定义文档和节点指针*/
  24. xmlDocPtrdoc;
  25. xmlNodePtrcur;
  26. /*进行解析,如果没成功,显示一个错误并停止*/
  27. doc=xmlParseFile(docname);
  28. if(doc==NULL){
  29. fprintf(stderr,"Documentnotparsesuccessfully.\n");
  30. return;
  31. }
  32. /*获取文档根节点,若无内容则释放文档树并返回*/
  33. cur=xmlDocGetRootElement(doc);
  34. if(cur==NULL){
  35. fprintf(stderr,"emptydocument\n");
  36. xmlFreeDoc(doc);
  37. return;
  38. }
  39. /*确定根节点名是否为story,不是则返回*/
  40. if(xmlStrcmp(cur->name,(constxmlChar*)"story")){
  41. fprintf(stderr,"documentofthewrongtype,rootnode!=story");
  42. xmlFreeDoc(doc);
  43. return;
  44. }
  45. /*遍历文档树*/
  46. cur=cur->xmlChildrenNode;
  47. while(cur!=NULL){
  48. /*找到storyinfo子节点*/
  49. if(!xmlStrcmp(cur->name,(constxmlChar*)"storyinfo")){
  50. parseStory(doc,cur);/*解析storyinfo子节点*/
  51. }
  52. cur=cur->next;/*下一个子节点*/
  53. }
  54. xmlFreeDoc(doc);/*释放文档树*/
  55. return;
  56. }
  57. intmain(intargc,char**argv){
  58. char*docname;
  59. if(argc<=1){
  60. printf("Usage:%sdocname\n",argv[0]);
  61. return0;
  62. }
  63. docname=argv[1];
  64. parseDoc(docname);
  65. return1;
  66. }
解析XML文档的基本流程如下:
(1)定义文档指针和节点指针。
(2)调用xmlParseFile()解析文档。如果不成功,注册一个错误并停止。一个常见错误是不适当的编码。XML标准文档除了用默认的UTF-8或UTF-16外,还可显式指定用其它编码保存。如果文档是这样,libxml2将自动地为你转换到UTF-8。更多关于XML编码信息包含在XML标准中。
(3)调用xmlDocGetRootElement()获取文档根节点,若无根节点则释放文档树并返回。
(4)确认文档是正确的类型,通过检查根节点名称来判断。
(5)检索节点的内容,这需要遍历文档树。对每个节点,遍历其子节点都需要一个循环。先用cur = cur->xmlChildrenNode获取第一个子节点,然后通过cur = cur->next不断向前遍历,直到cur==NULL。查找找指定节点时使用xmlStrcmp()函数,如果你指定的名称相同,就找到了你要的节点。通常把查找某个子节点的过程封装成函数。
(6)获取节点中的内容。查找到指定节点后,调用xmlNodeListGetString()获取节点下的文本。注意在XML中,包含在节点中的文本是这个节点的子节点,因此获取的是cur->xmlChildrenNode中的字符串。xmlNodeListGetString()会为返回的字符串分配内存,因此记得要用xmlFree()来释放它。
(7)调用xmlFreeDoc()释放文档树指针。
2、使用XPath查询信息
在xml文档中查询信息是一项核心工作。Libxml2支持使用XPath表达式来查找匹配的节点集。简而言之,XPath之于xml,好比SQL之于关系数据库。要在一个复杂的xml文档中查找所需的信息,XPath简直是必不可少的工具。下面代码查询所有keyword元素的内容。

  1. #include<libxml/parser.h>
  2. #include<libxml/xpath.h>
  3. /*解析文档*/
  4. xmlDocPtrgetdoc(char*docname){
  5. xmlDocPtrdoc;
  6. doc=xmlParseFile(docname);
  7. if(doc==NULL){
  8. fprintf(stderr,"Documentnotparsedsuccessfully.\n");
  9. returnNULL;
  10. }
  11. returndoc;
  12. }
  13. /*查询节点集*/
  14. xmlXPathObjectPtrgetnodeset(xmlDocPtrdoc,xmlChar*xpath){
  15. xmlXPathContextPtrcontext;
  16. xmlXPathObjectPtrresult;/*存储查询结果*/
  17. /*创建一个xpath上下文*/
  18. context=xmlXPathNewContext(doc);
  19. if(context==NULL){
  20. printf("ErrorinxmlXPathNewContext\n");
  21. returnNULL;
  22. }
  23. /*查询XPath表达式*/
  24. result=xmlXPathEvalExpression(xpath,context);
  25. xmlXPathFreeContext(context);/*释放上下文指针*/
  26. if(result==NULL){
  27. printf("ErrorinxmlXPathEvalExpression\n");
  28. returnNULL;
  29. }
  30. /*检查结果集是否为空*/
  31. if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
  32. xmlXPathFreeObject(result);/*如为这空就释放*/
  33. printf("Noresult\n");
  34. returnNULL;
  35. }
  36. returnresult;
  37. }
  38. intmain(intargc,char**argv){
  39. char*docname;
  40. xmlDocPtrdoc;
  41. /*查找所有keyword元素,而不管它们在文档中的位置*/
  42. xmlChar*xpath=(xmlChar*)"//keyword";
  43. xmlNodeSetPtrnodeset;
  44. xmlXPathObjectPtrresult;
  45. inti;
  46. xmlChar*keyword;
  47. if(argc<=1){
  48. printf("Usage:%sdocname\n",argv[0]);
  49. return(0);
  50. }
  51. docname=argv[1];
  52. doc=getdoc(docname);
  53. result=getnodeset(doc,xpath);
  54. if(result){
  55. /*得到keyword节点集*/
  56. nodeset=result->nodesetval;
  57. for(i=0;i<nodeset->nodeNr;i++){/*打印每个节点中的内容*/
  58. keyword=xmlNodeListGetString(doc,nodeset->nodeTab[i]->xmlChildrenNode,1);
  59. printf("keyword:%s\n",keyword);
  60. xmlFree(keyword);
  61. }
  62. xmlXPathFreeObject(result);/*释放结果集*/
  63. }
  64. xmlFreeDoc(doc);/*释放文档树*/
  65. xmlCleanupParser();/*清除库内存*/
  66. return(1);
  67. }
可以在story.xml中多插入几个keyword元素,然后运行一下本程序看看效果。使用XPath查询信息的基本流程如下:
(1)调用xmlXPathNewContext()给文档树创建一个上下文指针。
(2)调用xmlXPathEvalExpression(),传入XPath表达式和上下文指针,返回一个xmlXPathObjectPtr结果集指针。nodesetval对象包含keyword节点个数(nodeNr)和节点列表(nodeTab)。在使用之前要和xmlXPathNodeSetIsEmpty()检查nodesetval节点列表是否为空。
(3)遍历节点列表nodeTab,用xmlNodeListGetString()获取每个keyword节点的内容。
(4)用xmlXPathFreeObject()释放查询结果,用xmlFreeDoc()释放文档树。
更多关于Xpath的内容可以参考XPath官方规范http://www.w3.org/TR/xpath/。XPath语法的介绍,可参考w3school上的教程http://www.w3school.com.cn/xpath/index.asp,或者http://w3schools.com/xpath/default.asp。只有掌握XPath,才能掌握使用大型XML文件获取信息的方法,否则每寻找一个节点都要从根节点找起,很耗时耗力。
3、修改xml文档
这与上面的过程类似,首先遍历文档树,找到要插入(或删除)的节点处,然后插入(或删除)相关的内容。下面代码在storyinfo节点下插入一个keyword元素。
  1. #include<stdio.h>
  2. #include<string.h>
  3. #include<stdlib.h>
  4. #include<libxml/xmlmemory.h>
  5. #include<libxml/parser.h>
  6. void
  7. parseStory(xmlDocPtrdoc,xmlNodePtrcur,constxmlChar*keyword){
  8. /*在当前节点下插入一个keyword子节点*/
  9. xmlNewTextChild(cur,NULL,(constxmlChar*)"keyword",keyword);
  10. return;
  11. }
  12. xmlDocPtr
  13. parseDoc(char*docname,char*keyword){
  14. xmlDocPtrdoc;
  15. xmlNodePtrcur;
  16. doc=xmlParseFile(docname);
  17. if(doc==NULL){
  18. fprintf(stderr,"Documentnotparsedsuccessfully.\n");
  19. return(NULL);
  20. }
  21. cur=xmlDocGetRootElement(doc);
  22. if(cur==NULL){
  23. fprintf(stderr,"emptydocument\n");
  24. xmlFreeDoc(doc);
  25. return(NULL);
  26. }
  27. if(xmlStrcmp(cur->name,(constxmlChar*)"story")){
  28. fprintf(stderr,"documentofthewrongtype,rootnode!=story");
  29. xmlFreeDoc(doc);
  30. return(NULL);
  31. }
  32. cur=cur->xmlChildrenNode;
  33. while(cur!=NULL){
  34. if((!xmlStrcmp(cur->name,(constxmlChar*)"storyinfo"))){
  35. parseStory(doc,cur,(constxmlChar*)keyword);
  36. }
  37. cur=cur->next;
  38. }
  39. return(doc);
  40. }
  41. int
  42. main(intargc,char**argv){
  43. char*docname;
  44. char*keyword;
  45. xmlDocPtrdoc;
  46. if(argc<=2){
  47. printf("Usage:%sdocname,keyword\n",argv[0]);
  48. return(0);
  49. }
  50. docname=argv[1];
  51. keyword=argv[2];
  52. doc=parseDoc(docname,keyword);
  53. if(doc!=NULL){
  54. xmlSaveFormatFile(docname,doc,0);
  55. xmlFreeDoc(doc);
  56. }
  57. return(1);
  58. }
这里xmlNewTextChild函数在当前节点指针上添加一个子元素。如果希望元素有名字空间,则可以在这里加上。添加完后,就要用xmlSaveFormatFile()把修改后的文档写入到文件。我们这里使用原来doc文档指针,因此会覆盖原来的文件。第三个参数如果设置为1,则输出的文档会自动缩进。
若要删除某个节点,可以使用以下代码:
  1. if(!xmlStrcmp(cur->name,BAD_CAST"keyword")){
  2. xmlNodePtrtempNode;
  3. tempNode=cur->next;
  4. xmlUnlinkNode(cur);
  5. xmlFreeNode(cur);
  6. cur=tempNode;
  7. continue;
  8. }
注意libxml2并没有xmlDelNode或者xmlRemoveNode之类的函数。我们需要将当前节点从文档中断链(unlink),文档就不会再包含这个子节点。这样做需要使用一个临时变量来存储断链节点的后续节点,并记得要手动删除断链节点的内存。
若要给节点添加属性,可以这样:
  1. xmlDocPtr
  2. parseDoc(char*docname,char*uri){
  3. xmlDocPtrdoc;
  4. xmlNodePtrcur;
  5. xmlNodePtrnewnode;
  6. xmlAttrPtrnewattr;
  7. doc=xmlParseFile(docname);
  8. if(doc==NULL){
  9. fprintf(stderr,"Documentnotparsedsuccessfully.\n");
  10. return(NULL);
  11. }
  12. cur=xmlDocGetRootElement(doc);
  13. if(cur==NULL){
  14. fprintf(stderr,"emptydocument\n");
  15. xmlFreeDoc(doc);
  16. return(NULL);
  17. }
  18. if(xmlStrcmp(cur->name,(constxmlChar*)"story")){
  19. fprintf(stderr,"documentofthewrongtype,rootnode!=story");
  20. xmlFreeDoc(doc);
  21. return(NULL);
  22. }
  23. newnode=xmlNewTextChild(cur,NULL,"reference",NULL);
  24. newattr=xmlNewProp(newnode,"uri",uri);
  25. return(doc);
  26. }
我们用xmlAttrPtr声明一个属性指针。在找到story元素后,用xmlNewTextChild()新建一个reference子元素,用xmlNewProp()给这个子元素新建一个uri属性。文档修改完后要用xmlSaveFormatFile()写入到磁盘。
查询属性的过程类似。如下:
  1. void
  2. getReference(xmlDocPtrdoc,xmlNodePtrcur){
  3. xmlChar*uri;
  4. cur=cur->xmlChildrenNode;
  5. while(cur!=NULL){
  6. if((!xmlStrcmp(cur->name,(constxmlChar*)"reference"))){
  7. uri=xmlGetProp(cur,"uri");
  8. printf("uri:%s\n",uri);
  9. xmlFree(uri);
  10. }
  11. cur=cur->next;
  12. }
  13. return;
  14. }
关键函数为xmlGetProp(),用来获取节点中的指定属性。注意如果你使用DTD为属性声明一个固定的或默认的值,则该函数也查找这些值。
4、创建xml文档
有了上面的基础,创建一个xml文档显得非常简单,就是一个不断插入节点的过程。其流程如下:
(1)用xmlNewDoc函数创建一个文档指针doc;
(2)用xmlNewNode函数创建一个节点指针root_node;
(3)用xmlDocSetRootElement将root_node设置为doc的根结点;
(4)用xmlAddChild()给root_node添加一系列的子节点,并设置子节点的内容和属性;
(5)用xmlSaveFile将xml文档存入文件;
(6)用xmlFreeDoc函数关闭文档指针,并清除本文档中所有节点动态申请的内存。
下面代码创建一个xml文档:
  1. #include<stdio.h>
  2. #include<iostream>
  3. #include<libxml/parser.h>
  4. #include<libxml/tree.h>
  5. usingnamespacestd;
  6. intmain(intargc,char*argv[]){
  7. //定义文档和节点指针
  8. xmlDocPtrdoc=xmlNewDoc(BAD_CAST"1.0");
  9. xmlNodePtrroot_node=xmlNewNode(NULL,BAD_CAST"root");
  10. //设置根节点
  11. xmlDocSetRootElement(doc,root_node);
  12. //在根节点中直接创建节点
  13. xmlNewTextChild(root_node,NULL,BAD_CAST"newNode1",BAD_CAST"newNode1content");
  14. xmlNewTextChild(root_node,NULL,BAD_CAST"newNode2",BAD_CAST"newNode2content");
  15. xmlNewTextChild(root_node,NULL,BAD_CAST"newNode3",BAD_CAST"newNode3content");
  16. //创建一个节点,设置其内容和属性,然后加入根结点
  17. xmlNodePtrnode=xmlNewNode(NULL,BAD_CAST"node2");
  18. xmlNodePtrcontent=xmlNewText(BAD_CAST"NODECONTENT");
  19. xmlAddChild(root_node,node);
  20. xmlAddChild(node,content);
  21. xmlNewProp(node,BAD_CAST"attribute",BAD_CAST"yes");
  22. //创建一个儿子和孙子节点
  23. node=xmlNewNode(NULL,BAD_CAST"son");
  24. xmlAddChild(root_node,node);
  25. xmlNodePtrgrandson=xmlNewNode(NULL,BAD_CAST"grandson");
  26. xmlAddChild(node,grandson);
  27. xmlAddChild(grandson,xmlNewText(BAD_CAST"Thisisagrandsonnode"));
  28. //存储xml文档
  29. intnRel=xmlSaveFile("CreatedXml.xml",doc);
  30. if(nRel!=-1){
  31. cout<<"一个xml文档被创建,写入"<<nRel<<"个字节"<<endl;
  32. }
  33. //释放文档内节点动态申请的内存
  34. xmlFreeDoc(doc);
  35. return1;
  36. }
编译并运行这个程序,将创建CreatedXml.xml文档,内容如下:
  1. <root>
  2. <newNode1>newNode1content</newNode1>
  3. <newNode2>newNode2content</newNode2>
  4. <newNode3>newNode3content</newNode3>
  5. <node2attribute="yes">NODECONTENT</node2>
  6. <son>
  7. <grandson>Thisisagrandsonnode</grandson>
  8. </son>
  9. </root>
注意,有多种方式可以添加子节点。第一是用xmlNewTextChild直接添加一个文本子节点;第二是先创建新节点,然后用xmlAddChild将新节点加入上层节点。
5、编码转换
数据编码兼容性问题是很多开发人员都会遇到的一大难题,特别是在使用libxml时。libxml内部使用UTF-8格式存储和操作数据。你的应用程序数据如果使用其他格式的编码,例如ISO-8859-1编码,则在传给libxml之前必须转换成UTF-8格式。如果你的应用输出想用非UTF-8格式的编码,也需要进行转换。
Libxml2本身只支持把UTF-8, UTF-16和ISO-8859-1格式的外部数据转换成内部使用的UTF-8格式,以及处理完后输出成这些格式的数据。对其他的字符编码,需要使用libiconv(当然你也可以使用其他的国际化库,例如ICU)。当前libiconv支持150多种不同的字符编码,libiconv的实现尽量保证支持所有我们听过的编码格式。在使用libxml之前,一般是通过libiconv把数据先转换UTF-8格式。在使用libxml处理完之后,再通过libiconv把数据输出成你要的编码格式。
一个常见的错误是一份代码的不同部分的数据使用不同的编码格式。例如内部数据使用ISO-8859-1格式的应用程序,联合使用libxml,而它的内部数据格式为UTF-8。这样应用程序在运行不同的代码段时要不同地对待内部数据,这有可能导致解析数据出现错误。
例子1:使用Libxml内建的编码处理器
下面的例子创建一个简单的文档,添加从命令行得到的数据到文档根元素,并以合适的编码格式输出到stdout。对提供的数据我们使用ISO-8859-1编码,处理过程为从ISO-8859-1到UTF-8,再到ISO-8859-1。命令行上输入的字符串从ISO-8859-1格式转换成UTF-8格式,以供libxml使用,输出时又重新转换成ISO-8859-1格式。
  1. #include<string.h>
  2. #include<libxml/parser.h>
  3. /*对指定编码格式的外部数据,转换成libxml使用UTF-8格式*/
  4. unsignedchar*
  5. convert(unsignedchar*in,char*encoding){
  6. unsignedchar*out;
  7. intret,size,out_size,temp;
  8. /*定义一个编码处理器指针*/
  9. xmlCharEncodingHandlerPtrhandler;
  10. size=(int)strlen((constchar*)in)+1;/*输入数据长度*/
  11. out_size=size*2-1;/*输出数据长度*/
  12. out=(unsignedchar*)malloc((size_t)out_size);/*存放输出数据*/
  13. if(out){
  14. /*查找内建的编码处理器*/
  15. handler=xmlFindCharEncodingHandler(encoding);
  16. if(!handler){
  17. free(out);
  18. out=NULL;
  19. }
  20. }
  21. if(out){
  22. temp=size-1;
  23. /*对输入数据进行编码转换*/
  24. ret=handler->input(out,&out_size,in,&temp);
  25. if(ret||temp-size+1){/*转换不成功*/
  26. if(ret){/*转换失败*/
  27. printf("conversionwasn'tsuccessful.\n");
  28. }else{/*只转换了一部分数据*/
  29. printf("conversionwasn'tsuccessful.converted:%ioctets.\n",temp);
  30. }
  31. free(out);
  32. out=NULL;
  33. }else{/*转换成功*/
  34. out=(unsignedchar*)realloc(out,out_size+1);
  35. out[out_size]=0;/*输出的末尾加上null终止符*/
  36. }
  37. }else{
  38. printf("nomem\n");
  39. }
  40. return(out);
  41. }
  42. int
  43. main(intargc,char**argv){
  44. unsignedchar*content,*out;
  45. xmlDocPtrdoc;
  46. xmlNodePtrrootnode;
  47. char*encoding="ISO-8859-1";
  48. if(argc<=1){
  49. printf("Usage:%scontent\n",argv[0]);
  50. return(0);
  51. }
  52. content=(unsignedchar*)argv[1];
  53. /*转换成libxml2使用的UTF-8格式*/
  54. out=convert(content,encoding);
  55. doc=xmlNewDoc(BAD_CAST"1.0");
  56. rootnode=xmlNewDocNode(doc,NULL,(constxmlChar*)"root",out);
  57. xmlDocSetRootElement(doc,rootnode);
  58. /*以ISO-8859-1格式输出文档内容*/
  59. xmlSaveFormatFileEnc("-",doc,encoding,1);
  60. return(1);
  61. }
编译运行这个程序,假设在命令行上提供的数据"zhou"是ISO-8859-1格式(我的系统中不是),则输出文档为:
  1. <?xmlversion="1.0"encoding="ISO-8859-1"?>
  2. <root>zhou</root>
编码转换的基本流程如下:
(1)用xmlCharEncodingHandlerPtr定义一个编码处理器指针,用xmlFindCharEncodingHandler()查找libxml2中指定的编码处理器。libxml2内建只支持把UTF-8, UTF-16和ISO-8859-1格式的外部数据转换成内部使用的UTF-8格式。如果要转换其他格式的数据(如中文编码),则要使用独立的libiconv库给libxml2注册新编码处理器。
(2)调用编码处理器的input()函数,把外部数据转换成libxml2使用的格式。
(3)进行xml处理,处理完若要保存成非UTF-8格式的文档,使用xmlSaveFormatFileEnc()函数。若保存的编码格式libxml2不支持,则只能用libiconv把保存的文档转换成需要的编码格式。
例子2:通过iconv库给Libxml注册新的编码处理器
下面例子先编写GBK的编码处理器gbk_input()和gbk_output(),前者是GBK到UTF-8输入处理,后者是UTF-8到GBK输出处理,这两个处理器都要用到iconv转换函数。然后调用xmlNewCharEncodingHandler()注册输入输出处理器。对输入输出数据的编码转换由convertToUTF8From()和utf8ConvertTo()来完成,它们都是调用xmlFindCharEncodingHandler()查找已注册的处理器,然后在处理器上调用input()或output()对数据进行编码转换。
  1. #include<string.h>
  2. #include<iconv.h>
  3. #include<libxml/encoding.h>
  4. #include<libxml/xmlwriter.h>
  5. #include<libxml/xmlreader.h>
  6. /*输入编码处理器:GBK到UTF-8*/
  7. intgbk_input(unsignedchar*out,int*outlen,
  8. constunsignedchar*in,int*inlen){
  9. char*outbuf=(char*)out;
  10. char*inbuf=(char*)in;
  11. iconv_ticonv_from;/*gbk到utf-8的转换描述符*/
  12. size_tlen1,len2,rslt;
  13. /*注意一般不直接从int*到size_t*的转换
  14. 这在32位平台下是正常的,但到了64平台下size_t为64位,
  15. 那(size_t*)inlen将是一个未知的数据
  16. */
  17. len1=*inlen;
  18. len2=*outlen;
  19. /*分配一个从GBK到UTF-8的转换描述符*/
  20. iconv_from=iconv_open("utf-8","gbk");
  21. /*根据转换描述符,对数据进行编码转换*/
  22. rslt=iconv(iconv_from,&inbuf,&len1,&outbuf,&len2);
  23. if(rslt<0){
  24. returnrslt;
  25. }
  26. iconv_close(iconv_from);/*释放描述符*/
  27. *outlen=((unsignedchar*)outbuf-out);
  28. *inlen=((unsignedchar*)inbuf-in);
  29. return*outlen;
  30. }
  31. /*输出编码处理器:UTF-8到GBK*/
  32. intgbk_output(unsignedchar*out,int*outlen,
  33. constunsignedchar*in,int*inlen){
  34. char*outbuf=(char*)out;
  35. char*inbuf=(char*)in;
  36. iconv_ticonv_to;/*utf-8到gbk的转换描述符*/
  37. size_tlen1,len2,rslt;
  38. /*注意一般不直接从int*到size_t*的转换
  39. 这在32位平台下是正常的,但到了64平台下size_t为64位,
  40. 那(size_t*)inlen将是一个未知的数据
  41. */
  42. len1=*inlen;
  43. len2=*outlen;
  44. /*分配一个从UTF-8到GBK的转换描述符*/
  45. iconv_to=iconv_open("gbk","utf-8");
  46. /*根据转换描述符,对数据进行编码转换*/
  47. rslt=iconv(iconv_to,&inbuf,&len1,&outbuf,&len2);
  48. if(rslt<0){
  49. returnrslt;
  50. }
  51. iconv_close(iconv_to);/*释放描述符*/
  52. *outlen=((unsignedchar*)outbuf-out);
  53. *inlen=((unsignedchar*)inbuf-in);
  54. return*outlen;
  55. }
  56. /**
  57. *convertToUTF8From:
  58. *把encoding编码的输入数据in转换成utf-8格式返回
  59. *出错则返回NULL
  60. */
  61. xmlChar*convertToUTF8From(constchar*in,constchar*encoding){
  62. xmlChar*out;
  63. intret;
  64. intsize;
  65. intout_size;
  66. inttemp;
  67. xmlCharEncodingHandlerPtrhandler;
  68. if(in==0)
  69. return0;
  70. /*查找内建的编码处理器*/
  71. handler=xmlFindCharEncodingHandler(encoding);
  72. if(!handler){
  73. printf("convertToUTF8From:noencodinghandlerfoundfor'%s'\n",
  74. encoding?encoding:"");
  75. return0;
  76. }
  77. size=(int)strlen(in)+1;/*输入数据长度*/
  78. out_size=size*2-1;/*输出数据长度*/
  79. /*存放输出数据*/
  80. out=(unsignedchar*)xmlMalloc((size_t)out_size);
  81. memset(out,0,out_size);
  82. if(out!=NULL){
  83. temp=size-1;
  84. /*对输入数据进行编码转换,成功后返回0*/
  85. ret=handler->input(out,&out_size,(constxmlChar*)in,&temp);
  86. if(ret||temp-size+1){/*转换不成功*/
  87. if(ret){/*转换失败*/
  88. printf("convertToUTF8From:conversionwasn'tsuccessful.\n");
  89. }else{/*只转换了一部分数据*/
  90. printf("convertToUTF8From:conversionwasn'tsuccessful.converted:%ioctets.\n",temp);
  91. }
  92. xmlFree(out);/*释放输出缓冲区*/
  93. out=0;
  94. }else{/*转换成功,在输出末尾加上null终止符*/
  95. out=(unsignedchar*)xmlRealloc(out,out_size+1);
  96. out[out_size]=0;
  97. }
  98. }else{
  99. printf("convertToUTF8From:nomem\n");
  100. }
  101. returnout;
  102. }
  103. /**
  104. *utf8ConvertTo:
  105. *把utf-8的数据转换成encoding编码返回
  106. *出错则返回NULL
  107. */
  108. char*utf8ConvertTo(xmlChar*in,constchar*encoding){
  109. char*out;
  110. intret;
  111. intsize;
  112. intout_size;
  113. inttemp;
  114. xmlCharEncodingHandlerPtrhandler;
  115. if(in==0)
  116. return0;
  117. handler=xmlFindCharEncodingHandler(encoding);
  118. if(!handler){
  119. printf("utf8ConvertTo:noencodinghandlerfoundfor'%s'\n",
  120. encoding?encoding:"");
  121. return0;
  122. }
  123. size=(int)strlen((char*)in)+1;/*输入数据长度*/
  124. out_size=size*2-1;/*输出数据长度*/
  125. out=(char*)malloc((size_t)out_size);/*存放输出数据*/
  126. memset(out,0,out_size);
  127. if(out!=NULL){
  128. temp=size-1;
  129. /*对输入数据进行编码转换,成功后返回0*/
  130. ret=handler->output((xmlChar*)out,&out_size,(constxmlChar*)in,&temp);
  131. if(ret||temp-size+1){
  132. if(ret){
  133. printf("utf8ConvertTo:conversionwasn'tsuccessful.\n");
  134. }else{
  135. printf("utf8ConvertTo:conversionwasn'tsuccessful.converted:%ioctets.\n",temp);
  136. }
  137. free(out);
  138. out=0;
  139. }else{
  140. out=(char*)realloc(out,out_size+1);
  141. out[out_size]=0;/*末尾加上null终止符*/
  142. }
  143. }else{
  144. printf("utf8ConvertTo:nomem\n");
  145. }
  146. returnout;
  147. }
  148. intmain(intargc,char**argv){
  149. constchar*content;
  150. xmlChar*out;
  151. xmlDocPtrdoc;
  152. xmlNodePtrrootnode;
  153. if(argc<=1){
  154. printf("Usage:%scontent\n",argv[0]);
  155. return(0);
  156. }
  157. content=(constchar*)argv[1];
  158. /*添加gbk编码支持*/
  159. xmlNewCharEncodingHandler("gbk",gbk_input,gbk_output);
  160. /*添加gb2312编码支持:仍然可以使用GBK的输入输出处理器*/
  161. xmlNewCharEncodingHandler("gb2312",gbk_input,gbk_output);
  162. /*输入的GBK数据转换成libxml2使用的UTF-8格式*/
  163. out=convertToUTF8From(content,"gbk");
  164. /*创建xml文档*/
  165. doc=xmlNewDoc(BAD_CAST"1.0");
  166. rootnode=xmlNewDocNode(doc,NULL,(constxmlChar*)"root",out);
  167. xmlDocSetRootElement(doc,rootnode);
  168. /*以gb2312格式保存文档内容:"-"表示输出到终端*/
  169. xmlSaveFormatFileEnc("-",doc,"gb2312",1);
  170. xmlCleanupCharEncodingHandlers();/*释放编码处理器资源*/
  171. return(1);
  172. }
这个例子在32位与64位Linux平台下测试通过。iconv库是Linux默认自带的组件,因此在Linux中使用libxml非常方便。我们先建立utf-8编码与gbk编码的转换接口,并将接口插入到libxml2库中,这样xml库就支持对gb2312和gbk编码的支持了。当然,这个转换不会自动完成,我们需要使用从libxml库中查找特定编码的接口,libxml支持一些基本的编码接口,如ISO-8859-1,UTF-16等编码,但不支持gbk,所以在上述代码中,我们定义了gbk_input,与gbk_output两个接口,这两个接口的原型声明是libxml库的标准声明,即xmlCharEncodingInputFunc和xmlCharEncodingOutputFunc。在使用完libxml库之后,我们需要释放libxml库的转换资源。
例子3:直接使用iconv库进行转换
下面例子直接使用iconv函数对输入输出进行编码转换,而不是通过注册编码处理器的方式。
  1. #include<stdio.h>
  2. #include<string.h>
  3. #include<iconv.h>
  4. #include<libxml/parser.h>
  5. #include<libxml/tree.h>
  6. /*代码转换:从一种编码转为另一种编码*/
  7. intencoding_convert(constchar*from_charset,constchar*to_charset,
  8. char*inbuf,intinlen,
  9. char*outbuf,intoutlen){
  10. iconv_tcd;
  11. size_tlen1,len2,rslt;
  12. /*注意一般不直接从int*到size_t*的转换
  13. 这在32位平台下是正常的,但到了64平台下size_t为64位,
  14. 那(size_t*)inlen将是一个未知的数据
  15. */
  16. len1=inlen;
  17. len2=outlen;
  18. /*分配一个转换描述符*/
  19. cd=iconv_open(to_charset,from_charset);
  20. if(cd==0)
  21. return-1;
  22. memset(outbuf,0,len2);
  23. /*执行编码转换*/
  24. rslt=iconv(cd,&inbuf,&len1,&outbuf,&len2);
  25. if(rslt==-1)
  26. return-1;
  27. iconv_close(cd);/*释放描述符*/
  28. return0;
  29. }
  30. /*GB2312转换为UTF-8
  31. *成功则返回一个动态分配的char*变量,需要在使用完毕后手动free,失败返回NULL
  32. */
  33. char*gb2312_utf8(char*inbuf){
  34. intnOutLen=2*strlen(inbuf)-1;
  35. char*szOut=(char*)xmlMalloc(nOutLen);
  36. if(-1==encoding_convert("gb2312","uft-8",inbuf,strlen(inbuf),szOut,nOutLen)){
  37. xmlFree(szOut);
  38. szOut=NULL;
  39. }
  40. returnszOut;
  41. }
  42. /*UTF-8转换为GB2312
  43. *成功则返回一个动态分配的char*变量,需要在使用完毕后手动free,失败返回NULL
  44. */
  45. char*utf8_gb2312(char*inbuf){
  46. intnOutLen=2*strlen(inbuf)-1;
  47. char*szOut=(char*)xmlMalloc(nOutLen);
  48. if(-1==encoding_convert("utf-8","gb2312",inbuf,strlen(inbuf),szOut,nOutLen)){
  49. xmlFree(szOut);
  50. szOut=NULL;
  51. }
  52. returnszOut;
  53. }
  54. intmain(intargc,char**argv){
  55. /*定义文档节点和指针*/
  56. xmlDocPtrdoc=xmlNewDoc(BAD_CAST"1.0");
  57. xmlNodePtrroot_node=xmlNewNode(NULL,BAD_CAST"root");
  58. /*设置根节点*/
  59. xmlDocSetRootElement(doc,root_node);
  60. /*一个中文字符串转换为UTF-8字符串,然后写入*/
  61. char*szOut=gb2312_utf8("节点1的内容");
  62. /*在根节点中直接创建节点*/
  63. xmlNewTextChild(root_node,NULL,BAD_CAST"newNode1",BAD_CAST"newNode1content");
  64. xmlNewTextChild(root_node,NULL,BAD_CAST"newNode2",BAD_CAST"newNode2content");
  65. xmlNewTextChild(root_node,NULL,BAD_CAST"newNode3",BAD_CAST"newNode3content");
  66. xmlNewChild(root_node,NULL,BAD_CAST"node1",BAD_CASTszOut);
  67. xmlFree(szOut);
  68. /*创建一个节点,设置其内容和属性,然后加入根结点*/
  69. xmlNodePtrnode=xmlNewNode(NULL,BAD_CAST"node2");
  70. xmlNodePtrcontent=xmlNewText(BAD_CAST"NODECONTENT");
  71. xmlAddChild(root_node,node);
  72. xmlAddChild(node,content);
  73. szOut=gb2312_utf8("属性值");
  74. xmlNewProp(node,BAD_CAST"attribute",BAD_CASTszOut);
  75. xmlFree(szOut);
  76. /*创建一个中文节点*/
  77. szOut=gb2312_utf8("中文节点");
  78. xmlNewChild(root_node,NULL,BAD_CASTszOut,BAD_CAST"contentofchinesenode");
  79. xmlFree(szOut);
  80. /*存储xml文档*/
  81. intnRel=xmlSaveFormatFileEnc("CreatedXml_cn.xml",doc,"GB2312",1);
  82. if(nRel!=-1){
  83. printf("一个xml文档被创建,写入%d个字节",nRel);
  84. }
  85. xmlFreeDoc(doc);
  86. return1;
  87. }
这个例子中,当把中文数据写入到XML节点时,使用gb2312_utf8()直接转换成UTF-8格式,这种直接通过iconv转换的方式更高效。编译并运行程序,输出文档如下:
  1. <?xmlversion="1.0"encoding="GB2312"?>
  2. <root>
  3. <newNode1>newNode1content</newNode1>
  4. <newNode2>newNode2content</newNode2>
  5. <newNode3>newNode3content</newNode3>
  6. <node1>节点1的内容</node1>
  7. <node2attribute="属性值">NODECONTENT</node2>
  8. <中文节点>contentofchinesenode</中文节点>
  9. </root>
6、一个真实的例子
内容整理自http://xmlsoft.org/example.html
下面是一个真实的例子。应用程序数据的内容不使用DOM树,而是使用内部数据结构来保存。这是一个基于XML存储结构的数据库,它保存了与Gnome相关的任务。如下:
  1. <?xmlversion="1.0"?>
  2. <gjob:Helpingxmlns:gjob="http://www.gnome.org/some-location">
  3. <gjob:Jobs>
  4. <gjob:Job>
  5. <gjob:ProjectID="3"/>
  6. <gjob:Application>GBackup</gjob:Application>
  7. <gjob:Category>Development</gjob:Category>
  8. <gjob:Update>
  9. <gjob:Status>Open</gjob:Status>
  10. <gjob:Modified>Mon,07Jun199920:27:45-0400METDST</gjob:Modified>
  11. <gjob:Salary>USD0.00</gjob:Salary>
  12. </gjob:Update>
  13. <gjob:Developers>
  14. <gjob:Developer>
  15. </gjob:Developer>
  16. </gjob:Developers>
  17. <gjob:Contact>
  18. <gjob:Person>NathanClemons</gjob:Person>
  19. <gjob:Email>nathan@windsofstorm.net</gjob:Email>
  20. <gjob:Company>
  21. </gjob:Company>
  22. <gjob:Organisation>
  23. </gjob:Organisation>
  24. <gjob:Webpage>
  25. </gjob:Webpage>
  26. <gjob:Snailmail>
  27. </gjob:Snailmail>
  28. <gjob:Phone>
  29. </gjob:Phone>
  30. </gjob:Contact>
  31. <gjob:Requirements>
  32. Theprogramshouldbereleasedasfreesoftware,undertheGPL.
  33. </gjob:Requirements>
  34. <gjob:Skills>
  35. </gjob:Skills>
  36. <gjob:Details>
  37. AGNOMEbasedsystemthatwillallowasuperusertoconfigure
  38. compressedanduncompressedfilesand/orfilesystemstobebacked
  39. upwithasupportedmediainthesystem.Thisshouldbeableto
  40. performviafindcommandsgeneratingalistoffilesthatarepassed
  41. totar,dd,cpio,cp,gzip,etc.,tobedirectedtothetapemachine
  42. orviaoperationsperformedonthefilesystemitself.Email
  43. notificationandGUIstatusdisplayveryimportant.
  44. </gjob:Details>
  45. </gjob:Job>
  46. </gjob:Jobs>
  47. </gjob:Helping>
把XML文件加载到一个内部DOM树中只是调用几个函数的问题,而遍历整个树来收集数据,并生成内部结构则更困难,也更容易出错。
对输入结构的定义法则是非常宽松的。属性的顺序无关紧要(XML规范清楚地说明了这一点),不要依赖于一个节点的子节点顺序通常是一个好的主意,除非这样做真的使事情变得更困难了。下面是解析person信息的一段代码:
  1. /*
  2. *一个person记录
  3. */
  4. typedefstructperson{
  5. char*name;
  6. char*email;
  7. char*company;
  8. char*organisation;
  9. char*smail;
  10. char*webPage;
  11. char*phone;
  12. }person,*personPtr;
  13. /*
  14. *解析person的代码
  15. */
  16. personPtrparsePerson(xmlDocPtrdoc,xmlNsPtrns,xmlNodePtrcur){
  17. personPtrret=NULL;
  18. DEBUG("parsePerson\n");
  19. /*
  20. *为结构分配内存
  21. */
  22. ret=(personPtr)malloc(sizeof(person));
  23. if(ret==NULL){
  24. fprintf(stderr,"outofmemory\n");
  25. return(NULL);
  26. }
  27. memset(ret,0,sizeof(person));
  28. /*我们不关心顶层的元素名是什么*/
  29. cur=cur->xmlChildrenNode;
  30. while(cur!=NULL){
  31. if((!strcmp(cur->name,"Person"))&&(cur->ns==ns))
  32. ret->name=xmlNodeListGetString(doc,cur->xmlChildrenNode,1);
  33. if((!strcmp(cur->name,"Email"))&&(cur->ns==ns))
  34. ret->email=xmlNodeListGetString(doc,cur->xmlChildrenNode,1);
  35. cur=cur->next;
  36. }
  37. return(ret);
  38. }
下面是要注意的一些事项:
(1)通常一个递归的解析风格是更方便的:XML数据天然地遵循重复式地构造,并且是高度结构化的。
(2)两个参数是xmlDocPtr和xmlNsPtr类型,即指向XML文档和应用程序保留的命名空间的指针。文档信息非常广泛,为你的应用程序数据集定义一个命名空间并测试元素和属性是否属性这个空间是一个好的编程实践。这只需一个简单的相等测试(cur->ns == ns)。
(3)为了查询文本和属性值,你可以使用函数xmlNodeListGetString()来获取所有文本,和由DOM输出生成的引用节点,并生成一个单一的文本字符串。
下面是解析另外一个结构的代码片段:
  1. #include<libxml/tree.h>
  2. /*
  3. *一个Job的描述
  4. */
  5. typedefstructjob{
  6. char*projectID;
  7. char*application;
  8. char*category;
  9. personPtrcontact;
  10. intnbDevelopers;
  11. personPtrdevelopers[100];/*usingdynamicallocisleftasanexercise*/
  12. }job,*jobPtr;
  13. /*
  14. *解析Job的代码
  15. */
  16. jobPtrparseJob(xmlDocPtrdoc,xmlNsPtrns,xmlNodePtrcur){
  17. jobPtrret=NULL;
  18. DEBUG("parseJob\n");
  19. /*
  20. *为结构分配内存
  21. */
  22. ret=(jobPtr)malloc(sizeof(job));
  23. if(ret==NULL){
  24. fprintf(stderr,"outofmemory\n");
  25. return(NULL);
  26. }
  27. memset(ret,0,sizeof(job));
  28. /*我们不关心顶层元素名是什么*/
  29. cur=cur->xmlChildrenNode;
  30. while(cur!=NULL){
  31. if((!strcmp(cur->name,"Project"))&&(cur->ns==ns)){
  32. ret->projectID=xmlGetProp(cur,"ID");
  33. if(ret->projectID==NULL){
  34. fprintf(stderr,"ProjecthasnoID\n");
  35. }
  36. }
  37. if((!strcmp(cur->name,"Application"))&&(cur->ns==ns))
  38. ret->application=xmlNodeListGetString(doc,cur->xmlChildrenNode,1);
  39. if((!strcmp(cur->name,"Category"))&&(cur->ns==ns))
  40. ret->category=xmlNodeListGetString(doc,cur->xmlChildrenNode,1);
  41. if((!strcmp(cur->name,"Contact"))&&(cur->ns==ns))
  42. ret->contact=parsePerson(doc,ns,cur);
  43. cur=cur->next;
  44. }
  45. return(ret);
  46. }

一旦你会使用libxml2,编写这种类型的代码是非常简单的,也很无趣。最终,你可以写一个拥有C数据结构和一组XML文档例子或一个XML DTD的桩模块,并生成在C数据和XML存储之间导入和导出数据的代码。

7、详细代码示例
对Libxml2更详细的使用介绍,可参考官方的详细代码示例http://xmlsoft.org/examples/index.html。上面提供了Libxml2各个组件怎么使用的详细代码示例,包括以下部分:
xmlWriter: 测试xmlWriter的各个API,包括写入到文件、写入到内存缓冲区、写入到新的文档或子树、字符串编码转换、对输出文档进行序列化。
InputOutput: 演示使用xmlRegisterInputCallbacks来建立一个客户I/O层,这被用在XInclude方法上下文中,以显示怎样构建动态文档。还演示使用xmlDocDumpMemory来输出文档到字符缓冲区中。
Parsing: 演示使用xmlReadMemory()读取XML文档,xmlFreeDoc()释放文档树;使用xmlCreatePushParserCtxt()和xmlParseChunk()一块一块地读取XML文档到文档树中。演示为XML文档创建一个解析上下文,然后解析并验证这个文档;创建一个文档树,检查并验证结果,最后用xmlFreeDoc()释放文档树。演示使用xmlReadFile()读取XML文档并用xmlFreeDoc()释放它。
Tree: 演示怎样创建文档和节点,并把数据dump到标准输出或文件中。演示使用xmlDocGetRootElement()获取根元素,然后遍历文档并打印各个元素名。
XPath: 演示怎样计算XPath表达式,并在XPath上下文注册名称空间,打印结果节点集。演示怎么加载一个文档、用XPath定位到某个子元素、修改这个元素并保存结果。这包含了加载/编辑/保存的一个完整来回。
xmlReader: 演示使用xmlReaderForFile()解析XML文档,并dump出节点的信息。演示在用xmlReaderForFile()解析时验证文档的内容,激活各种选项,诸如实体替换、DTD属性不一致等。演示使用xmlTextReaderPreservePattern()提取XML文档中某一部分的子文档。演示重用xmlReader对象来解析多个XML文档。

更多 0
分享到:
评论

相关推荐

    Ubuntu11.10 libxml2-dev

    Ubuntu11.10 libxml2-dev 安装包 libxml2_2.7.8.dfsg-4_i386.deb libxml2-dev_2.7.8.dfsg-4_i386.deb python-libxml2_2.7.8.dfsg-4_i386.deb zlib1g_1.2.3.4.dfsg-3ubuntu3_i386.deb zlib1g-dev_1.2.3.4.dfsg-3...

    libxml2接口API教程

    目录 介绍 数据类型 解析文件 获取元素内容 用XPath获取元素内容 写入元素内容 写入属性 获取属性 编码转换 A. 编译 B. 例子文档 C. Keyword例子代码 D. Xpath例子代码 E. 添加Keyword的例子代码 F....G....H....I....

    libxml2.so.2

    libxml2.so.2

    libxml2 使用示例

    libxml2 使用示例, readNode countNode insertNode updateNode deleteNode

    libxml2 安装Php5时会提示缺少libxml2

    安装Php5时会提示缺少libxml2 ...Perl中也可以使用XML::LibXML模块。它最初是为GNOME开发的项目,但现在可以用在各种各样的方面。libXML 代码可移植性非常好,因为它基于标准的ANSI C库, 并采用MIT许可证。

    libxml2开源库 libxml2开源库 libxml2开源库

    开源库libxml2库,

    libxml2.dll

    libxml2

    LIBXML2库windows使用指南

    LIBXML2库windows使用指南

    libxml2-2.7.4库(附编译安装使用教程).zip

    网上搜到了一些教程,但是因为公司服务器没有权限,所以不能把库放到“/usr/lib”下,所以搜了好久“libxml2 指定其他路径 编译”,最后才发现,使用“./configure --prefix 路径”就可以了,哎! 好吧,希望大家...

    libxml2-2.9.2_compile_20170118_1636.tar.gz

    我使用的是最新版本libxml2-2.9.2.tar.gz 二、安装 我在在官网提供的网址https://git.gnome.org/browse/libxml2/上下载了几个版本的.tar.gz在Ubuntu下解压以后都没有看到configure文件,然后在ftp://xmlsoft.org/...

    libxml2教程

    libxml2教程,给大家分享一下,此文档并非本人整理,如有侵犯他们权益请通知本人

    libxml很好的学习教程

    libxml教程libxml教程libxml教程libxml教程libxml教程libxml教程libxml教程libxml教程libxml教程libxml教程

    linux libxml2-2.6.26

    the proper functionning of libxml2 main APIs while testapi does a full coverage check. Report failures to the list. To report bugs, follow the instructions at: http://xmlsoft.org/bugs.html A ...

    基于android,移植libxml2和libiconv

    最近一段时间,在android上想用c++处理xml数据,在网上找了半天资料,大多数教程都是在编译android系统的时候顺便把libxml2和libiconv源码放到系统指定路径下编译出来。可是没干过这样的事,也不想这么做。最后还是...

    libxml2-2.9.1-6.el7.5.x86_64.rpm

    yum install 提示ImportError: libxml2.so.2: cannot open shared object file: No such file or directory,需要安装libxml2.so.2

    C语言库libxml2实例详解

    很详细的libxml实例详解参考,c语言解析xml文件不错的学习文档

    c++中使用libxml2读取xml文件

    c++中使用libxml2读取xml文件,使用MarkUp库读取,简单方便

    libxml2-2.9.1.tar.gz

    libxml2是一个用来解析XML文档的函数库。它用 C 语言写成,并且能被多种语言所调用,如 C、C++、XSH、C#、Python、Kylix、Delphi、Ruby、PHP 等。 注:先解压得到libxml2-2.9.1.tar.gz文件,然后再拷贝到linux系统...

    libxml2-2.9.8源码及编译方法

    libxml2-2.9.8源码及编译方法,libxml2-2.9.8源码及编译方法

    使用mingw编译后的libxml2-2.7.7安装包

    使用mingw4.9.2编译后的libxml2-2.7.7安装包,包括lib、bin、include等

Global site tag (gtag.js) - Google Analytics