代码设计:C++ 一个CSV功能类(源码)

初级代码游戏的专栏介绍与文章目录-CSDN博客

我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。

这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。


        CSV格式是逗号分隔的文本文件,几十年前挺常用,现在几乎没人用,但是因为很多工具都支持,可以作为各种数据迁移的中介,所以始终没有退出历史舞台。

        CSV格式非常简单,就是一行一条记录、逗号分隔字段而已。

        下面是个代码,主要是为了用excel编辑数据而做的,不保证和非execl的兼容性。

        主要的细节是对不同语言编码的处理和文本转换。因为用的只是UTF-8,所以只检测了UTF-8的BOM。

//csv格式文件处理

#include <sys/stat.h>

	//基本csv功能
	class CCSV
	{
	private:
		CStdOSFile m_file;
		vector<string > m_Heads;//头标,如果no_head则没有头标
	public:
		//根据路径名创建所有目录,不包括最后的文件名
		static bool CreateDir(char const* filename)
		{
			string dirname;
			char const* p = filename;
			for (; '\0' != *p; ++p)
			{
				dirname += *p;
				if ('/' == *p)
				{
					//thelog<<dirname<<endi;
					mkdir(dirname.c_str(), S_IRWXU | S_IRWXG);
				}
			}
			return true;
		}
		//csv格式,双引号内为普通文本,文本内双引号要用两个双引号表达,简单起见全部加上了双引号
		static string& CSVEncode(char const* in, string& out)
		{
			out = "";
			out += '\"';
			for (char const* p = in; *p != '\0'; ++p)
			{
				if ('\"' == *p)out += '\"';
				out += *p;
			}
			out += '\"';
			return out;
		}
		//csv格式,双引号内为普通文本,文本内双引号要用两个双引号表达,简单起见全部加上了双引号
		static string CSVEncode(string const& in)
		{
			string out;
			return CSVEncode(in.c_str(), out);
		}
		//打开文件
		bool OpenCSV(char const* filepatnname, bool no_head = false)
		{
			m_Heads.clear();

			if (!CreateDir(filepatnname))return false;
			if (!m_file.OpenR(filepatnname))return false;

			if (!no_head)
			{
				bool isLineEnd;
				string str;
				while (_ReadField(isLineEnd, str))
				{
					if (isLineEnd)
					{
						break;
					}
					if (0 == m_Heads.size())
					{//对第一个消除可能存在的UTF-8签名(EF BB BF)
						if (0 == strncmp("\xEF\xBB\xBF", str.c_str(), 3))
						{
							str.erase(0, 3);
						}
					}
					//thelog << str << endi;
					m_Heads.push_back(str);
				}
				thelog << "获得列头 " << m_Heads.size() << " 个" << endi;
			}
			return true;
		}
		//获得字段序号
		long GetFieldIndex(char const* field)const
		{
			vector<string >::const_iterator it;
			for (it = m_Heads.begin(); it != m_Heads.end(); ++it)
			{
				if (*it == field)return it - m_Heads.begin();
			}
			return -1;
		}
		//获得字段数
		long GetFieldCount()const
		{
			return m_Heads.size();
		}
		//获得字段名
		string GetFieldName(long field)const
		{
			return m_Heads[field];
		}
		//读取字段,或者是一个字段,或者是行结束
		bool _ReadField(bool& isLineEnd, string& ret)
		{
			bool tmp = __ReadField(isLineEnd, ret);
			TrimAll(ret);
			return tmp;
		}
		bool __ReadField(bool& isLineEnd, string& ret)
		{
			isLineEnd = false;
			ret = "";

			char m_c;
			string str;
			bool isFirstChar = true;
			bool isInStr = false;
			bool isInStrQuote = false;//是否在字符串内出现了一个双引号,这意味着字符串结束,或者一个内嵌双引号的开始
			while (1 == m_file.Read(&m_c, 1))
			{
				if (isFirstChar && ('\r' == m_c || '\n' == m_c))
				{//仅当第一个字符就是行结束才认为是行结束而没有获得数据
					isLineEnd = true;
					if ('\r' == m_c)
					{
						if (1 == m_file.Read(&m_c, 1))
						{
							if ('\n' != m_c)
							{
								m_file.SeekCur(-1);//不是连续\r\n,吐回去
							}
						}
					}
					return true;
				}
				else
				{
					isFirstChar = false;
				}

				if (isInStr && isInStrQuote && '\"' == m_c)
				{//内嵌双引号
					ret = +m_c;
					isInStrQuote = false;
					continue;
				}
				if (isInStr && isInStrQuote && ',' == m_c)
				{//字符串结束,逗号标记字段结束
					return true;
				}
				if (isInStr && isInStrQuote && ('\r' == m_c || '\n' == m_c))
				{//字符串结束,行结束
					m_file.SeekCur(-1);//吐回行结束,否则永远不能发现单独的行结束
					return true;
				}
				if (isInStr && isInStrQuote)
				{//字符串结束
					isInStr = false;
					continue;
				}
				if (isInStr && !isInStrQuote && '\"' == m_c)
				{//遇到内部第一个双引号
					isInStrQuote = true;
					continue;
				}
				if (isInStr && !isInStrQuote)
				{//字符串内容,除了双引号都不用特殊判断
					ret += m_c;
					continue;
				}

				if (!isInStr && '\"' == m_c)
				{//字符串开始
					isInStr = true;
					continue;
				}
				if (!isInStr && ',' == m_c)
				{//字段结束
					return true;
				}
				if (!isInStr && ('\r' == m_c || '\n' == m_c))
				{//行结束
					m_file.SeekCur(-1);//吐回行结束,否则永远不能发现单独的行结束
					return true;
				}
				if (!isInStr)
				{//非字符串的其余
					ret += m_c;
					continue;
				}
			}

			return false;
		}
		bool ReadField(string& ret)
		{
			bool isLineEnd;
			while (_ReadField(isLineEnd, ret))
			{
				if (!isLineEnd)return true;
			}
			return false;
		}
		//关闭文件
		bool CloseCSV()
		{
			return m_file.Close();
		}
		//文件到表格
		bool LoadFromFile(char const* file, CHtmlDoc::CHtmlTable2 & table)
		{
			table.Clear();

			CCSV csv;
			if (!csv.OpenCSV(file))
			{
				return false;
			}
			long i;
			for (i = 0; i < csv.GetFieldCount(); ++i)
			{
				table.AddCol(csv.GetFieldName(i));
			}
			bool isLineEnd;
			string str;
			bool need_new_line = false;
			table.AddLine();
			while (csv._ReadField(isLineEnd, str))
			{
				if (isLineEnd)
				{
					need_new_line = true;
				}
				else
				{
					if (need_new_line)
					{
						table.AddLine();
						need_new_line = false;
					}
					table.AddData(str);
				}
			}
			table.Fill();
			return csv.CloseCSV();
		}
	};
	class CText
	{
	public:
		//删除两端的空白,连续回车换行缩减为一个换行
		static string FormatText(char const* text)
		{
			string ret = text;
			TrimAll(ret);

			string::size_type pos = 0;
			bool isInNewLine = false;
			while (pos < ret.size())
			{
				if ('\r' == ret[pos] || '\n' == ret[pos])
				{
					if (!isInNewLine)
					{
						isInNewLine = true;
						ret[pos] = '\n';
						++pos;
					}
					else
					{//丢弃连续的换行,pos不变
						ret.erase(pos, 1);
					}
				}
				else
				{
					isInNewLine = false;
					++pos;
				}
			}
			return ret;
		}

	};

        这段代码中用到一些别的东西,基本上属于看到名字就能猜到功能的:

CStdOSFile 就是一个文件类,可以用任何别的文件类替换掉

CHtmlDoc::CHtmlTable2 这是一个表格类,可以输出为文本表格、HTML表格、变更JS,以后我可能会把这个类整理出来

TrimAll 删除两端空白字符

        这个代码是在UNIX、Linux上运行的。

        看注释应该能理解这个代码了,有问题也可以来问我。


(这里是结束)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

初级代码游戏

知识究竟是有价还是无价

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值