TinyXML2解析XML文件

上次介绍了用 libxml2 解析XML文档,用起来也挺“痛苦”的,不过这次即将介绍的同为开源 tinyxml2 可再简单不够了。。。

TinyXML2

TinyXML2是什么?

TinyXML-2 is a simple, small, efficient, C++ XML parser that can be easily integrated into other programs.

面向过程 的libxml2相比,TinyXML2是 面向对象 的,所有的操作都被封装在 里面,因此变得极其简单。
而且 tinyxml2 实现的代码文件为 tinyxml2.h tinyxml2.cpp,而 tinyxml2.cpp 仅大约2800行代码实现了xml解析,实在令人佩服。

Simply compile and run. There is a visual studio 2015 project included, a simple Makefile, an Xcode project, a Code::Blocks project, and a cmake CMakeLists.txt included to help you.

可从Github上获取其源文件: https://github.com/leethomason/tinyxml2

tinyxml2主要包括了一下几个类

Class Class
XMLAttribute XMLComment
XMLConstHandle XMLDeclaration
XMLDocument XMLElement
XMLHandle XMLNode
XMLPrinter XMLText
XMLUnknown XMLVisitor

顾名思义,这些类的功能直接从名字就知道了。其中 XMLNode 为大多数类的基类,即一个节点对象。 <?xml version="1.0" encoding="UTF-8"?> 也是一个节点
注释(Comment)也是一个节点对象,但属性(Attribute)却不是,因为它没有从 XMLNode类 派生出来

要解析一个XML文档,一般可通过 XMLDocument 加载XML文档

1
2
XMLError tinyxml2::XMLDocument::LoadFile(const char * filename)	
XMLError tinyxml2::XMLDocument::LoadFile(FILE * )

相反,tinyxml2::XMLDocument::SaveFile 可保存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
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
#include <iostream>
#include <tinyxml2.h>
using namespace std;
using namespace tinyxml2;
void ParserXMLFile(int &depth,XMLNode *pNode){
int index=0;
// 根节点
XMLNode *node=pNode;
do{
// 如果是节点是元素
if(node->ToElement()){
index++;
XMLElement *element=node->ToElement();
cout.width(depth);
cout<<index<<"."<<element->Name();
// 获取元素的文本
if(element->GetText()){
cout<<"-->"<<element->GetText();
}
cout<<endl;
// 解析属性
const XMLAttribute *attribute=element->FirstAttribute();
bool ishas_attr=false;
if(attribute) {
ishas_attr=true;
cout.width(depth);
cout<<"=> ";
while (attribute){
cout<<attribute->Name()<<":"<<attribute->Value()<<" | ";
attribute=attribute->Next();
}
if(ishas_attr)cout<<endl;
}
// 当前节点node的下一个子节点
if(node->FirstChild()){
depth+=10;
ParserXMLFile(depth,node->FirstChild());
}
}
node=node->NextSibling();
}while(node);
if(depth>0){
depth-=10;
}
}
void testParserXML(){
XMLDocument document;
XMLError xmlError;
if((xmlError= document.LoadFile("test1.xml"))==XML_SUCCESS){
cout<<"Load xml file ok!"<<endl;
}else{
cout<<document.ErrorStr()<<endl;
exit(-1);
}
// <?xml version="1.0" encoding="UTF-8"?>
if(document.FirstChild()->ToDeclaration()){
cout<<document.FirstChild()->ToDeclaration()->Value()<<endl;
}
// <!--This is a comment...-->
if(document.FirstChild()->NextSibling()->ToComment()){
cout<<document.FirstChild()->NextSibling()->ToComment()->Value()<<endl;
}
cout<<"Root Element: "<< document.RootElement()->Name()<<endl;
int depth=0;
// 根节点下的第一个子节点
ParserXMLFile(depth,document.RootElement()->FirstChild());
}
int main(int argc,char **argv) {
testParserXML();
return 0;
}

上面的代码是不是很熟悉?

