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

  本文乃Siliphen原创,转载请注明出处:

  本文分为5小节,基本上就是我刚接触CSV文件到思考、实践做一个CSV解析器的过程的还原。希望我的思路也能带领你一步步从浅到深认识CSV文件格式。

  1.简单的CSV解析器实现。

  2.简单实现的CSV解析器的问题

  3. CSV格式的定义

  4.用FSM(有限状态机)来做CSV格式解析。

  5.为什么使用CSV格式

  1.简单的CSV解析器实现。

  最近有一个需求,读取CSV格式的配置。CSV是CommaSeparated Value(逗号分隔值)的缩写,通常用文本表示数据。CSV格式数据的结构类似表格,不同的记录占用一行,一行中的字段用“,”(逗号)分隔,例如:

  名字,职业,工作经验(年),

  Siliphen Lee,软件工程师(码畜),5

  Edison Chou,游戏服务器端主程,1

  Deson,钢琴教师兼游戏策划,1

  … …

  咋一看,CSV格式比较简单。就是用行来分隔不同的记录,记录中用“,”逗号分隔不同的字段域。仅仅是这样考虑的话,那么编写CSV解析器也很简单了。就是字符串的分割而已。

  好,下面来动手实现下这个思路。C#等语言的字符串都有Split函数,C++的标准库却连这个很常用的函数都没,C++的标准库简直是弱爆了!而就算用了boost,因为boost接近std的风格,类似Split功能的函数,使用起来也比较麻烦。

  没办法,只好自己编写Split来实现CSV的解析了。对于CSV的解析功能,为了实现“组件化”,“复用”的理想,可以单独写一个Csv类,封装一些相关操作。以后在别的工程项目中,也可以直接把这个类拿过去用。

  CSV类,三下五除二,就编写好了。代码如下:

  头文件

#pragma once#include <vector>#include <string>using namespace std;class Csv{public:Csv();~Csv();public:// 载入一个CSV文件void Load(const string& strFileName);// 从字符串从解析void Parse(const string& strText);public :/*分割字符串str 要分隔的字符串seperator 分隔符Ret 分割后的结果*/static void Split(const string &str, const string& seperator, vector< string >& Ret);/*读取整个文件的数据*/static void ReadAll(const string& strFileName , string& Data );public :vector< vector< string > >& GetGridData(){ return m_GridData; }private : // 原始表格数据vector< vector< string > > m_GridData;};  实现文件:#include "Csv.h"#include <stdio.h>Csv::Csv(){}Csv::~Csv(){}void Csv::ReadAll(const string& strFileName, string& Data){// 读取文件数据FILE* pFile = fopen(strFileName.c_str(), "rb");if (pFile == 0){return;}fseek(pFile, 0, SEEK_END);long len = ftell(pFile);char *pBuffer = new char[len + 1];fseek(pFile, 0, SEEK_SET);fread(pBuffer, 1, len, pFile);fclose(pFile);pBuffer[len] = 0;Data.assign(pBuffer, len );delete[] pBuffer;}void Csv::Load(const string& strFileName){string Data; ReadAll(strFileName, Data); Parse(Data );}void Csv::Parse(const string& strText){// 清除之前的数据m_GridData.clear();// 分出行,分出字段。vector< string > ret;Split(strText, "\r\n", ret);for (size_t i = 0; i < ret.size(); ++i){vector< string > Fields;Split(ret[i], ",", Fields);m_GridData.push_back(Fields);}}void Csv::Split(const string &str, const string& seperator, vector< string >& Ret){Ret.clear();size_t nStartPosFound = str.find(seperator, 0);size_t nFieldStart = 0;for (; nStartPosFound != -1; nStartPosFound = str.find(seperator, nStartPosFound)){string strSub = str.substr(nFieldStart, nStartPosFound – nFieldStart);nStartPosFound = nStartPosFound + seperator.size();nFieldStart = nStartPosFound;Ret.push_back(strSub);}// 加入最后一个字段if (nFieldStart < str.size()){string strSub = str.substr(nFieldStart, str.size() – nFieldStart);Ret.push_back(strSub);}}

  对代码做一些简要说明。设计Csv类首先考虑的就是“独立性”。Csv类不应该耦合(依赖)任何其他库,比如说:尽量避免使用Cocos2d-x,QT里面的函数。Cocos2d-x有跨平台的文件读取方法,QT有字符串的split函数。如果用了这些库的现成机制,会导致类的通用性下降。比如,在另一个非Cocos2d-x,非QT的项目中,就不能直接用了。

  同样地,从可移植性考虑。用VS编写读取文件,也不应该使用CreateFile, ReadFile等Win32 Api。fopen虽然不太好用,但由于其是C语言标准库的,移植性好,故用之。

  这里有一个问题需要注意下,用VS2013编辑和编译的话,可能会对fopen函数提示有错误。如下:

世界没有永久的冬天;不要讨厌麻烦,

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

相关文章:

你感兴趣的文章:

标签云: