Libxml2解析XML文件

Libxml2是由C语言开发的一个库,除了解析XML文档外,还可以解析HTML文档(不过这我没有研究)。同时,libxml2库可移植于多个平台
大致可与运行在如下平台: Linux, Unix, Windows, CygWin, MacOS, MacOS X, RISC Os, OS/2, VMS, QNX, MVS, VxWorks

可以从官网: http://xmlsoft.org 获取最新版本(2.9.8)的libxml2库。Linux下编译也很简单
./configure
make
make install

官网上也有一些例子,以及API接口文档,常见问题,Tutorial,建议去看看。也可以从下载的源文件目录doc中离线浏览

libxml2库囊括了以下的模块,

Table of Contents

DOCBparser: old DocBook SGML parser
HTMLparser: interface for an HTML 4.0 non-verifying parser
HTMLtree: specific APIs to process HTML tree, especially serialization
SAX: Old SAX version 1 handler, deprecated
SAX2: SAX2 parser interface used to build the DOM tree
c14n: Provide Canonical XML and Exclusive XML Canonicalization
catalog: interfaces to the Catalog handling system
chvalid: Unicode character range checking
debugXML: Tree debugging APIs
dict: string dictionary
encoding: interface for the encoding conversion functions
entities: interface for the XML entities handling
globals: interface for all global variables of the library
hash: Chained hash tables
list: lists interfaces
nanoftp: minimal FTP implementation
nanohttp: minimal HTTP implementation
parser: the core parser module
parserInternals: internals routines and limits exported by the parser.
pattern: pattern expression handling
relaxng: implementation of the Relax-NG validation
schemasInternals: internal interfaces for XML Schemas
schematron: XML Schemastron implementation
threads: interfaces for thread handling
tree: interfaces for tree manipulation
uri: library of generic URI related routines
valid: The DTD validation
xinclude: implementation of XInclude
xlink: unfinished XLink detection module
xmlIO: interface for the I/O interfaces used by the parser
xmlautomata: API to build regexp automata
xmlerror: error handling
xmlexports: macros for marking symbols as exportable/importable.
xmlmemory: interface for the memory allocator
xmlmodule: dynamic module loading
xmlreader: the XMLReader implementation
xmlregexp: regular expressions handling
xmlsave: the XML document serializer
xmlschemas: incomplete XML Schemas structure implementation
xmlschemastypes: implementation of XML Schema Datatypes
xmlstring: set of routines to process strings
xmlunicode: Unicode character APIs
xmlversion: compile-time version informations
xmlwriter: text writing API for XML
xpath: XML Path Language implementation
xpathInternals: internal interfaces for XML Path Language implementation
xpointer: API to handle XML Pointers

因为libxml2包含大量函数,实现有不同的方式。这里介绍一些常用的。至于其他的功能有兴趣自己研究吧……
而这里主要用到的为 parser,tree,xmlreader,xmlwriter 这几个模块

parser/tree

读取XML文档

首先介绍xml文档的解析吧,大致调用函数如下

  • xmlReadFile/xmlParseFile 打开一个XML文档并返回一个文档对象指针xmlDocPtr
  • xmlDocGetRootElement 获取XML文档的根节点xmlNodePtr
  • 获取根节点,以及childNode的名称、属性名/值。而这一步骤可以通过递归实现。
  • 最后由 xmlFreeDoc、xmlCleanupParser 释放所有分配的内存

