简单的csv文件解析

csv文件的结构很简单,最基本的规则,就是用逗号分隔每一个单元格,用换行( 或者 )分隔每一列。其中需要注意的就是双引号为特殊的转义字符。详细的csv文件格式定义,在rfc4180中,主要的定义为:

file = [header CRLF] record *(CRLF record) [CRLF]
header = name *(COMMA name)
record = field *(COMMA field)
name = field
field = (escaped / non-escaped)
escaped = DQUOTE *(TEXTDATA / COMMA / CR / LF / 2DQUOTE) DQUOTE
non-escaped = *TEXTDATA
COMMA = %x2C
CR = %x0D
DQUOTE =  %x22
LF = %x0A
CRLF = CR LF
TEXTDATA =  %x20-21 / %x23-2B / %x2D-7E
根据这个定义,用状态机的方法,定义状态的枚举:
enum CSV_STATE 
{
CELL_BEGIN, //单元格开始
CELL, //单元格
CELL_END, //单元格结束
QUOTATION_1, //第一个引号
QUOTATION_2, //第二个引号
QUOTATION_3, //第三个引号
ESCAPED_CELL, //转义的单元格
};
之前画的状态迁移图没有保存,直接上代码:
case CELL_BEGIN:
if (*iter == '\"') //第一个引号开始
{
_state = QUOTATION_1;
}
else if(*iter == ',') //逗号,一个空的单元格
{
row._rowData.push_back("");
_cell.clear();
}
else //有中文,其他字符都保存
{
_cell.push_back(*iter);
_state = CELL;
}
break;
case CELL:
if(*iter == '\"') //rfc4180 未转义的单元格内不能出现引号
{
//简单处理,直接抛出异常
throw std::runtime_error("error parse csv format arround: " + line);
}
else if(*iter == ',') //逗号,改单元格结束
{
row._rowData.push_back(_cell); //rfc4180规定单元格能够有空格,后面的空格暂时不管
_cell.clear();
_state = CELL_BEGIN;
}
else //因为有中文,不过滤rfc4180中排除的其他字符
{
_cell.push_back(*iter);
}
break;
case QUOTATION_1:
if(*iter == '\"') //第二个引号
{
_state = QUOTATION_2;
}
else
{
_cell.push_back(*iter);
_state = ESCAPED_CELL;
}
break;
case QUOTATION_2:
if(*iter == '\"') //第三个引号,为引号的转义
{
_cell.push_back(*iter);
_state = ESCAPED_CELL;
}
else if(*iter == ',') //没有第三个引号,单元格结束
{
row._rowData.push_back(_cell);
_cell.clear();
_state = CELL_BEGIN;
}
else //其他字符,全部忽略
{
row._rowData.push_back(_cell);
_cell.clear();
_state = CELL_END;
continue;
}
break;
case CELL_END:
if(*iter == ',') //忽略其他字符,遇到逗号,表示新的单元格开始了
{
_state = CELL_BEGIN;
}
break;
case ESCAPED_CELL:
if(*iter == '\"') //第二个引号
{
_state = QUOTATION_2;
}
else
{
_cell.push_back(*iter);
}
break;
每次读取一行进行解析,开始解析前,初始状态是CELL_BEGIN,解析完成后,有两种可能的状态:
1、处于CELL或者QUOTATION_2状态,因为最后一个单元格是没有逗号的,解析完成后,将最后一个单元格的数据push_back进去
2、处于ESCAPED_CELL状态,因为转义的单元格中间是可以包含换行的,所以需要返回false,继续下一行的读取
3、处于其他状态,应该是不可能的,
if(_state == ESCAPED_CELL) //转义单元格可以包含换行,还在这个单元格,需要继续解析下一行
{
_cell.push_back('\n'); //getline把本来应该存在的换行吃了,补回去
return false;
else if (_state == CELL || _state == QUOTATION_2) //最后一个单元格,保存下
{
row._rowData.push_back(_cell);
_cell.clear();
_state = CELL_BEGIN;
}
这里做的和rfc文档不同的是,rfc定义的文本字符,是 %x20-21 / %x23-2B / %x2D-7E,但因为csv中可能包含中文,所以除了特殊字符(逗号和双引号)之外,没有再判断字符的有效性。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据