test.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<!--This is a comment...-->
<Theme version="2.0" magnet="9" alpha="255">
<Bitmap id="main" file="main.bmp" alphacolor="#FF0001" />
<Font id="playlist_font" file="FreeSansBold.ttf" size="11" />
<BitmapFont id="digits_font" file="nums_ex.bmp" type="digits" />
<Window id="playlist_window" x="100" y="332">
<Layout id="pl_small_layout" width="275" height="116" minwidth="275" minheight="116" maxwidth="1000" maxheight="800">
<Group>
<Text font="text_font" x="4" y="4" width="239" text="$N" />
</Group>
</Layout>
<Layout id="pl_big_layout" width="275" height="116" minwidth="275" minheight="116" maxwidth="1000" maxheight="800">
<Group x="0" y="0">
<Anchor x="0" y="116" priority="30" range="15" />
</Group>
</Layout>
</Window>
</Theme>

最后的结果就不显示出来了。。。思路也很简单,通过递归获取每个子节点的信息。官网也有类文档可参考,这里就不在详细介绍了。不过有个地方的确要注意

const char* tinyxml2::XMLNode::Value()const

1
2
3
4
5
Document:   empty (NULL is returned,not an empty string)
Element: name of the element
Comment: the comment text
Unknown: the tag contents
Text: the text string

这是基类XMLNode的Value成员函数,而文档(Document)对象,返回确实一个 NULL,不是空字符串!文档对象是一个特殊的节点对象。

而生成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
void GenerateXMLFile(const char *filename){
XMLDocument document;
// 添加 <?xml version="1.0" encoding="UTF-8"?>
document.InsertFirstChild(document.NewDeclaration());
// 添加注释
document.InsertEndChild(document.NewComment("Hello World!"));
// 添加根节点
XMLElement *root= document.NewElement("People");
document.InsertEndChild(root);
// 添加一个新元素
XMLElement *student=document.NewElement("Student");
// 设置元素属性
student->SetAttribute("name","XiaoMing");
student->SetAttribute("age",16);
XMLElement *stu_0=document.NewElement("Hobby");
stu_0->SetText("Play Football");
// 插入子节点
student->InsertFirstChild(stu_0);
XMLElement *teacher=document.NewElement("Teacher");
teacher->SetText("I am a teacher");
// 删除属性
student->DeleteAttribute("age");
root->InsertFirstChild(student);
root->InsertEndChild(teacher);
// 保存文件
document.SaveFile(filename);
}

要打印XML文档的内容到标准输出,可以用 XMLPrinter 类,其构造函数为

1
2
3
4
5
6
7
8
/** Construct the printer. If the FILE* is specified,
this will print to the FILE. Else it will print
to memory, and the result is available in CStr().
If 'compact' is set to true, then output is created
with only required whitespace and newlines.
*/
XMLPrinter( FILE* file=0, bool compact = false, int depth = 0 );

调用如下

1
2
3
XMLPrinter printer;
document.Print( &printer );
cout<<printer.CStr()<<endl;

关于 XMLHandle 的作用,官网也说得很清楚,假如有一下xml

1
2
3
4
5
6
<Document>
<Element attributeA = "valueA">
<Child attributeB = "value1" />
<Child attributeB = "value2" />
</Element>
</Document>

如果要获取 attributeB 属性的值 “value2”,一般情况下,通过如下方法获取最终的值

1
2
3
4
5
6
7
8
9
10
11
12
13
XMLElement* root = document.FirstChildElement( "Document" );
if ( root )
{
XMLElement* element = root->FirstChildElement( "Element" );
if ( element )
{
XMLElement* child = element->FirstChildElement( "Child" );
if ( child )
{
XMLElement* child2 = child->NextSiblingElement( "Child" );
if ( child2 )
{
// Finally do something useful.

每一步都必须判断是否为空指针,这样的话代码就显得十分繁琐,
于是 XMLHandle 提供了这样一种方法

1
2
3
4
5
XMLHandle docHandle( &document );
XMLElement* child2 = docHandle.FirstChildElement( "Document" ).FirstChildElement( "Element" ).FirstChildElement().NextSiblingElement();
if ( child2 )
{
// do something useful

这样的话,就不用每步都进行一次判断了。如果在这中间过程中返回了空指针,那么结果就是空指针,只有每一步都正确,结果才正确。

思考以下代码,看看那个代码写法正确?

1
2
3
if(document.FirstChildElement("notPeople")->FirstChildElement("Student")){
cout<<document.FirstChildElement("People")->FirstChildElement("Student")->Value()<<endl;
}
1
2
3
4
XMLHandle handle(&document); 
if(handle.FirstChildElement("notPeople").FirstChildElement("Student").ToElement()){
cout<<document.FirstChildElement("People")->FirstChildElement("Student")->Value()<<endl;
}

Bye~


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