我需要在c++中加载和使用CSV文件数据。在这一点上,它实际上只是一个以逗号分隔的解析器(即不用担心转义新行和逗号)。主要需要的是逐行解析器,它将在每次调用方法时为下一行返回一个向量。
我发现这篇文章看起来很有前途: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp
我从未使用过Boost's Spirit,但我愿意尝试一下。但前提是我忽略了一个更直接的解决方案。
我需要在c++中加载和使用CSV文件数据。在这一点上,它实际上只是一个以逗号分隔的解析器(即不用担心转义新行和逗号)。主要需要的是逐行解析器,它将在每次调用方法时为下一行返回一个向量。
我发现这篇文章看起来很有前途: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp
我从未使用过Boost's Spirit,但我愿意尝试一下。但前提是我忽略了一个更直接的解决方案。
当前回答
就像每个人都把他的解决方案,这里是我的使用模板,lambda和tuple。
它可以将任何具有所需列的CSV转换为tuple的c++向量。
它通过在元组中定义每个CSV行元素类型来工作。
您还需要为每个元素定义std::string到类型转换Formatter lambda(例如使用std::atod)。
然后你就得到了这个结构的一个向量,对应于你的CSV数据。
您可以很容易地重用它来匹配任何CSV结构。
StringsHelpers.hpp
#include <string>
#include <fstream>
#include <vector>
#include <functional>
namespace StringHelpers
{
template<typename Tuple>
using Formatter = std::function<Tuple(const std::vector<std::string> &)>;
std::vector<std::string> split(const std::string &string, const std::string &delimiter);
template<typename Tuple>
std::vector<Tuple> readCsv(const std::string &path, const std::string &delimiter, Formatter<Tuple> formatter);
};
StringsHelpers.cpp
#include "StringHelpers.hpp"
namespace StringHelpers
{
/**
* Split a string with the given delimiter into several strings
*
* @param string - The string to extract the substrings from
* @param delimiter - The substrings delimiter
*
* @return The substrings
*/
std::vector<std::string> split(const std::string &string, const std::string &delimiter)
{
std::vector<std::string> result;
size_t last = 0,
next = 0;
while ((next = string.find(delimiter, last)) != std::string::npos) {
result.emplace_back(string.substr(last, next - last));
last = next + 1;
}
result.emplace_back(string.substr(last));
return result;
}
/**
* Read a CSV file and store its values into the given structure (Tuple with Formatter constructor)
*
* @tparam Tuple - The CSV line structure format
*
* @param path - The CSV file path
* @param delimiter - The CSV values delimiter
* @param formatter - The CSV values formatter that take a vector of strings in input and return a Tuple
*
* @return The CSV as vector of Tuple
*/
template<typename Tuple>
std::vector<Tuple> readCsv(const std::string &path, const std::string &delimiter, Formatter<Tuple> formatter)
{
std::ifstream file(path, std::ifstream::in);
std::string line;
std::vector<Tuple> result;
if (file.fail()) {
throw std::runtime_error("The file " + path + " could not be opened");
}
while (std::getline(file, line)) {
result.emplace_back(formatter(split(line, delimiter)));
}
file.close();
return result;
}
// Forward template declarations
template std::vector<std::tuple<double, double, double>> readCsv<std::tuple<double, double, double>>(const std::string &, const std::string &, Formatter<std::tuple<double, double, double>>);
} // End of StringHelpers namespace
Main.cpp(一些用法)
#include "StringHelpers.hpp"
/**
* Example of use with a CSV file which have (number,Red,Green,Blue) as line values. We do not want to use the 1st value
* of the line.
*/
int main(int argc, char **argv)
{
// Declare CSV line type, formatter and template type
typedef std::tuple<double, double, double> CSV_format;
typedef std::function<CSV_format(const std::vector<std::string> &)> formatterT;
enum RGB { Red = 1, Green, Blue };
const std::string COLOR_MAP_PATH = "/some/absolute/path";
// Load the color map
auto colorMap = StringHelpers::readCsv<CSV_format>(COLOR_MAP_PATH, ",", [](const std::vector<std::string> &values) {
return CSV_format {
// Here is the formatter lambda that convert each value from string to what you want
std::strtod(values[Red].c_str(), nullptr),
std::strtod(values[Green].c_str(), nullptr),
std::strtod(values[Blue].c_str(), nullptr)
};
});
// Use your colorMap as you wish...
}
其他回答
你可能想看看我的自由/开源软件项目CSVfix(更新链接),这是一个用c++编写的CSV流编辑器。CSV解析器不是什么好东西,但它完成了工作,整个包可以在不编写任何代码的情况下满足您的需要。
CSV解析器请参见alib/src/a_csv.cpp,使用示例请参见csvlib/src/csved_ioman.cpp (IOManager::ReadCSV)。
由于我现在不习惯boost,我将建议一个更简单的解决方案。假设您的.csv文件有100行,每行有10个数字,用“,”分隔。你可以用下面的代码以数组的形式加载这个数据:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
int A[100][10];
ifstream ifs;
ifs.open("name_of_file.csv");
string s1;
char c;
for(int k=0; k<100; k++)
{
getline(ifs,s1);
stringstream stream(s1);
int j=0;
while(1)
{
stream >>A[k][j];
stream >> c;
j++;
if(!stream) {break;}
}
}
}
不管怎样,下面是我的实现。它处理wstring输入,但是可以很容易地调整为string。它不处理字段中的换行符(因为我的应用程序也不这样做,但添加它的支持并不太难),它不符合RFC中的“\r\n”行尾(假设您使用std::getline),但它确实正确地处理空格修剪和双引号(希望如此)。
using namespace std;
// trim whitespaces around field or double-quotes, remove double-quotes and replace escaped double-quotes (double double-quotes)
wstring trimquote(const wstring& str, const wstring& whitespace, const wchar_t quotChar)
{
wstring ws;
wstring::size_type strBegin = str.find_first_not_of(whitespace);
if (strBegin == wstring::npos)
return L"";
wstring::size_type strEnd = str.find_last_not_of(whitespace);
wstring::size_type strRange = strEnd - strBegin + 1;
if((str[strBegin] == quotChar) && (str[strEnd] == quotChar))
{
ws = str.substr(strBegin+1, strRange-2);
strBegin = 0;
while((strEnd = ws.find(quotChar, strBegin)) != wstring::npos)
{
ws.erase(strEnd, 1);
strBegin = strEnd+1;
}
}
else
ws = str.substr(strBegin, strRange);
return ws;
}
pair<unsigned, unsigned> nextCSVQuotePair(const wstring& line, const wchar_t quotChar, unsigned ofs = 0)
{
pair<unsigned, unsigned> r;
r.first = line.find(quotChar, ofs);
r.second = wstring::npos;
if(r.first != wstring::npos)
{
r.second = r.first;
while(((r.second = line.find(quotChar, r.second+1)) != wstring::npos)
&& (line[r.second+1] == quotChar)) // WARNING: assumes null-terminated string such that line[r.second+1] always exist
r.second++;
}
return r;
}
unsigned parseLine(vector<wstring>& fields, const wstring& line)
{
unsigned ofs, ofs0, np;
const wchar_t delim = L',';
const wstring whitespace = L" \t\xa0\x3000\x2000\x2001\x2002\x2003\x2004\x2005\x2006\x2007\x2008\x2009\x200a\x202f\x205f";
const wchar_t quotChar = L'\"';
pair<unsigned, unsigned> quot;
fields.clear();
ofs = ofs0 = 0;
quot = nextCSVQuotePair(line, quotChar);
while((np = line.find(delim, ofs)) != wstring::npos)
{
if((np > quot.first) && (np < quot.second))
{ // skip delimiter inside quoted field
ofs = quot.second+1;
quot = nextCSVQuotePair(line, quotChar, ofs);
continue;
}
fields.push_back( trimquote(line.substr(ofs0, np-ofs0), whitespace, quotChar) );
ofs = ofs0 = np+1;
}
fields.push_back( trimquote(line.substr(ofs0), whitespace, quotChar) );
return fields.size();
}
当对CSV文件使用Boost Tokenizer escaped_list_separator时,应该注意以下几点:
它需要一个转义字符(默认的反斜杠- \) 它需要一个分割符/分隔符-字符(默认逗号-,) 它需要一个引号字符(默认的引号- ")
wiki指定的CSV格式规定数据字段可以包含引号分隔符(支持):
1997年,福特E350,“超级豪华卡车”
由wiki指定的CSV格式规定单引号应该用双引号处理(escaped_list_separator将剥离所有引号字符):
1997年,福特E350,“超级”“豪华”“卡车”
CSV格式没有指定应该删除任何反斜杠字符(escaped_list_separator将删除所有转义字符)。
修复boost escaped_list_separator的默认行为的一个可能的变通方法:
首先将所有反斜杠字符(\)替换为两个反斜杠字符(\\),这样它们就不会被剥离。 其次,将所有双引号("")替换为一个反斜杠字符和一个引号(\")
这种变通方法有一个副作用,即由双引号表示的空数据字段将被转换为单引号标记。在遍历令牌时,必须检查令牌是否是单引号,并将其视为空字符串。
不漂亮,但它工作,只要在引号中没有换行。
你可以在escaped_list_separator中使用Boost Tokenizer。
Escaped_list_separator解析csv的超集。Boost::记号赋予器
这只使用Boost标记器头文件,不需要链接到Boost库。
下面是一个例子,(详情请参阅c++中使用Boost Tokenizer解析CSV文件或Boost:: Tokenizer):
#include <iostream> // cout, endl
#include <fstream> // fstream
#include <vector>
#include <string>
#include <algorithm> // copy
#include <iterator> // ostream_operator
#include <boost/tokenizer.hpp>
int main()
{
using namespace std;
using namespace boost;
string data("data.csv");
ifstream in(data.c_str());
if (!in.is_open()) return 1;
typedef tokenizer< escaped_list_separator<char> > Tokenizer;
vector< string > vec;
string line;
while (getline(in,line))
{
Tokenizer tok(line);
vec.assign(tok.begin(),tok.end());
// vector now contains strings from one row, output to cout here
copy(vec.begin(), vec.end(), ostream_iterator<string>(cout, "|"));
cout << "\n----------------------" << endl;
}
}