C ++에서 CSV 파일을 읽고 구문 분석하는 방법 줄과 쉼표를 피하는 것에

C ++에서 CSV 파일 데이터를로드하고 사용해야합니다. 이 시점에서 실제로는 쉼표로 구분 된 파서 일 수 있습니다 (즉, 새 줄과 쉼표를 피하는 것에 대해 걱정하지 마십시오). 주요 요구 사항은 한 줄씩 파서인데,이 메서드는 메서드가 호출 될 때마다 다음 줄의 벡터를 반환합니다.

나는 유망하게 보이는이 기사를 발견했다 :
http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

나는 Boost ‘s Spirit을 사용한 적이 없지만 그것을 기꺼이 시도합니다. 그러나 더 간단한 해결책이없는 경우에만 간과하고 있습니다.



답변

쉼표와 줄 바꿈을 신경 쓰지 않고
쉼표와 줄 바꿈을 따옴표로 묶을 수 없다면 (이스케이프 할 수 없다면 …)
약 세 줄의 코드 (OK 14-> 그러나 전체 파일을 읽으려면 15입니다.)

std::vector<std::string> getNextLineAndSplitIntoTokens(std::istream& str)
{
    std::vector<std::string>   result;
    std::string                line;
    std::getline(str,line);

    std::stringstream          lineStream(line);
    std::string                cell;

    while(std::getline(lineStream,cell, ','))
    {
        result.push_back(cell);
    }
    // This checks for a trailing comma with no data after it.
    if (!lineStream && cell.empty())
    {
        // If there was a trailing comma then add an empty element.
        result.push_back("");
    }
    return result;
}

그냥 행을 나타내는 클래스를 만들 것입니다.
그런 다음 해당 객체로 스트리밍하십시오.

#include <iterator>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>

class CSVRow
{
    public:
        std::string const& operator[](std::size_t index) const
        {
            return m_data[index];
        }
        std::size_t size() const
        {
            return m_data.size();
        }
        void readNextRow(std::istream& str)
        {
            std::string         line;
            std::getline(str, line);

            std::stringstream   lineStream(line);
            std::string         cell;

            m_data.clear();
            while(std::getline(lineStream, cell, ','))
            {
                m_data.push_back(cell);
            }
            // This checks for a trailing comma with no data after it.
            if (!lineStream && cell.empty())
            {
                // If there was a trailing comma then add an empty element.
                m_data.push_back("");
            }
        }
    private:
        std::vector<std::string>    m_data;
};

std::istream& operator>>(std::istream& str, CSVRow& data)
{
    data.readNextRow(str);
    return str;
}
int main()
{
    std::ifstream       file("plop.csv");

    CSVRow              row;
    while(file >> row)
    {
        std::cout << "4th Element(" << row[3] << ")\n";
    }
}

그러나 약간의 작업으로 기술적으로 반복자를 만들 수 있습니다.

class CSVIterator
{
    public:
        typedef std::input_iterator_tag     iterator_category;
        typedef CSVRow                      value_type;
        typedef std::size_t                 difference_type;
        typedef CSVRow*                     pointer;
        typedef CSVRow&                     reference;

        CSVIterator(std::istream& str)  :m_str(str.good()?&str:NULL) { ++(*this); }
        CSVIterator()                   :m_str(NULL) {}

        // Pre Increment
        CSVIterator& operator++()               {if (m_str) { if (!((*m_str) >> m_row)){m_str = NULL;}}return *this;}
        // Post increment
        CSVIterator operator++(int)             {CSVIterator    tmp(*this);++(*this);return tmp;}
        CSVRow const& operator*()   const       {return m_row;}
        CSVRow const* operator->()  const       {return &m_row;}

        bool operator==(CSVIterator const& rhs) {return ((this == &rhs) || ((this->m_str == NULL) && (rhs.m_str == NULL)));}
        bool operator!=(CSVIterator const& rhs) {return !((*this) == rhs);}
    private:
        std::istream*       m_str;
        CSVRow              m_row;
};


int main()
{
    std::ifstream       file("plop.csv");

    for(CSVIterator loop(file); loop != CSVIterator(); ++loop)
    {
        std::cout << "4th Element(" << (*loop)[3] << ")\n";
    }
}

답변

Boost Tokenizer를 사용하는 솔루션 :

std::vector<std::string> vec;
using namespace boost;
tokenizer<escaped_list_separator<char> > tk(
   line, escaped_list_separator<char>('\\', ',', '\"'));
for (tokenizer<escaped_list_separator<char> >::iterator i(tk.begin());
   i!=tk.end();++i)
{
   vec.push_back(*i);
}

답변

내 버전은 표준 C ++ 11 라이브러리 이외의 것을 사용하지 않습니다. Excel CSV 인용에 잘 대처합니다.

spam eggs,"foo,bar","""fizz buzz"""
1.23,4.567,-8.00E+09

이 코드는 유한 상태 머신으로 작성되었으며 한 번에 한 문자 씩 소비합니다. 추론하기가 더 쉽다고 생각합니다.

#include <istream>
#include <string>
#include <vector>

enum class CSVState {
    UnquotedField,
    QuotedField,
    QuotedQuote
};

