在创建doc前调用xmlKeepBlanksDefault(0);,忽略空白字符,忽略了text节点,这样解析就不存在问题,需要注意每次doc前都需要设置。
可参考资料:http://www.360doc.com/content/12/0323/14/9262861_196955765.shtml
<------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------>
参考
- http://xmlsoft.org/
- http://www.miidoo.cn/info_detail-204.html
- http://www.blogjava.net/wxb_nudt/archive/2007/11/28/161340.html
- http://www.ibm.com/developerworks/cn/aix/library/au-libxml2.html
- http://www.cppblog.com/lymons/archive/2009/03/30/37553.html
- XPath 教程
XML
介绍:XML 和 DOM
XML是eXtensible Markup Language的缩写,它是一种可扩展性标识语言,能够让你自己创造标识,标识你所表示的内容。DOM全称是Document Object Model(文档对象模型),定义了一组与平台和语言无关的接口,以便程序和脚本能够动态访问和修改XML文档内容、结构及样式。XML创建了标识,而 DOM的作用就是告诉程序如何操作和显示这些标识。
XML将数据组织成为一棵树,DOM通过解析XML文档,为XML文档在逻辑上建立一个树模型,树的节点是一个个的对象。这样通过操作这棵树和这些对象就可以完成对XML文档的操作,为处理文档的所有方面提供了一个完美的概念性框架。
XML 中共有12种节点类型,其中最常见的节点类型有5种:
- 元素
- 元素是 XML 的基本组成单元。,描述XML的基本信息。
- 属性
- 属性节点包含关于元素节点的信息,通常包含在元素里面,描述元素的属性。
- 文本
- 包含许多文本信息或者只是空白。
- 文档
- 文档节点是整个文档中所有其它节点的父节点。
- 注释
- 注释是对相关的信息进行描述、注释。
libxml
介绍
本文所介绍的 libxml 是针对 C 语言的一套 API 接口。其他如 ruby,python 亦有对应的基于 libxml 开发的绑定库接口。
数据类型 — xmlChar
在 libXml 中用 xmlChar 替代 char,XML 使用 UTF-8 编码的一字节字符串。如果你的数据使用其它编码,它必须被转换到 UTF-8 才能使用libxml的函数。
如同标准 C 中的 char 类型一样, xmlChar 也有动态内存分配、字符串操作等相关函数。例如 xmlMalloc 是动态分配内存的函数; xmlFree 是配套的释放内存函数; xmlStrcmp 是字符串比较函数等等。基本上 xmlChar 字符串相关函数都在xmlstring.h 中定义;而动态内存分配函数在 xmlmemory.h 中定义。另外要注意,因为总是要在 xmlChar* 和 char* 之间进行类型转换,所以定义了一个宏 BAD_CAST ,其定义如下: xmlstring.h
#define BAD_CAST (xmlChar *)
原则上来说, unsigned char 和 char 之间进行强制类型转换是没有问题的。
数据结构
- xmlDoc
- 代表DOM结构中的文档类型。包含由解析文档建立的树结构, xmlDocPtr 是指向这个结构的指针。
- xmlNode
- 代表DOM结构中的除文档类型类型外的其它节点类型。包含单一结点的结构, xmlNodePtr 是指向这个结构的指针,它被用于遍历文档树。节点应该是xml中最重要的元素了, xmlNode 代表了xml文档中的一个节点,实现为一个 struct ,内容很丰富: tree.h
1: typedef struct _xmlNode xmlNode; 2: typedef xmlNode *xmlNodePtr; 3: struct _xmlNode { 4: void *_private;/* application data */ 5: xmlElementType type; /* type number,must be second ! */ 6: const xmlChar *name; /* the name of the node,or the entity */ 7: struct _xmlNode *children; /* parent->childs link */ 8: struct _xmlNode *last; /* last child link */ 9: struct _xmlNode *parent;/* child->parent link */ 10: struct _xmlNode *next; /* next sibling link */ 11: struct _xmlNode *prev; /* prevIoUs sibling link */ 12: struct _xmlDoc *doc;/* the containing document */ 13: /* End of common part */ 14: xmlNs *ns; /* pointer to the associated namespace */ 15: xmlChar *content; /* the content */ 16: struct _xmlAttr *properties;/* properties list */ 17: xmlNs *nsDef; /* namespace definitions on this node */ 18: void *psvi;/* for type/PSVI informations */ 19: unsigned short line; /* line number */ 20: unsigned short extra; /* extra data for XPath/XSLT */ 21: }; 22:
可以看到,节点之间是以链表和树两种方式同时组织起来的,next和prev指针可以组成链表,而parent和children可以组织为树。所有节点都是文档 xmlDoc 节点的直接或间接子节点。同时还有以下重要元素:
xml 文档的操作其根本原理就是在节点之间移动、查询节点的各项信息,并进行增加、删除、修改的操作。 xmlDocSetRootElement 函数可以将一个节点设置为某个文档的根节点,这是将文档与节点连接起来的重要手段,当有了根结点以后,所有子节点就可以依次连接上根节点,从而组织成为一个 xml 树。
创建 XML 文档
创建一个 XML 文档流程如下:
- 用 xmlNewDoc 函数创建一个文档指针 doc;
- 用 xmlNewNode 函数创建一个节点指针 root_node;
- 用 xmlDocSetRootElement 将 root_node 设置为 doc 的根节点;
- 给 root_node 添加一系列子节点,并设置字节点的内容和属性;
- 用 xmlSaveFile 保存 xml 到文件;
- 用 xmlFreeDoc 函数关闭文档指针,清除内存。
示例
下面用一个例子说明一些函数的使用,和创建一个 XML 文档的大致步骤:
1: #include <stdio.h> 2: #include <stdlib.h> 3: #include <libxml/parser.h> 4: #include <libxml/tree.h> 5: 6: int main (int argc, char **argv) 7: { 8: xmlDocPtr pdoc = NULL; 9: xmlNodePtr proot_node = NULL,pnode = NULL,pnode1 = NULL; 10: 11: // 创建一个新文档并设置 root 节点 12: // 一个 XML 文件只有一个 root 节点 13: pdoc = xmlNewDoc (BAD_CAST "1.0"); 14: proot_node = xmlNewNode (NULL, BAD_CAST "根节点"); 15: xmlNewProp (proot_node, BAD_CAST "版本", BAD_CAST "1.0"); 16: xmlDocSetRootElement (pdoc, proot_node); 17: 18: pnode = xmlNewNode (NULL, BAD_CAST "子节点1"); 19: // 创建上面 pnode 的子节点 20: xmlNewChild (pnode, NULL, BAD_CAST "子子节点1", BAD_CAST "信息"); 21: // 添加子节点到 root 节点 22: xmlAddChild (proot_node, pnode); 23: 24: pnode1 = xmlNewNode (NULL, BAD_CAST "子子节点1"); 25: xmlAddChild (pnode, pnode1); 26: xmlAddChild (pnode1,xmlNewText (BAD_CAST "这是更低的节点,子子子节点1")); 27: 28: // 还可以这样直接创建一个子节点到 root 节点上 29: xmlNewTextChild (proot_node, BAD_CAST "子节点2", BAD_CAST "子节点2的内容"); 30: xmlNewTextChild (proot_node, BAD_CAST "子节点3", BAD_CAST "子节点3的内容"); 31: 32: // 保存 xml 为文件,如果没有给出文件名参数,就输出到标准输出 33: xmlSaveFormatFileEnc (argc > 1 ? argv[1]:"-", pdoc, "UTF-8", 1); 34: 35: // 释放资源 36: xmlFreeDoc (pdoc); 37: xmlCleanupParser (); 38: xmlMemoryDump (); 39: return 0; 40: } 41:
编译这个例子,先看看系统里面的 libxml2 库的 pkgconfig 信息:
root@jianlee:~/lab/xml# cat /usr/lib/pkgconfig/libxml-2.0.pc prefix=/usr exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include modules=1 Name: libXML Version: 2.6.32 Description: libXML library version2. Requires: Libs: -L${libdir} -lxml2 Libs.private: -lz -lm Cflags: -I${includedir}/libxml2 root@jianlee:~/lab/xml# pkg-config libxml-2.0 --cflags --libs -I/usr/include/libxml2 -lxml2
编译:
root@jianlee:~/lab/xml# gcc -Wall `pkg-config libxml-2.0 --cflags --libs` create_xml.c
root@jianlee:~/lab/xml# ./a.out <?xml version="1.0" encoding="UTF-8"?> <根节点 版本="1.0"> <子节点1> <子子节点1>信息</子子节点1> <子子节点1>这是更低的节点,子子子节点1</子子节点1> </子节点1> <子节点2>子节点2的内容</子节点2> <子节点3>子节点3的内容</子节点3> </根节点>
示例补充说明
输出的各节点不要在一行
上面使用下面方式保存 xml 文档,输出的文件各子节点间自动加入回车:
// 保存 xml 为文件,如果没有给出文件名参数,就输出到标准输出 xmlSaveFormatFileEnc (argc > 1 ? argv[1]:"-",pdoc,"UTF-8",1);
如果把上面的 1 换成 0 ,输出格式是放在一行。
用到的函数说明
上面涉及几个函数和类型定义,不过意思很明了,下面解释一个(重要的是自己动手写程序,反复实验,所谓熟能生巧)。
- xmlDocPtr
- 指向 XML 文档对象的指针
- xmlNodePtr
- 指向 XML 文档对象中的节点对象(根节点和子节点都是一样的)
- xmlNewDoc
- 创建一个 XML 文档对象
- xmlNewNode
- 创建一个 XML 文档的指针对象
- xmlNewProp
- 给一个节点增加属性信息,包括在 <> 中,如:
xmlNewProp (proot_node,BAD_CAST "版本",BAD_CAST "1.0");
最后显示是这个样子:
<根节点 版本="1.0">
- xmlDocSetRootElement
- 设置 XML 文档对象的根节点,只有一个根节点
- xmlNewChild
- 指定一个节点,会创建这个节点的子节点。这样不需要使用 xmlNewNode 创建一个节点,再使用 xmlAddChild 添加到其父节点中。
- xmlAddChild
- 把一个节点设置为另外一个节点的子节点。
- xmlNewText
- 创建一个描述节点,没有 <> 符号,需要添加到其他节点上。比如上例中的:
xmlAddChild (pnode1,xmlNewText (BAD_CAST "这是更低的节点,子子子节点1"));
会出现下面的结果:
<子子节点1>这是更低的节点,子子子节点1</子子节点1>
- xmlNewTextChild
- 和 xmlNewText 的区别如同 xmlNewNodeChild 和 xmlNewNode 的区别一样!
- xmlSaveFormatFileEnc
- 保存 xml 对象为文件。
- xmlFreeDoc
- 释放 xml 对象
- xmlCleanupParser
- 清理
- xmlMemoryDump
- 清理
解析 XML 文档
解析一个xml文档,从中取出想要的信息,例如节点中包含的文字,或者某个节点的属性,其流程如下:
- 用 xmlReadFile 函数读出一个文档指针 doc ;
- 用 xmlDocGetRootElement 函数得到根节点 curNode ;
- curNode->xmlChildrenNode 就是根节点的子节点集合 ;
- 轮询子节点集合,找到所需的节点,用 xmlNodeGetContent 取出其内容 ;
- 用 xmlHasProp 查找含有某个属性的节点 ;
- 取出该节点的属性集合,用 xmlGetProp 取出其属性值 ;
- 用 xmlFreeDoc 函数关闭文档指针,并清除本文档中所有节点动态申请的内存。
注意: 节点列表的指针依然是 xmlNodePtr ,属性列表的指针也是 xmlAttrPtr ,并没有 xmlNodeList 或者 xmlAttrList 这样的类型 。看作列表的时候使用它们的 next 和 prev 链表指针来进行轮询 。只有在 Xpath 中有 xmlNodeSet 这种类型。
示例
1: #include <stdio.h> 2: #include <stdlib.h> 3: #include <libxml/parser.h> 4: #include <libxml/tree.h> 5: 6: 7: int main (int argc , char **argv) 8: { 9: xmlDocPtr pdoc = NULL; 10: xmlNodePtr proot = NULL, curNode = NULL; 11: char *psfilename; 12: 13: if (argc < 1) 14: { 15: printf ("用法: %s xml文件名\n", argv[0]); 16: exit (1); 17: } 18: 19: psfilename = argv[1]; 20: // 打开 xml 文档 21: //xmlKeepBlanksDefault(0); 22: pdoc = xmlReadFile (psfilename, XML_PARSE_RECOVER); 23: 24: if (pdoc == NULL) 25: { 26: printf ("打开文件 %s 出错!\n", psfilename); 27: exit (1); 28: } 29: 30: // 获取 xml 文档对象的根节对象 31: proot = xmlDocGetRootElement (pdoc); 32: 33: if (proot == NULL) 34: { 35: printf("错: %s 是空文档(没有root节点)!\n", psfilename); 36: exit (1); 37: } 38: 39: /* 我使用上面程序创建的 xml 文档,它的根节点是“根节点”,这里比较是否 40: 正确。*/ 41: if (xmlStrcmp (proot->name, BAD_CAST "根节点") != 0) 42: { 43: printf ("错误文档" ); 44: exit (1); 45: } 46: 47: /* 如果打开的 xml 对象有 version 属性,那么就输出它的值。 */ 48: if (xmlHasProp (proot, BAD_CAST "版本")) 49: { 50: xmlChar *szAttr = xmlGetProp (proot, BAD_CAST "版本"); 51: printf ("版本: %s \n根节点:%s\n" , szAttr, proot->name); 52: } 53: else 54: { 55: printf (" xml 文档没有版本信息\n"); 56: } 57: 58: curNode = proot->xmlChildrenNode; 59: 60: char n=0; 61: while (curNode != NULL) 62: { 63: if (curNode->name != BAD_CAST "text") 64: { 65: printf ("子节点%d: %s\n", n++,curNode->name); 66: } 67: curNode = curNode->next; 68: } 69: 70: /* 关闭和清理 */ 71: xmlFreeDoc (pdoc); 72: xmlCleanupParser (); 73: return 0; 74: } 75:
编译运行(使用上例创建的 my.xml 文件):
root@jianlee:~/lab/xml# cat my.xml <?xml version="1.0" encoding="UTF-8"?> <根节点 版本="1.0"> <子节点1> <子子节点1>信息</子子节点1> <子子节点1>这是更低的节点,子子子节点1</子子节点1> </子节点1> <子节点2>子节点2的内容</子节点2> <子节点3>子节点3的内容</子节点3> </根节点> root@jianlee:~/lab/xml# gcc -Wall `pkg-config libxml-2.0 --cflags --libs` read_xml.c root@jianlee:~/lab/xml# ./a.out my.xml 版本: 1.0 根节点:根节点 子节点0: text 子节点1: 子节点1 子节点2: text 子节点3: 子节点2 子节点4: text 子节点5: 子节点3 子节点6: text
为什么 my.xml 文件中显示只有 ”子节点1“、 ”子节点2“和 “子节点3”三个子节点,而程序显示有 7 个子节点呢?!而且 0、2、4、6 都是 text 名字?
这是因为其他四个分别是元素前后的空白文本符号,而 XML 把它们也当做一个 Node !元素是 Node 的一种类型。XML 文档对象模型 (DOM) 定义了几种不同的 Nodes 类型,包括 Elements(如 files 或者 age)、Attributes(如 units)和 Text(如 root 或者 10)。元素可以具有子节点。
在打开 xml 文档之前加上一句(取消上面程序中的此句注释就可以):
xmlKeepBlanksDefault(0);
或者使用下面参数读取 xml 文档:
//读取xml文件时忽略空格 doc = xmlReadFile(docname,NULL,XML_PARSE_NOBLANKS);
这样就可以按我们所想的运行了:
root@jianlee:~/lab/xml# gcc -Wall `pkg-config libxml-2.0 --cflags --libs` read_xml.c root@jianlee:~/lab/xml# ./a.out my.xml 版本: 1.0 根节点:根节点 子节点0: 子节点1 子节点1: 子节点2 子节点2: 子节点3
还有一点注意: my.xml 文件中的子节点名字一次是 “子节点1”、“子节点2”、 “子节点3”。程序中的 n 值确是从 0 开始计算。从 0 还是 1 是个人喜好。我有时候喜好从 0 开始,有时候喜好从 1 开始。
- xmlFreeDoc
- 释放文档指针。特别注意,当你调用 xmlFreeDoc 时,该文档所有包含的节点内存都被释放,所以一般来说不需要手动调用 xmlFreeNode 或者 xmlFreeNodeList 来释放动态分配的节点内存,除非你把该节点从文档中移除了。一般来说,一个文档中所有节点都应该动态分配,然后加入文档,最后调用 xmlFreeDoc 一次释放所有节点申请的动态内存,这也是为什么我们很少看见 xmlNodeFree 的原因。
- xmlSaveFile
- 将文档以默认方式存入一个文件。
- xmlSaveFormatFileEnc
- 可将文档以某种编码/格式存入一个文件中,创建 xml 文档是的示例中用到
修改 xml 文档
首先打开一个已经存在的xml文档,顺着根结点找到需要添加、删除、修改的地方,调用相应的xml函数对节点进行增、删、改操作。
删除节点
1: if (!xmlStrcmp(curNode->name, BAD_CAST "newNode1")) 2: { 3: xmlNodePtr tempNode; 4: tempNode = curNode->next; 5: xmlUnlinkNode(curNode); 6: xmlFreeNode(curNode); 7: curNode = tempNode; 8: continue; 9: } 10:
即将当前节点从文档中断链(unlink),这样本文档就不会再包含这个子节点。这样做需要使用一个临时变量来存储断链节点的后续节点,并记得要手动删除断链节点的内存。
示例
1: #include <stdio.h> 2: #include <stdlib.h> 3: #include <libxml/parser.h> 4: 5: int main(int argc, char* argv[]) 6: { 7: xmlDocPtr doc; //定义解析文档指针 8: xmlNodePtr curNode; //定义结点指针(你需要它为了在各个结点间移动) 9: char *szDocName; 10: 11: if (argc <= 1) 12: { 13: printf("Usage: %s docname\n", argv[0]); 14: return(0); 15: } 16: 17: szDocName = argv[1]; 18: xmlKeepBlanksDefault(0); 19: doc = xmlReadFile(szDocName,"UTF-8",XML_PARSE_RECOVER); //解析文件 20: 21: if (NULL == doc) 22: { 23: fprintf(stderr,"Document not parsed successfully. \n"); 24: return -1; 25: } 26: 27: curNode = xmlDocGetRootElement(doc); 28: 29: /*检查确认当前文档中包含内容*/ 30: if (NULL == curNode) 31: { 32: fprintf(stderr,"empty document\n"); 33: xmlFreeDoc(doc); 34: return -1; 35: } 36: 37: curNode = curNode->children; 38: while (NULL != curNode) 39: { 40: //删除 "子节点1" 41: if (!xmlStrcmp(curNode->name, BAD_CAST "子节点1")) 42: { 43: xmlNodePtr tempNode; 44: tempNode = curNode->next; 45: xmlUnlinkNode(curNode); 46: xmlFreeNode(curNode); 47: curNode = tempNode; 48: continue; 49: } 50: 51: //修改 "子节点2" 的属性值 52: if (!xmlStrcmp(curNode->name, BAD_CAST "子节点2")) 53: { 54: xmlSetProp(curNode,BAD_CAST "属性1", BAD_CAST "设置"); 55: } 56: 57: //修改 “子节点2” 的内容 58: if (!xmlStrcmp(curNode->name, BAD_CAST "子节点2")) 59: { 60: xmlNodeSetContent(curNode, BAD_CAST "内容变了"); 61: } 62: 63: //增加一个属性 64: if (!xmlStrcmp(curNode->name, BAD_CAST "子节点3")) 65: { 66: xmlNewProp(curNode, BAD_CAST "新属性", BAD_CAST "有"); 67: } 68: 69: //增加 "子节点4" 70: if (!xmlStrcmp(curNode->name, BAD_CAST "子节点3")) 71: { 72: xmlNewTextChild(curNode, BAD_CAST "新子子节点1", BAD_CAST "新内容"); 73: } 74: 75: curNode = curNode->next; 76: } 77: 78: // 保存文件 79: xmlSaveFormatFileEnc (szDocName, doc,1); 80: 81: xmlFreeDoc (doc); 82: xmlCleanupParser (); 83: xmlMemoryDump (); 84: return 0; 85: } 86:
编译运行:
root@jianlee:~/lab/xml# cat my.xml <?xml version="1.0" encoding="UTF-8"?> <根节点 版本="1.0"> <子节点1> <子子节点1>信息</子子节点1> <子子节点1>这是更低的节点,子子子节点1</子子节点1> </子节点1> <子节点2>子节点2的内容</子节点2> <子节点3>子节点3的内容</子节点3> </根节点> root@jianlee:~/lab/xml# gcc -Wall `pkg-config libxml-2.0 --cflags --libs` modify_xml.c root@jianlee:~/lab/xml# ./a.out my.xml root@jianlee:~/lab/xml# cat my.xml <?xml version="1.0" encoding="UTF-8"?> <根节点 版本="1.0"> <子节点2 属性1="设置">内容变了</子节点2> <子节点3 新属性="有">子节点3的内容<新子子节点1>新内容</新子子节点1></子节点3> </根节点> root@jianlee:~/lab/xml# ./a.out my.xml # 看看再运行一次的结果! root@jianlee:~/lab/xml# cat my.xml <?xml version="1.0" encoding="UTF-8"?> <根节点 版本="1.0"> <子节点2 属性1="设置">内容变了</子节点2> <子节点3 新属性="有" 新属性="有">子节点3的内容<新子子节点1>新内容</新子子节点1><新子子节点1>新内容</新子子节点1></子节点3> </根节点>
Xpath — 处理大型 XML 文档
libxml2 库函数
要注意的函数
xmlKeepBlanksDefault
int xmlKeepBlanksDefault (int val)
设置是否忽略空白节点,比如空格,在分析前必须调用,默认值是0,最好设置成1.
xmlKeepBlanksDefault(0) 除了在读入xml文件时忽略空白之外,还会在写出xml 文件时在每行前面放置缩进(indent)。如果使用xmlKeepBlanksDefault(1) 则你会发现每行前面的缩进就没有了,但不会影响回车换行。
xmlSaveFormatFile
// 保存 xml 为文件,1);
xmlSaveFormatFile 的 format 参数设置成 0,保存后的 xml 文档里是会把所有的结点都放到一行里显示。设置为 1,就可以自动添加回车。
读取 xml 文件
xmlParseFile
xmlDocPtr xmlParseFile (const char * filename)
以默认方式读入一个 UTF-8 格式的 xml 文档,并返回一个文档对象指针 <libxml/tree.h>
xmlReadFile
指定编码读取一个 xml 文档,返回指针。
xml 操作基本结构及其指针类型
xmlDoc,xmlDocPtr
文档对象的结构体及其指针
xmlNode,xmlNodePtr
节点对象的结构体及其指针
xmlAttr,xmlAttrPtr
节点属性的结构体及其指针
xmlNs,xmlNsPtr
节点命名空间的结构及其指针
根节点相关函数
xmlDocGetRootElement
xmlNodePtr xmlDocGetRootElement (xmlDocPtr doc)获取文档根节点
xmlDocSetRootElement
xmlNodePtr xmlDocSetRootElement (xmlDocPtr doc,xmlNodePtr root)设置文档根节点
创建子节点相关函数
xmlNewNode
xmlNodePtr xmlNewNode (xmlNsPtr ns,const xmlChar * name)创建新节点
xmlNewChild
xmlNodePtr xmlNewChild (xmlNodePtr parent,xmlNsPtr ns,const xmlChar * name,const xmlChar * content)创建新的子节点
xmlCopyNode
xmlNodePtr xmlCopyNode (const xmlNodePtr node,int extended)复制当前节点
添加子节点相关函数
xmlAddChild
xmlNodePtr xmlAddChild (xmlNodePtr parent,xmlNodePtr cur)给指定节点添加子节点
xmlAddNextSibling
xmlNodePtr xmlAddNextSibling (xmlNodePtr cur,xmlNodePtr elem)添加后一个兄弟节点
xmlAddPrevSibling
xmlNodePtr xmlAddPrevSibling (xmlNodePtr cur,xmlNodePtr elem)添加前一个兄弟节点
xmlAddSibling
xmlNodePtr xmlAddSibling (xmlNodePtr cur,xmlNodePtr elem)添加兄弟节点
属性相关函数
xmlNewProp
xmlAttrPtr xmlNewProp (xmlNodePtr node,const xmlChar * value)创建新节点属性
xmlGetProp
xmlChar * xmlGetProp (xmlNodePtr node,const xmlChar * name)读取节点属性
xmlSetProp
xmlAttrPtr xmlSetProp (xmlNodePtr node,const xmlChar * value)设置节点属性
<---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------->
摘要
Libxml是一个有免费许可的用于处理XML、可以轻松跨越多个平台的C语言库。这个指南提供它的基本函数的例子。
绪论
Libxml是一个实现读、创建及操纵XML数据功能的C语言库。这个指南提供例子代码并给出它基本功能的解释。在这个项目的主页上有Libxml及更多关于它可用的资料。包含有完整的API文档。这个指南并不能替代这些完整的文档,但是阐明功能需要使用库来完成基本操作。
这个指南基于一个简单的XML应用,它使用我写的一篇文章生成,它包含有元数据和文章的主体。
本指南中的例子代码示范如何做到:
• 解析文档
• 取得指定元素的文本
• 添加一个元素及它的内容
• 添加一个属性
• 取得一个属性的值
例子的完整代码包含在附录中
数据类型
Libxml定义了许多数据类型,我们将反复碰到它们,它隐藏了杂乱的来源以致你不必处理它除非你有特定的需要。xmlChar 替代char,使用UTF-8编码的一字节字符串。如果你的数据使用其它编码,它必须被转换到UTF-8才能使用libxml的函数。在libxml编码支持WEB页面有更多关于编码的有用信息。
XmlDoc 包含由解析文档建立的树结构,xmlDocPtr是指向这个结构的指针。
xmlNodePtr and xmlNode 包含单一结点的结构xmlNodePtr是指向这个结构的指针,它被用于遍历文档树。
解析文档
解析文档时仅仅需要文件名并只调用一个函数,并有错误检查。完整代码:附录C,Keyword例程代码
①xmlDocPtr doc;
②xmlNodePtr cur;
③doc = xmlParseFile(docname);
④if (doc == NULL ) {
fprintf(stderr,"Document not parsed successfully. \n");
return;
}
⑤cur = xmlDocGetRootElement(doc);
⑥if (cur == NULL) {
fprintf(stderr,"empty document\n");
xmlFreeDoc(doc);
return;
}
⑦if (xmlStrcmp(cur->name,(const xmlChar *) "story")) {
fprintf(stderr,"document of the wrong type,root node != 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,(const xmlChar *)"storyinfo"))){
parseStory (doc,cur);
}
cur = cur->next;
}
①取得cur的第一个子结点,cur指向文档的根,即“story”元素。
②这个循环迭代通过“story”的子元素查找“storyinfo”。这是一个包含有我们将查找的“keywords”的元素。它使用了libxml字符串比较函数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: %s\n",key);
xmlFree(key);
}
cur = cur->next;
}
return;
}
① 再次取得第一个子结点。
② 像上面那个循环一样,我们能过迭代,查找我们感兴趣的叫做“keyword”的元素。
③ 当我们找到元素“keyword”时,我们需要打印它包含在XML中的记录的内容,文本被包含于元素的子结点中,因此我们借助了cur->xmlChildrenNode,为了取得文本,我们使用函数xmlNodeListGetString,它有一个文档指针参数,在这个例子中,我们仅仅打印它。
注释
因为xmlNodeListGetString为它返回的字符串分配内存,你必须使用xmlFree释放它。
使用XPath取得元素内容
除了一步步遍历文档树查找元素外,Libxml2包含支持使用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
The function returns an xmlXPathObjectPtr,which includes the set of nodes satisfying the XPath expression.
使用XPath需要安装xmlXPathContext才支持XPath表达式及xmlXPathEvalExpression函数,这个函数返回一个xmlXPathObjectPtr,它包含有
xmlXPathObjectPtr
getnodeset (xmlDocPtr doc,xmlChar *xpath){
①xmlXPathContextPtr context;
xmlXPathObjectPtr result;
②context = xmlXPathNewContext(doc);
③result = xmlXPathEvalExpression(xpath,context);
④if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
printf("No result\n");
return NULL;
}
xmlXPathFreeContext(context);
return result;
}
①首先定义变量
②初始化变量context
③应用XPath表达式
④检查结果
由函数返回的xmlPathObjectPtr包含一个结点集和其它需要被迭代及操作的信息。在这个例子中我们的函数返回xmlXPathObjectPtr,我们使用它打印我们文档中keyword结点的内容。这个结点集对象包含在集合(nodeNr)中的元素数目及一个结点(nodeTab)数组。
①for (i=0; i < nodeset->nodeNr; i++) {
②keyword = xmlNodeListGetString(doc,
nodeset->nodeTab[i]->xmlChildrenNode,printf("keyword: %s\n",keyword);
xmlFree(keyword);
}
①变量nodeset->Nr持有结点集中元素的数量。我们使用它遍历数组。
②打印每个结点包含的内容。
注释
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元素的内容是一个子文本结点。
本例中主要的不同在于parseStory
void
parseStory (xmlDocPtr doc,xmlNodePtr cur,char *keyword) {
①xmlNewTextChild (cur,"keyword",keyword);
return;
}
①XmlNewTextChild函数添加一个当前结点的新的子元素到树中
一旦结点被添加,我们应当写文档到文件中。你是否想给元素指定一个命名空间?你能添加它,在我们的例子中,命名空间是NULL。
xmlSaveFormatFile (docname,doc,1);
第一个参数是写入文件的名,你注意到和我们刚刚读入的文件名是一样的。在这个例子中,我们仅仅覆盖原来的文件。第二个参数是一个xmlDoc结构指针,第三个参数设定为1,保证在输出上写入。
libxml(二)
写属性
写属性类似于给一个新元素写文本。在这个例子中,我们将添加一个reference结点URI属性到我们的文档中。完整代码:附录F,添加属性例程代码。reference是story元素的一个子结点,所以找到并插入新元素及其属性是简单的。一旦我们在parseDoc进行了错误检查,我们将在正确的位置加放我们的新元素。但进行之前我们需要定义一个此前我们不见过的数据类型。
xmlAttrPtr newattr;
我们也需要xmlNodePtr:
xmlNodePtr newnode;
剩下的parseDoc则和前面一样,检查根结点是否为story。如果是的,那我们知道我们将在指定的位置添加我们的元素。
① newnode = xmlNewTextChild (cur,"reference",NULL);
②newattr = xmlNewProp (newnode,"uri",uri);
①使用xmlNewTextChild函数添国一个新结点到当前结点位置。
一旦结点被添加,文件应像前面的例子将我们添加的元素及文本内容写入磁盘。
取得属性
取得属性值类似于前面我们取得一个结点的文本内容。在这个例子中,我们将取出我们在前一部分添加的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 = xmlGetProp(cur,"uri");
printf("uri: %s\n",uri);
xmlFree(uri);
}
cur = cur->next;
}
return;
}
①关键函数是xmlGetProp,它返回一个包含属性值的xmlChar。在本例中,我们仅仅打印它。
注释
如果你使用DTD定义属性的固定值或缺省值,这个函数也将取得它。
编码转换
数据编码兼容问题是程序员新建普通的XML或特定XML时最常见的困难。按照这里
你的程序使用其它的数据格式,比如常见的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函数。
①xmlCharEncodingHandlerPtr handler;
②size = (int)strlen(in)+1;
out_size = size*2-1;
out = malloc((size_t)out_size);
…
③handler = xmlFindCharEncodingHandler(encoding);
…
④handler->input(out,&out_size,in,&temp);
…
⑤xmlSaveFormatFileEnc("-",encoding,1);
①定义一个xmlCharEncodingHandler函数指针。
②XmlCharEncodingHandler函数需要给出输入和输出字符串的大小,这里计算输入输出字符串。
③XmlFindCharEncodingHandler使用数据初始编码作为参数搜索libxml已经完成的转换器句柄并将找到的函数指针返回,如果没有找到则返回NULL。
④The conversion function identified by handler requires as its arguments pointers to the input and output strings,along with the length of each. The lengths must be determined separately by the application.
由句柄指定的转换函数请求输入、输出字符中及它们的长度作为参数。这个长度必须由应用程序分别指定。
⑤用指定编码而不是UTF-8输出,我们使用xmlSaveFormatFileEnc指不定期编码方式。
A. 编译
Libxml包含一个脚本xml2-config,它一般用于编译和链接程序到库时产生标志。
B. 示例文档
<?xml version="1.0"?>
<story>
<storyinfo>
<author>John Fleck</author>
<datewritten>June 2,2002</datewritten>
<keyword>example keyword</keyword>
</storyinfo>
<body>
<headline>This is the headline</headline>
<para>This is the body text.</para>
</body>
</story>
C. Keyword例程代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
void
parseStory (xmlDocPtr doc,xmlNodePtr cur) {
xmlChar *key;
cur = cur->xmlChildrenNode;
while (cur != NULL) {
if ((!xmlStrcmp(cur->name,(const xmlChar *)"keyword"))) {
key = xmlNodeListGetString(doc,key);
xmlFree(key);
}
cur = cur->next;
}
return;
}
static void
parseDoc(char *docname) {
xmlDocPtr doc;
xmlNodePtr cur;
doc = xmlParseFile(docname);
if (doc == NULL ) {
fprintf(stderr,"Document not parsed successfully. \n");
return;
}
cur = xmlDocGetRootElement(doc);
if (cur == NULL) {
fprintf(stderr,"empty document\n");
xmlFreeDoc(doc);
return;
}
if (xmlStrcmp(cur->name,root node != story");
xmlFreeDoc(doc);
return;
}
cur = cur->xmlChildrenNode;
while (cur != NULL) {
if ((!xmlStrcmp(cur->name,cur);
}
cur = cur->next;
}
xmlFreeDoc(doc);
return;
}
int
main(int argc,char **argv) {
char *docname;
if (argc <= 1) {
printf("Usage: %s docname\n",argv[0]);
return(0);
}
docname = argv[1];
parseDoc (docname);
return (1);
}
D. XPath例程代码
#include <libxml/parser.h>
#include <libxml/xpath.h>
xmlDocPtr
getdoc (char *docname) {
xmlDocPtr doc;
doc = xmlParseFile(docname);
if (doc == NULL ) {
fprintf(stderr,"Document not parsed successfully. \n");
return NULL;
}
return doc;
}
xmlXPathObjectPtr
getnodeset (xmlDocPtr doc,xmlChar *xpath){
xmlXPathContextPtr context;
xmlXPathObjectPtr result;
context = xmlXPathNewContext(doc);
result = xmlXPathEvalExpression(xpath,context);
if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
printf("No result\n");
return NULL;
}
xmlXPathFreeContext(context);
return result;
}
int
main(int argc,char **argv) {
char *docname;
xmlDocPtr doc;
xmlChar *xpath = ("//keyword");
xmlNodeSetPtr nodeset;
xmlXPathObjectPtr result;
int i;
xmlChar *keyword;
if (argc <= 1) {
printf("Usage: %s docname\n",argv[0]);
return(0);
}
docname = argv[1];
doc = getdoc(docname);
result = getnodeset (doc,xpath);
if (result) {
nodeset = result->nodesetval;
for (i=0; i < nodeset->nodeNr; i++) {
keyword = xmlNodeListGetString(doc,nodeset->nodeTab[i]->printf
xmlFree(keyword);
}
xmlXPathFreeObject (result);
}
xmlFreeDoc(doc);
xmlCleanupParser();
return (1);
}
E. 添加keyword例程代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
void
parseStory (xmlDocPtr doc,char *keyword) {
xmlNewTextChild (cur,keyword);
return;
}
xmlDocPtr
parseDoc(char *docname,char *keyword) {
xmlDocPtr doc;
xmlNodePtr cur;
doc = xmlParseFile(docname);
if (doc == NULL ) {
fprintf(stderr,"Document not parsed successfully. \n");
return (NULL);
}
cur = xmlDocGetRootElement(doc);
if (cur == NULL) {
fprintf(stderr,"empty document\n");
xmlFreeDoc(doc);
return (NULL);
}
if (xmlStrcmp(cur->name,root node != story");
xmlFreeDoc(doc);
return (NULL);
}
cur = cur->xmlChildrenNode;
while (cur != NULL) {
if ((!xmlStrcmp(cur->name,cur,keyword);
}
cur = cur->next;
}
return(doc);
}
int
main(int argc,char **argv) {
char *docname;
char *keyword;
xmlDocPtr doc;
if (argc <= 2) {
printf("Usage: %s docname,keyword\n",argv[0]);
return(0);
}
docname = argv[1];
keyword = argv[2];
doc = parseDoc (docname,keyword);
if (doc != NULL) {
xmlSaveFormatFile (docname,0);
xmlFreeDoc(doc);
}
return (1);
}
F. 添加属性例程代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
xmlDocPtr
parseDoc(char *docname,char *uri) {
xmlDocPtr doc;
xmlNodePtr cur;
xmlNodePtr newnode;
xmlAttrPtr newattr;
doc = xmlParseFile(docname);
if (doc == NULL ) {
fprintf(stderr,root node != story");
xmlFreeDoc(doc);
return (NULL);
}
newnode = xmlNewTextChild (cur,NULL);
newattr = xmlNewProp (newnode,uri);
return(doc);
}
int
main(int argc,char **argv) {
char *docname;
char *uri;
xmlDocPtr doc;
if (argc <= 2) {
printf("Usage: %s docname,uri\n",argv[0]);
return(0);
}
docname = argv[1];
uri = argv[2];
doc = parseDoc (docname,uri);
if (doc != NULL) {
xmlSaveFormatFile (docname,1);
xmlFreeDoc(doc);
}
return (1);
}
G. 取得属性值例程代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
void
getReference (xmlDocPtr doc,(const xmlChar *)"reference"))) {
uri = xmlGetProp(cur,uri);
xmlFree(uri);
}
cur = cur->next;
}
return;
}
void
parseDoc(char *docname) {
xmlDocPtr doc;
xmlNodePtr cur;
doc = xmlParseFile(docname);
if (doc == NULL ) {
fprintf(stderr,root node != story");
xmlFreeDoc(doc);
return;
}
getReference (doc,cur);
xmlFreeDoc(doc);
return;
}
int
main(int argc,argv[0]);
return(0);
}
docname = argv[1];
parseDoc (docname);
return (1);
}
H. 编码转换例程代码
#include <string.h>
#include <libxml/parser.h>
unsigned char*
convert (unsigned char *in,char *encoding)
{
unsigned char *out;
int ret,size,out_size,temp;
xmlCharEncodingHandlerPtr handler;
size = (int)strlen(in)+1;
out_size = size*2-1;
out = malloc((size_t)out_size);
if (out) {
handler = xmlFindCharEncodingHandler(encoding);
if (!handler) {
free(out);
out = NULL;
}
}
if (out) {
temp=size-1;
ret = handler->input(out,&temp);
if (ret || temp-size+1) {
if (ret) {
printf("conversion wasn't successful.\n");
} else {
printf("conversion wasn't successful. converted: }
free(out);
out = NULL;
} else {
out = realloc(out,out_size+1);
out[out_size]=0; /*null terminating out*/
}
} else {
printf("no mem\n");
}
return (out);
}
int
main(int argc,char **argv) {
unsigned char *content,*out;
xmlDocPtr doc;
xmlNodePtr rootnode;
char *encoding = "ISO-8859-1";
if (argc <= 1) {
printf("Usage: %s content\n",argv[0]);
return(0);
}
content = argv[1];
out = convert(content,encoding);
doc = xmlNewDoc ("1.0");
rootnode = xmlNewDocNode(doc,(const xmlChar*)"root",out);
xmlDocSetRootElement(doc,rootnode);
xmlSaveFormatFileEnc("-",1);
return (1);
}
…………………………………………………………………………………………
{
xmlCharEncodingHandlerPtr handler;
xmlBufferPtr in,out;