工具类库系列(四)-CsvReader

时间:2022-03-05 06:22:51

第四个工具类:CsvReader


CsvReader是用来支持读取Csv表格用的


Csv格式其实就是一个有固定格式的txt,一行每一列用英文','隔开


游戏项目中,很多游戏静态表。策划提供的是Csv表格,可以用Excel编辑方便。客户端Unity也倾向于读取Csv表格,纯文本,格式简单,读取方便


然后为了读取更方便,我们人为规定了Csv前3行的内容有特殊意义,下图是一个示例

工具类库系列(四)-CsvReader

第一行的每一列为列名称

第二行的每一列为列类型

第三行的每一列为列名称中文说明

第四行开始为实际数据


具体说明一下支持的列类型

4个基本类型:

u:对应unsigned int

i:对应int

f:对应float

s:对应std::string

子结构:如果该列是一个子结构,则具体写明子结构的字段名和字段类型,用英文分号';'分割

如上图中的StructId;u;StructNum;i;StructFloat;f;StruceName;s:

表示这个子结构有4个字段:StructId,unsigned int类型,StructNum,int类型,StructFloat,float类型,StruceName,字符串类型

实际数据中子结构字段之间用英文与运算符‘&’分割

如上图中的1&-2&3.4&aaa:

表示StructId=1,StructNum=-2,StructFloat=3.4f,StruceName=aaa,即{1, -2, 3.4f, "aaa"}


列类型的限定类型:

k:表示该列是主键,作为索引使用

支持最多3列主键,即3列组合起来确定唯一一行记录

主键不一定放在前3列,可以用中间的某些列做主键

主键的类型限定为整数,即k后面的列类型为i或者u

lst:表示该列为不定长数组

如上列中的lst:u:

表示该列是一个unsigned int的数组,即对应std::vector<unsigned int>

如上例中的lst:StructId;u;StructNum;i;StructFloat;f;StruceName;s:

表示该列是一个子结构的数组

实际数据中数组的元素和元素之间用英文分号';'分割

如上例中的1&-2&3.4&aaa;5&-6&7.8&bbb:

表示该子结构数组有2个元素,第一个元素是{1, -2, 3.4f, "aaa"},第二个元素是{5, -6, 7.8f, "bbb"}


k或者lst后面跟英文冒号“:”,再跟具体列类型


以上为人为规定


那么对于这样的一张表,读进内存对应的结构可能如下所示:

class Struct
{
public:
unsigned int m_StructId;
int m_StructNum;
float m_StructFloat;
std::string m_StruceName;
};

class Test
{
public:
unsigned int m_Id1; //测试主键1
int m_Id2; //测试主键2
unsigned int m_Id3; //测试主键3
std::string m_Str; //测试字符串
std::vector<unsigned int> m_UInts; //测试数字列表
float m_Float; //测试数字
Struct m_Struct; //测试子结构
std::vector<Struct> m_StructLists; //测试子结构列表
};


本文的CsvReader就是为了实现将如上人为规定了格式的Csv文件,读取成对应的类对象的一个工具类

下一篇会写一个代码自动生成工具,自动生成每张表对应的类对象的代码

(因为策划对表结构的修改是很频繁的,表结构一旦修改,对应的类结构就要跟着修改,干脆自动生成省事)


CsvReader提供这样几个接口:

1、ReadLine,读取一行内容,读到一个缓存中,按Csv格式保留的分隔符英文逗号',',分割成每一列的值

这里有一个文件编码的问题,即在windows下,Utf8格式的文件有个BOM头,即文件前3个字节是0xEF,0xBB,0xBF需要过滤掉,行末尾的'\r',‘\n',需要过滤掉

2、CheckLine,校验一行内容按逗号分隔之后的列数,是否和第一行的列名称的列数一致

如果不一致,说明实际数据中,尤其是字符串类型的列中,存在英文的逗号,但是英文逗号是Csv文件保留的分隔符

所以如果实际数据中出现英文逗号,就无法确定哪一列对应哪一列了

3、Name2Index,根据列名称,返回该列是第几列,首列为0

4、GetValue,读取单字段类型(非数组)的列的值

5、GetValueList,读取数组列的值

6、LoadFile,加载指定文件,并处理前3行内容,识别出列名称,以及列数量


对于子结构,需要子结构重载赋值运算符,参数为一个string,如

XXX& operator = (const std::string &other),实现类似上例中“1&-2&3.4&aaa”这样一个字符串的解析


这里也用到了之前提到的StringTool,用来进行字符串的分割,以及校验是否是一个合法的数字


上代码

CsvReader.h

#ifndef __CsvReader_h__
#define __CsvReader_h__

#include <string>
#include <vector>
#include <map>
#include <fstream>

#include "StringTool.h"

