一种准标准CSV格式的介绍和分析以及解析算法

原创 2013年12月04日 19:43:02

        CSV是一种古老的数据传输格式,它的全称是Comma-Separated Values(逗号分隔值)。出生在那个标准缺失的蛮荒年代,CSV的标准一直(到2005年)是NULL——世间存在着N种CSV格式,它们自成体系,相互不兼容。比如我们从名字可以认为CSV至少是一种使用逗号分隔的格式,但是实际上,有的CSV格式却是使用分号(;)去做分隔。假如,不存在一种标准,那么这东西最终会因为碎片化而发展缓慢,甚至没落。本文讨论的CSV格式是基于2005年发布的RFC4180规范。我想,在这个规范发布之后,大家应该会更加自觉的遵从这套规范去开发——虽然这套标准依旧存在着一些致命的缺陷。(转载请指明出于breaksoftware的csdn博客)

        我们可以从IETF上获得包含了CSV格式定义的文档。当然,如果你觉得看英文文档麻烦,你可以直接看我的下文。

  1. 在不包含换行符(CRLF即 \r\n)的单条信息时,数据要保持在一行,并且使用\r\n结束。
    aaa,bbb,ccc,dddCRLF   合法

    aaa,b                            内容中无换行符,而单条信息被换行,不合法
    bb.ccc,dddCRLF
  2. 最后一条信息可以没有换行符(当然有换行符也是合法的)
    aaa,bbb,ccc,dddCRLF
    eee,fff,ggg,hhh           合法

    aaa,bbb,ccc,dddCRLF
    eee,fff,ggg,hhhCRLF     合法
  3. 第一条信息可能是一个头信息。这个头信息和之后信息格式是相同的,并且和之后的信息有相同的模块数(上例中,aaa和bbb和ccc和ddd各被视为一个模块)。(个人认为这是RFC设计这个CSV格式的一个缺陷,因为这个规则将无法让我们从规则的角度去确认第一条信息到底是头信息还是普通信息。当然RFC这么设计肯定有它的原因。)
    index,character          合法,从字面意思上我们可以认为这个是头,当然我们也可以认为它不是头
    1,aCRLF
    2,bCRLF

    indexCRLF                 非法,模块数不统一
    1,aCRLF
  4. 每条信息都要使用半角逗号(,)分隔出若干模块。每条信息的模块数要相等。每条信息的最后一个模块之后不可以使用半角逗号。空格符被视为一个模块的内容而不可被忽略。(这条规则包含的信息量相对较多)
    aaa,bbbCRLF                合法
    ccc,ddd,CRLF                非法,一条信息的最后一个模块不可以使用半角逗号
    eee;ffffCRLF                   非法,要使用半角逗号分隔,而不是分号
    ggg,       h h h  CRLF     合法,注意hhh模块的若干个空格,它属于模块内容而不可以被忽略
    iii,jjj,kkkkCRLF               非法,模块数和上面不统一
  5. 每个模块首尾可以使用双引号扩住(当然也可以不使用)。如果不使用双引号扩住的模块,模块中不可以出现双引号。(言外之意:如果模块中出现双引号,则这个模块要用双引号将首尾扩住)
    “aaa”,bbbCRLF             合法
    a"aa,bbbCRLF              不合法,因为a"aa中包含了双引号,而这个模块没有被双引号扩住
  6. 如果模块中包含双引号、半角逗号或换行符,则模块首尾要用双引号扩住。
    "a\r\na"a,bbbCRLF       合法,第一个模块包含了换行符,要用双引号包含
    "a,aa",bbbCRLF            合法
  7. 当双引号出现在模块中,要将模块的首尾用双引号扩住,并且将模块中的一个双引号变成一对双引号。
    “a""aa”,bbbCRLF          合法,原始数据为a"aa,bbb

        有了以上规则,我们可以编写出相应的提取算法。以下是我在工作中编写的一套从CSV文件中提取信息的核心代码

