天天看点

CSV解析器实现

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分别为各个产生式对应的自动机。

CSV解析器实现

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;
}
           

继续阅读