2016-02-25 2 views
2

Я чтение файла CSV в C++ и формат строки как таковой:C++ CSV строку с запятыми и строки в двойных кавычках

"первичный, вторичный, Третий", "Primary", "Secondary" , 18, 4, 0, 0, 0

(обратите внимание на пустое значение)

Когда я делаю:

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

Это разбивает первую строку на куски, не является правильным.

Как сохранить строку при повторении? Я попытался сделать комбинацию из вышеизложенного, а также захватить строки, разделенные двойной цитатой, но я получил дикие результаты.

+1

У кого-нибудь есть хорошее решение для этого, используя 'std :: quoted'? В стандарте специально упоминаются CSV-файлы при описании 'std :: quoted', но я не могу придумать элегантный способ его использования. – user2093113

ответ

1

Вам нужно интерпретировать запятую в зависимости от того, используете ли вы цитату или нет. Это слишком сложно для 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

+0

Спасибо @ Christophe! Это отлично работает! – dimxasnewfrozen

2

Использование 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

Проблема заключается в том, что строки в кавычках только извлечены, если первый непробельным характер значения является двойной кавычки. Кроме того, любые символы после цитируемых строк будут отброшены до следующей запятой.

+0

В соответствии с моим комментарием к вопросу, я чувствую, что должен быть хороший способ использовать 'std :: quoted' для синтаксического анализа CSV. Дайте мне знать, если у вас есть улучшение. – user2093113

+0

Согласно http://en.cppreference.com/w/cpp/io/manip/quoted, cin пропускает пробелы по умолчанию, а 'quoted' извлекает первый символ через' stream >> c', поэтому он работает, даже если между запятой и стартовой цитатой есть пробел. Таким образом, вам не нужно заглядывать. –

+0

@MooingDuck Если пробел пропускается по умолчанию, тогда всегда вызов 'std :: quoted' будет разделять строки без кавычек, содержащие пробелы, потенциально перед разделителем запятой. Я не уверен, что это не удастся разобрать типичные форматы CSV? Или, может быть, я неправильно понял ваше предложение. – user2093113

0

Как сохранить строку при повторении?

Ниже приведен подход C++, который я использовал.

Я заметил, что у вас есть только 3 типа полей: string, null и int.

Следующий подход использует эти типы полей (в методе «void init()») в порядке, в котором каждая строка представляет поля, иногда используя string :: find() (вместо getline()), чтобы найти поле конец.

Каждый из трех методов использует символы из строки со стиранием. Я знаю, что стирание происходит медленно, но я сделал этот выбор для своего удобства. (стирание легче проверить, просто добавьте 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); 
} 
Смежные вопросы