下面给出一个例子(C++)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <iostream>
#include <libxml/parser.h>
#include <libxml/tree.h>
using namespace std;
///
/// \param depth 递归深度
/// \param _xmlNodePtr 节点对象指针
void ParserXML(int &depth,xmlNodePtr _xmlNodePtr){
bool ishas_child=false;
// 所有子节点
xmlNodePtr xmlNodePtr1= _xmlNodePtr->children;
int count=0;
while (xmlNodePtr1){
// 判断节点类型
if(xmlNodePtr1->type!=XML_TEXT_NODE){
// xmlStrcmp(xmlNodePtr1->name,BAD_CAST "text")
count++;
// 子节点个数
int childEleCount= xmlChildElementCount(xmlNodePtr1);
cout.width(depth);
if(childEleCount==0){
ishas_child=false;
// 表明无子节点
cout<<count<<"."<<xmlNodePtr1->name<<" --> "<<xmlNodeGetContent(xmlNodePtr1)<<endl;
}else{
ishas_child=true;
// 表明有子节点
cout<<count<<"."<<xmlNodePtr1->name<<endl;
}
// 遍历节点属性
xmlAttr* xmlAttr1=xmlNodePtr1->properties;
if(xmlAttr1){
cout.width(depth+1);
cout<<"=>";
while (xmlAttr1!=NULL){
// 判断使用存在属性 , xmlGetProp获取属性值
if(xmlHasProp(xmlNodePtr1,xmlAttr1->name)){
cout<<" "<<xmlAttr1->name<<":"<<
xmlGetProp(xmlNodePtr1,xmlAttr1->name);
}
xmlAttr1=xmlAttr1->next;
}
cout<<endl;
}
// 递归调用
if(ishas_child){
depth+=10;
ParserXML(depth,xmlNodePtr1);
}
}
xmlNodePtr1=xmlNodePtr1->next;
}
if(depth>0){
depth-=10;
}
}
int main(int argc,char** argv) {
if(argc!=2)
return 0;
xmlDocPtr xmlDocPtr1=NULL;
//xmlDocPtr1=xmlParseFile(argv[1]);
xmlInitParser();
// 读取文件
xmlDocPtr1=xmlReadFile(argv[1],"UTF-8",0);
if(xmlDocPtr1==NULL) {
xmlErrorPtr xmlErrorPtr1 =xmlGetLastError();
cout << xmlErrorPtr1->message << endl;
exit(-1);
}
cout<<"Version: "<<xmlDocPtr1->version<<endl;
cout<<"Encoding: "<<xmlDocPtr1->encoding<<endl;
// 获取根节点
xmlNodePtr xmlNodePtr1= xmlDocGetRootElement(xmlDocPtr1);
cout<<"Root Element: "<<xmlNodePtr1->name<<endl<<endl;
// 遍历所有信息
int depth=0;
ParserXML(depth,xmlNodePtr1);
// 释放内存
xmlFreeDoc(xmlDocPtr1);
xmlCleanupParser();
return 0;
}

有如下 test.xml 文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<People>
<Student id="0x00000000">
<name>Jack</name>
<age>14</age>
<sex>Man</sex>
<hobby>Computer Programming</hobby>
</Student>
<Student id="0x11111111">
<name>Tony</name>
<age>17</age>
<sex>Man</sex>
<hobby>Play Basketball</hobby>
</Student>
<Student id="0x22222222">
<name>Job</name>
<age>20</age>
<sex>Man</sex>
<hobby>Play Football</hobby>
</Student>
</People>

编译运行 g++ main.cpp -o main xml2-config –libs –cflags&& ./main test.xml

输出信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Version: 1.0
Encoding: UTF-8
ROOT Element: People
1.Student
=> id:0x00000000
1.name --> Jack
2.age --> 14
3.sex --> Man
4.hobby --> Computer Programming
2.Student
=> id:0x11111111
1.name --> Tony
2.age --> 17
3.sex --> Man
4.hobby --> Play Basketball
3.Student
=> id:0x22222222
1.name --> Job
2.age --> 20
3.sex --> Man
4.hobby --> Play Football

可以看到,成功的解析xml文档并格式化的输出信息。这个例子的核心代码就是那个递归函数。现在我们来看看它到底干了些什么。
不过在此之前,先谈谈 xmlParseFilexmlReadFile 的区别。它们都是打开一个XML文档并返回一个文档对象指针 xmlDocPtr ,不同之处就在于它们提供的参数不同

1
2
xmlDocPtr xmlParseFile(const char *filename);
xmlDocPtr xmlReadFile(const char *URL,**const char *encoding**,int options);

xmlReadFile 以指定的编码格式打开xml文档,而 xmlParseFile 默认以UTF-8编码格式打开文档。
比如,刚才的test.xml 中通过encoding指定了UTF-8编码格式,那么用 xmlParseFile 能成功解析,用 xmlReadFile 函数无论
const char *encoding 参数为NULL还是“UTF-8” 也能成功解析。
但是 test.xml 只有 那么 xmlParseFile 会解析失败,而 xmlReadFile 只有 第二个参数为 “UTF-8”时才能解析成功。