BOOL CCSV2Json::Parse()
{
    BOOL bSuc = FALSE;
    do {
        if ( INVALID_HANDLE_VALUE == m_hFile ) {
            break;
        }
        OVERLAPPED ov;
        memset(&ov, 0, sizeof(OVERLAPPED));
        BYTE lpBuffer[BUFFERSIZE] = {0};
        DWORD dwHaveRead = 0;
        std::string strSingle;
        BOOL bFirstDoubleQuotes = FALSE;    // 第一个字符是否为"
        BOOL bBeforeIsDoubleQuotes = FALSE; 
        BOOL bBeforeIsX0D = FALSE;
        ListString Liststr;
        BOOL bPairDoubleQuotes = FALSE;
        while ( ReadFile(m_hFile, lpBuffer, sizeof(lpBuffer), &dwHaveRead, &ov ) ) {
            ov.Offset += dwHaveRead;
            for ( DWORD dwIndex = 0; dwIndex < dwHaveRead; dwIndex++ ) {
                BYTE& by = *(lpBuffer + dwIndex);

                if ( bFirstDoubleQuotes ) {
                    // 有前置"
                    if ( IsDoubleQuotes(by) ) {
                        bBeforeIsX0D = FALSE;
                        if ( bBeforeIsDoubleQuotes ) {
                            strSingle.append(1, (char)(by));
                            bBeforeIsDoubleQuotes = FALSE;
                        }
                        else {
                            bBeforeIsDoubleQuotes = TRUE;
                        }
                    }
                    else {
                        if ( bBeforeIsDoubleQuotes ) {
                            bFirstDoubleQuotes = FALSE;
                        }
                        bBeforeIsDoubleQuotes = FALSE;
                        if ( IsCRLF( by ) ){
                            if ( bFirstDoubleQuotes ) {
                                strSingle.append(1, (char)(by));
                            }
                            else if (FALSE == bBeforeIsX0D) {
                                Liststr.push_back(strSingle);
                                m_Listliststr.push_back(Liststr);
                                Liststr.clear();
                                strSingle.clear();
                                bFirstDoubleQuotes = FALSE;
                            }
                            bBeforeIsX0D = IsX0D(by);
                        }
                        else if ( IsSep(by) ) {
                            bBeforeIsX0D = FALSE;
                            if ( bFirstDoubleQuotes ) {
                                strSingle.append(1, (char)(by));
                            }
                            else {
                                bBeforeIsX0D = FALSE;
                                Liststr.push_back(strSingle);
                                strSingle.clear();
                            }
                        }
                        else {
                            bBeforeIsX0D = FALSE;
                            strSingle.append(1, (char)(by));
                        }
                    }
                }
                else{
                    // 如果无前置"
                    if ( IsDoubleQuotes(by) ) {
                        bBeforeIsX0D = FALSE;
                        if ( strSingle.empty() ) {
                            // 空串,第一个是"
                            bFirstDoubleQuotes = TRUE;
                            bBeforeIsDoubleQuotes = FALSE;
                        }
                        else {
                            strSingle.append(1,(char)(by));
                            continue;
                        }
                    }
                    else {
                        bBeforeIsDoubleQuotes = FALSE;
                        if ( IsCRLF( by ) ){
                            if (FALSE == bBeforeIsX0D) {
                                Liststr.push_back(strSingle);
                                m_Listliststr.push_back(Liststr);
                                Liststr.clear();
                                strSingle.clear();
                                bFirstDoubleQuotes = FALSE;
                                bBeforeIsDoubleQuotes = FALSE;
                            }
                            else {
                                // 连续\r\n不考虑设置为新的行
                            }
                            bBeforeIsX0D = IsX0D(by);
                        }
                        else if ( IsSep(by) ) {
                            bBeforeIsX0D = FALSE;
                            Liststr.push_back(strSingle);
                            strSingle.clear();
                        }
                        else {
                            bBeforeIsX0D = FALSE;
                            strSingle.append(1, (char)(by));
                        }
                    }
                }

            }
            memset(lpBuffer, 0, sizeof(lpBuffer));     
        }
        
        if ( false == strSingle.empty() ) {
//             while ( IsCRLF(strSingle.at(strSingle.length() - 1) ) && strSingle.length() > 0) {
//                 strSingle = strSingle.substr(0, strSingle.length() - 1 );
//             }
            Liststr.push_back(strSingle);
            m_Listliststr.push_back(Liststr);
            Liststr.clear();
            strSingle.clear();
        }

        bSuc = TRUE;
    } while (0);
    
    if ( NULL != m_hFile ) {
        CloseHandle(m_hFile);
        m_hFile = NULL;
    }
    
    return bSuc;
}
        这段代码将CSV文件提取出来一个std::list<std::list<std::string>>结构。如上面名字所示,我这个功能是要将CSV文件转换为json格式,相应的我也编写了从json格式转换为CSV格式文件的代码。这些代码都在工程中

