工具类库系列(四)-CsvReader

第四个工具类:CsvReader


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


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


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


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


第一行的每一列为列名称

第二行的每一列为列类型

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

第四行开始为实际数据


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

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;
			}
		}

	}
}


  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值