Cocos2d-x数据模块教程03:XML数据操作

2015年03月24日 09:05 0 点赞 0 评论 更新于 2025-11-21 14:44

XML,即可扩展标记语言,在游戏开发中应用广泛,常被用于保存游戏数据信息,如最高分、游戏等级等,也用于描述一些资源。像加载动画的plist文件、瓦片地图编辑器导出的地图格式tmx文件,实际上都是特定格式的XML文件。此外,Cocos2d-x中UserDefault单例类保存的数据,也是存储在XML文件中的。Cocos2d-x已经集成了tinyxml2库用于XML的解析,在3.x版本中,该库位于external/tinyxml2目录下。

本节将详细介绍如何使用tinyxml2库来操作处理XML文件。

XML简介

参考资料:http://www.w3school.com.cn/xml/index.asp

什么是XML?

  • XML 指可扩展标记语言(EXtensible Markup Language)。
  • XML 是一种标记语言,与 HTML 类似。
  • XML 的设计宗旨是传输数据,而非显示数据。
  • XML 标签没有预定义,需要自行定义。
  • XML 具有自我描述性。
  • XML 是 W3C 的推荐标准。

XML的一个例子

以下是一个简单的 XML 示例:

<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>

代码分析:

  1. 第一行是 XML 声明,定义了 XML 的版本(1.0)和所使用的编码(UTF-8 字符集)。
  2. 下一行 <note> 描述文档的根元素,表明本文档是一个便签。
  3. 接下来 4 行描述根元素的 4 个子元素(to,from,heading,body)。
  4. 最后一行 </note> 定义根元素的结尾。

从这个简单的 XML 文档可以看出,XML 的语法简单,标签可自定义,元素可以有子元素,形成树形结构。

XML树结构

  • XML 文档必须包含根元素,它是所有其他元素的父元素。
  • 所有元素均可拥有多个子元素。
  • 所有元素均可拥有文本内容和属性(类似 HTML)。
  • 父、子以及同胞等术语用于描述元素之间的关系:父元素拥有子元素;相同层级上的子元素称为同胞(兄弟或姐妹)。
  • XML 文档中的元素形成一棵文档树的结构,从“根部”开始,并扩展到树的“枝叶”。

以下是一个树结构的实例,它表示 XML 中的一本书:

<bookstore>
<book category="COOKING">
<title>Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title>Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>

在这个例子中:

  • 根元素是 <bookstore>,文档中的所有 <book> 元素都被包含在其中。
  • <book> 元素有 4 个子元素:<title><author><year><price>
  • categorylang 均为元素的属性。

语法规则

  1. 根元素:XML 文档必须有根元素。
  2. 结束标签:XML 文档必须有结束标签,例如 <p>This is a paragraph.</p>
  3. 大小写敏感:XML 标签对大小写敏感,标签 <Letter><letter> 是不同的。
  4. 正确嵌套:XML 元素必须被正确嵌套,例如:
    <!-- 错误的嵌套 -->
    <b><i>This text is bold and italic</b></i>
    <!-- 正确的嵌套 -->
    <b><i>This text is bold and italic</i></b>
    
  5. 属性值加引号:XML 属性值必须加引号,单引号 ' '、双引号 " " 均可,例如:
    <note date="08/08/2008">
    <note date='08/08/2008'>
    

注释

在 XML 中编写注释的语法与 HTML 相似:

<!-- This is a comment -->

保留空格

HTML 会把多个连续的空格字符裁减(合并)为一个,而在 XML 中,空格不会被删节,例如:

content :Hello    my name is David.
HTML    :Hello my name is David.
XML     :Hello    my name is David.

实体引用

在 XML 中,一些字符拥有特殊的意义。如果把字符 < 放在 XML 元素中,会发生错误,因为解析器会把它当作新元素的开始。为了避免这个错误,需要用实体引用来代替 < 字符,例如:

<message>if salary < 1000 then</message> <!-- 会产生 XML 错误 -->
<message>if salary &lt; 1000 then</message> <!-- 正确写法 -->

在 XML 中,有 5 个预定义的实体引用。

XML元素

XML 元素指的是从(且包括)开始标签,直到(且包括)结束标签的部分。元素可包含其他元素、文本,或者两者的混合物,也可以拥有属性。例如:

<bookstore>
<book category="CHILDREN">
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
</bookstore>

在这个例子中,<bookstore><book> 都拥有元素内容,因为它们包含了其他元素;<author> 只有文本内容,因为它仅包含文本;只有 <book> 元素拥有属性(category="CHILDREN")。

