C++ CSV строка с запятыми и строками в двойных кавычках

Я читаю файл CSV на C++, и формат строки таков:

"Первичный, Вторичный, Третий", "Первичный", "Вторичный", 18, 4, 0, 0, 0

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

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

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

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

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

3 ответа

Решение

Вы должны интерпретировать запятую в зависимости от того, используете ли вы цитату или нет. Это слишком сложно для getline(),

Решением было бы прочитать полную строку с getline()и проанализируйте строку, просматривая строку символ за символом и поддерживая индикатор, независимо от того, заключены ли вы в двойные кавычки или нет.

Вот первый "сырой" пример (двойные кавычки не удаляются в полях, а escape-символы не интерпретируются):

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

Живой пример

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

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

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

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

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

Каждый из 3 методов потребляет символы из строки с удалением. Я знаю, что стирание происходит медленно, но я сделал этот выбор для моего удобства. (Стирание легче проверить, просто добавьте метку после каждого извлечения). Стирание может быть удалено / заменено соответствующей обработкой (при необходимости) индекса начала поиска.

#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);
}
Другие вопросы по тегам