【iOS-cocos2d-X 游戏开发之九】Cocos2dx利用CCSAXParser解析xml数据&CCMutableDictionary使用与注意!

时间:2022-11-25 13:57:17

本站文章均为​ 李华明Himi ​​原创,转载务必在明显处注明


本章Himi给大家分享如何在Cocos2dX中解析xml数据;对于数据存取有很多方式,流文件,plist,xml等,那么为了跨平台更好的支持,Himi想到之前写的CCUserDefault 存储数据一节,Cocos2dx自带的存储类,一旦存入数据都会以xml格式进行保存,适用于iOS、Android等平台,所以这里Himi使用xml进行游戏的一些数据录入  = =.. 另外一方面Himi本章节也是基于Cocos2dx引擎代码进行的一次简单对xml数据解析的封装;

为了更保险的去考虑跨平台,所以对于xml存储这块的解析,也做了些搜索,最后发现王哥(王哲-cocos2dx引擎作者)也有给我们提示过,内容如下:

​​cocos2dx里面集成了libxml2,ios上会调用sdk里面内置的,​​       

​​android和win32上则带了已经编译好的静态/动态库。​​

​​你可以参考CCSAXParser里面的代码来使用libxml2​​


那么既然如此,就对Cocos2dx引擎源码的CCSAXParser类进行了剖析,那么这里Himi,先给出代码,然后再详细讲解下:

Himi封装的HXmlParse类:


​HXmlParse.h​


 


​//​


​//  HXmlParse.h​


​//  HAnimation​


​//​


​//  Created by Himi on 12-3-22.​


​//  Copyright (c) 2012年 Augustimpression. All rights reserved.​


​//​


 


​#ifndef HAnimation_HXmlParse_h​


​#define HAnimation_HXmlParse_h​


 


​#include "cocos2d.h"​


​#include "CCSAXParser.h"​


​#include "CCObject.h"​


​#include "CCMutableDictionary.h"​


 


​using​​ ​​namespace​​ ​​cocos2d;​


 


​class​​ ​​CC_DLL HXmlParse :​​ ​​public​​ ​​CCObject, ​​ ​​public​​ ​​CCSAXDelegator​


​{​


 


​public​​ ​​:​


 


​static​​ ​​HXmlParse * parserWithFile(​​ ​​const​​ ​​char​​ ​​*tmxFile);​


 


​bool​​ ​​initHXmlParse(​​ ​​const​​ ​​char​​ ​​* xmlName);​


 


​//  使用 CCSAXDelegator 重写3个回调函数​


 


​void​​ ​​startElement(​​ ​​void​​ ​​*ctx, ​​ ​​const​​ ​​char​​ ​​*name, ​​ ​​const​​ ​​char​​ ​​**atts);​


 


​void​​ ​​endElement(​​ ​​void​​ ​​*ctx, ​​ ​​const​​ ​​char​​ ​​*name);​


 


​void​​ ​​textHandler(​​ ​​void​​ ​​*ctx, ​​ ​​const​​ ​​char​​ ​​*ch, ​​ ​​int​​ ​​len);​


 


​std::string root_name; ​


 


​bool​​ ​​isJumpHeadData;​


 


​CCMutableDictionary<std::string,CCString*> *mDic;​


 


​private​​ ​​:​


 


​std::string startXmlElement;​​ ​​//用来记录每个key前字段​


 


​std::string endXmlElement;​​ ​​//用来记录每个key后字段​


 


​std::string currString;​​ ​​//记录每个value的值​


 


​};​


 


​#endif​



​HXmlParse.cpp​


 


​//​


​//  HXmlParse.cpp​


​//  HAnimation​


​//​


​//  Created by Himi on 12-3-22.​


​//  Copyright (c) 2012年 Augustimpression. All rights reserved.​


​//​


 


​#include "HXmlParse.h"​


​#include "CCSAXParser.h"​


 


​HXmlParse * HXmlParse::parserWithFile(​​ ​​const​​ ​​char​​ ​​*tmxFile)​