std::vector<std::string> readCSVRow(const std::string &row) {
    CSVState state = CSVState::UnquotedField;
    std::vector<std::string> fields {""};
    size_t i = 0; // index of the current field
    for (char c : row) {
        switch (state) {
            case CSVState::UnquotedField:
                switch (c) {
                    case ',': // end of field
                              fields.push_back(""); i++;
                              break;
                    case '"': state = CSVState::QuotedField;
                              break;
                    default:  fields[i].push_back(c);
                              break; }
                break;
            case CSVState::QuotedField:
                switch (c) {
                    case '"': state = CSVState::QuotedQuote;
                              break;
                    default:  fields[i].push_back(c);
                              break; }
                break;
            case CSVState::QuotedQuote:
                switch (c) {
                    case ',': // , after closing quote
                              fields.push_back(""); i++;
                              state = CSVState::UnquotedField;
                              break;
                    case '"': // "" -> "
                              fields[i].push_back('"');
                              state = CSVState::QuotedField;
                              break;
                    default:  // end of quote
                              state = CSVState::UnquotedField;
                              break; }
                break;
        }
    }
    return fields;
}

/// Read CSV file, Excel dialect. Accept "quoted fields ""with quotes"""
std::vector<std::vector<std::string>> readCSV(std::istream &in) {
    std::vector<std::vector<std::string>> table;
    std::string row;
    while (!in.eof()) {
        std::getline(in, row);
        if (in.bad() || in.fail()) {
            break;
        }
        auto fields = readCSVRow(row);
        table.push_back(fields);
    }
    return table;
}

답변

C ++ 문자열 툴킷 라이브러리 (StrTk는) 당신이 중 하나에서 데이터를로드 할 수있는 토큰 그리드 클래스가 텍스트 파일, 문자열이나 문자 버퍼 행 열 방식으로, 및 구문 분석 / 처리 그들에게 있습니다.

행 구분 기호와 열 구분 기호를 지정하거나 기본값을 사용할 수 있습니다.

void foo()
{
   std::string data = "1,2,3,4,5\n"
                      "0,2,4,6,8\n"
                      "1,3,5,7,9\n";

   strtk::token_grid grid(data,data.size(),",");

   for(std::size_t i = 0; i < grid.row_count(); ++i)
   {
      strtk::token_grid::row_type r = grid.row(i);
      for(std::size_t j = 0; j < r.size(); ++j)
      {
         std::cout << r.get<int>(j) << "\t";
      }
      std::cout << std::endl;
   }
   std::cout << std::endl;
}

더 많은 예제는 여기 에서 찾을 수 있습니다


답변

escaped_list_separator와 함께 Boost Tokenizer를 사용할 수 있습니다.

escaped_list_separator 는 csv의 상위 집합을 구문 분석합니다. 부스트 :: 토큰 화기

부스트 토크 나이저 헤더 파일 만 사용하며 부스트 라이브러리에 대한 링크는 필요하지 않습니다.

다음은 예입니다 (자세한 내용은 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;
    }
}

답변

CSV를 구문 분석하는 데 Spirit을 사용하는 것은 과도하지 않습니다. Spirit은 마이크로 파싱 작업에 적합합니다. 예를 들어 Spirit 2.1을 사용하면 다음과 같이 쉽습니다.

bool r = phrase_parse(first, last,

    //  Begin grammar
    (
        double_ % ','
    )
    ,
    //  End grammar

    space, v);

벡터 v는 값으로 채워집니다. 일련의 튜토리얼이 있습니다Boost 1.41과 함께 출시 된 새로운 Spirit 2.1 문서 .

튜토리얼은 단순에서 복합으로 진행됩니다. CSV 파서는 가운데 어딘가에 표시되며 Spirit 사용에 대한 다양한 기술을 다룹니다. 생성 된 코드는 직접 작성한 코드만큼 빡빡합니다. 생성 된 어셈블러를 확인하십시오!


답변

당신이 경우 DO 제대로 CSV 구문 분석에 대한주의를,이 그것을 할 것입니다 … 비교적 천천히 한 번에 하나 개의 문자를 작동한다.

 void ParseCSV(const string& csvSource, vector<vector<string> >& lines)
    {
       bool inQuote(false);
       bool newLine(false);
       string field;
       lines.clear();
       vector<string> line;

       string::const_iterator aChar = csvSource.begin();
       while (aChar != csvSource.end())
       {
          switch (*aChar)
          {
          case '"':
             newLine = false;
             inQuote = !inQuote;
             break;

          case ',':
             newLine = false;
             if (inQuote == true)
             {
                field += *aChar;
             }
             else
             {
                line.push_back(field);
                field.clear();
             }
             break;

          case '\n':
          case '\r':
             if (inQuote == true)
             {
                field += *aChar;
             }
             else
             {
                if (newLine == false)
                {
                   line.push_back(field);
                   lines.push_back(line);
                   field.clear();
                   line.clear();
                   newLine = true;
                }
             }
             break;

          default:
             newLine = false;
             field.push_back(*aChar);
             break;
          }

          aChar++;
       }

       if (field.size())
          line.push_back(field);

       if (line.size())
          lines.push_back(line);
    }