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中可能包含中文,所以除了特殊字符(逗号和双引号)之外,没有再判断字符的有效性。
转载自:https://coolex.info/blog/171.html