​{​


​HXmlParse *pRet = ​​ ​​new​​ ​​HXmlParse();​


​if​​ ​​(pRet->initHXmlParse(tmxFile))​


​{​


​pRet->autorelease();​


​return​​ ​​pRet;​


​}​


​CC_SAFE_DELETE(pRet);​


​return​​ ​​NULL;​


​}​


 


​bool​​ ​​HXmlParse::initHXmlParse(​​ ​​const​​ ​​char​​ ​​* xmlName)​


 


​{​


 


​mDic = ​​ ​​new​​ ​​CCMutableDictionary<std::string,CCString*>();​


 


​CCSAXParser _par;  ​


 


​if​​ ​​(​​ ​​false​​ ​​== _par.init(​​ ​​"UTF-8"​​ ​​) )​


 


​{​


​CCLog(​​ ​​"-----请使用utf-8格式!"​​ ​​);​


​return​​ ​​false​​ ​​;​


​}​


​_par.setDelegator(​​ ​​this​​ ​​);​


​const​​ ​​char​​ ​​* _path =CCFileUtils::fullPathFromRelativePath(xmlName);​


​return​​ ​​_par.parse(_path);​


 


​}​


 


​//回调函数​


 


​void​​ ​​HXmlParse::startElement(​​ ​​void​​ ​​*ctx, ​​ ​​const​​ ​​char​​ ​​*name, ​​ ​​const​​ ​​char​​ ​​**atts)​


 


​{   ​


 


​CC_UNUSED_PARAM(ctx);​


 


​startXmlElement = (​​ ​​char​​ ​​*)name;​


​if​​ ​​(!isJumpHeadData){​​ ​​//跳过数据头​


​CCLog(​​ ​​"------跳过root name"​​ ​​);​


​isJumpHeadData=​​ ​​true​​ ​​;​


​root_name=startXmlElement;​


​return​​ ​​;​


​}​


 


​//    CCLog("-startElement----%s",startXmlElement.c_str());​


 


​}​


 


​void​​ ​​HXmlParse::endElement(​​ ​​void​​ ​​*ctx, ​​ ​​const​​ ​​char​​ ​​*name)​


 


​{​


​CC_UNUSED_PARAM(ctx);​


 


​endXmlElement = (​​ ​​char​​ ​​*)name;​


​if​​ ​​(endXmlElement==root_name){​​ ​​//数据尾​


​CCLog(​​ ​​"读取xml结束"​​ ​​);​


​isJumpHeadData=​​ ​​false​​ ​​;​


​root_name=​​ ​​""​​ ​​;​


​return​​ ​​;​


​}​


 


​//    CCLog("-endElement----%s",endXmlElement.c_str());​


​}​


​//键值对的结束字段​


​void​​ ​​HXmlParse::textHandler(​​ ​​void​​ ​​*ctx, ​​ ​​const​​ ​​char​​ ​​*ch, ​​ ​​int​​ ​​len)​


​{​


 


​CC_UNUSED_PARAM(ctx);​


​currString=string((​​ ​​char​​ ​​*)ch,0,len);​


​CCString *ccStr =​​ ​​new​​ ​​CCString();​​ ​​//备注3​


​ccStr->m_sString=currString;​


​if​​ ​​(root_name!=​​ ​​""​​ ​​){​


​mDic->setObject(ccStr,startXmlElement);​


​CCLog(​​ ​​"-----key:%s, value:%s"​​ ​​,startXmlElement.c_str(),mDic->objectForKey(startXmlElement)->m_sString.c_str());​


​}​


 


​//    CCLog("-textHandler----%s",currString.c_str());​


 


​}​


OK,代码呢我们先从.h中来说,首先我们使用CCSAXDelegator,为了让CCSAXParser解析数据后将数据回调给如下三个函数:


​//  使用 CCSAXDelegator 重写3个回调函数​


 


​void​​ ​​startElement(​​ ​​void​​ ​​*ctx, ​​ ​​const​​ ​​char​​ ​​*name, ​​ ​​const​​ ​​char​​ ​​**atts);​


 