namespace common{
namespace tool{

class CsvReader
{
public:
CsvReader();
~CsvReader();

// csv文件打开
bool OpenFile(const std::string& file_path_name);

// csv文件打开,并处理前3行数据
bool LoadFile(const std::string& file_path_name);

// 读取一行数据(首行去BOM,每行行尾去换行符)
size_t ReadLine(bool firstLine = false);

// 读取一行数据后,获取每一列的原始内容
const std::vector<std::string>& GetLine();

// 校验列数是否和列名称数量匹配
bool CheckLine();

// 根据列名获得列编号,第一列编号为0
size_t Name2Index(const std::string& name);

// 取当前行的某一列的值
bool GetValueList(size_t index, std::vector<std::string>& values, const std::string& split);

bool GetValue(size_t index, std::string& value);

bool GetValueList(size_t index, std::vector<unsigned int>& values, const std::string& split);

bool GetValue(size_t index, unsigned int& value);

bool GetValueList(size_t index, std::vector<int>& values, const std::string& split);

bool GetValue(size_t index, int& value);

bool GetValueList(size_t index, std::vector<float>& values, const std::string& split);

bool GetValue(size_t index, float& value);

template <class T>
bool GetStructList(size_t index, std::vector<T>& values, const std::string& split);

template <class T>
bool GetStruct(size_t index, T& value);

private:
// 文件输入流
std::ifstream m_file;

// 列名称 <-> 列下标 映射表
std::map<std::string, size_t> m_names;
// 当前行每一列的值
std::vector<std::string> m_values;
};

template <class T>
bool CsvReader::GetStructList(size_t index, std::vector<T>& values, const std::string& split)
{
if (index < m_values.size())
{
std::vector<std::string> tempStrs;
StringTool::SplitStr2List(m_values[index], split, tempStrs);
for (size_t i = 0; i < tempStrs.size(); i++)
{
if (tempStrs[i].length() > 0)
{
T tempT;
tempT = tempStrs[i];
values.push_back(tempT);
}
}
return true;
}
else
{
return false;
}
}

template <class T>
bool CsvReader::GetStruct(size_t index, T& value)
{
if (index < m_values.size())
{
value = m_values[index];
return true;
}
else
{
return false;
}
}

}
}

#endif


CsvReader.cpp

#include "CsvReader.h"

#include <string.h>

namespace common{
namespace tool{

const unsigned int MaxLineLen = 10240;
const std::string CsvSplit = ",";

CsvReader::CsvReader()
{
m_names.clear();
m_values.clear();
}

CsvReader::~CsvReader()
{
m_names.clear();
m_values.clear();

if (m_file)
{
m_file.close();
}
}

size_t CsvReader::ReadLine(bool firstLine)
{
if (m_file)
{
char line[MaxLineLen];
memset(line, 0x00, sizeof(char)* MaxLineLen);
m_file.getline(line, MaxLineLen);
size_t len = strlen(line);

// 去除首行BOM
if (3 <= len)
{
if (firstLine)
{
if (0xEF == (unsigned char)line[0] &&
0xBB == (unsigned char)line[1] &&
0xBF == (unsigned char)line[2])
{
memcpy(line, line + 3, len + 1 - 3);
len = len - 3;
}
}
}

// 去除每行末尾\r\n
while (1 <= len)
{
if ('\r' == line[len - 1] || '\n' == line[len - 1])
{
line[len - 1] = '\0';
len = len - 1;
}
else
{
break;
}
}

if (0 < len)
{
m_values.clear();
StringTool::SplitStr2List(line, CsvSplit, m_values);
}

return len;
}
else
{
return 0;
}
}

const std::vector<std::string>& CsvReader::GetLine()
{
return m_values;
}

bool CsvReader::CheckLine()
{
if (m_values.size() == m_names.size())
{
return true;
}
else
{
return false;
}
}

bool CsvReader::OpenFile(const std::string& file_path_name)
{
m_file.open(file_path_name.c_str(), std::ios::in);
if (m_file)
{
return true;
}
else
{
return false;
}
}

bool CsvReader::LoadFile(const std::string& file_path_name)
{
m_file.open(file_path_name.c_str(), std::ios::in);
if (m_file)
{
// 读列名称
ReadLine(true);
// 保存列名称,用于字段验证,和根据列名称取列下标
for (size_t i = 0; i < m_values.size(); i++)
{
m_names[StringTool::UpcaseFirstChar(m_values[i])] = i;
}

// 读列类型
ReadLine();

// 读注释
ReadLine();

return true;
}
else
{
return false;
}
}

size_t CsvReader::Name2Index(const std::string& name)
{
std::map<std::string, size_t>::iterator it = m_names.find(name);
if (it != m_names.end())
{
return it->second;
}
else
{
return -1;
}
}

bool CsvReader::GetValueList(size_t index, std::vector<std::string>& values, const std::string& split)
{
if (index < m_values.size())
{
if (0 < m_values[index].length())
{
StringTool::SplitStr2List(m_values[index], split, values);
}

return true;
}
else
{
return false;
}
}

bool CsvReader::GetValue(size_t index, std::string& value)
{
if (index < m_values.size())
{
value = m_values[index];
return true;
}
else
{
return false;
}
}

bool CsvReader::GetValueList(size_t index, std::vector<unsigned int>& values, const std::string& split)
{
if (index < m_values.size())
{
if (0 < m_values[index].length())
{
return StringTool::SplitStr2List(m_values[index], split, values);
}

return true;
}
else
{
return false;
}
}

bool CsvReader::GetValue(size_t index, unsigned int& value)
{
if (index < m_values.size())
{
if (StringTool::IsUInt(m_values[index]))
{
value = static_cast<unsigned int>(atoi(m_values[index].c_str()));
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}

bool CsvReader::GetValueList(size_t index, std::vector<int>& values, const std::string& split)
{
if (index < m_values.size())
{
if (0 < m_values[index].length())
{
return StringTool::SplitStr2List(m_values[index], split, values);
}

return true;
}
else
{
return false;
}
}

bool CsvReader::GetValue(size_t index, int& value)
{
if (index < m_values.size())
{
if (StringTool::IsInt(m_values[index]))
{
value = atoi(m_values[index].c_str());
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}

bool CsvReader::GetValueList(size_t index, std::vector<float>& values, const std::string& split)
{
if (index < m_values.size())
{
if (0 < m_values[index].length())
{
return StringTool::SplitStr2List(m_values[index], split, values);
}

return true;
}
else
{
return false;
}
}

bool CsvReader::GetValue(size_t index, float& value)
{
if (index < m_values.size())
{
if (StringTool::IsFloat(m_values[index]))
{
value = static_cast<float>(atof(m_values[index].c_str()));
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}

}
}