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

 更新时间:2016年12月09日 15:30:33   投稿:lqh  
这篇文章主要介绍了标准CSV格式的介绍和分析以及解析算法实例详解的相关资料,需要的朋友可以参考下

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

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

相关文章

  • C语言左旋字符串的三种实现方式

    C语言左旋字符串的三种实现方式

    本文章将使用三种思路实现字符串的左旋(循环移动,截取和拼接,逆序反转),文中通过代码示例和图文介绍的非常详细,具有一定的参考价值,需要的朋友可以参考下
    2024-03-03
  • 带头结点单链表与不带头结点单链表的区别

    带头结点单链表与不带头结点单链表的区别

    这篇文章主要介绍了带头结点单链表与不带头结点单链表的区别,需要的朋友可以参考下
    2023-07-07
  • C++ 将文件数据一次性加载进内存实例代码

    C++ 将文件数据一次性加载进内存实例代码

    这篇文章主要介绍了C++ 将文件数据一次性加载进内存实例代码的相关资料,需要的朋友可以参考下
    2017-05-05
  • 基于select、poll、epoll的区别详解

    基于select、poll、epoll的区别详解

    本篇文章是对select、poll、epoll之间的区别进行了详细的分析介绍。需要的朋友参考下
    2013-05-05
  • Qt信号与槽知识点总结归纳

    Qt信号与槽知识点总结归纳

    信号和槽是一种高级接口,应用于对象之间的通信,它是QT的核心特性,下面这篇文章主要给大家介绍了关于Qt信号与槽知识点总结归纳的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • C/C++利用筛选法算素数的方法示例

    C/C++利用筛选法算素数的方法示例

    这篇文章主要给大家介绍了关于利用C/C++筛选法算素数的相关资料,文中给大家列举了普通枚举法和筛选法两种方法实现的方法示例,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
    2017-12-12
  • 深入分析Linux下如何对C语言进行编程

    深入分析Linux下如何对C语言进行编程

    本篇文章介绍了,如何在Linux下对C语言进行编程的详细概述。需要的朋友参考下
    2013-05-05
  • vc获取计算机名和ip地址的方法

    vc获取计算机名和ip地址的方法

    这篇文章主要介绍了vc获取计算机名和ip地址的方法,涉及通过VC对系统相关信息操作技巧,需要的朋友可以参考下
    2015-05-05
  • 详解C++编程中的析构函数

    详解C++编程中的析构函数

    这篇文章主要介绍了C++编程中的析构函数,是C++入门学习中的基础知识,需要的朋友可以参考下
    2015-09-09
  • C++中头文件与源文件的作用详解

    C++中头文件与源文件的作用详解

    这篇文章主要给大家介绍了关于C++中头文件与源文件的作用的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用C++具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-05-05

最新评论