标准CSV格式的介绍和分析以及解析算法实例详解

 更新时间:2020年4月25日 17:33  点击:1414

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

        我们可以从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格式文件的代码。这些代码都在工程中。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

[!--infotagslink--]

相关文章

  • MYSQL事务回滚的2个问题分析

    因此,正确的原子操作是真正被执行过的。是物理执行。在当前事务中确实能看到插入的记录。最后只不过删除了。但是AUTO_INCREMENT不会应删除而改变值。1、为什么auto_increament没有回滚?因为innodb的auto_increament的...2014-05-31
  • 浅析Promise的介绍及基本用法

    Promise是异步编程的一种解决方案,在ES6中Promise被列为了正式规范,统一了用法,原生提供了Promise对象。接下来通过本文给大家介绍Promise的介绍及基本用法,感兴趣的朋友一起看看吧...2021-10-21
  • Mysql索引会失效的几种情况分析

    索引并不是时时都会生效的,比如以下几种情况,将导致索引失效: 1.如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)  注意:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引 ...2014-06-07
  • python 爬取京东指定商品评论并进行情感分析

    本文主要讲述了利用Python网络爬虫对指定京东商城中指定商品下的用户评论进行爬取,对数据预处理操作后进行文本情感分析,感兴趣的朋友可以了解下...2021-05-28
  • Underscore源码分析

    Underscore 是一个 JavaScript 工具库,它提供了一整套函数式编程的实用功能,但是没有扩展任何 JavaScript 内置对象。这篇文章主要介绍了underscore源码分析相关知识,感兴趣的朋友一起学习吧...2016-01-02
  • PHP中print_r、var_export、var_dump用法介绍

    文章详细的介绍了关于PHP中print_r、var_export、var_dump区别比较以及这几个在php不同的应用中的用法,有需要的朋友可以参考一下 可以看出print_r跟var_export都...2016-11-25
  • Framewrok7 视图介绍(views、view)使用介绍

    下面我们来看一篇关于Framewrok7 视图介绍(views、view)使用介绍吧,希望这篇文章能够帮助到各位朋友。 一、Views 与View的介绍 1,Views (<div class="views">) (1)Vi...2016-10-02
  • Google会不会取消PR的理由分析

    Google是这样介绍PageRank的:   Google 出类拔萃的地方在于专注开发“完美的搜索引擎”,联合创始人拉里&middot;佩奇将这种搜索引擎定义为可“确解用户...2017-07-06
  • Fatal error: Cannot redeclare class 原因分析与解决办法

    我使用的都是php __autoload状态自动加载类的,今天好好的程序不知道怎么在运行时提示Fatal error: Cannot redeclare class 了,看是重复定义了类,下面我来分析一下解决办...2016-11-25
  • Monolog PHP日志类库使用详解介绍

    PHP日志类库在低版本中我们都没有看到了但在高版本的php中就有了,下面我们来看一篇关于PHP日志类库使用详解介绍吧. Monolog遵循PSR3的接口规范,可以很轻易的替换...2016-11-25
  • php获取当前url地址的方法介绍

    这篇文章介绍了php获取当前url地址的方法小结,有兴趣的同学可以参考一下 本文实例讲述了php获取当前url地址的方法。分享给大家供大家参考,具体如下: js 获取: ...2017-01-22
  • PHP-GTK 介绍及其应用

    1. PHP-GTK介绍 1.1 PHP-GTK PHP-GTK是PHP的延伸模组,它可以让程式设计师写出在客户端执行的、且独立的GUI的程式。这个模组不允许在浏览器上显视GTK+的程式,它一开始就...2016-11-25
  • 不错的mod_perl编程的简单应用实例介绍

    介绍性指南 mod_perl 是个庞大而复杂的工具,它内建了许多模块帮助你方便地构建动态网站。这篇指南的目的是帮助你构建一个良好的 mod_perl 模块,并从中理解 mod_perl 的实现...2020-06-29
  • PHP 获取文件目录权限函数fileperms介绍

    在php中要获取或目录权限我们可使用fileperms函数来获取,fileperms() 函数返回文件或目录的权限,。若成功,则返回文件的访问权限。若失败,则返回 false。 例子 1 ...2016-11-25
  • 关于PHP文件包含一些漏洞分析

    文章简单的分析了在php文件包含时inlcude的一个漏洞分析,下面希望对大家有点用处哦。 基本的文件包含漏洞: 代码如下 复制代码 <?php include...2016-11-25
  • php echo print print_r三者区别分析

    php教程 echo print print_r三者区别分析 echo是PHP语句, print和print_r是函数,语句没有返回值,函数可以有返回值(即便没有用) print() 只能打印出简单类型变量的...2016-11-25
  • php设置时区方法介绍

    php默认时区是欧美国家的所以与我们中国时区相差了整整8小时哦,下面我来给各位介绍php设置时区方法,有需要了解的朋友可进入参考。 在 php.ini 中,默认是 date.timez...2016-11-25
  • www.111cn.net 排名大跌分析降权原因

    我的主题爱这个站以前排名非常好,后来由于自己操作不当至今还没恢复到以后的那种好的排名,希望各位站长吸取点教训哦。 下面以我们http://www.111cn.net 为例吧,做SE...2016-10-10
  • OpenCart网站迁移步骤详细介绍

    OpenCart是国外著名的开源电子商务系统,由英国人Daniel一人独立开发,其社区非常活跃,由各国网友翻译出来的语言包已经达到18种,其中包括中文,俄文,法文,西班牙文,德文等等,下面...2016-10-10
  • Mysql创建数据库和独立数据库帐号方法介绍

    本文章来给各位同学介绍phpMyadmin创建Mysql数据库及独立数据库帐号建立,如果你不会mysql命令来创建数据库之类的我们可以使用phpmyadmin来完成哦。 phpMyadmin创...2016-11-25