上次介绍了用 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文档
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; } 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 ); } if (document.FirstChild ()->ToDeclaration ()){ cout<<document.FirstChild ()->ToDeclaration ()->Value ()<<endl; } 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"?> <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
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; 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 类,其构造函数为
XMLPrinter ( FILE* file=0 , bool compact = false , int depth = 0 );
调用如下
XMLPrinter printer; document.Print ( &printer ); cout<<printer.CStr ()<<endl;
关于 XMLHandle 的作用,官网也说得很清楚,假如有一下xml
<Document > <Element attributeA = "valueA" > <Child attributeB = "value1" /> <Child attributeB = "value2" /> </Element > </Document >
如果要获取 attributeB 属性的值 “value2”,一般情况下,通过如下方法获取最终的值
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 ) {
每一步都必须判断是否为空指针,这样的话代码就显得十分繁琐,
于是 XMLHandle 提供了这样一种方法
XMLHandle docHandle ( &document ) ; XMLElement* child2 = docHandle.FirstChildElement ( "Document" ).FirstChildElement ( "Element" ).FirstChildElement ().NextSiblingElement ();if ( child2 ) {
这样的话,就不用每步都进行一次判断了。如果在这中间过程中返回了空指针,那么结果就是空指针,只有每一步都正确,结果才正确。
思考以下代码,看看那个代码写法正确?
if (document.FirstChildElement ("notPeople" )->FirstChildElement ("Student" )){ cout<<document.FirstChildElement ("People" )->FirstChildElement ("Student" )->Value ()<<endl; }
XMLHandle handle (&document) ; if (handle.FirstChildElement ("notPeople" ).FirstChildElement ("Student" ).ToElement ()){ cout<<document.FirstChildElement ("People" )->FirstChildElement ("Student" )->Value ()<<endl; }
Bye~