1.2 读写XML文件
XML(Extensible Markup Language,可扩展标记性语言)文件是一种广为人知的文件格式,是SGML(标准通用标记性语言)的子集,HTML也是一种标记性语言,以ML结尾的一般都是标记性语言,它们的共同点就是使用标签来描述信息。例如,HTML文件格式都会有一对这样的标签<html></html>,而XML也是用自定义的标签来描述数据的。但HTML和XML最大的区别在于,HTML描述的是显示信息,而XML描述的是存储信息。
XML作为一种存储格式,特点是简单,能够很快速地将要存储的内容描述出来,哪怕内容很复杂,XML都可以描述出来。而且在程序实现方面,XML有众多的开源库,这些开源库大多简单易用,可以轻易地使用XML,在Cocos2d-x里也集成了XML库。所以当你需要读点什么配置的话,XML应该会快速出现在脑海中。
那么XML有什么弊端吗?首先它是一种文本格式,也就是说,安全性很低,一般不适用于做游戏的存档文件,只要玩家找到存档的XML文件,即可修改存档,这对有些游戏可能无所谓,但对有些游戏可能是致命的。除了不适用于存储,用它来做配置文件应该是妥妥的了吧?也不一定,程序员一般喜欢XML,但大部分的游戏策划是不喜欢XML的,因为对他们而言,直接导出Excel表格要省事多了。策划的表格一般都是Excel表格,如果你的文件需要由策划人员来维护,最好还是使用CSV格式。另外如果是用于网络传输,那么XML相比起二进制或JSON、protocolbuffer等格式而言,需要耗费更多的流量。
1.2.1 XML格式简介
接下来简单介绍一下XML格式是什么样的。在XML中,使用节点来描述一个对象,一个节点由一对标签括起来,节点有名字和属性,节点之间可以嵌套,一个节点下可以有多个子节点,可以参考下面这个XML文件。
<? xml version="1.0" encoding="utf-8"? > <root> <! -- 注释 --> <player attack="99" hp="100" def="50" speed="666"> <weapon attack="50" /> <shoes speed="100" /> </player> </root>
上面的XML文件开头的第一行是XML的序言,告诉我们使用的XML版本以及文件编码,这一行可以直接复制到你的XML文件中。接下来是节点,每个XML文件都有且只有一个根节点。每个节点可以有任意的属性,这里的根节点叫root,在根节点下,有一个叫player的子节点,在这个player子节点下,又有两个子节点,分别是weapon和shoes,这个简单的XML文件描述了一个Player对象,Player对象有攻击力、生命值、防御力、速度等属性,Player装备了武器和鞋子对象。
1.2.2 使用TinyXML读取XML
Cocos2d-x在早期是使用libxml2来处理XML文件,但后面改用了tinyxml,这里简单介绍一下如何使用tinyxml来处理XML文件。以前面介绍的XML文件为例,了解一下如何使用tinyxml来读取XML文件。首先需要包含tinyxml的头文件:
#include "tinyxml2/tinyxml2.h"
在读取的时候,先创建XML文档对象,然后从文档中获取根节点,再根据XML文件的设计,按照设计的格式来读取XML中的节点,并查询节点相关的属性。
节点是XML文件中最基础的对象,tinyxml使用XMLNode来定义它,XMLDocument(XML文档)、XMLElement(XML元素)、XMLComment(XML注释)、XMLDeclaration(XML声明)、XMLText(XML文本)等对象都是节点,它们都对应XML文件中的一个标签。另外tinyxml还使用了XMLAttribute来描述节点的属性。下面的代码演示了如何读取XML示例文件。
//初始化XML tinyxml2::XMLDocument* xmlDoc = new tinyxml2::XMLDocument(); std::string xmlFile = FileUtils::getInstance()->getWritablePath() + "myxml.xml"; std::string xmlBuffer = FileUtils::getInstance()->getStringFromFile(xmlFile); if (xmlBuffer.empty()) { CCLOG("load xml file %s faile", xmlFile.c_str()); return ret; } //将XML文件的内容进行解析 xmlDoc->Parse(xmlBuffer.c_str(), xmlBuffer.size()); //获取XML文档的根节点 auto root = xmlDoc->RootElement(); if (root == nullptr) { return ret; } //获取根节点下面的player节点 auto playerNode = root->FirstChildElement(); if (playerNode == nullptr) { return ret; } //递归打印节点的属性以及其所有的子节点 dumpXmlNode(playerNode, ""); delete xmlDoc;
上面的代码使用了一个dumpXmlNode()方法来递归打印节点的属性及其所有的子节点,下面是dumpXmlNode的实现。
void dumpXmlNode(tinyxml2::XMLElement* node, std::string prefix) { CCLOG((prefix + "%s").c_str(), node->Name()); auto nodeAttr = node->FirstAttribute(); //逐个取出属性,并打印属性名和属性值 prefix += "\t"; while (nodeAttr) { CCLOG((prefix + "%s attribute %s - %d").c_str(), node->Name(), nodeAttr->Name(), nodeAttr->IntValue()); nodeAttr = nodeAttr->Next(); } //递归查找子节点 auto child = node->FirstChildElement(); while (child) { dumpXmlNode(child, prefix); child = child->NextSiblingElement(); } }
运行程序后会输出以下内容:
player1 player1 attribute attack -99 player1 attribute hp -100 player1 attribute def -50 player1 attribute speed -666 weapon weapon attribute attack -50 shoes shoes attribute speed -100
1.2.3 使用TinyXML写入XML
接下来了解一下如何写入XML文件,首先需要创建一个XML文档对象,然后构建一个节点树挂载到文档下,最后将文档对象保存起来。如果我们要做的是在已有的XML文件中进行修改,可以先调用XMLDocument的Parse()方法将XML文件解析到文档对象中,然后再对文档对象进行修改。下面的代码演示了如何创建XML文档对象并保存到XML文件中。
//创建一个XML文档 tinyxml2::XMLDocument* xmlDoc = new tinyxml2::XMLDocument(); //添加一个XML文档声明 tinyxml2::XMLDeclaration *pDeclaration = xmlDoc->NewDeclaration(nullptr); xmlDoc->LinkEndChild(pDeclaration); std::string xmlFile = FileUtils::getInstance()->getWritablePath() + "myxml.xml"; //添加根节点 tinyxml2::XMLElement *pRootEle = xmlDoc->NewElement("root"); xmlDoc->LinkEndChild(pRootEle); //构建节点,并将节点添加到根节点下 tinyxml2::XMLElement* playerNode = buildXmlNode(xmlDoc); pRootEle->LinkEndChild(playerNode); //保存文档 xmlDoc->SaveFile(xmlFile.c_str()); delete xmlDoc;
buildXmlNode()方法构建了与示例文件一样的节点结构,buildXmlNode()的实现如下。
tinyxml2::XMLElement* buildXmlNode(tinyxml2::XMLDocument* doc) { //玩家节点 tinyxml2::XMLElement* playerNode = doc->NewElement("player"); playerNode->SetAttribute("attack", 99); playerNode->SetAttribute("hp", 100); playerNode->SetAttribute("def", 50); playerNode->SetAttribute("speed", 666); //武器节点 tinyxml2::XMLElement* weapon = doc->NewElement("weapon"); weapon->SetAttribute("attack", 50); playerNode->LinkEndChild(weapon); //装备节点 tinyxml2::XMLElement* shoes = doc->NewElement("shoes"); shoes->SetAttribute("speed", 100); playerNode->LinkEndChild(shoes); return playerNode; }
更多关于XML的读写操作,可以参考Cocos2d-x引擎源码中UserDefault的实现。