基于状态机的 CSV 文件数据解析

#include <tchar.h>
#include <afxtempl.h>
#include <stdio.h>
#include <string.h>
#ifndef CStringA
#define CStringA CString
#endif //CStringA

//解析CSV行数据核心代码
//chLine 输入行
//szDataArray 子项解析输出
//返回值:子项个数
int AnalysCSVDataLine(LPCSTR chLine, CStringArray &szDataArray)
{
  ASSERT(chLine != NULL);

  //先清理输出项
  szDataArray.RemoveAll();

  //子项状态
  enum _tagItemType_t
  {
    _Invalid_Item, //无效项
      _Comma_Item, //逗号分隔项
      _Quota_Item, //引号分隔项
  };
  
  //子项状态初始化为无效
  _tagItemType_t eItem = _Invalid_Item;
  //子项数据字符缓存区
  CStringA szSubData;
  //行长度
  int iLineLen = (int)strlen(chLine);
    
  //解析子项
  for(int iPos=0; iPos<iLineLen; )      
  {
    //读取两个字符
    char c0 = chLine[iPos+0];
    char c1 = (iPos+1 <iLineLen)? chLine[iPos+1]:0;

    //插入子项?
    int iInsertFlag = 0;
   
    //依据子项状态处理
    switch(eItem)
    {
    case(_Invalid_Item): //无效
      {
        szSubData.Empty(); //先清空子项
        
        if(c0 == '\"') //引号起始
        {
          eItem = _Quota_Item; //转为引号分隔
          iPos += 1; //跳过"
        }
        else
        {
          eItem = _Comma_Item; //逗号分割符
        }
        
        break;
      }
    case(_Quota_Item): //引号分隔
      {
        if(c0 == '\"' && c1 == '\"') //"" 遇两个引号
        {
          szSubData += "\""; //数据添加一个"
          iPos += 2; //跳过""
        }
        else if(c0 == '\"' && (c1==',' || c1=='\n' || c1=='\0')) //",或 "\0 遇行结束符
        {
          iInsertFlag = 2; //插入子项
        }
        else //正常数据
        {
          szSubData += c0; //追加数据
          iPos++; //跳到下一字符
        }
        
        break;
      }
    case(_Comma_Item): //逗号风格符
      {
        if(c0==',' || c0=='\n' || c0=='\0') //逗号或行结束
        {
          iInsertFlag = 1; //插入子项
        }
        else //正常数据
        {
          szSubData += c0; //追加数据
          iPos++; //跳到下一字符
        }
      }
    }
    
    //插入子项
    if(iInsertFlag)
    {
      szDataArray.Add(szSubData); //添加子项
      eItem = _Invalid_Item; //等待下一项
      iPos += iInsertFlag; //跳过长度
    }
    
  }

  //返回子项个数
  return (INT)szDataArray.GetSize();
}


int AnalsysCSVDataFile(LPCTSTR szFile, 
                       CTypedPtrArray <CPtrArray, CStringArray *> &szDataArray)
{
  FILE *fp = NULL;
  if((fp = _tfopen(szFile, _T("rt"))) != NULL)
  { 
    while(!feof(fp))
    {
      //行字符缓存区
      CHAR chLine[8192];
      memset(chLine, 0, sizeof(chLine));

      //读行
      if(fgets(chLine, sizeof(chLine), fp) == NULL)
        break;
      
      //解析行
      CStringArray szDataLines;
      if(AnalysCSVDataLine(chLine, szDataLines) > 0)
      {
        //追加行
        CStringArray *pStrA = new CStringArray;
        pStrA->Copy(szDataLines);
        szDataArray.Add(pStrA);
      }
    }
    
    //关闭文件
    fclose(fp);
  }  

  return (INT)szDataArray.GetSize();
}


 

//调用示例
void CDlg3Dlg::OnButton1()
{
  //打开文件
  CFileDialog dlg(TRUE, _T(".csv"), NULL, 0, 
    _T("csv files(*.csv)|*.csv||"), this);
  if(dlg.DoModal() == IDOK)
  {
    //解析文件
    CTypedPtrArray <CPtrArray, CStringArray *>sData;
    AnalsysCSVDataFile(dlg.GetPathName(), sData);

    //输出内容
    for(INT_PTR iLine=0; iLine<sData.GetSize(); iLine++)
    {
      CStringArray *sLine = sData.GetAt(iLine);
      INT iMaxItem = (INT)sLine->GetSize();
      TRACE(_T("Line%d==>(%d)==>"), iLine, iMaxItem);
      for(INT iItem=0; iItem<iMaxItem; iItem++)
      {
        CString sItem = sLine->GetAt(iItem);
        TRACE(_T("[%s]"), sItem);
      }
      TRACE(_T("\n"));
    }

    //结束清理
    while(sData.GetSize() > 0)
    {
      delete sData.GetAt(0);
      sData.RemoveAt(0);
    }
  }
}

 

//测试用csv文件内容

1,"""2""","3,ABC",4,"""""",",",""",""",,"aaa"",""bbb"
1,"""2""","3,ABC",4,"""""",",",""",""",,"aaa""??bbb"""
1,"""2""","3,ABC",4,"""""",",",""",""",,"aaa""??bbb"""

//调试输出

Line0==>(9)==>[1]["2"][3,ABC][4][""][,][","][][aaa","bbb]
Line1==>(9)==>[1]["2"][3,ABC][4][""][,][","][][aaa"??bbb"]
Line2==>(9)==>[1]["2"][3,ABC][4][""][,][","][][aaa"??bbb"]

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
问题描述 设计C++类,使其能够统计文本文件中字符个数,在终端中显示结果,并将结果保存到指定的文件中。需要统计的字符有:a、英文字母总数b、英文单词总数从c、中文字符总数(包括标点符号)d、行总数e、其他字符总数(包括英文标点、空白符等)。 问题分析 从问题描述中可以看到,我们所要做的就是将一篇文章(txt格式,字符格式为ANSI)中出现的中英文字符出现的次数进行统计。而一篇文本文件中出现的字符种类和顺序没有规律,如何对当前读取的字符的种类进行判断并转入到相应的统计状态成为了问题的关键。 但是,我们知道对于英文字母其ASCII码范围在65~90和97~122两个区间,而英文单词的判断就是在相邻两个非英文字母之间夹着一串英文字母,所以我们可以在读取了第一个非英文字母后判断下一个读取的是否为英文字母,如果是便转入英文字符的处理状态,其中可同时对字母个数进行统计,在读到非英文字符时退出该状态,并将英文单词数加1,然后转入“状态转换中心”进行状态转换的判断。 同理,对于中文字符的处理,也同上所述。但是由于中文字符占两个字节,而且各个编码格式中中文字符的编码范围不一致,所以处理起来比较麻烦,但是原理是一致的。在本例中仅对ANSI格式的编码进行处理,所以中文字符每个字节的范围为0x80~0xff,不过在实际操作中发现有些中文字符还是被漏掉了,应该是中文字符的编码范围并没有全部包括进去的原因。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值