我的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上运行的。
看注释应该能理解这个代码了,有问题也可以来问我。
(这里是结束)