版权声明:本文为博主原创文章,未经博主允许不得转载。

CSV文件格式

CSV   (逗号分隔值文件格式)  编辑 逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式...
  • evilcry2012
  • evilcry2012
  • 2016年11月10日 10:03
  • 1310

CSV文件格式介绍

CSV文件格式介绍在网上冲浪的时候碰到了CSV文件,不解。就去找了一些资料,整理了一下。 CSV : Comma Separate Values CSV 英文文档  CSV即Comma Separat...
  • abcpanpeng
  • abcpanpeng
  • 2007年07月31日 08:37
  • 10768

如何将mysql数据库的数据导出并转为.csv文件格式为UTF-8无BOM

1、通过数据库管理工具,例如HeidiSQL ,导出CSV文件 2、如果需要对导出的数据进行处理,则需要用Java第三方Excel类库,导出.csv/.xls 然后再将数据转换为.c...
  • zsf5201314z
  • zsf5201314z
  • 2017年05月02日 16:24
  • 1325

CSV文件格式解析器的实现:从字符串Split到FSM

本文乃Siliphen原创,转载请注明出处:   本文分为5小节,基本上就是我刚接触CSV文件到思考、实践做一个CSV解析器的过程的还原。希望我的思路也能带领你一步步从浅到深认识CSV文件格式。   ...
  • StevenKyleLee
  • StevenKyleLee
  • 2014年07月31日 00:37
  • 14721

CSV标准格式

1. CSV的全称是叫Comma Separated Value 2. CSV的MIME类型是text/csv 3 CSV文件中的每一行数据,作为一行记录,也就是一个条目(99%的情况,排除有些换...
  • zlzlei
  • zlzlei
  • 2013年07月03日 17:54
  • 9566

CSV是什么文件格式

CSV即Comma Separate Values,这种文件格式经常用来作为不同程序之间的数据交互的格式。  具体文件格式  每条记录占一行以逗号为分隔符逗号前后的空格会被忽略字段中包含有逗号,该字段...
  • huyanping
  • huyanping
  • 2011年05月02日 16:05
  • 3802

Java解析CSV文件

1、CSV文件是什么 csv(Comma Separate Values)文件即逗号分隔符文件,它是一种文本文件,可以直接以文本打开,以逗号分隔。windows默认用excel打开。...
  • zry112233
  • zry112233
  • 2016年05月17日 15:26
  • 2773

Java解析CSV文件

package com.test; import java.io.BufferedReader; import java.io.FileInputStream; import java.io...
  • NotOnlyForShe
  • NotOnlyForShe
  • 2012年03月22日 17:31
  • 23610

利用Python解析CSV文件

1.CSV的特点: 每行文本以行为单位呈现 字段被分隔符(通常为逗号)隔开 只存储数据 不需要任何软件就可以读取 2.手动解析CSV文件# Your task is to read the inp...
  • baidu_27438681
  • baidu_27438681
  • 2017年04月06日 23:03
  • 598

Hbase 数据迁移的常见方式(四)

要使用Hadoop,需要将现有的各种类型的数据库或数据文件中的数据导入HBase。一般而言,有三种常见方式:使用HBase的API中的Put方法,使用HBase 的bulk load工具和使用定制的M...
  • kinglyjn
  • kinglyjn
  • 2017年08月29日 19:46
  • 500
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:一种准标准CSV格式的介绍和分析以及解析算法
举报原因:
原因补充:

(最多只允许输入30个字)