好了,继续看那个递归函数吧。。。
首先用 xmlNodePtr xmlNodePtr1= _xmlNodePtr->children; 获取的 _xmlNodePtr 节点的所有子节点,然后在while循环中判断每个子节点的类型,xmlNodePtr1->type!=XML_TEXT_NODE 如果不是 XML_TEXT_NODE 那就继续。接着 用 xmlChildElementCount 获取 xmlNodePtr1 节点指针的所有子节点个数,并用一个 ishas_child 标识是否存在子节点,对于存在子节点的节点,就进行递归。然后显示了节点的属性,通过 xmlHasProp 判断是否存在指定名字的属性,存在用 xmlGetProp 获取其值。注意,我获取了 一个属性对象(链表) xmlAttr* xmlAttr1=xmlNodePtr1->properties 之后进行遍历。在libxml2中,xmlNode、xmlDoc、xmlAttr 都是一个链表。
之后进入递归,depth 只是表示深度,用于格式化输出
if(ishas_child){
depth+=10;
ParserXML(depth,xmlNodePtr1);
}
在进入的每个递归函数执行结束之前减去刚才进入的深度
if(depth>0){
depth-=10;
}

接下来就是生成XML文档。。。

生成XML文档

生成就简单一些了,下面是一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <libxml/parser.h>
#include <libxml/tree.h>
using namespace std;
void GenerateXML(const char *filename){
xmlDocPtr xmlDocPtr1= xmlNewDoc(BAD_CAST XML_DEFAULT_VERSION);
if(!xmlDocPtr1){
cout<<"ERROR: "<<xmlGetLastError()->message<<endl;
}
// 创建根节点
xmlNodePtr xmlRootPtr=xmlNewNode(NULL,BAD_CAST"Root");
xmlDocSetRootElement(xmlDocPtr1,xmlRootPtr);
// 创建一个文本子节点,父节点为 xmlRootPtr
xmlNodePtr xmlNodePtr1= xmlNewTextChild(xmlRootPtr,NULL,BAD_CAST"Management",BAD_CAST"None");
// 添加一个节点,父节点为 xmlNodePtr1
xmlNewTextChild(xmlNodePtr1,NULL,BAD_CAST"Score",BAD_CAST"None");

// 创建一个新的节点
xmlNodePtr xmlAnimalPtr= xmlNewNode(NULL,BAD_CAST"Animals");
xmlAddChild(xmlRootPtr,xmlAnimalPtr);
// 创建元素
xmlAttrPtr xmlAttrPtr1= xmlNewProp(xmlAnimalPtr,BAD_CAST"Number",BAD_CAST"888");
xmlSetProp(xmlAnimalPtr,BAD_CAST"Number",BAD_CAST"9999");
// xmlNodeSetName(xmlAnimalPtr,BAD_CAST"The_Animal");
// xmlRemoveProp(xmlAttrPtr1);
// 保存文件
if(xmlSaveFileEnc(filename,xmlDocPtr1,"UTF-8")){
cout<<"生成 XML 文件成功!"<<endl;
}
// 释放内存
xmlFreeDoc(xmlDocPtr1);
}
int main()
{
GenerateXML("test.xml");
}

这里通用的做法为 xmlNewDoc 创建一个新的XML文档并返回一个 xmlDocPtr ,创建一个根节点就是创建一个 xmlNodePtr 并通过 xmlDocSetRootElement 设置 xmlDocPtr 的根节点为 xmlNodePtr 。之后在继续添加子节点时,只需在 xmlNodePtr 的基础上创建新节点并添加即可。

xmlNewTextChild 函数用于文件一个文本子节点。如 <node>Hello</node>

创建新节点的方法为 xmlNewNode 它返回一个 xmlNodePtr ,之后 xmlAddChild 把一个节点添加到父节点上。

通过 xmlNewProp 创建一个属性 xmlAttrPtr ,之后可通过
xmlSetProp或xmlNodeSetName 设置属性的值。删除属性 xmlRemoveProp

最后,全部OK了, xmlSaveFileEnc 来保存XML文档

编译运行生成test.xml,可能格式有点混乱,我修改了一下

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<Management>None
<Score>None</Score>
</Management>
<Animals Number="9999"/>
</Root>

OK,基本上解析XML也不算太难。除了libxml2库外,还有其他的库也可以解析XML。看个人爱好了 😃

bye~


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!