1.4 读取CSV文件
CSV格式是一种以逗号作为分隔符(也可以是其他符号,CSV格式并没有一个通用的标准),存储表格数据的文本格式,可以在Excel和WPS中方便地编辑CSV文件,使用Excel强大的功能,可以很好地管理CSV表格中的数据,例如,用公式来批量修改数值,对表格中的数据进行排序、筛选等。如图1-1演示了在WPS中打开的一个CSV文件。
图1-1 CSV表格
CSV由逗号作为分隔符来描述每一个字段,用换行符来描述每一列数据。因为CSV格式本身非常简单,所以解析工作也非常简单,接下来会把CSV的解析工作实现。
在开始写代码之前,先来见识CSV的庐山真面目,像图1-1这样的一个表格,用文本编辑器打开,看到的内容是下面这样的,每一行数据占一行,数据之间用逗号分开。
用户ID,用户名,等级,阵营 1, kx,10,部落 2, wang,2,联盟 3, abc,4,联盟
这只是一个简单的CSV文件,当字段中包含了逗号、双引号或者换行符时,解析会变得麻烦,当一个字段中包含了逗号或换行符时,Excel会自动使用双引号将整个字段包裹起来,而如果字段中包含了双引号且整个字段被双引号包裹,Excel则会自动将字段中的双引号替换成两个双引号。具体的规则可以参考RFC4180规范http://tools.itef.org/html/rfc4180。
1.4.1 解析CSV
要解析前面的CSV文件,只需要进行简单的字符串解析即可,因为CSV配置文件一般都放在Resource目录下,所以需要用fullPathFromRelativePath将完整的路径取出来,并且使用getFileData来读文件。
//获取路径 string path = FileUtils::getInstance()->fullPathForFilename(fileName); //读取文件 string csvFile = FileUtils::getInstance()->getStringFromFile(path);
现在得到了一个字符串csvFile,对这个字符串进行解析可以得到文件中每一个字段的内容,这里封装了一个CSVLoader用于解析CSV文件格式,使用它来解析前面的CSV文件,然后将每个字段的内容打印出来。代码如下:
CCsvLoader loader; if (loader.LoadCSV("test.csv")) { //跳过第一行,从第二行开始打印CSV文件内容 while (loader.NextLine()) { //顺序取出CSV文件每一行的每个字段,并进行打印 int uid = loader.NextInt(); string name = loader.NextStr(); int lv = loader.NextInt(); string camp = loader.NextStr(); CCLOG("uid %d name %s lv %d camp %s", uid, name.c_str(), lv, camp. c_str()); } }
运行程序后打印的结果如下。
uid 1 name kx lv 10 camp red uid 2 name wang lv 2 camp blue uid 3 name abc lv 4 camp blue
如果直接在CCLOG语句中打印则有可能输出其他的结果,因为函数参数入栈的顺序是从右到左的,也就是最后的NextStr先执行。
CCLOG("uid %d name %s lv %d camp %s", loader.NextInt(), loader.NextStr().c_str(), loader.NextInt(), loader.NextStr().c_str());
1.4.2 描述复杂结构
在CSV中描述复杂的数据结构是比较麻烦的事情,例如,要在一个字段里面表示多个信息,那么可以用其他的分隔符来描述这个结构,如我们的位置字段包含了x和y两个坐标的信息,那么可以用x|y或者x+y、x! y之类的写法,用分隔符来区分一个字段中的多个信息,又如我们希望将一个不定长度的int数组存储到CSV文件中。
可以用CSVLoader的SplitStrToVector()方法,传入这个字段和设定的分隔符,来解析这串数据。当然可以用多个字段来描述,但这作为一种比较灵活的方法,可以在有限的字段里,表现更加复杂的结构,丰富的内容,对于要描述一些动态的属性是比较有帮助的。
//传入要解析的字符串str、分隔符sep以及用于接收分隔后的字符串容器out bool CCsvLoader::SplitStrToVector(const std::string &str, char sep, std:: vector<std::string>& out) { int pos = 0; int step = 0; while (static_cast<unsigned int>(pos) < str.length() && step ! = -1) { step = str.find_first_of(sep, pos); string seg = str.substr(pos, step); out.push_back(seg); pos = step + 1; } return out.size() > 0; }
通过调用SplitStrToVector()方法,可以先将字符串解析到vector中,然后再将vector中的字符串进行解析。通过使用不同的分隔符,还可以在CSV文件中描述多维数组。CSV文件的写入一般不会用到,如果需要生成CSV文件,可以直接按照CSV的格式(逗号分隔加换行)写入一个文本文件。