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的格式(逗号分隔加换行)写入一个文本文件。