2D游戏引擎制作:读取XML文件 3
2018-03-03 21:29
399 查看
读取XML文件 3
前言
经过前两篇的练习,笔者大概知道应该怎么写,本篇想再写一次能通用的读取XML文件的代码,所以上文代码再次全删。这篇文章不是新建的而是将上一篇博客:读取XML文件 2的内容全部替换了(只是想复制上篇内容的格式,结果没有新建就在原来文章上改了),读取XML文件 2的内容已经看不到了(感觉上一篇白写了,笔者的心血啊!虽然没人看,技术水平也不高,但也是辛苦了一天的成果)。
多说一句:番茄助手不想让注释中出现红色波浪线可以取消此复选框。
正文
笔者知道的编码模式是UNICODE、ANSI,当笔者使用notepad++给文件进行转码时,给出编码选项却是这样:上网一查才知道UCS-2是什么,不清楚的读者可以参考链接:http://www.fmddlmyy.cn/text6.html,
http://blog.csdn.net/ztsinghua/article/details/44277833,
笔者希望接下来读取XML文件的方法能够通用一点,所以参考了SlimXML的写法。
关于转码部分参考链接:http://blog.csdn.net/x_iya/article/details/16849765。
第三阶段:
首先,要读取文件内容,先判断文件使用什么编码规则。UTF_16大端小端和带BOM的UTF_8比较容易分辨,
至于UTF_8还是ANSI,判断过程可以参考链接:http://blog.csdn.net/bladeandmaster88/article/details/54767487,
伪代码如下:
首先新建一个文件用来放读取XML文件的代码。
头文件:
#pragma once #include <fstream> #include <string> #include <codecvt> #include <unordered_map> #include <stack> using namespace std;
定义一个用来存储属性的类:
class LLXMLProperty { public: LLXMLProperty(wstring name); void SetValue(wstring value); wstring GetName(); wstring GetValue(); int GetValueInt(); float GetValueFloat(); bool GetValueBool(); private: wstring name; wstring value; }; LLXMLProperty::LLXMLProperty(wstring name) { this->name = name; } void LLXMLProperty::SetValue(wstring value) { this->value = value; } wstring LLXMLProperty::GetName() { return name; } wstring LLXMLProperty::GetValue() { return value; } int LLXMLProperty::GetValueInt() { return _wtoi(value.c_str()); } float LLXMLProperty::GetValueFloat() { return _wtof(value.c_str()); } bool LLXMLProperty::GetValueBool() { return !(value == L"" 10cae || value == L"0" || value == L"false" || value == L"False" || value == L"FALSE") ; }
定义一个用来存储节点的类(没有写获得子节点和属性的方法,最简单的方法就是将propertyMap和childNodeList改成public,也可以写一个GetNextNode()之类的方法。):
class LLXMLNode { public: LLXMLNode(wstring name); void AddProperty(LLXMLProperty* llProperty); void AddNode(LLXMLNode* llNode); wstring GetName(); private: wstring name; unordered_map<wstring, LLXMLProperty*> propertyMap; list<LLXMLNode*> childNodeList; }; LLXMLNode::LLXMLNode(wstring name) { this->name = name; } void LLXMLNode::AddProperty(LLXMLProperty* llProperty) { propertyMap[llProperty->GetName()] = llProperty; } void LLXMLNode::AddNode(LLXMLNode* llNode) { childNodeList.push_back(llNode); } wstring LLXMLNode::GetName() { return name; }
定义常用文件编码:
enum class FileEncode { ANSI,//ANSI编码 UTF_8_WITH_BOM,//UTF_8使用BOM标记 UTF_8_NO_BOM,//UTF_8无BOM标记 UTF_16_LITTLE_ENDIAN,//UTF_16小端,低字节在前,高字节在后 UTF_16_BIG_ENDIAN//UTF_16大端,高字节在前,低字节在后 };
定义一个用来存储XML文件的类:
class LLXMLDocument { public: bool LoadXMLFromFile(wstring filePath); bool SaveXMLToFile(wstring filePath); LLXMLNode* GetRootNode(); private: FileEncode CheckFileEncode(wifstream& file); bool WCharCanIgnore(wchar_t wc); bool WCharIsLegalNameStart(wchar_t wc); bool WCharIsLegalName(wchar_t wc); bool LoadUnknown(wchar_t*& fileBuffer,int& bufferSize);//不知道接下来的内容,用于判断应该使用哪个方法继续读取。 bool LoadDefine(wchar_t*& fileBuffer, int& bufferSize);//加载声明 bool LoadComment(wchar_t*& fileBuffer, int& bufferSize);//加载注释 bool LoadNode(wchar_t*& fileBuffer, int& bufferSize);//加载节点 bool LoadProperty(wchar_t*& fileBuffer, int& bufferSize);//加载属性 wstring LoadFormatValue(wchar_t*& fileBuffer, int& bufferSize);//加载属性值 LLXMLNode* rootNode; stack<LLXMLNode*> nodeStack; };
最关键的就是怎么读取XML文件里的内容:
bool LLXMLDocument::LoadXMLFromFile(wstring filePath) { while (!nodeStack.empty())//清空栈 { nodeStack.pop(); } wifstream file(filePath, wifstream::binary );//一定要使用二进制读取,否则文件表头会读不到。而且在Windows下,文件中回车是“\n\r”,在获得文件长度时是2,读取却当成一个字符,有可能会影响编码判断。 if (file) { FileEncode fe = CheckFileEncode(file);//检查文件编码 int markBufferNum = 0; int markBufferLength = 0; switch (fe) { case FileEncode::ANSI: file.imbue(locale("")); break; case FileEncode::UTF_8_WITH_BOM: markBufferNum = 1; markBufferLength = 3; case FileEncode::UTF_8_NO_BOM: file.imbue(locale(locale::empty(), new codecvt_utf8<wchar_t>)); break; case FileEncode::UTF_16_LITTLE_ENDIAN: //只有这种编码笔者没有找到可用的编码转换,先空出来,有高手可以帮忙吗?。 markBufferNum = 1; markBufferLength = 1; break; case FileEncode::UTF_16_BIG_ENDIAN: markBufferNum = 1; markBufferLength = 1; file.imbue(locale(locale::empty(), new codecvt_utf16<wchar_t>)); break; default: break; } file.seekg(0, ios_base::end); // 移动到文件尾。 int fileLength = file.tellg(); // 取得当前位置的指针长度,即文件长度。 file.seekg(0, ios_base::beg); fileLength++;//需要多一位来存储文件结尾标记。 wchar_t* fileBuffer = new wchar_t[fileLength]; //使用file.read(fileBuffer, fileLength);读取文件时,正文部分正常,结尾处多出一串乱码,不知道原因。 file.get(fileBuffer, fileLength,EOF);// 读取到文件结尾,笔者在网上找到方法都是file.get(fileBuffer, fileLength);而且fileLength不需要加1位,怎么到笔者这儿都有问题? wchar_t* fileBufferStart = fileBuffer;//标记开头位置,用来delete。 wchar_t* fileBufferEnd = fileBuffer + fileLength; fileBuffer += markBufferNum; fileLength -= markBufferLength; //这两行用来移除开头标记,应该移多少是笔者试出来的,有一点需要注意,使用二进制读取后按字符判断和移动可能会有问题,使用read读取出现乱码可能就是这个原因,也不确定。 //开始分析语法。 //秘技:多重判断之术 while (fileBuffer<fileBufferEnd&&fileLength>0&&*fileBuffer!= L'\0') { if(WCharCanIgnore(*fileBuffer))//检验是否为可以过滤的字符。 { fileBuffer++; fileLength--; } else if(!LoadUnknown(fileBuffer, fileLength))//如果需要考虑,再进行判断。 { delete[] fileBufferStart; file.close(); return false; } } delete[] fileBufferStart; //在这儿没有将fileBufferStart==NULL;因为fileBufferStart的作用域结束了,其他地方不会再使用fileBufferStart了,所以应该不会出现野指针的情况吧! file.close(); return true; } else { return false; } return false; } //检查文件编码 FileEncode LLXMLDocument::CheckFileEncode(wifstream& file) { FileEncode fileEncode = FileEncode::UTF_8_NO_BOM;//默认UTF_8 wchar_t wch1, wch2; file.get(wch1); file.get(wch2);//从文件读取前四个字节 //不同的编码符合不同的规则 if (wch1 == 0xFF && wch2 == 0xFE) { fileEncode = FileEncode::UTF_16_LITTLE_ENDIAN; } else if(wch1 == 0xFE && wch2 == 0xFF) { fileEncode = FileEncode::UTF_16_BIG_ENDIAN; } else if (wch1 == 0xEF && wch2 == 0xBB) { wchar_t wch3; file.get(wch3); if (wch3 == 0xBF) { fileEncode = FileEncode::UTF_8_WITH_BOM; } } else { //之后就要判断是无BOM型UTF_8还是ANSI,需要将全文遍历找规则,这部分笔者直接拷贝了SlimXML的写法。 //(其实在Window下可以不用考虑无BOM型UTF_8,新建的文件会自动带标志,除非在别处下载的或强行转成无BOM格式,但为了通用性还是需要加上判断。) file.seekg(0, ios_base::end); // 移动到文件尾。 int fileLength = (int)file.tellg(); // 取得当前位置的指针长度,即文件长度。 fileLength++;//需要多一位来存储文件结尾标记。 file.seekg(0, ios_base::beg); // 移动到文件头。 wchar_t* fileBuffer = new wchar_t[fileLength]; wchar_t* fileBufferStartPos = fileBuffer; file.get(fileBuffer, fileLength,EOF); while (fileLength > 0) { wchar_t w = *fileBuffer; if ((w & 0x80) == 0)//0x80代表10000000,该方法判断首位是否为0,以下类似逐位判断。 { ++fileBuffer; --fileLength; } else { wchar_t w1 = *(fileBuffer + 1); if ((w & 0xf0) == 0xe0) { if (fileLength < 3) { fileEncode = FileEncode::ANSI; break; } if ((w1 & 0xc0) != 0x80 || (*(fileBuffer + 2) & 0xc0) != 0x80) { fileEncode = FileEncode::ANSI; break; } fileBuffer += 3; fileLength -= 3; } else if ((w1 & 0xe0) == 0xc0) { if (fileLength < 2) { fileEncode = FileEncode::ANSI; break; } int a = (w1 & 0xc0); if (a != 0x80) { fileEncode = FileEncode::ANSI; break; } fileBuffer += 2; fileLength -= 2; } else if ((w1 & 0xf8) == 0xf0) { if (fileLength < 4) { fileEncode = FileEncode::ANSI; break; } if ((w1 & 0xc0) != 0x80 || (*(fileBuffer + 2) & 0xc0) != 0x80 || (*(fileBuffer + 3) & 0xc0) != 0x80) { fileEncode = FileEncode::ANSI; break; } fileBuffer += 4; fileLength -= 4; } else { fileEncode = FileEncode::ANSI; break; } } } delete[] fileBufferStartPos; file.seekg(0, ios_base::beg); } return fileEncode; } //可以忽略的字符 bool LLXMLDocument::WCharCanIgnore(wchar_t wc) { return (wc == L' ')//半角空格 || (wc == L' ') //全角空格(输入法快捷键Shift+Space可以切换半角和全角) || wc == L'\n' //换行 || wc == L'\r'//回车(“\r”和“\r\n”编码一样) || wc == L'\t'//水平制表符 ; } //合法的命名开头,只判断首字符。 bool LLXMLDocument::WCharIsLegalNameStart(wchar_t wc) { return (L'a' <= wc&&wc <= L'z') || (L'A' <= wc&&wc <= L'Z'); } ////合法的命名,除首字符外使用此方法判断。 bool LLXMLDocument::WCharIsLegalName(wchar_t wc) { return (L'a' <= wc&&wc <= L'z') || (L'A' <= wc&&wc <= L'Z') || (L'0' <= wc&&wc <= L'9') || (wc == L'_'); } //在不知道接下来是什么时使用此方法判断,所以叫Unknown。 bool LLXMLDocument::LoadUnknown(wchar_t*& fileBuffer, int& bufferSize) { if (*fileBuffer == L'<')//xml的标记。 { fileBuffer++; bufferSize--; if (*fileBuffer==L'?')//声明标志。 { fileBuffer++; bufferSize--; LoadDefine(fileBuffer, bufferSize); } else if (*fileBuffer == L'!')//注释标志。 { if (*(fileBuffer + 1) == L'-'&&*(fileBuffer + 2) == L'-') { fileBuffer+=3; bufferSize-=3; LoadComment(fileBuffer, bufferSize); } else { return false; } } else if(*fileBuffer == L'/')//上一个节点结束标志。 { fileBuffer++; bufferSize--; wchar_t* nameStart = fileBuffer; while (WCharIsLegalName(*fileBuffer)) { fileBuffer++; bufferSize--; if (bufferSize == 0) { return false; } } wstring nodeName = wstring(nameStart, fileBuffer - nameStart); if (nodeStack.top()->GetName() == nodeName&& *fileBuffer==L'>') { nodeStack.pop(); fileBuffer++; bufferSize--; } else { return false; } } else { LoadNode(fileBuffer, bufferSize);//以上情况都不是的情况下开始读取节点。 } } else { return false; } return true; } //读取声明和注释的先略过 bool LLXMLDocument::LoadDefine(wchar_t*& fileBuffer, int& bufferSize) { while (*fileBuffer != L'>') { fileBuffer++; bufferSize--; if (bufferSize == 0) { return false; } } fileBuffer++; bufferSize--; return true; } bool LLXMLDocument::LoadComment(wchar_t *& fileBuffer, int & bufferSize) { while (!((*fileBuffer == L'-')&& (*(fileBuffer+1) == L'-')&&(*(fileBuffer + 2) == L'>'))) { fileBuffer++; bufferSize--; if (bufferSize == 0) { return false; } } fileBuffer+=3; bufferSize-=3; return true; } //加载节点 bool LLXMLDocument::LoadNode(wchar_t*& fileBuffer, int& bufferSize) { //直接开始读首字符,笔者使用NodePad++写XML文件时,’<’和命名之间好像不能有空格,其实加上忽略空格的判断也可以。 if (WCharIsLegalNameStart(*fileBuffer)) { wchar_t* nameStart = fileBuffer; fileBuffer++; bufferSize--; while (WCharIsLegalName(*fileBuffer)) { fileBuffer++; bufferSize--; if (bufferSize == 0) { return false; } } wstring nodeName = wstring(nameStart, fileBuffer- nameStart); LLXMLNode* node = new LLXMLNode(nodeName); if (!nodeStack.empty())//使用栈来存节点。 { nodeStack.top()->AddNode(node); } else { rootNode = node;//空节点。 } nodeStack.push(node); while (true)//循环读取属性,是不是不应该使用while true? { while (WCharCanIgnore(*fileBuffer)) { fileBuffer++; bufferSize--; if (bufferSize == 0) { return false; } } if (*fileBuffer == L'>') { fileBuffer ++; bufferSize --; return true; } else if (*fileBuffer == L'/'&&*(fileBuffer + 1)==L'>') { fileBuffer += 2; bufferSize -= 2; nodeStack.pop(); return true; } else { if (!LoadProperty(fileBuffer, bufferSize))//加载属性 { return false; } } } } else { return false; } return true; } //加载属性,和加载节点类似,先加载属性名,之后判断’=’,然后读取属性值。 bool LLXMLDocument::LoadProperty(wchar_t *& fileBuffer, int & bufferSize) { if (WCharIsLegalNameStart(*fileBuffer)) { wchar_t* nameStart = fileBuffer; fileBuffer++; bufferSize--; while (WCharIsLegalName(*fileBuffer)) { fileBuffer++; bufferSize--; if (bufferSize == 0) { return false; } } wstring propertyName = wstring(nameStart, fileBuffer - nameStart); LLXMLProperty* llProperty = new LLXMLProperty(propertyName); while (*fileBuffer != L'=') { fileBuffer++; bufferSize--; if (bufferSize == 0) { return false; } } fileBuffer++; bufferSize--; while (*fileBuffer != L'"') { fileBuffer++; bufferSize--; if (bufferSize == 0) { return false; } } fileBuffer++; bufferSize--; llProperty->SetValue(LoadFormatValue(fileBuffer, bufferSize));//将属性值进行格式化。 if (bufferSize == 0) { return false; } if (!nodeStack.empty()) { nodeStack.top()->AddProperty(llProperty); } else { return false; } } else { return false; } return true; } //格式化属性值,暂时将从起点到符号’”’之间的内容当成属性是,转义字符之类的判断应该在此完成,但先不考虑这么多。 wstring LLXMLDocument::LoadFormatValue(wchar_t*& fileBuffer, int& bufferSize) { wchar_t* valueStart = fileBuffer; while (*fileBuffer!=L'"') { fileBuffer++; bufferSize--; } wstring value = wstring(valueStart, fileBuffer - valueStart); fileBuffer++; bufferSize--; return value; }
至此,读取XML格式的文件基本完成。
尚未解决的问题:
1、 在判断编码和进行语法分析时读取了两次文件,可能会影响速度,可是只读一次的话,先进行编码判断后就要自行转码了,笔者不想在代码里出现大段转码的内容,除非必要,像官方提供的file.imbue(locale(“”));直接转成ANSI来读取是最好的。
2、 全文中使用了fileBuffer和bufferSize,但是笔者发现好像bufferSize好像没有用上,判断文件结尾直接使用”\0”就可以了,要不要删掉它?
3、 格式化属性值还没有完成。
4、 UTF_16 LittleEndian还没有完成。
5、 保存等内容也没有完成。
6、 判断可忽略字符使用while(······)和buffer++,size—次数太多,应该可以精简。
结束语
不删了,绝对不删了,只不过是读取XML文件,太影响进度了,再有需求直接在上文代码基础上改动就好了。
相关文章推荐
- 2D游戏引擎制作:读取XML文件 1
- 2D游戏引擎制作:读取XML文件 4
- 2D游戏引擎制作:窗体封装和唯一窗体运行
- Unity3D 游戏引擎之IOS Android支持中文与本地文件的读取写入
- [TORQUE游戏引擎DTS文件]制作动物的DTS模型和对应的DSQ文件(DTS for animal)
- 2D游戏引擎制作:前言
- 【Unity 3D 游戏引擎】使用 2DToolkit 插件 制作2D精灵动画
- 2D游戏引擎制作:添加游戏类并修改XML读取类
- flash开源游戏引擎pushButton学习笔记(4)----xml组件文件
- Android下使用TinyXml读取xml配置文件(Cocos2d-x游戏开发)
- Android下使用TinyXml读取xml配置文件(Cocos2d-x游戏开发)
- Cocos2d-x 游戏开发中读取XML文件拷贝
- Android_Sax引擎读取xml文件
- 《Genesis-3D开源游戏引擎--横版格斗游戏制作教程:简介及目录》(附上完整工程文件)
- XML文件读取方式
- 解决 IDEA 中src下xml等资源文件无法读取的问题
- Java眼中的XML---文件读取
- XML文件读取
- 使用Cocos2d-x 3.0和物理引擎制作简单的platformer游戏
- 用JAVA读取XML文件