CSV解析器实现
确切地说应该是TSV,即分隔符是Tab而不是Comma。除分隔符之外,CSV与TSV大同小异。
这里使用TSV主要原因是:Excel导出的CSV不是Unicode编码,这样会导致某些符号丢失,而导出TXT时,可以选择Unicode,但格式却是TSV。
TSV语法可以用以下文法来描述:
p0:P -> Te
p1:T -> RT|ε
p2:R -> CMb
p3:M -> tCM|ε
p4:C -> s|ε
其中各非终结符与终结符的含义为:
P:The Whole TSV File
T:Like foobar
R:One Row in TSV
M:Context among One Row
C:Context of One Cell in Row
e:End Of File
t:Tab
s:String
b:LF or CRLF
ε:EmptyString
该文法是一个正规文法,因为它可以用有限状态自动机来表示。m0,m1,m2,m3,m4分别为各个产生式对应的自动机。

m01->m012->m0123->m01234是将各产生式自动机组合的过程。
显然得到最后的m01234是一个有限状态自动机,该文法是一个正则文法,是可以用正则表达式来描述的。
出于简化考虑,这里把显然没有必要的两条ε边去掉,即到状态Start与B合并到A中,为各个状态标上序号后,得到M0.
边ε的存在会让程序实现极不美观,所以使用子集法去掉ε,得到状态转换矩阵:
s | t | b | e | |
012 | 12 | 32 | 012 | Accept |
12 | 12 | 32 | 012 | X |
32 | 32 | 32 | 012 | X |
根据转换矩阵很容易得到它的对应自动机M1.
下面是TSV的词法分析器与语法分析器的粗略实现:
#define TOKEN_STRING 0
#define TOKEN_LINEBREAK 2
#define TOKEN_TAB 1
#define TOKEN_EOF 3
#define STATUS_012 0
#define STATUS_32 1
#define STATUS_12 2
#define STATUS_4 3
#define STATUS_X 4
static int GetToken( const wchar_t* pBuff, int* pLen )
{
int nToken = TOKEN_EOF;
const wchar_t* pos;
if ( NULL == pBuff || 0 == pBuff[0] )
{
*pLen = 0;
nToken = TOKEN_EOF;
}
else
{
switch ( pBuff[0] )
{
case '\n':
nToken = TOKEN_LINEBREAK;
*pLen = 1;
break;
case '\r':
nToken = TOKEN_LINEBREAK;
*pLen = ('\n' == pBuff[1] ? 2 : 1);
break;
case '\t':
nToken = TOKEN_TAB;
*pLen = 1;
break;
case '"':
nToken = TOKEN_STRING;
{
pos = pBuff+1;
while ( *pos )
{
if ( '"' == pos[0] && '"' == pos[1] )
{
pos+=2;
}
else if ( '"' == pos[0] )
{
pos++;
break;
}
else
{
pos++;
}
}
*pLen = pos-pBuff;
}
break;
default:
nToken = TOKEN_STRING;
{
pos = pBuff+1;
while ( true )
{
wchar_t ch = *pos;
if ( 0 == ch || '\t' == ch || '\n' == ch || '\r' == ch )
break;
else
pos++;
}
*pLen = pos-pBuff;
}
break;
}
}
return nToken;
}
// Return value: true: accept, false: grammar error
static bool AutoMachine( const wchar_t* pBuff, vector<vector<wstring>>& table )
{
static int statusMatrix[3][4] =
{
{ STATUS_12, STATUS_32, STATUS_012, STATUS_4 },
{ STATUS_12, STATUS_32, STATUS_012, STATUS_X },
{ STATUS_32, STATUS_32, STATUS_012, STATUS_X },
};
table.clear();
vector<wstring> line; // Used to store the strings in one row
int nStatus = STATUS_012;
const wchar_t* pos = pBuff;
bool bEpsilon = false;
int nToken = TOKEN_LINEBREAK;
int nLastToken = nToken;
int nLen = 0;
int tmpStatus;
while ( STATUS_4 != nStatus && STATUS_X != nStatus )
{
nToken = GetToken( pos, &nLen );
tmpStatus = nStatus;
nStatus = statusMatrix[nStatus][nToken];
switch ( nToken )
{
case TOKEN_STRING:
if ( TOKEN_STRING == nLastToken ) // Merge the string at the last element
*line.rbegin() += wstring(pos, pos+nLen);
else
line.push_back( wstring(pos, pos+nLen) );
break;
case TOKEN_TAB:
if ( TOKEN_TAB == nLastToken || TOKEN_LINEBREAK == nLastToken )
line.push_back( L"" );
break;
case TOKEN_LINEBREAK:
if ( TOKEN_TAB == nLastToken || TOKEN_LINEBREAK == nLastToken )
line.push_back( L"" );
table.push_back( line );
line.clear();
break;
}
pos += nLen;
nLastToken = nToken;
}
return nStatus == STATUS_4;
}