#ifndef _INI_DOCUMENT_H_
#define _INI_DOCUMENT_H_
#define ASCIILINESZ 1024 //每行文本能容纳文字的最大长度
#define ASCIISECTIONSZ 256 //每个Section的最大长度
#define ASCIIKEYSZ 256 //每个Key的最大长度
#define ASCIIVALUESZ 256 //每个Value的最大长度
#define ASCIIDESCSZ 512 //每个注释的最大长度
#include "RBTree.h"
#include "Section.h"
#include "Key.h"
#include "Value.h"
namespace INI
{
using KeyMap = RBTREE::RBTree<Key, Value>;
using SectionMap = RBTREE::RBTree<Section, KeyMap>;
class IniDocument
{
typedef enum class _line_status_ : unsigned char
{
LINE_ERROR,//未知行
LINE_DESCRIPTION,//注释行
LINE_SECTION,//节点行
LINE_KEY//键值行
} line_status;
public:
IniDocument(const char* _szFile);
~IniDocument();
KeyMap& operator[](const char* _szSection);
void Clear();
private:
void load();
line_status parse(char* _szLine, char* _szSection, char* _szKey, char* _szValue, char* _szDesc);
private:
char* m_szFile;//文件路径
SectionMap m_sectionMap; //存储section的映射
};
}
#endif // !_INI_DOCUMENT_H_
#include "../inc/IniDocument.h"
INI::IniDocument::IniDocument(const char* _szFile) :
m_szFile(nullptr)
{
if (!_szFile || _szFile[0] == '\0')
{
//错误处理: 文件路径不能为空
throw("文件路径不能为空.");
}
size_t nLen = strlen(_szFile);
size_t nSize = ((nLen + 1) + 3) & ~3;
if (nLen >= _MAX_PATH)
{
//错误处理: 文件路径过长
throw("文件路径过长.");
}
m_szFile = new char[nSize];
memcpy(m_szFile, _szFile, nLen + 1);
load(); //加载文件内容到内存
}
INI::IniDocument::~IniDocument()
{
Clear();
}
INI::KeyMap& INI::IniDocument::operator[](const char* _szSection)
{
//返回对应Section的映射
return m_sectionMap[Section(_szSection)];
}
void INI::IniDocument::Clear()
{
if (m_szFile)
{
delete[] m_szFile;
m_szFile = nullptr;
}
for (SectionMap::iterator it = m_sectionMap.begin(); it != m_sectionMap.end(); ++it)
{
//清空每个Section的映射
it->second.Clear();
}
m_sectionMap.Clear(); //清空Section映射
}
void INI::IniDocument::load()
{
/*特别注意
读出的ini内容一般都是utf-8,而保存在内存中的内容也是utf-8.直接保存到文件没问题
但如果查找就会出问题。要把查找的中文字串 转成utf8再查找才能找到
但每次查找section或者key都转一次utf8就浪费了
解决方案:
从文件解析出来的utf8内容转回普通char字串。再保存到内存中,这样代码直接就能查找到。
而保存回文件的时候,把内存的字串转回utf8再保存即可
如果不想频繁转换,直接使用ANSI编码文件即可
*/
#pragma warning(disable:4996)
FILE* pFile = fopen(m_szFile, "r");
#pragma warning(default:4996)
if (!pFile)
{
printf("<IniDocument::load> 打开Ini文件失败,路径: %s\n", m_szFile);
return;
}
char szLineBuffer[ASCIILINESZ] = { 0 };//把文件中的内容逐行读入此内存中
char szSection[ASCIISECTIONSZ] = { 0 };;//把节点解析完成后放入此内存中
char szKey[ASCIIKEYSZ] = { 0 };;//把Key解析完成后放入此内存中
char szValue[ASCIIVALUESZ] = { 0 };;//把Value解析完成后放入此内存中
char szDesc[ASCIIDESCSZ] = { 0 };;//把注释读入此内存中
size_t nLines = 0; //记录当前的行数
size_t nLen = 0; //记录当前行的长度
KeyMap* pKeyMap = nullptr; //用于存储当前解析的Key映射
Desc desc; //用于存储当前解析的全行注释
while (fgets(szLineBuffer, ASCIILINESZ, pFile))//每次读取一行内容
{
++nLines; //行数自增
if (szLineBuffer[0] == '\n')continue;//如果取出来的内容第一个字符就是换行符,无条件取下一行内容
nLen = strlen(szLineBuffer);//更新行内容的字串长度
if (szLineBuffer[nLen - 1] != '\n' && !feof(pFile))
{
//如果此行的结束符 不是换行符\n 并且 文件并未结束 -> 如果都满足条件,说明此行肯定是太长了。应当调整行的最大长度。默认:ASCIILINESZ=1024
printf("<IniDocument::load> 行 %zu 内容过长,请检查文件内容.\n", nLines);
continue; //跳过此行
}
//开始进行字串解析
switch (parse(szLineBuffer, szSection, szKey, szValue, szDesc))//进入解析过程
{
case line_status::LINE_ERROR://未知行
break;
case line_status::LINE_DESCRIPTION://全行注释
{
desc = szLineBuffer; //将注释内容存储到Desc对象中
break;
}
case line_status::LINE_SECTION://节点
{
Section section(szSection); //创建Section对象
if (desc.m_szDesc)section.m_vecDesc[0] = std::move(desc); //将注释内容存储到Section对象中
if (szDesc[0] != '\0')section.m_vecDesc[1] = szDesc; //如果有第二个注释内容,存储到Section对象中
pKeyMap = &m_sectionMap[section]; //获取当前Section映射
break;
}
case line_status::LINE_KEY://键值
{
if (!pKeyMap)
{
printf("<IniDocument::load> 键值行 %zu 解析失败<没有Section>,请检查文件内容.\n", nLines);
continue; //如果没有Section映射,跳过此行
}
Key key(szKey); //创建Key对象
Value value(szValue); //创建Value对象
if (desc.m_szDesc) //如果有全行注释内容
{
//将注释内容存储到Key对象中
key.m_vecDesc[0] = std::move(desc);
}
if (szDesc[0] != '\0') //如果有行块注释内容
{
key.m_vecDesc[1] = szDesc; //将行块注释内容存储到Key对象中
}
(*pKeyMap)[key] = std::move(value); //将Key和Value存储到当前Section映射中
break;
}
default:
break;
}
szLineBuffer[0] = 0; //清空行内容
szSection[0] = 0; //清空Section内容
szKey[0] = 0; //清空Key内容
szValue[0] = 0; //清空Value内容
szDesc[0] = 0; //清空注释内容
}
fclose(pFile); //关闭文件
}
INI::IniDocument::line_status INI::IniDocument::parse(char* _szLine, char* _szSection, char* _szKey, char* _szValue, char* _szDesc)
{
#pragma warning(disable:4996)
line_status sta = line_status::LINE_ERROR;//分析结果
size_t nCount = 0; //记录sccanf匹配的数量
if (_szLine[0] == '#' || _szLine[0] == ';')//开头就是单行注释的解析符
{//说明全行都是注释
sta = line_status::LINE_DESCRIPTION;
}
//开始解析节点 Section
//(必须遵守一个规则,从有到无,比如结尾带分号的应该先做匹配。
//然后再到结尾不带分号的。因为如果先匹配不带分号的,带分号的也会匹配得上。就会莫名多个分号了)
//例如: key = value; 如果先匹配无分号的,那么值就是 value; 达不到预期效果,结尾多了个分号
else if
(
//以下匹配 节点(后面带注释)
(nCount = sscanf(_szLine, "[%[^]]]%[^\n]", _szSection, _szDesc)) == 2 ||//例子: [section01]#this is description 01或者[section01];this is description 01
(nCount = sscanf(_szLine, "[%[^]]]%[^\n]", _szSection, _szDesc)) == 1//例子: [section]
)
{
sta = line_status::LINE_SECTION;
}
//开始解析键值 key = value
else if
(
//(必须遵守一个规则,从有到无,比如结尾带分号的应该先做匹配。然后再到结尾不带分号的。因为如果先匹配不带分号的,带分号的也会匹配得上。就会莫名多个分号了)
(nCount = sscanf(_szLine, "%[^=]=%[^;]%[^\n]", _szKey, _szValue, _szDesc)) == 3 || //例子: key1=value1;this is description 01
(nCount = sscanf(_szLine, "%[^=]=%[^#]%[^\n]", _szKey, _szValue, _szDesc)) == 3 ||//例子: key2=value2#this is description 01
(nCount = sscanf(_szLine, "%[^=]=%[^\n]", _szKey, _szValue)) == 2//例子: key1=value1
)
{
sta = line_status::LINE_KEY;
}
#pragma warning(default:4996)
return sta;
}
把我提供给你的所有代码再结合这次给你的代码,你可以尝试重构组合,是否已经形成一个完整的ini解析器
最新发布