XML属性

XML 元素可以在开始标签中包含属性,类似 HTML。属性提供关于元素的额外信息,通常提供不属于数据组成部分的信息。例如:

<file type="gif">computer.gif</file>

属性的值必须加引号,单引号、双引号均可。

元素 vs 属性

有时候属性和元素均可以提供相同的信息,例如:

<person sex="female">
<name>Alice</name>
</person>
<person>
<sex>female</sex>
<name>Alice</name>
</person>

但这样的定义会造成数据的混乱,并且不易阅读。因此,最好的做法是:

  • 属性:用来提供不属于数据组成部分的信息,如图片格式、书籍分类、ID 索引等。
  • 元素:用来描述数据信息。

元素的命名规则

XML 元素必须遵循以下命名规则:

  • 名称可以含字母、数字以及其他的字符。
  • 名称不能以数字或者标点符号开始。
  • 名称不能以字符 “xml”(或者 XML、Xml)开始。
  • 名称不能包含空格。

XML 元素的命名习惯:

  • 使名称具有描述性,使用下划线的名称也不错。
  • 名称应当比较简短,例如 <book_title>,而不是 <the_title_of_the_book>
  • 避免使用 - 字符,因为一些软件会认为你需要提取第一个单词。
  • 避免使用 . 字符,因为一些软件会认为 name 是对象 first 的属性。
  • 避免使用 : 字符,冒号会被转换为命名空间来使用。

tinyxml2

Cocos2d-x 已经加入了 tinyxml2 库用于 XML 的解析,3.x 版本位于 external/tinyxml2 下。

相关类

  • XMLNode:表示一个节点,包含一般方法,如访问子节点、兄弟节点、编辑自身、编辑子节点。
  • XMLDocument:表示整个 XML 文档,不对应其中某个特定的节点。
  • XMLElement:表示元素节点,可以包含子节点 XMLElement 和属性 XMLAttribute
  • XMLAttribute:表示一个元素的属性。
  • XMLText:表示文本节点。
  • XMLComment:表示注释。
  • XMLDeclaration:表示声明。

添加头文件

#include "tinyxml2/tinyxml2.h"
using namespace tinyxml2;

XML数据解析

假设 XML 文档如下:

<?xml version="1.0" encoding="UTF-8"?>
<person>
<student id="1111">
<name>Alice</name>
<age>20</age>
</student>
<teacher id="2222">
<name>Bob</name>
<age>30</age>
</teacher>
</person>

以下是 XML 解析的示例代码:

//[0] 文件路径
std::string path = "/soft/cocos2d-x-3.4/projects/Demo34/Resources/testXML.xml";
//[1] 创建管理XML文档的对象:XMLDocument
XMLDocument* doc = new XMLDocument();
//[2] 解析xml文件
// 方式一:
// Data data = FileUtils::getInstance()->getDataFromFile(path.c_str());
// XMLError errorID = doc->Parse((const char*)data.getBytes());
// 方式二:
// std::string data = FileUtils::getInstance()->getStringFromFile(path.c_str());
// XMLError errorID = doc->Parse(data.c_str());
// 方式三:
XMLError errorID = doc->LoadFile(path.c_str());
//[3] 判断是否解析错误
if (errorID != 0) {
CCLOG("Parse Error!");
return;
}
//[4] 获取根元素 <person>
XMLElement* root = doc->RootElement();
//[5] 获取子元素信息
//[5.1] 遍历root的子元素 <student> , <teacher>
// FirstChildElement()  : 获取 root 的第一个子元素
// NextSiblingElement() : 获取 chd  的下一个兄弟元素
for (XMLElement* chd = root->FirstChildElement(); chd; chd = chd->NextSiblingElement()) {
//[5.2] 获取子元素名称
CCLOG("chd : %s", chd->Name());
//[5.3] 遍历子元素的属性 id
// FirstAttribute() : 获取 chd元素          的第一个属性
// Next()           : 获取 chd元素的attr属性 的下一个属性
for (const XMLAttribute* attr = chd->FirstAttribute(); attr; attr = attr->Next()) {
// Name()  : 属性名称
// Value() : 属性值
CCLOG("chd_attr : %s , %s", attr->Name(), attr->Value());
}
// 也可以通过属性名称,来获取属性值
// CCLOG("id = %s", chd->Attribute("id"));
//[5.4] 遍历子元素chd的子元素 <name> , <age>
for (XMLElement* e = chd->FirstChildElement(); e; e = e->NextSiblingElement()) {
// 子元素e 为文本内容
// GetText() : 文本内容
CCLOG("e : %s , %s", e->Name(), e->GetText());
}
}
//[6] 释放内存
delete doc;

