C++ CSV 行,双引号内有逗号和字符串
C++ CSV line with commas and strings within double quotes
我正在用 C++ 读取 CSV 文件,行格式如下:
"Primary, Secondary, Third", "Primary", , "Secondary", 18, 4, 0, 0, 0
(注意空值)
当我这样做时:
while (std::getline(ss, csvElement, ',')) {
csvColumn.push_back(csvElement);
}
这将第一个字符串分成了不正确的部分。
如何在迭代时保留字符串?我尝试将上述内容结合起来,同时也抓住了用双引号分隔的行,但我得到了疯狂的结果。
您需要根据是否在引号之间来解释逗号。这对于 getline()
来说太复杂了。
解决方案是使用 getline()
读取整行,并通过逐个字符地遍历字符串来解析该行,并维护一个指示符是否在双引号之间。
这是第一个 "raw" 示例(字段中的双引号未被删除且转义字符未被解释):
string line;
while (std::getline(cin, line)) { // read full line
const char *mystart=line.c_str(); // prepare to parse the line - start is position of begin of field
bool instring{false};
for (const char* p=mystart; *p; p++) { // iterate through the string
if (*p=='"') // toggle flag if we're btw double quote
instring = !instring;
else if (*p==',' && !instring) { // if comma OUTSIDE double quote
csvColumn.push_back(string(mystart,p-mystart)); // keep the field
mystart=p+1; // and start parsing next one
}
}
csvColumn.push_back(string(mystart)); // last field delimited by end of line instead of comma
}
使用 std::quoted
允许您从输入流中读取带引号的字符串。
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::stringstream ss;
ss << "\"Primary, Secondary, Third\", \"Primary\", , \"Secondary\", 18, 4, 0, 0, 0";
while (ss >> std::ws) {
std::string csvElement;
if (ss.peek() == '"') {
ss >> std::quoted(csvElement);
std::string discard;
std::getline(ss, discard, ',');
}
else {
std::getline(ss, csvElement, ',');
}
std::cout << csvElement << "\n";
}
}
需要注意的是,只有当值的第一个非空白字符是双引号时,才会提取带引号的字符串。此外,引号字符串之后的所有字符都将被丢弃,直到下一个逗号。
How do I preserve the string when iterating?
这是我使用的 C++ 方法。
我注意到您只有 3 种字段类型:string、null 和 int。
以下方法使用这些字段类型(在方法 "void init()" 中),按照每行显示字段的顺序,有时使用 string::find() (而不是 getline() )来定位场结束。
这 3 种方法中的每一种都使用擦除字符串中的字符。我知道擦除很慢,但为了方便起见,我做出了这个选择。 (擦除更容易测试,只需在每次提取后添加一个cout)。可以通过适当处理(在需要时)搜索开始索引来删除/替换擦除。
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <cassert>
class CSV_t
{
typedef std::vector<int> IntVec_t;
// private nested class -- holds contents of 1 csv record
class CSVRec_t
{
public:
std::string primary;
std::string secondary;
std::string nullary;
std::string thirdary;
IntVec_t i5;
std::string show()
{
std::stringstream ss;
ss << std::setw(25) << primary
<< " " << std::setw(10) << secondary
<< " " << std::setw(12)<< thirdary << " ";
for (size_t i=0;
i<i5.size(); ++i) ss << std::setw(5) << i5[i];
ss << std::endl;
return (ss.str());
}
}; // class CSVRec_t
typedef std::vector<CSVRec_t> CSVRecVec_t;
CSVRecVec_t csvRecVec; // holds all csv record
public:
CSV_t() { };
void init(std::istream& ss)
{
do // read all rows of file
{
CSVRec_t csvRec;
std::string s;
(void)std::getline(ss, s);
if(0 == s.size()) break;
assert(s.size()); extractQuotedField(s, csvRec.primary); // 1st quoted substring
assert(s.size()); extractQuotedField(s, csvRec.secondary); // 2nd quoted substring
assert(s.size()); confirmEmptyField(s, csvRec.nullary); // null field
assert(s.size()); extractQuotedField(s, csvRec.thirdary); // 3rd quoted substring
assert(s.size()); extract5ints(s, csvRec.i5); // handle 5 int fields
csvRecVec.push_back(csvRec); // capture
if(ss.eof()) break;
}while(1);
}
void show()
{
std::cout << std::endl;
for (size_t i = 0; i < csvRecVec.size(); ++i)
std::cout << std::setw(5) << i+1 << " " << csvRecVec[i].show();
std::cout << std::endl;
}
private:
void extractQuotedField(std::string& s, std::string& s2)
{
size_t indx1 = s.find('"', 0);
assert(indx1 != std::string::npos);
size_t indx2 = s.find('"', indx1+1);
assert(indx2 != std::string::npos);
size_t rng1 = indx2 - indx1 + 1;
s2 = s.substr(indx1, rng1);
s.erase(indx1, rng1+1);
}
void confirmEmptyField(std::string& s, std::string nullary)
{
size_t indx1 = s.find('"');
nullary = s.substr(0, indx1);
// tbd - confirm only spaces and comma's in this substr()
s.erase(0, indx1);
}
void extract5ints(std::string& s, IntVec_t& i5)
{
std::stringstream ss(s);
int t = 0;
for (int i=0; i<5; ++i)
{
ss >> t;
ss.ignore(1); // skip ','
assert(!ss.bad()); // confirm ok
i5.push_back(t);
}
s.erase(0, std::string::npos);
}
}; // class CSV_t
int t288(void) // test 288
{
std::stringstream ss;
ss << "\"Primary, Secondary, Third\", \"Primary\", , \"Secondary\", 18, 4, 0, 0, 0\n"
<< "\"Pramiry, Secandory, Thrid\", \"Pramiry\", , \"Secandory\", 19, 5, 1, 1, 1\n"
<< "\"Pri-mary, Sec-ondary, Trd\", \"Pri-mary\", , \"Sec-ondary\", 20, 6, 2, 3, 4\n"
<< std::endl;
CSV_t csv;
csv.init(ss);
csv.show(); // results
return (0);
}
我正在用 C++ 读取 CSV 文件,行格式如下:
"Primary, Secondary, Third", "Primary", , "Secondary", 18, 4, 0, 0, 0
(注意空值)
当我这样做时:
while (std::getline(ss, csvElement, ',')) {
csvColumn.push_back(csvElement);
}
这将第一个字符串分成了不正确的部分。
如何在迭代时保留字符串?我尝试将上述内容结合起来,同时也抓住了用双引号分隔的行,但我得到了疯狂的结果。
您需要根据是否在引号之间来解释逗号。这对于 getline()
来说太复杂了。
解决方案是使用 getline()
读取整行,并通过逐个字符地遍历字符串来解析该行,并维护一个指示符是否在双引号之间。
这是第一个 "raw" 示例(字段中的双引号未被删除且转义字符未被解释):
string line;
while (std::getline(cin, line)) { // read full line
const char *mystart=line.c_str(); // prepare to parse the line - start is position of begin of field
bool instring{false};
for (const char* p=mystart; *p; p++) { // iterate through the string
if (*p=='"') // toggle flag if we're btw double quote
instring = !instring;
else if (*p==',' && !instring) { // if comma OUTSIDE double quote
csvColumn.push_back(string(mystart,p-mystart)); // keep the field
mystart=p+1; // and start parsing next one
}
}
csvColumn.push_back(string(mystart)); // last field delimited by end of line instead of comma
}
使用 std::quoted
允许您从输入流中读取带引号的字符串。
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::stringstream ss;
ss << "\"Primary, Secondary, Third\", \"Primary\", , \"Secondary\", 18, 4, 0, 0, 0";
while (ss >> std::ws) {
std::string csvElement;
if (ss.peek() == '"') {
ss >> std::quoted(csvElement);
std::string discard;
std::getline(ss, discard, ',');
}
else {
std::getline(ss, csvElement, ',');
}
std::cout << csvElement << "\n";
}
}
需要注意的是,只有当值的第一个非空白字符是双引号时,才会提取带引号的字符串。此外,引号字符串之后的所有字符都将被丢弃,直到下一个逗号。
How do I preserve the string when iterating?
这是我使用的 C++ 方法。
我注意到您只有 3 种字段类型:string、null 和 int。
以下方法使用这些字段类型(在方法 "void init()" 中),按照每行显示字段的顺序,有时使用 string::find() (而不是 getline() )来定位场结束。
这 3 种方法中的每一种都使用擦除字符串中的字符。我知道擦除很慢,但为了方便起见,我做出了这个选择。 (擦除更容易测试,只需在每次提取后添加一个cout)。可以通过适当处理(在需要时)搜索开始索引来删除/替换擦除。
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <cassert>
class CSV_t
{
typedef std::vector<int> IntVec_t;
// private nested class -- holds contents of 1 csv record
class CSVRec_t
{
public:
std::string primary;
std::string secondary;
std::string nullary;
std::string thirdary;
IntVec_t i5;
std::string show()
{
std::stringstream ss;
ss << std::setw(25) << primary
<< " " << std::setw(10) << secondary
<< " " << std::setw(12)<< thirdary << " ";
for (size_t i=0;
i<i5.size(); ++i) ss << std::setw(5) << i5[i];
ss << std::endl;
return (ss.str());
}
}; // class CSVRec_t
typedef std::vector<CSVRec_t> CSVRecVec_t;
CSVRecVec_t csvRecVec; // holds all csv record
public:
CSV_t() { };
void init(std::istream& ss)
{
do // read all rows of file
{
CSVRec_t csvRec;
std::string s;
(void)std::getline(ss, s);
if(0 == s.size()) break;
assert(s.size()); extractQuotedField(s, csvRec.primary); // 1st quoted substring
assert(s.size()); extractQuotedField(s, csvRec.secondary); // 2nd quoted substring
assert(s.size()); confirmEmptyField(s, csvRec.nullary); // null field
assert(s.size()); extractQuotedField(s, csvRec.thirdary); // 3rd quoted substring
assert(s.size()); extract5ints(s, csvRec.i5); // handle 5 int fields
csvRecVec.push_back(csvRec); // capture
if(ss.eof()) break;
}while(1);
}
void show()
{
std::cout << std::endl;
for (size_t i = 0; i < csvRecVec.size(); ++i)
std::cout << std::setw(5) << i+1 << " " << csvRecVec[i].show();
std::cout << std::endl;
}
private:
void extractQuotedField(std::string& s, std::string& s2)
{
size_t indx1 = s.find('"', 0);
assert(indx1 != std::string::npos);
size_t indx2 = s.find('"', indx1+1);
assert(indx2 != std::string::npos);
size_t rng1 = indx2 - indx1 + 1;
s2 = s.substr(indx1, rng1);
s.erase(indx1, rng1+1);
}
void confirmEmptyField(std::string& s, std::string nullary)
{
size_t indx1 = s.find('"');
nullary = s.substr(0, indx1);
// tbd - confirm only spaces and comma's in this substr()
s.erase(0, indx1);
}
void extract5ints(std::string& s, IntVec_t& i5)
{
std::stringstream ss(s);
int t = 0;
for (int i=0; i<5; ++i)
{
ss >> t;
ss.ignore(1); // skip ','
assert(!ss.bad()); // confirm ok
i5.push_back(t);
}
s.erase(0, std::string::npos);
}
}; // class CSV_t
int t288(void) // test 288
{
std::stringstream ss;
ss << "\"Primary, Secondary, Third\", \"Primary\", , \"Secondary\", 18, 4, 0, 0, 0\n"
<< "\"Pramiry, Secandory, Thrid\", \"Pramiry\", , \"Secandory\", 19, 5, 1, 1, 1\n"
<< "\"Pri-mary, Sec-ondary, Trd\", \"Pri-mary\", , \"Sec-ondary\", 20, 6, 2, 3, 4\n"
<< std::endl;
CSV_t csv;
csv.init(ss);
csv.show(); // results
return (0);
}