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_X },{ STATUS_32,}; 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; }