​void​​ ​​endElement(​​ ​​void​​ ​​*ctx, ​​ ​​const​​ ​​char​​ ​​*name);​


 


​void​​ ​​textHandler(​​ ​​void​​ ​​*ctx, ​​ ​​const​​ ​​char​​ ​​*ch, ​​ ​​int​​ ​​len);​


startElement   函数解析的是xml的每个key前字段

textHandler  函数解析出来的是xml每个key对应的value值

endElement   函数解析出来的是xml的每个key后字段

这里Himi随便写了一个xml来做测试,himi.xml,如下:


​<?xml version=​​ ​​"1.0"​​ ​​encoding=​​ ​​"utf-8"​​ ​​?><himiTestData><key1>1000</key1><key2>娃哈哈</key2><key3>82.3</key3><key4>4000</key4><key5>himi</key5><key6>​​ ​​true​​ ​​</key6></himiTestData>​


那么CCSAXParser类解析第一次回调 startElement 是读取的是root name(xml数据头标识名称->“<himiTestData>”),然后才读取正式数据key和value,最后读取的也是xml数据尾标识名称“</himiTestData>”

当然在Himi封装的HXmlParse类中对于数据标识的读取都跳过了,使用变量isJumpHeadData来处理的;

其他的都很容易没有什么可说的,主要要说还有一点就是关于CCMutableDictionary的使用,对于此类主要结构是形成map&NSMutableDictionary类似是个键值对容器,key-value;那么使用时候要注意4点:

1.  比如Himi解析数据后都会默认将key和value数据存放在CCMutableDictionary中,那么这里我肯定传入的是两个string,但是细心的童鞋会发现代码中第二个并不是std::string,而是CCString对象,嗯 没错,CCMutableDictionary要求传入的是CCObject对象而不是基本类型!CCString中有m_sString这个属性,所以转换起来也是很方便的;

2. 使用CCMutableDictionary进行添加数据setObject的时候要注意此函数的两个参数:


​bool​​ ​​setObject(_ValueT pObject, ​​ ​​const​​ ​​_KeyT& key)​


​{​


​pair<CCObjectMapIter, ​​ ​​bool​​ ​​> pr;​


 


​pr = m_Map.insert( Int_Pair(key, pObject) );​


 


​if​​ ​​(pr.second == ​​ ​​true​​ ​​)​


​{​


​pObject->retain();​


​return​​ ​​true​​ ​​;​


​}​


 


​return​​ ​​false​​ ​​;​


​}​


上面这个是cocos2dx引擎中源码中setObject函数实现代码,这里可以很清晰的看到,第一个参数表示《CCObject》,第二个参数才是《Key》!这点对于之前做过java开发的我来说比较郁闷,因为一般都是key在第一个参数。。。。。

3. 请大家仔细看HXmlParse.cpp类中的备注3 ,当你使用CCMutableDictionary的setObject函数的时候,务必要注意,此函数的存入的CCObject参数,引擎中实现代码是对你这个CCObject进行retain()的一个内存地址引用!也就是说这里不要使用一个成员变量来使用,否则你从CCMutableDictionary取出来的数据全部是你最后一个CCObject数据!

4. CCMutableDictionary是cocos2d-x自己封装的类,功能类似NSMutableDictionary。但是Himi通过测试发现!它有一点和NSMutableDictionary是不一样的。NSMutableDictionary的setObjectForKey方法是:如果发现这个key已经存在于字典中的时候,它会自动用新的object覆盖掉原有的object。而CCMutableDictionary由于它是使用map实现的字典功能,而在map里面,如果key已存在,是不会用新的object覆盖掉原有object的。在使用CCMutableDictionary的时候需要特别注意这一点。

HXmlParse解析xml类使用方法很简单:


​#include "HXmlParse.h"​


 


​HXmlParse::parserWithFile(​​ ​​"himi.xml"​​ ​​);​


然后Himi为了证实此解析类在Android也可以正常运行,那么这里Himi将读出的数据展示在画面上,iOS运行截图如下:

​​

 

​​