【问题标题】:C++ CSV line with commas and strings within double quotes双引号内带有逗号和字符串的 C++ CSV 行
【发布时间】:2016-06-08 22:08:51
【问题描述】:

我正在读取 C++ 中的 CSV 文件,行格式如下:

"小学、中学、大学", "小学", , "中学", 18, 4, 0, 0, 0

(注意空值)

当我这样做时:

while (std::getline(ss, csvElement, ',')) {
   csvColumn.push_back(csvElement);
}

这会将第一个字符串分成不正确的部分。

如何在迭代时保留字符串?我尝试将上述方法结合起来,同时还抓住了由双引号分隔的行,但我得到了疯狂的结果。

【问题讨论】:

  • 有没有人使用std::quoted 有一个好的解决方案?该标准在描述 std::quoted 时特别提到了 CSV 文件,但我想不出一种优雅的方式来使用它。

标签: c++ csv double quotes comma


【解决方案1】:

使用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";
    }
}

Live Example

需要注意的是,只有当值的第一个非空白字符是双引号时,才会提取带引号的字符串。此外,引用字符串之后的任何字符都将被丢弃,直到下一个逗号。

【讨论】:

  • 根据我对这个问题的评论,我觉得应该有一个很好的方法来利用std::quoted 进行 CSV 解析。如果您对此有任何改进,请告诉我。
  • 根据en.cppreference.com/w/cpp/io/manip/quoted,cin默认跳过空格,quoted通过stream &gt;&gt; c提取第一个字符,所以即使逗号和开头引号之间有空格也可以。因此,您不需要偷看。
  • @MooingDuck 如果默认跳过空格,则始终调用std::quoted 将拆分包含空格的未引用字符串,可能在逗号分隔符之前。我不确定这是否无法解析典型的 CSV 格式?或者我误解了你的建议。
  • 是的,你是对的,我错了。唔。奇怪的是它似乎无法解决自己的任务
  • @MooingDuck 我同意,感觉好像我错过了什么!我想它至少对于编写 CSV 文件很方便。
【解决方案2】:

您需要根据是否在引号之间来解释逗号。这对于getline() 来说太复杂了。

解决方案是使用getline() 读取整行,并通过逐个字符地遍历字符串来解析该行,并维护一个指示符是否在双引号之间。

这是第一个“原始”示例(字段中没有删除双引号,也没有解释转义字符):

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
}

Online demo

【讨论】:

    【解决方案3】:

    迭代时如何保留字符串?

    这是我使用的 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);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-04-15
      • 2016-01-18
      • 2018-03-20
      • 1970-01-01
      • 2017-08-08
      • 2017-08-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多