XML数据存储

以上面解析的 XML 文档为例,通过代码生成相应的 XML 文档并保存到文件中,示例代码如下:

//[0] 文件路径
std::string path = "/soft/cocos2d-x-3.4/projects/Demo34/Resources/testXML.xml";

//[1] 创建管理XML文档的对象:XMLDocument
XMLDocument* doc = new XMLDocument();
// <!-- begin -->

//[2] 创建XML声明,并连接到XML文档中
XMLDeclaration* declare = doc->NewDeclaration("xml version=\"1.0\" encoding=\"UTF-8\"");
doc->LinkEndChild(declare);  // 添加到文档尾部

//[3] 创建注释,并连接到XML文档中
XMLComment* comment = doc->NewComment("this is xml comment");
doc->LinkEndChild(comment);

//[4] 创建根节点,并连接到XML文档
XMLElement* root = doc->NewElement("person");
doc->InsertEndChild(root);   // 与LinkEndChild()功能相同

//[5] 创建两个子元素,并连接到root元素中,作为root的子节点
XMLElement* student = doc->NewElement("student");
XMLElement* teacher = doc->NewElement("teacher");
root->LinkEndChild(student); // 添加到root子元素中
root->LinkEndChild(teacher); // 添加到root子元素中

//[6] 设置/添加 <student>、<teacher> 的属性值
student->SetAttribute("id", "1111");
teacher->SetAttribute("id", "2222");

//[7] 创建子元素,并连接到<student>、<teacher>元素中,作为子节点
XMLElement* name1 = doc->NewElement("name");
XMLElement* name2 = doc->NewElement("name");
XMLElement* age1  = doc->NewElement("age");
XMLElement* age2  = doc->NewElement("age");
student->LinkEndChild(name1);
student->LinkEndChild(age1);
teacher->LinkEndChild(name2);
teacher->LinkEndChild(age2);

//[8] 创建文本内容,并添加到<name>、<age>元素中,作为文本内容
XMLText* name1_text = doc->NewText("Alice");
XMLText* name2_text = doc->NewText("Bob");
XMLText* age1_text  = doc->NewText("20");
XMLText* age2_text  = doc->NewText("30");
name1->LinkEndChild(name1_text);
name2->LinkEndChild(name2_text);
age1->LinkEndChild(age1_text);
age2->LinkEndChild(age2_text);
// <!-- ended -->

//[9] 保存XMLDocument数据到XML文档中
doc->SaveFile(path.c_str());
//[10] 释放内存
delete doc;

XML数据修改

以上面存储的 XML 文档为例,进行数据的修改操作,示例代码如下:

//[0] 文件路径
std::string path = "/soft/cocos2d-x-3.4/projects/Demo34/Resources/testXML.xml";
//[1] 创建管理XML文档的对象:XMLDocument
XMLDocument* doc = new XMLDocument();
//[2] 解析xml文件
XMLError errorID = doc->LoadFile(path.c_str());
//[3] 判断是否解析错误
if (errorID != 0) {
CCLOG("Parse Error!");
return;
}
//[4] 获取根元素 <person>
XMLElement* root = doc->RootElement();
//[5] 获取子元素 <student>、<teacher>
XMLElement* student = root->FirstChildElement();
XMLElement* teacher = student->NextSiblingElement();
// <!-- begin -->
//[6] 修改数据
//[6.1] 添加新元素 <city>
XMLElement* city = doc->NewElement("city");
XMLText* city_text = doc->NewText("北京");
city->LinkEndChild(city_text);
student->LinkEndChild(city);
//[6.2] 添加新属性 <type>
root->SetAttribute("type", "学校人群");
//[6.3] 修改元素名称
student->SetName("学生");
//[6.4] 修改属性值
student->SetAttribute("id", "9999");
//[6.5] 删除元素
root->DeleteChild(teacher);
// <!-- ended -->
//[7] 保存XMLDocument修改后的数据,到XML文档中
doc->SaveFile(path.c_str());
//[8] 释放内存
delete doc;

常用数据操作

这里介绍一下常用的 4 个类的使用方法。

XMLNode

表示一个节点,包含一般方法,如访问子节点、兄弟节点、编辑自身、编辑子节点。它是 XMLDocumentXMLElementXMLAttribute 的父类。常用方法如下:

// 获得该XMLNode节点所在的XMLDocument文档
XMLDocument* GetDocument();
// 获取'value'值
//  Document : 空
//  Element  : 元素名称
//  Comment  : 注释内容
//  Text     : 文本内容
const char* Value();
// 设置Node节点的value值
void SetValue(const char* val);
// 获取关联节点
// 获取父节点
XMLNode* Parent();
// 获取第一个子节点,若没有返回null
// 获取最后一个子节点,若没有返回null
// 获取前一个兄弟节点
// 获取下一个兄弟节点
XMLNode* FirstChild();
XMLNode* LastChild();
XMLNode* PreviousSibling();
XMLNode* NextSibling();
// 获取第一个子元素
// 获取最后一个子元素
// 获取前一个兄弟元素
// 获取下一个兄弟元素
XMLElement* FirstChildElement();
XMLElement* LastChildElement();
XMLElement* PreviousSiblingElement();
XMLElement* NextSiblingElement();
// 插入子节点
// 放在最前面
XMLNode* InsertFirstChild(XMLNode* addThis);
// 放在最后面
XMLNode* InsertEndChild(XMLNode* addThis);
XMLNode* LinkEndChild(XMLNode* addThis) { return InsertEndChild(addThis); }
// 放在指定afterThis子节点的后面
XMLNode* InsertAfterChild(XMLNode* afterThis, XMLNode* addThis);
// 删除子节点
// 删除所有子节点
void DeleteChildren();
// 删除指定node子节点
void DeleteChild(XMLNode* node);

XMLDocument

表示整个 XML 文档,不对应其中某个特定的节点。父类为 XMLNode,拥有父类所有的方法。常用方法如下:

// 解析xml串,需要先通过FileUtils类获取xml文件的内容串
XMLError Parse(const char* xml);
// 解析xml文件
XMLError LoadFile(const char* filename);
// 将XMLDocument的xml内容保存到filename文件中
XMLError SaveFile(const char* filename);
// 获取根节点
XMLElement* RootElement() { return FirstChildElement(); }
// 新建
// 元素 <a></a>
XMLElement* NewElement(const char* name);
// 注释 <!-- ... -->
XMLComment* NewComment(const char* comment);
// 文本内容 hello world
XMLText* NewText(const char* text);
// 声明 <? ... ?>
//  如:<?xml version="1.0" encoding="UTF-8"?>
XMLDeclaration* NewDeclaration(const char* text=0);
// 删除
// 删除指定节点
void DeleteNode(XMLNode* node) { node->_parent->DeleteChild( node ); }

XMLElement

表示元素节点,可以包含子节点 XMLElement 和属性 XMLAttribute。父类为 XMLNode,拥有父类所有的方法。常用方法如下:

// 获取/设置 元素名称,等价于Value
const char* Name()            { return Value(); }
void SetName(const char* str) { SetValue( str, staticMem ); }
// 获取元素的文本内容,若没有返回空
const char* GetText() const;
// 获取指定名称属性的属性值
const char* Attribute(const char* name);      // 字符串
bool BoolAttribute(const char* name);         // bool
int IntAttribute( const char* name );         // int
unsigned UnsignedAttribute(const char* name); // unsigned int
float FloatAttribute( const char* name );     // float
double DoubleAttribute(const char* name);     // double
// 获取第一个属性
const XMLAttribute* FirstAttribute();
// 获取指定名称属性
const XMLAttribute* FindAttribute(const char* name);
// 设置属性
void SetAttribute(const char* name, const char* value);
void SetAttribute(const char* name, bool value);
void SetAttribute(const char* name, int value);
void SetAttribute(const char* name, unsigned value);
void SetAttribute(const char* name, double value);
// 删除指定属性
void DeleteAttribute(const char* name);

XMLAttribute

表示一个元素的属性,没有父类。常用方法如下:

// 获取属性名称
const char* Name();
// 获取下一个属性
// 该属性对应的元素中,定义在该属性后面的属性
XMLAttribute* Next();
// 获取属性值
const char* Value();
bool BoolValue();
int IntValue();
unsigned int UnsignedValue();
float FloatValue();
double DoubleValue();
// 设置属性值
void SetAttribute(const char *value);
void SetAttribute(bool value);
void SetAttribute(int value);
void SetAttribute(unsigned int value);
void SetAttribute(float value);
void SetAttribute(double value);