Làm cách nào tôi có thể đọc và phân tích các tệp CSV trong C ++?


264

Tôi cần tải và sử dụng dữ liệu tệp CSV trong C ++. Tại thời điểm này, nó thực sự có thể chỉ là một trình phân tích cú pháp được phân tách bằng dấu phẩy (nghĩa là đừng lo lắng về việc thoát các dòng và dấu phẩy mới). Nhu cầu chính là một trình phân tích cú pháp từng dòng sẽ trả về một vectơ cho dòng tiếp theo mỗi khi phương thức được gọi.

Tôi tìm thấy bài viết này có vẻ khá hứa hẹn: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundTHER/list_parser.cpp

Tôi chưa bao giờ sử dụng Spirit của Boost, nhưng sẵn sàng dùng thử. Nhưng chỉ khi không có giải pháp đơn giản hơn thì tôi mới xem.


11
Tôi đã xem xét boost::spiritđể phân tích cú pháp. Nó là nhiều hơn để phân tích ngữ pháp cảm ơn phân tích một định dạng tệp đơn giản. Một người nào đó trong nhóm của tôi đã cố gắng sử dụng nó để phân tích cú pháp XML và thật khó để gỡ lỗi. Tránh xa boost::spiritnếu có thể.
chrish

50
Xin lỗi chrish, nhưng đó là lời khuyên khủng khiếp. Tinh thần không phải lúc nào cũng là một giải pháp thích hợp nhưng tôi đã sử dụng nó - và tiếp tục sử dụng nó - thành công trong một số dự án. So với các công cụ tương tự (Antlr, Lex / yacc, v.v.), nó có những lợi thế đáng kể. Bây giờ, để phân tích CSV, có lẽ nó quá mức cần thiết ...
MattyT

4
@MattyT IMHO spiritkhá khó sử dụng cho thư viện trình kết hợp trình phân tích cú pháp. Có một số kinh nghiệm (rất dễ chịu) với (atto)parseccác thư viện Haskells, tôi hy vọng nó (tinh thần) sẽ hoạt động tốt tương tự, nhưng đã từ bỏ nó sau khi chiến đấu với 600 lỗi trình biên dịch dòng.
fho

Câu trả lời:


296

Nếu bạn không quan tâm đến việc thoát dấu phẩy và dòng mới,
VÀ bạn không thể nhúng dấu phẩy và dòng mới trong dấu ngoặc kép (Nếu bạn không thể thoát thì ...)
thì chỉ có khoảng ba dòng mã (OK 14 -> Nhưng nó chỉ 15 để đọc toàn bộ tập tin).

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

Tôi sẽ chỉ tạo một lớp đại diện cho một hàng.
Sau đó truyền vào đối tượng đó:

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

Nhưng với một chút công việc, chúng tôi có thể tạo ra một trình vòng lặp:

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

20
đầu tiên () tiếp theo (). Java này là gì! Chỉ nói đùa.
Martin York

4
@DarthVader: Một tuyên bố bao quát rằng độ rộng của nó là ngớ ngẩn. Nếu bạn muốn làm rõ tại sao nó xấu và tại sao điều xấu này lại áp dụng trong bối cảnh này.
Martin York

12
@DarthVader: Tôi nghĩ thật ngớ ngẩn khi đưa ra những khái quát rộng rãi. Đoạn mã trên hoạt động chính xác để tôi thực sự có thể thấy bất cứ điều gì sai với nó. Nhưng nếu bạn có bất kỳ nhận xét cụ thể nào ở trên, tôi chắc chắn sẽ xem xét trong bối cảnh này. Nhưng tôi có thể thấy làm thế nào bạn có thể đi đến kết luận đó bằng cách vô thức tuân theo một bộ quy tắc chung cho C # và áp dụng nó vào ngôn ngữ khác.
Martin York

5
Ngoài ra, nếu bạn gặp phải các vấn đề liên kết kỳ lạ với đoạn mã trên bởi vì một thư viện khác ở đâu đó xác định istream::operator>>(như Eigen), hãy thêm một inlinetrước khi khai báo toán tử để sửa nó.
sebastian_k

3
Đây là ví dụ đơn giản và rõ ràng nhất về cách tạo một lớp lặp mà tôi từng thấy.
Giancarlo Sportelli

46

Giải pháp sử dụng 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);
}

9
Mã thông báo tăng cường không hỗ trợ đầy đủ tiêu chuẩn CSV hoàn chỉnh, nhưng có một số cách giải quyết nhanh. Xem stackoverflow.com/questions/1120140/csv-parser-in-c/ory
Rolf Kristensen

3
Bạn có phải có toàn bộ thư viện boost trên máy của mình không, hay bạn chỉ có thể sử dụng một tập hợp con mã của chúng để làm điều này? 256mb có vẻ như rất nhiều cho phân tích cú pháp CSV ..
NPike

6
@NPike: Bạn có thể sử dụng tiện ích bcp đi kèm với boost để chỉ trích xuất các tiêu đề bạn thực sự cần.
ildjarn

46

Phiên bản của tôi không sử dụng bất cứ thứ gì ngoài thư viện C ++ 11 tiêu chuẩn. Nó phù hợp với trích dẫn Excel CSV:

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

Mã được viết dưới dạng một máy trạng thái hữu hạn và đang tiêu thụ một ký tự một lần. Tôi nghĩ rằng nó dễ dàng hơn để lý do về.

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

6
cảm ơn, tôi nghĩ rằng đây là câu trả lời đầy đủ nhất, quá tệ, nó bị chôn vùi ở đây.
mihai

vectơ lồng nhau này là không có đối với các bộ xử lý hiện đại. Vứt bỏ khả năng lưu trữ của họ
Nikolaos Giotis

cộng với bạn có tất cả các tuyên bố chuyển đổi
Nikolaos Giotis

Câu trả lời hàng đầu không làm việc cho tôi, vì tôi là một trình biên dịch cũ hơn. Câu trả lời này đã có hiệu quả, khởi tạo véc tơ có thể yêu cầu điều này:const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
dr_rk

31

Các thư viện C ++ Chuỗi Toolkit (StrTk) có một lớp lưới token cho phép bạn tải dữ liệu hoặc từ các file văn bản, chuỗi hoặc đệm char , và để phân tích cú pháp / quy trình chúng trong một thời trang hàng cột.

Bạn có thể chỉ định các dấu phân cách hàng và dấu phân cách cột hoặc chỉ sử dụng mặc định.

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

Thêm ví dụ có thể được tìm thấy ở đây


1
Mặc dù strtk hỗ trợ các trường được chia đôi và thậm chí tước các trích dẫn xung quanh (thông qua options.trim_dquotes = true), nhưng nó không hỗ trợ loại bỏ các chuỗi kép nhân đôi (ví dụ: trường "She said ""oh no"", and left."dưới dạng chuỗi c "She said \"oh no\", and left."). Bạn sẽ phải tự làm điều đó.
hung hăng

1
Khi sử dụng strtk, bạn cũng sẽ phải xử lý thủ công các trường được trích dẫn có chứa các ký tự dòng mới.
hung hăng

29

Bạn có thể sử dụng Boost Tokenizer với escoped_list_separator.

escoped_list_separator phân tích cú pháp superset của csv. Tăng :: mã thông báo

Điều này chỉ sử dụng các tệp tiêu đề Boost tokenizer, không có liên kết để tăng các thư viện cần thiết.

Dưới đây là một ví dụ, (xem Parse CSV File With Boost Tokenizer trong C ++ để biết chi tiết hoặc 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;
    }
}

Và nếu bạn muốn phân tích các dòng mới nhúng mybyteofcode.blogspot.com/2010/11/iêu .
stefanB

Trong khi kỹ thuật này hoạt động, tôi đã thấy nó có hiệu suất rất kém. Phân tích tệp CSV 90000 dòng với mười trường trên mỗi dòng mất khoảng 8 giây trên Xeon 2 GHz của tôi. Mô-đun csv của Thư viện tiêu chuẩn Python phân tích cùng một tệp trong khoảng 0,3 giây.
Rob Smallshire

@Rob thật thú vị - Python csv làm gì khác?
tofutim

1
@RobSmallshire đó là một mã ví dụ đơn giản không phải là mã hiệu suất cao. Mã này tạo bản sao của tất cả các trường trên mỗi dòng. Để có hiệu suất cao hơn, bạn sẽ sử dụng các tùy chọn khác nhau và chỉ trả về các tham chiếu đến các trường trong bộ đệm thay vì tạo các bản sao.
stefanB

29

Sẽ không quá mức khi sử dụng Spirit để phân tích cú pháp CSV. Spirit rất phù hợp cho các nhiệm vụ phân tích vi mô. Chẳng hạn, với Spirit 2.1, nó dễ như:

bool r = phrase_parse(first, last,

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

    space, v);

Các vectơ, v, được nhồi với các giá trị. Có một loạt các hướng dẫn liên quan đến vấn đề này trong các tài liệu Spirit 2.1 mới vừa được phát hành với Boost 1.41.

Hướng dẫn tiến triển từ đơn giản đến phức tạp. Các trình phân tích cú pháp CSV được trình bày ở đâu đó ở giữa và chạm vào các kỹ thuật khác nhau trong việc sử dụng Spirit. Mã được tạo ra chặt chẽ như mã viết tay. Kiểm tra trình biên dịch được tạo ra!


18
Trên thực tế, nó là quá mức cần thiết, thời gian biên dịch đạt được là rất lớn và khiến việc sử dụng Spirit cho các "tác vụ phân tích vi mô" đơn giản là không hợp lý.
Gerdiner

13
Ngoài ra, tôi muốn chỉ ra rằng đoạn mã trên không phân tích CSV, nó chỉ phân tích một phạm vi loại vectơ được phân định bằng dấu phẩy. Nó không xử lý các trích dẫn, các loại cột khác nhau, v.v ... Trong 19 phiếu ngắn cho một câu trả lời nào đó có vẻ hơi đáng nghi đối với tôi.
Gerdiner

9
@Gerdiner Vô nghĩa. Thời gian biên dịch đạt được cho các trình phân tích cú pháp nhỏ không phải là lớn, nhưng nó cũng không liên quan vì bạn nhồi mã vào đơn vị biên dịch riêng của nó và biên dịch nó một lần . Sau đó, bạn chỉ cần liên kết nó và đó là hiệu quả như nó được. Và đối với nhận xét khác của bạn, có nhiều phương ngữ của CSV cũng như có bộ xử lý cho nó. Đây chắc chắn không phải là một phương ngữ rất hữu ích nhưng nó có thể được mở rộng một cách tầm thường để xử lý các giá trị được trích dẫn.
Konrad Rudolph

11
@konrad: Đơn giản chỉ bao gồm "#include <boost / Spirit / include / qi.hpp>" trong một tệp trống chỉ có một tệp chính và không có gì khác mất 9,7 giây với MSVC 2012 trên corei7 chạy ở 2.ghz. Nó không cần thiết phình to. Câu trả lời được chấp nhận sẽ biên dịch dưới 2 giây trên cùng một máy, tôi không muốn tưởng tượng ví dụ Boost.Sprite 'đúng' sẽ mất bao lâu để biên dịch.
Gerdiner

11
@Gerdiner Tôi phải đồng ý với bạn về việc sử dụng tinh thần cho một thứ đơn giản như việc xử lý cvs là quá tuyệt vời.

18

Nếu bạn DO chăm sóc về phân tích cú pháp CSV một cách chính xác, điều này sẽ làm điều đó ... tương đối chậm như nó hoạt động một char cùng một lúc.

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

AFAICT điều này sẽ không xử lý các dấu ngoặc kép được nhúng một cách chính xác (ví dụ: "Chuỗi này có" "dấu ngoặc kép được nhúng" "", "foo", 1))
Jeremy Friesner

14

Khi sử dụng Boost Tokenizer escoped_list_separator cho các tệp CSV, thì ta cần lưu ý những điều sau:

  1. Nó yêu cầu một ký tự thoát (dấu gạch chéo ngược mặc định - \)
  2. Nó đòi hỏi một bộ chia / ký tự-ký tự (dấu phẩy mặc định -,)
  3. Nó đòi hỏi một ký tự trích dẫn (trích dẫn mặc định - ")

Định dạng CSV được chỉ định bởi wiki nói rằng các trường dữ liệu có thể chứa dấu phân cách trong dấu ngoặc kép (được hỗ trợ):

1997, Ford, E350, "Siêu xe tải sang trọng"

Định dạng CSV được chỉ định bởi wiki nói rằng các trích dẫn đơn phải được xử lý bằng dấu ngoặc kép (escoped_list_separator sẽ loại bỏ tất cả các ký tự trích dẫn):

1997, Ford, E350, "siêu" "sang trọng" "xe tải"

Định dạng CSV không chỉ định rằng mọi ký tự gạch chéo ngược sẽ bị loại bỏ (escoped_list_separator sẽ loại bỏ tất cả các ký tự thoát).

Một cách khắc phục có thể để khắc phục hành vi mặc định của boost escoped_list_separator:

  1. Trước tiên, thay thế tất cả các ký tự gạch chéo ngược (\) bằng hai ký tự gạch chéo ngược (\\) để chúng không bị xóa.
  2. Thứ hai thay thế tất cả các dấu ngoặc kép ("") bằng một ký tự gạch chéo ngược đơn và dấu ngoặc kép (\ ")

Cách xử lý này có tác dụng phụ là các trường dữ liệu trống được biểu thị bằng một trích dẫn kép sẽ được chuyển thành một mã thông báo đơn. Khi lặp qua các mã thông báo, sau đó người ta phải kiểm tra xem mã thông báo có phải là một trích dẫn hay không và coi nó như một chuỗi rỗng.

Không đẹp nhưng nó hoạt động, miễn là không có dòng mới trong dấu ngoặc kép.


8

Bạn có thể muốn xem CSVfix dự án FOSS của tôi ( liên kết được cập nhật ), đây là trình chỉnh sửa luồng CSV được viết bằng C ++. Trình phân tích cú pháp CSV không có giải thưởng, nhưng thực hiện công việc và toàn bộ gói có thể làm những gì bạn cần mà không cần viết bất kỳ mã nào.

Xem alib / src / a_csv.cpp để biết trình phân tích cú pháp CSV và csvlib / src / csved_ioman.cpp ( IOManager::ReadCSV) để biết ví dụ sử dụng.


Có vẻ tuyệt vời ... Còn bản beta / sản xuất thì sao?
Neuro

Trạng thái là "đang phát triển", như được đề xuất bởi các số phiên bản. Tôi thực sự cần thêm phản hồi từ người dùng trước khi chuyển sang phiên bản 1.0. Ngoài ra, tôi có thêm một vài tính năng tôi muốn thêm vào, để thực hiện với việc sản xuất XML từ CSV.

Đánh dấu trang và sẽ thử lại lần sau khi tôi phải xử lý các tệp CSV tiêu chuẩn tuyệt vời đó ...
neuro

8

Vì tất cả các câu hỏi CSV dường như được chuyển hướng ở đây, tôi nghĩ rằng tôi sẽ đăng câu trả lời của mình ở đây. Câu trả lời này không trực tiếp giải quyết câu hỏi của người hỏi. Tôi muốn có thể đọc trong một luồng được biết là ở định dạng CSV và các loại của từng trường đã được biết đến. Tất nhiên, phương pháp dưới đây có thể được sử dụng để coi mọi trường là một kiểu chuỗi.

Ví dụ về cách tôi muốn có thể sử dụng luồng đầu vào CSV, hãy xem xét đầu vào sau (lấy từ trang wikipedia trên CSV ):

const char input[] =
"Year,Make,Model,Description,Price\n"
"1997,Ford,E350,\"ac, abs, moon\",3000.00\n"
"1999,Chevy,\"Venture \"\"Extended Edition\"\"\",\"\",4900.00\n"
"1999,Chevy,\"Venture \"\"Extended Edition, Very Large\"\"\",\"\",5000.00\n"
"1996,Jeep,Grand Cherokee,\"MUST SELL!\n\
air, moon roof, loaded\",4799.00\n"
;

Sau đó, tôi muốn có thể đọc dữ liệu như thế này:

std::istringstream ss(input);
std::string title[5];
int year;
std::string make, model, desc;
float price;
csv_istream(ss)
    >> title[0] >> title[1] >> title[2] >> title[3] >> title[4];
while (csv_istream(ss)
       >> year >> make >> model >> desc >> price) {
    //...do something with the record...
}

Đây là giải pháp tôi đã kết thúc.

struct csv_istream {
    std::istream &is_;
    csv_istream (std::istream &is) : is_(is) {}
    void scan_ws () const {
        while (is_.good()) {
            int c = is_.peek();
            if (c != ' ' && c != '\t') break;
            is_.get();
        }
    }
    void scan (std::string *s = 0) const {
        std::string ws;
        int c = is_.get();
        if (is_.good()) {
            do {
                if (c == ',' || c == '\n') break;
                if (s) {
                    ws += c;
                    if (c != ' ' && c != '\t') {
                        *s += ws;
                        ws.clear();
                    }
                }
                c = is_.get();
            } while (is_.good());
            if (is_.eof()) is_.clear();
        }
    }
    template <typename T, bool> struct set_value {
        void operator () (std::string in, T &v) const {
            std::istringstream(in) >> v;
        }
    };
    template <typename T> struct set_value<T, true> {
        template <bool SIGNED> void convert (std::string in, T &v) const {
            if (SIGNED) v = ::strtoll(in.c_str(), 0, 0);
            else v = ::strtoull(in.c_str(), 0, 0);
        }
        void operator () (std::string in, T &v) const {
            convert<is_signed_int<T>::val>(in, v);
        }
    };
    template <typename T> const csv_istream & operator >> (T &v) const {
        std::string tmp;
        scan(&tmp);
        set_value<T, is_int<T>::val>()(tmp, v);
        return *this;
    }
    const csv_istream & operator >> (std::string &v) const {
        v.clear();
        scan_ws();
        if (is_.peek() != '"') scan(&v);
        else {
            std::string tmp;
            is_.get();
            std::getline(is_, tmp, '"');
            while (is_.peek() == '"') {
                v += tmp;
                v += is_.get();
                std::getline(is_, tmp, '"');
            }
            v += tmp;
            scan();
        }
        return *this;
    }
    template <typename T>
    const csv_istream & operator >> (T &(*manip)(T &)) const {
        is_ >> manip;
        return *this;
    }
    operator bool () const { return !is_.fail(); }
};

Với các trợ giúp sau có thể được đơn giản hóa bằng các mẫu tính trạng tích phân mới trong C ++ 11:

template <typename T> struct is_signed_int { enum { val = false }; };
template <> struct is_signed_int<short> { enum { val = true}; };
template <> struct is_signed_int<int> { enum { val = true}; };
template <> struct is_signed_int<long> { enum { val = true}; };
template <> struct is_signed_int<long long> { enum { val = true}; };

template <typename T> struct is_unsigned_int { enum { val = false }; };
template <> struct is_unsigned_int<unsigned short> { enum { val = true}; };
template <> struct is_unsigned_int<unsigned int> { enum { val = true}; };
template <> struct is_unsigned_int<unsigned long> { enum { val = true}; };
template <> struct is_unsigned_int<unsigned long long> { enum { val = true}; };

template <typename T> struct is_int {
    enum { val = (is_signed_int<T>::val || is_unsigned_int<T>::val) };
};

Hãy thử trực tuyến!


6

Tôi đã viết một trình phân tích cú pháp CSV C ++ 11 chỉ tiêu đề . Nó được kiểm tra tốt, nhanh chóng, hỗ trợ toàn bộ thông số CSV (các trường được trích dẫn, dấu phân cách / dấu kết thúc trong dấu ngoặc kép, thoát trích dẫn, v.v.) và có thể định cấu hình để giải thích cho các CSV không tuân thủ thông số kỹ thuật.

Cấu hình được thực hiện thông qua một giao diện trôi chảy:

// constructor accepts any input stream
CsvParser parser = CsvParser(std::cin)
  .delimiter(';')    // delimited by ; instead of ,
  .quote('\'')       // quoted fields use ' instead of "
  .terminator('\0'); // terminated by \0 instead of by \r\n, \n, or \r

Phân tích cú pháp chỉ là một phạm vi dựa trên vòng lặp:

#include <iostream>
#include "../parser.hpp"

using namespace aria::csv;

int main() {
  std::ifstream f("some_file.csv");
  CsvParser parser(f);

  for (auto& row : parser) {
    for (auto& field : row) {
      std::cout << field << " | ";
    }
    std::cout << std::endl;
  }
}

1
Công việc tuyệt vời, nhưng bạn cần thêm ba điều nữa: (1) đọc tiêu đề (2) cung cấp các trường lập chỉ mục theo tên (3) không phân bổ lại bộ nhớ trong vòng lặp bằng cách sử dụng lại cùng một vectơ của chuỗi
Maksym Ganenko

@MaksymGanenko Tôi làm # 3. Bạn có thể giải thích về # 2?
m0meni

1
Rất hữu ích để có được các trường không theo vị trí trong một hàng, mà theo tên được đưa ra trong tiêu đề (trong hàng đầu tiên của bảng CSV). Ví dụ: tôi mong đợi bảng CSV có trường "Ngày", nhưng tôi không biết chỉ mục trường "Ngày" là gì.
Maksym Ganenko

1
@MaksymGanenko ah Tôi hiểu ý của bạn. Có github.com/ben-strasser/fast-cpp-csv-parser khi bạn biết các cột CSV của mình tại thời điểm biên dịch và có lẽ nó tốt hơn của tôi. Điều tôi muốn là một trình phân tích cú pháp CSV cho các trường hợp bạn muốn sử dụng cùng một mã cho nhiều CSV khác nhau và không biết chúng trông như thế nào trước thời hạn. Vì vậy, tôi có thể sẽ không thêm # 2, nhưng đôi khi tôi sẽ thêm # 1 trong tương lai.
m0meni

5

Một thư viện I / O CSV khác có thể được tìm thấy ở đây:

http://code.google.com.vn/p/fast-cpp-csv-parser/

#include "csv.h"

int main(){
  io::CSVReader<3> in("ram.csv");
  in.read_header(io::ignore_extra_column, "vendor", "size", "speed");
  std::string vendor; int size; double speed;
  while(in.read_row(vendor, size, speed)){
    // do stuff with the data
  }
}

2
Đẹp, nhưng nó buộc bạn phải chọn số lượng cột tại thời gian biên dịch. Không hữu ích cho nhiều ứng dụng.
quant_dev

5

Một giải pháp khác tương tự như câu trả lời của Loki Astari , trong C ++ 11. Hàng ở đây là std::tuples của một loại nhất định. Mã này quét một dòng, sau đó quét cho đến khi mỗi dấu phân cách, sau đó chuyển đổi và chuyển giá trị trực tiếp vào bộ dữ liệu (với một chút mã mẫu).

for (auto row : csv<std::string, int, float>(file, ',')) {
    std::cout << "first col: " << std::get<0>(row) << std::endl;
}

Lời khuyên:

  • khá sạch sẽ và đơn giản để sử dụng, chỉ có C ++ 11.
  • tự động chuyển đổi thành std::tuple<t1, ...>thông qua operator>>.

Cái gì còn thiếu:

  • thoát và trích dẫn
  • không xử lý lỗi trong trường hợp CSV không đúng định dạng.

Mã chính:

#include <iterator>
#include <sstream>
#include <string>

namespace csvtools {
    /// Read the last element of the tuple without calling recursively
    template <std::size_t idx, class... fields>
    typename std::enable_if<idx >= std::tuple_size<std::tuple<fields...>>::value - 1>::type
    read_tuple(std::istream &in, std::tuple<fields...> &out, const char delimiter) {
        std::string cell;
        std::getline(in, cell, delimiter);
        std::stringstream cell_stream(cell);
        cell_stream >> std::get<idx>(out);
    }

    /// Read the @p idx-th element of the tuple and then calls itself with @p idx + 1 to
    /// read the next element of the tuple. Automatically falls in the previous case when
    /// reaches the last element of the tuple thanks to enable_if
    template <std::size_t idx, class... fields>
    typename std::enable_if<idx < std::tuple_size<std::tuple<fields...>>::value - 1>::type
    read_tuple(std::istream &in, std::tuple<fields...> &out, const char delimiter) {
        std::string cell;
        std::getline(in, cell, delimiter);
        std::stringstream cell_stream(cell);
        cell_stream >> std::get<idx>(out);
        read_tuple<idx + 1, fields...>(in, out, delimiter);
    }
}

/// Iterable csv wrapper around a stream. @p fields the list of types that form up a row.
template <class... fields>
class csv {
    std::istream &_in;
    const char _delim;
public:
    typedef std::tuple<fields...> value_type;
    class iterator;

    /// Construct from a stream.
    inline csv(std::istream &in, const char delim) : _in(in), _delim(delim) {}

    /// Status of the underlying stream
    /// @{
    inline bool good() const {
        return _in.good();
    }
    inline const std::istream &underlying_stream() const {
        return _in;
    }
    /// @}

    inline iterator begin();
    inline iterator end();
private:

    /// Reads a line into a stringstream, and then reads the line into a tuple, that is returned
    inline value_type read_row() {
        std::string line;
        std::getline(_in, line);
        std::stringstream line_stream(line);
        std::tuple<fields...> retval;
        csvtools::read_tuple<0, fields...>(line_stream, retval, _delim);
        return retval;
    }
};

/// Iterator; just calls recursively @ref csv::read_row and stores the result.
template <class... fields>
class csv<fields...>::iterator {
    csv::value_type _row;
    csv *_parent;
public:
    typedef std::input_iterator_tag iterator_category;
    typedef csv::value_type         value_type;
    typedef std::size_t             difference_type;
    typedef csv::value_type *       pointer;
    typedef csv::value_type &       reference;

    /// Construct an empty/end iterator
    inline iterator() : _parent(nullptr) {}
    /// Construct an iterator at the beginning of the @p parent csv object.
    inline iterator(csv &parent) : _parent(parent.good() ? &parent : nullptr) {
        ++(*this);
    }

    /// Read one row, if possible. Set to end if parent is not good anymore.
    inline iterator &operator++() {
        if (_parent != nullptr) {
            _row = _parent->read_row();
            if (!_parent->good()) {
                _parent = nullptr;
            }
        }
        return *this;
    }

    inline iterator operator++(int) {
        iterator copy = *this;
        ++(*this);
        return copy;
    }

    inline csv::value_type const &operator*() const {
        return _row;
    }

    inline csv::value_type const *operator->() const {
        return &_row;
    }

    bool operator==(iterator const &other) {
        return (this == &other) or (_parent == nullptr and other._parent == nullptr);
    }
    bool operator!=(iterator const &other) {
        return not (*this == other);
    }
};

template <class... fields>
typename csv<fields...>::iterator csv<fields...>::begin() {
    return iterator(*this);
}

template <class... fields>
typename csv<fields...>::iterator csv<fields...>::end() {
    return iterator();
}

Tôi đặt một ví dụ hoạt động nhỏ trên GitHub ; Tôi đã sử dụng nó để phân tích một số dữ liệu số và nó phục vụ mục đích của nó.


1
Bạn có thể không quan tâm đến nội tuyến, bởi vì hầu hết các trình biên dịch tự quyết định nó. Ít nhất tôi chắc chắn trong Visual C ++. Nó có thể phương thức nội tuyến độc lập với đặc điểm kỹ thuật phương pháp của bạn.
MrPisarik

1
Đó chính xác là lý do tại sao tôi đánh dấu chúng rõ ràng. Gcc và Clang, những cái tôi chủ yếu sử dụng, cũng có những quy ước riêng của họ. Một từ khóa "nội tuyến" chỉ nên là một sự khích lệ.
Spak

4

Đây là một triển khai khác của trình phân tích cú pháp Unicode CSV (hoạt động với wchar_t). Tôi đã viết một phần của nó, trong khi Jonathan Leffler viết phần còn lại.

Lưu ý: Trình phân tích cú pháp này nhằm mục đích sao chép hành vi của Excel càng sát càng tốt, cụ thể là khi nhập các tệp CSV bị hỏng hoặc không đúng định dạng .

Đây là câu hỏi ban đầu - Phân tích tệp CSV bằng các trường đa dòng và thoát dấu ngoặc kép

Đây là mã dưới dạng SSCCE (Ví dụ ngắn, tự chứa, chính xác).

#include <stdbool.h>
#include <wchar.h>
#include <wctype.h>

extern const wchar_t *nextCsvField(const wchar_t *p, wchar_t sep, bool *newline);

// Returns a pointer to the start of the next field,
// or zero if this is the last field in the CSV
// p is the start position of the field
// sep is the separator used, i.e. comma or semicolon
// newline says whether the field ends with a newline or with a comma
const wchar_t *nextCsvField(const wchar_t *p, wchar_t sep, bool *newline)
{
    // Parse quoted sequences
    if ('"' == p[0]) {
        p++;
        while (1) {
            // Find next double-quote
            p = wcschr(p, L'"');
            // If we don't find it or it's the last symbol
            // then this is the last field
            if (!p || !p[1])
                return 0;
            // Check for "", it is an escaped double-quote
            if (p[1] != '"')
                break;
            // Skip the escaped double-quote
            p += 2;
        }
    }

    // Find next newline or comma.
    wchar_t newline_or_sep[4] = L"\n\r ";
    newline_or_sep[2] = sep;
    p = wcspbrk(p, newline_or_sep);

    // If no newline or separator, this is the last field.
    if (!p)
        return 0;

    // Check if we had newline.
    *newline = (p[0] == '\r' || p[0] == '\n');

    // Handle "\r\n", otherwise just increment
    if (p[0] == '\r' && p[1] == '\n')
        p += 2;
    else
        p++;

    return p;
}

static wchar_t *csvFieldData(const wchar_t *fld_s, const wchar_t *fld_e, wchar_t *buffer, size_t buflen)
{
    wchar_t *dst = buffer;
    wchar_t *end = buffer + buflen - 1;
    const wchar_t *src = fld_s;

    if (*src == L'"')
    {
        const wchar_t *p = src + 1;
        while (p < fld_e && dst < end)
        {
            if (p[0] == L'"' && p+1 < fld_s && p[1] == L'"')
            {
                *dst++ = p[0];
                p += 2;
            }
            else if (p[0] == L'"')
            {
                p++;
                break;
            }
            else
                *dst++ = *p++;
        }
        src = p;
    }
    while (src < fld_e && dst < end)
        *dst++ = *src++;
    if (dst >= end)
        return 0;
    *dst = L'\0';
    return(buffer);
}

static void dissect(const wchar_t *line)
{
    const wchar_t *start = line;
    const wchar_t *next;
    bool     eol;
    wprintf(L"Input %3zd: [%.*ls]\n", wcslen(line), wcslen(line)-1, line);
    while ((next = nextCsvField(start, L',', &eol)) != 0)
    {
        wchar_t buffer[1024];
        wprintf(L"Raw Field: [%.*ls] (eol = %d)\n", (next - start - eol), start, eol);
        if (csvFieldData(start, next-1, buffer, sizeof(buffer)/sizeof(buffer[0])) != 0)
            wprintf(L"Field %3zd: [%ls]\n", wcslen(buffer), buffer);
        start = next;
    }
}

static const wchar_t multiline[] =
   L"First field of first row,\"This field is multiline\n"
    "\n"
    "but that's OK because it's enclosed in double quotes, and this\n"
    "is an escaped \"\" double quote\" but this one \"\" is not\n"
    "   \"This is second field of second row, but it is not multiline\n"
    "   because it doesn't start \n"
    "   with an immediate double quote\"\n"
    ;

int main(void)
{
    wchar_t line[1024];

    while (fgetws(line, sizeof(line)/sizeof(line[0]), stdin))
        dissect(line);
    dissect(multiline);

    return 0;
}

3

Tôi cần một thư viện C ++ dễ sử dụng để phân tích tệp CSV nhưng không thể tìm thấy bất kỳ tệp nào có sẵn, vì vậy tôi đã kết thúc việc xây dựng một thư viện. Rapidcsv là một thư viện chỉ dành cho tiêu đề C ++ 11, cho phép truy cập trực tiếp vào các cột (hoặc hàng) được phân tích cú pháp dưới dạng vectơ, trong kiểu dữ liệu được lựa chọn. Ví dụ:

#include <iostream>
#include <vector>
#include <rapidcsv.h>

int main()
{
  rapidcsv::Document doc("../tests/msft.csv");

  std::vector<float> close = doc.GetColumn<float>("Close");
  std::cout << "Read " << close.size() << " values." << std::endl;
}

1
Công việc tốt, nhưng thư viện không hoạt động đúng nếu tiêu đề có nhãn trống. Đó là điển hình cho bảng NxN của Excel / LibreOffice. Ngoài ra, nó có thể bỏ qua dòng dữ liệu cuối cùng. Thật không may, lib của bạn không mạnh mẽ.
Maksym Ganenko

1
Cảm ơn phản hồi @MaksymGanenko Tôi đã sửa lỗi "dòng dữ liệu cuối cùng" cho các dòng cuối cùng không bị ngắt dòng. Đối với vấn đề khác được đề cập - "tiêu đề có nhãn trống" - Tôi không chắc nó đề cập đến vấn đề gì? Thư viện nên xử lý các nhãn trống (cả trích dẫn và không trích dẫn). Nó cũng có thể đọc CSV mà không cần hàng / cột tiêu đề, nhưng sau đó nó yêu cầu người dùng chỉ định điều này (tiêu đề col id -1 và tiêu đề id id -1). Vui lòng cung cấp thêm một số chi tiết hoặc báo cáo lỗi tại trang GitHub nếu bạn có một số trường hợp sử dụng cụ thể mà bạn muốn thấy được hỗ trợ. Cảm ơn!
d99kris

2

Xin lỗi, nhưng tất cả điều này có vẻ giống như rất nhiều cú pháp phức tạp để ẩn một vài dòng mã.

Tại sao không phải là điều này:

/**

  Read line from a CSV file

  @param[in] fp file pointer to open file
  @param[in] vls reference to vector of strings to hold next line

  */
void readCSV( FILE *fp, std::vector<std::string>& vls )
{
    vls.clear();
    if( ! fp )
        return;
    char buf[10000];
    if( ! fgets( buf,999,fp) )
        return;
    std::string s = buf;
    int p,q;
    q = -1;
    // loop over columns
    while( 1 ) {
        p = q;
        q = s.find_first_of(",\n",p+1);
        if( q == -1 ) 
            break;
        vls.push_back( s.substr(p+1,q-p-1) );
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::vector<std::string> vls;
    FILE * fp = fopen( argv[1], "r" );
    if( ! fp )
        return 1;
    readCSV( fp, vls );
    readCSV( fp, vls );
    readCSV( fp, vls );
    std::cout << "row 3, col 4 is " << vls[3].c_str() << "\n";

    return 0;
}

Erm, tại sao sẽ có ",\n"trong chuỗi?
Timmmm

@Timmmm tra cứu phương thức cơ sở của lớp String và trong trường hợp này, bạn sẽ thấy rằng nó có nhiều ký tự, \ n là ký tự dòng mới, vì vậy, nó được tính là một ký tự, trong trường hợp này. Nó không tìm kiếm toàn bộ giá trị. Đó là tìm kiếm cho từng nhân vật; cụ thể là dấu phẩy hoặc dòng mới. chất nền sẽ trả về vị trí của ký tự đầu tiên mà nó tìm thấy và -1 nếu nó không tìm thấy, điều đó có nghĩa là nó đã đọc xong dòng. fp theo dõi vị trí trong tệp bên trong, vì vậy mỗi lệnh gọi readCSV sẽ di chuyển nó một hàng tại một thời điểm.
Martyn Shutt

2

Đây là mã để đọc một ma trận, lưu ý bạn cũng có hàm csvwrite trong matlab

void loadFromCSV( const std::string& filename )
{
    std::ifstream       file( filename.c_str() );
    std::vector< std::vector<std::string> >   matrix;
    std::vector<std::string>   row;
    std::string                line;
    std::string                cell;

    while( file )
    {
        std::getline(file,line);
        std::stringstream lineStream(line);
        row.clear();

        while( std::getline( lineStream, cell, ',' ) )
            row.push_back( cell );

        if( !row.empty() )
            matrix.push_back( row );
    }

    for( int i=0; i<int(matrix.size()); i++ )
    {
        for( int j=0; j<int(matrix[i].size()); j++ )
            std::cout << matrix[i][j] << " ";

        std::cout << std::endl;
    }
}

2

Bạn có thể mở và đọc tệp .csv bằng các hàm fopen, fscanf, nhưng điều quan trọng là phân tích dữ liệu. Cách đơn giản nhất để phân tích dữ liệu bằng cách sử dụng dấu phân cách. Trong trường hợp .csv, dấu phân cách là ','.

Giả sử tệp data1.csv của bạn như sau:

A,45,76,01
B,77,67,02
C,63,76,03
D,65,44,04

bạn có thể mã hóa dữ liệu và lưu trữ trong mảng char và sau đó sử dụng hàm atoi () vv để chuyển đổi phù hợp

FILE *fp;
char str1[10], str2[10], str3[10], str4[10];

fp = fopen("G:\\data1.csv", "r");
if(NULL == fp)
{
    printf("\nError in opening file.");
    return 0;
}
while(EOF != fscanf(fp, " %[^,], %[^,], %[^,], %s, %s, %s, %s ", str1, str2, str3, str4))
{
    printf("\n%s %s %s %s", str1, str2, str3, str4);
}
fclose(fp);

[^,], ^ -it đảo ngược logic, có nghĩa là khớp với bất kỳ chuỗi nào không chứa dấu phẩy sau đó, nói khớp với dấu phẩy đã chấm dứt chuỗi trước đó.


2

Điều đầu tiên bạn cần làm là đảm bảo tập tin tồn tại. Để thực hiện điều này, bạn chỉ cần thử và mở luồng tệp tại đường dẫn. Sau khi bạn đã mở luồng tệp, hãy sử dụng stream.fail () để xem nó có hoạt động như mong đợi hay không.

bool fileExists(string fileName)
{

ifstream test;

test.open(fileName.c_str());

if (test.fail())
{
    test.close();
    return false;
}
else
{
    test.close();
    return true;
}
}

Bạn cũng phải xác minh rằng tệp được cung cấp là loại tệp chính xác. Để thực hiện điều này, bạn cần xem qua đường dẫn tệp được cung cấp cho đến khi bạn tìm thấy phần mở rộng tệp. Khi bạn có phần mở rộng tệp, hãy đảm bảo rằng đó là tệp .csv.

bool verifyExtension(string filename)
{
int period = 0;

for (unsigned int i = 0; i < filename.length(); i++)
{
    if (filename[i] == '.')
        period = i;
}

string extension;

for (unsigned int i = period; i < filename.length(); i++)
    extension += filename[i];

if (extension == ".csv")
    return true;
else
    return false;
}

Hàm này sẽ trả về phần mở rộng tệp được sử dụng sau này trong thông báo lỗi.

string getExtension(string filename)
{
int period = 0;

for (unsigned int i = 0; i < filename.length(); i++)
{
    if (filename[i] == '.')
        period = i;
}

string extension;

if (period != 0)
{
    for (unsigned int i = period; i < filename.length(); i++)
        extension += filename[i];
}
else
    extension = "NO FILE";

return extension;
}

Hàm này thực sự sẽ gọi các kiểm tra lỗi được tạo ở trên và sau đó phân tích cú pháp qua tệp.

void parseFile(string fileName)
{
    if (fileExists(fileName) && verifyExtension(fileName))
    {
        ifstream fs;
        fs.open(fileName.c_str());
        string fileCommand;

        while (fs.good())
        {
            string temp;

            getline(fs, fileCommand, '\n');

            for (unsigned int i = 0; i < fileCommand.length(); i++)
            {
                if (fileCommand[i] != ',')
                    temp += fileCommand[i];
                else
                    temp += " ";
            }

            if (temp != "\0")
            {
                // Place your code here to run the file.
            }
        }
        fs.close();
    }
    else if (!fileExists(fileName))
    {
        cout << "Error: The provided file does not exist: " << fileName << endl;

        if (!verifyExtension(fileName))
        {
            if (getExtension(fileName) != "NO FILE")
                cout << "\tCheck the file extension." << endl;
            else
                cout << "\tThere is no file in the provided path." << endl;
        }
    }
    else if (!verifyExtension(fileName)) 
    {
        if (getExtension(fileName) != "NO FILE")
            cout << "Incorrect file extension provided: " << getExtension(fileName) << endl;
        else
            cout << "There is no file in the following path: " << fileName << endl;
    }
}

2

Bạn phải cảm thấy tự hào khi bạn sử dụng một cái gì đó đẹp như boost::spirit

Ở đây, nỗ lực của tôi về một trình phân tích cú pháp (gần như) tuân thủ các thông số kỹ thuật CSV trên thông số kỹ thuật CSV liên kết này (Tôi không cần ngắt dòng trong các trường. Ngoài ra, các khoảng trắng xung quanh dấu phẩy bị loại bỏ).

Sau khi bạn vượt qua trải nghiệm gây sốc khi chờ 10 giây để biên dịch mã này :), bạn có thể ngồi lại và thưởng thức.

// csvparser.cpp
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>

#include <iostream>
#include <string>

namespace qi = boost::spirit::qi;
namespace bascii = boost::spirit::ascii;

template <typename Iterator>
struct csv_parser : qi::grammar<Iterator, std::vector<std::string>(), 
    bascii::space_type>
{
    qi::rule<Iterator, char()                                           > COMMA;
    qi::rule<Iterator, char()                                           > DDQUOTE;
    qi::rule<Iterator, std::string(),               bascii::space_type  > non_escaped;
    qi::rule<Iterator, std::string(),               bascii::space_type  > escaped;
    qi::rule<Iterator, std::string(),               bascii::space_type  > field;
    qi::rule<Iterator, std::vector<std::string>(),  bascii::space_type  > start;

    csv_parser() : csv_parser::base_type(start)
    {
        using namespace qi;
        using qi::lit;
        using qi::lexeme;
        using bascii::char_;

        start       = field % ',';
        field       = escaped | non_escaped;
        escaped     = lexeme['"' >> *( char_ -(char_('"') | ',') | COMMA | DDQUOTE)  >> '"'];
        non_escaped = lexeme[       *( char_ -(char_('"') | ',')                  )        ];
        DDQUOTE     = lit("\"\"")       [_val = '"'];
        COMMA       = lit(",")          [_val = ','];
    }

};

int main()
{
    std::cout << "Enter CSV lines [empty] to quit\n";

    using bascii::space;
    typedef std::string::const_iterator iterator_type;
    typedef csv_parser<iterator_type> csv_parser;

    csv_parser grammar;
    std::string str;
    int fid;
    while (getline(std::cin, str))
    {
        fid = 0;

        if (str.empty())
            break;

        std::vector<std::string> csv;
        std::string::const_iterator it_beg = str.begin();
        std::string::const_iterator it_end = str.end();
        bool r = phrase_parse(it_beg, it_end, grammar, space, csv);

        if (r && it_beg == it_end)
        {
            std::cout << "Parsing succeeded\n";
            for (auto& field: csv)
            {
                std::cout << "field " << ++fid << ": " << field << std::endl;
            }
        }
        else
        {
            std::cout << "Parsing failed\n";
        }
    }

    return 0;
}

Biên dịch:

make csvparser

Kiểm tra (ví dụ bị đánh cắp từ Wikipedia ):

./csvparser
Enter CSV lines [empty] to quit

1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00
Parsing succeeded
field 1: 1999
field 2: Chevy
field 3: Venture "Extended Edition, Very Large"
field 4: 
field 5: 5000.00

1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00"
Parsing failed

2

Giải pháp này phát hiện 4 trường hợp này

lớp học hoàn thành

https://github.com/pedro-vicente/csv-parser

1,field 2,field 3,
1,field 2,"field 3 quoted, with separator",
1,field 2,"field 3
with newline",
1,field 2,"field 3
with newline and separator,",

Nó đọc ký tự tệp theo ký tự và đọc 1 hàng một lần vào một vectơ (của chuỗi), do đó phù hợp với các tệp rất lớn.

Cách sử dụng là

Lặp lại cho đến khi một hàng trống được trả về (cuối tệp). Một hàng là một vectơ trong đó mỗi mục là một cột CSV.

read_csv_t csv;
csv.open("../test.csv");
std::vector<std::string> row;
while (true)
{
  row = csv.read_row();
  if (row.size() == 0)
  {
    break;
  }
}

khai báo lớp

class read_csv_t
{
public:
  read_csv_t();
  int open(const std::string &file_name);
  std::vector<std::string> read_row();
private:
  std::ifstream m_ifs;
};

việc thực hiện

std::vector<std::string> read_csv_t::read_row()
{
  bool quote_mode = false;
  std::vector<std::string> row;
  std::string column;
  char c;
  while (m_ifs.get(c))
  {
    switch (c)
    {
      /////////////////////////////////////////////////////////////////////////////////////////////////////
      //separator ',' detected. 
      //in quote mode add character to column
      //push column if not in quote mode
      /////////////////////////////////////////////////////////////////////////////////////////////////////

    case ',':
      if (quote_mode == true)
      {
        column += c;
      }
      else
      {
        row.push_back(column);
        column.clear();
      }
      break;

      /////////////////////////////////////////////////////////////////////////////////////////////////////
      //quote '"' detected. 
      //toggle quote mode
      /////////////////////////////////////////////////////////////////////////////////////////////////////

    case '"':
      quote_mode = !quote_mode;
      break;

      /////////////////////////////////////////////////////////////////////////////////////////////////////
      //line end detected
      //in quote mode add character to column
      //return row if not in quote mode
      /////////////////////////////////////////////////////////////////////////////////////////////////////

    case '\n':
    case '\r':
      if (quote_mode == true)
      {
        column += c;
      }
      else
      {
        return row;
      }
      break;

      /////////////////////////////////////////////////////////////////////////////////////////////////////
      //default, add character to column
      /////////////////////////////////////////////////////////////////////////////////////////////////////

    default:
      column += c;
      break;
    }
  }

  //return empty vector if end of file detected 
  m_ifs.close();
  std::vector<std::string> v;
  return v;
}

1

Bạn cũng có thể xem khả năng của Qtthư viện.

Nó có hỗ trợ biểu thức chính quy và lớp QString có các phương thức hay, ví dụ split()trả về QStringList, danh sách các chuỗi thu được bằng cách tách chuỗi gốc bằng một dấu phân cách được cung cấp. Nên đủ cho tập tin csv ..

Để có được một cột với một tên tiêu đề đã cho, tôi sử dụng như sau: c ++ thừa kế Qt vấn đề qt


điều này sẽ không xử lý dấu phẩy trong dấu ngoặc kép
Ezee

1

Nếu bạn không muốn đối phó với việc tăng cường trong dự án của mình (nó khá lớn nếu tất cả những gì bạn sẽ sử dụng nó là phân tích cú pháp CSV ...)

Tôi đã có may mắn với phân tích cú pháp CSV ở đây:

http://www.zingwood.com/article/112/cpp-csv-parser

Nó xử lý các trường được trích dẫn - nhưng không xử lý các ký tự nội tuyến (có thể tốt cho hầu hết các mục đích sử dụng).


1
Trình biên dịch có nên loại bỏ mọi thứ không cần thiết không?
tofutim

1

Đây là một chủ đề cũ nhưng nó vẫn ở đầu kết quả tìm kiếm, vì vậy tôi đang thêm giải pháp của mình bằng cách sử dụng std :: stringstream và một phương pháp thay thế chuỗi đơn giản bằng Yves Baume tôi tìm thấy ở đây.

Ví dụ sau sẽ đọc một dòng tệp theo từng dòng, bỏ qua các dòng nhận xét bắt đầu bằng // và phân tích các dòng khác thành một chuỗi kết hợp của chuỗi, ints và double. Chuỗi dòng thực hiện phân tích cú pháp, nhưng mong muốn các trường được phân định bằng khoảng trắng, vì vậy tôi sử dụng chuỗi ký tự để biến dấu phẩy thành khoảng trắng trước tiên. Nó xử lý các tab ok, nhưng không xử lý các chuỗi được trích dẫn.

Đầu vào xấu hoặc thiếu chỉ đơn giản là bị bỏ qua, có thể có hoặc không tốt, tùy thuộc vào hoàn cảnh của bạn.

#include <string>
#include <sstream>
#include <fstream>

void StringReplace(std::string& str, const std::string& oldStr, const std::string& newStr)
// code by  Yves Baumes
// http://stackoverflow.com/questions/1494399/how-do-i-search-find-and-replace-in-a-standard-string
{
  size_t pos = 0;
  while((pos = str.find(oldStr, pos)) != std::string::npos)
  {
     str.replace(pos, oldStr.length(), newStr);
     pos += newStr.length();
  }
}

void LoadCSV(std::string &filename) {
   std::ifstream stream(filename);
   std::string in_line;
   std::string Field;
   std::string Chan;
   int ChanType;
   double Scale;
   int Import;
   while (std::getline(stream, in_line)) {
      StringReplace(in_line, ",", " ");
      std::stringstream line(in_line);
      line >> Field >> Chan >> ChanType >> Scale >> Import;
      if (Field.substr(0,2)!="//") {
         // do your stuff 
         // this is CBuilder code for demonstration, sorry
         ShowMessage((String)Field.c_str() + "\n" + Chan.c_str() + "\n" + IntToStr(ChanType) + "\n" +FloatToStr(Scale) + "\n" +IntToStr(Import));
      }
   }
}

1

Đối với những gì nó có giá trị, đây là thực hiện của tôi. Nó liên quan đến đầu vào chuỗi, nhưng có thể được điều chỉnh thành chuỗi dễ dàng. Nó không xử lý dòng mới trong các trường (vì ứng dụng của tôi cũng không, nhưng việc thêm hỗ trợ của nó không quá khó) và nó không tuân thủ cuối dòng "\ r \ n" theo RFC (giả sử bạn sử dụng std :: getline), nhưng nó xử lý cắt xén khoảng trắng và trích dẫn kép một cách chính xác (hy vọng).

using namespace std;

// trim whitespaces around field or double-quotes, remove double-quotes and replace escaped double-quotes (double double-quotes)
wstring trimquote(const wstring& str, const wstring& whitespace, const wchar_t quotChar)
{
    wstring ws;
    wstring::size_type strBegin = str.find_first_not_of(whitespace);
    if (strBegin == wstring::npos)
        return L"";

    wstring::size_type strEnd = str.find_last_not_of(whitespace);
    wstring::size_type strRange = strEnd - strBegin + 1;

    if((str[strBegin] == quotChar) && (str[strEnd] == quotChar))
    {
        ws = str.substr(strBegin+1, strRange-2);
        strBegin = 0;
        while((strEnd = ws.find(quotChar, strBegin)) != wstring::npos)
        {
            ws.erase(strEnd, 1);
            strBegin = strEnd+1;
        }

    }
    else
        ws = str.substr(strBegin, strRange);
    return ws;
}

pair<unsigned, unsigned> nextCSVQuotePair(const wstring& line, const wchar_t quotChar, unsigned ofs = 0)
{
    pair<unsigned, unsigned> r;
    r.first = line.find(quotChar, ofs);
    r.second = wstring::npos;
    if(r.first != wstring::npos)
    {
        r.second = r.first;
        while(((r.second = line.find(quotChar, r.second+1)) != wstring::npos)
            && (line[r.second+1] == quotChar)) // WARNING: assumes null-terminated string such that line[r.second+1] always exist
            r.second++;

    }
    return r;
}

unsigned parseLine(vector<wstring>& fields, const wstring& line)
{
    unsigned ofs, ofs0, np;
    const wchar_t delim = L',';
    const wstring whitespace = L" \t\xa0\x3000\x2000\x2001\x2002\x2003\x2004\x2005\x2006\x2007\x2008\x2009\x200a\x202f\x205f";
    const wchar_t quotChar = L'\"';
    pair<unsigned, unsigned> quot;

    fields.clear();

    ofs = ofs0 = 0;
    quot = nextCSVQuotePair(line, quotChar);
    while((np = line.find(delim, ofs)) != wstring::npos)
    {
        if((np > quot.first) && (np < quot.second))
        { // skip delimiter inside quoted field
            ofs = quot.second+1;
            quot = nextCSVQuotePair(line, quotChar, ofs);
            continue;
        }
        fields.push_back( trimquote(line.substr(ofs0, np-ofs0), whitespace, quotChar) );
        ofs = ofs0 = np+1;
    }
    fields.push_back( trimquote(line.substr(ofs0), whitespace, quotChar) );

    return fields.size();
}

1

Đây là một chức năng sẵn sàng để sử dụng nếu tất cả những gì bạn cần là tải một tệp dữ liệu nhân đôi (không có số nguyên, không có văn bản).

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

using namespace std;

/**
 * Parse a CSV data file and fill the 2d STL vector "data".
 * Limits: only "pure datas" of doubles, not encapsulated by " and without \n inside.
 * Further no formatting in the data (e.g. scientific notation)
 * It however handles both dots and commas as decimal separators and removes thousand separator.
 * 
 * returnCodes[0]: file access 0-> ok 1-> not able to read; 2-> decimal separator equal to comma separator
 * returnCodes[1]: number of records
 * returnCodes[2]: number of fields. -1 If rows have different field size
 * 
 */
vector<int>
readCsvData (vector <vector <double>>& data, const string& filename, const string& delimiter, const string& decseparator){

 int vv[3] = { 0,0,0 };
 vector<int> returnCodes(&vv[0], &vv[0]+3);

 string rowstring, stringtoken;
 double doubletoken;
 int rowcount=0;
 int fieldcount=0;
 data.clear();

 ifstream iFile(filename, ios_base::in);
 if (!iFile.is_open()){
   returnCodes[0] = 1;
   return returnCodes;
 }
 while (getline(iFile, rowstring)) {
    if (rowstring=="") continue; // empty line
    rowcount ++; //let's start with 1
    if(delimiter == decseparator){
      returnCodes[0] = 2;
      return returnCodes;
    }
    if(decseparator != "."){
     // remove dots (used as thousand separators)
     string::iterator end_pos = remove(rowstring.begin(), rowstring.end(), '.');
     rowstring.erase(end_pos, rowstring.end());
     // replace decimal separator with dots.
     replace(rowstring.begin(), rowstring.end(),decseparator.c_str()[0], '.'); 
    } else {
     // remove commas (used as thousand separators)
     string::iterator end_pos = remove(rowstring.begin(), rowstring.end(), ',');
     rowstring.erase(end_pos, rowstring.end());
    }
    // tokenize..
    vector<double> tokens;
    // Skip delimiters at beginning.
    string::size_type lastPos = rowstring.find_first_not_of(delimiter, 0);
    // Find first "non-delimiter".
    string::size_type pos     = rowstring.find_first_of(delimiter, lastPos);
    while (string::npos != pos || string::npos != lastPos){
        // Found a token, convert it to double add it to the vector.
        stringtoken = rowstring.substr(lastPos, pos - lastPos);
        if (stringtoken == "") {
      tokens.push_back(0.0);
    } else {
          istringstream totalSString(stringtoken);
      totalSString >> doubletoken;
      tokens.push_back(doubletoken);
    }     
        // Skip delimiters.  Note the "not_of"
        lastPos = rowstring.find_first_not_of(delimiter, pos);
        // Find next "non-delimiter"
        pos = rowstring.find_first_of(delimiter, lastPos);
    }
    if(rowcount == 1){
      fieldcount = tokens.size();
      returnCodes[2] = tokens.size();
    } else {
      if ( tokens.size() != fieldcount){
    returnCodes[2] = -1;
      }
    }
    data.push_back(tokens);
 }
 iFile.close();
 returnCodes[1] = rowcount;
 return returnCodes;
}

1

Một cách nhanh chóng và dễ dàng khác là sử dụng Boost.Fusion I/O:

#include <iostream>
#include <sstream>

#include <boost/fusion/adapted/boost_tuple.hpp>
#include <boost/fusion/sequence/io.hpp>

namespace fusion = boost::fusion;

struct CsvString
{
    std::string value;

    // Stop reading a string once a CSV delimeter is encountered.
    friend std::istream& operator>>(std::istream& s, CsvString& v) {
        v.value.clear();
        for(;;) {
            auto c = s.peek();
            if(std::istream::traits_type::eof() == c || ',' == c || '\n' == c)
                break;
            v.value.push_back(c);
            s.get();
        }
        return s;
    }

    friend std::ostream& operator<<(std::ostream& s, CsvString const& v) {
        return s << v.value;
    }
};

int main() {
    std::stringstream input("abc,123,true,3.14\n"
                            "def,456,false,2.718\n");

    typedef boost::tuple<CsvString, int, bool, double> CsvRow;

    using fusion::operator<<;
    std::cout << std::boolalpha;

    using fusion::operator>>;
    input >> std::boolalpha;
    input >> fusion::tuple_open("") >> fusion::tuple_close("\n") >> fusion::tuple_delimiter(',');

    for(CsvRow row; input >> row;)
        std::cout << row << '\n';
}

Đầu ra:

(abc 123 true 3.14)
(def 456 false 2.718)

1

Tôi đã viết một cách hay để phân tích tệp CSV và tôi nghĩ tôi nên thêm nó dưới dạng câu trả lời:

#include <algorithm>
#include <fstream>
#include <iostream>
#include <stdlib.h>
#include <stdio.h>

struct CSVDict
{
  std::vector< std::string > inputImages;
  std::vector< double > inputLabels;
};

/**
\brief Splits the string

\param str String to split
\param delim Delimiter on the basis of which splitting is to be done
\return results Output in the form of vector of strings
*/
std::vector<std::string> stringSplit( const std::string &str, const std::string &delim )
{
  std::vector<std::string> results;

  for (size_t i = 0; i < str.length(); i++)
  {
    std::string tempString = "";
    while ((str[i] != *delim.c_str()) && (i < str.length()))
    {
      tempString += str[i];
      i++;
    }
    results.push_back(tempString);
  }

  return results;
}

/**
\brief Parse the supplied CSV File and obtain Row and Column information. 

Assumptions:
1. Header information is in first row
2. Delimiters are only used to differentiate cell members

\param csvFileName The full path of the file to parse
\param inputColumns The string of input columns which contain the data to be used for further processing
\param inputLabels The string of input labels based on which further processing is to be done
\param delim The delimiters used in inputColumns and inputLabels
\return Vector of Vector of strings: Collection of rows and columns
*/
std::vector< CSVDict > parseCSVFile( const std::string &csvFileName, const std::string &inputColumns, const std::string &inputLabels, const std::string &delim )
{
  std::vector< CSVDict > return_CSVDict;
  std::vector< std::string > inputColumnsVec = stringSplit(inputColumns, delim), inputLabelsVec = stringSplit(inputLabels, delim);
  std::vector< std::vector< std::string > > returnVector;
  std::ifstream inFile(csvFileName.c_str());
  int row = 0;
  std::vector< size_t > inputColumnIndeces, inputLabelIndeces;
  for (std::string line; std::getline(inFile, line, '\n');)
  {
    CSVDict tempDict;
    std::vector< std::string > rowVec;
    line.erase(std::remove(line.begin(), line.end(), '"'), line.end());
    rowVec = stringSplit(line, delim);

    // for the first row, record the indeces of the inputColumns and inputLabels
    if (row == 0)
    {
      for (size_t i = 0; i < rowVec.size(); i++)
      {
        for (size_t j = 0; j < inputColumnsVec.size(); j++)
        {
          if (rowVec[i] == inputColumnsVec[j])
          {
            inputColumnIndeces.push_back(i);
          }
        }
        for (size_t j = 0; j < inputLabelsVec.size(); j++)
        {
          if (rowVec[i] == inputLabelsVec[j])
          {
            inputLabelIndeces.push_back(i);
          }
        }
      }
    }
    else
    {
      for (size_t i = 0; i < inputColumnIndeces.size(); i++)
      {
        tempDict.inputImages.push_back(rowVec[inputColumnIndeces[i]]);
      }
      for (size_t i = 0; i < inputLabelIndeces.size(); i++)
      {
        double test = std::atof(rowVec[inputLabelIndeces[i]].c_str());
        tempDict.inputLabels.push_back(std::atof(rowVec[inputLabelIndeces[i]].c_str()));
      }
      return_CSVDict.push_back(tempDict);
    }
    row++;
  }

  return return_CSVDict;
}

1

Có thể sử dụng std::regex.

Tùy thuộc vào kích thước tệp của bạn và bộ nhớ có sẵn cho bạn, có thể đọc từng dòng hoặc hoàn toàn trong một std::string.

Để đọc tệp người ta có thể sử dụng:

std::ifstream t("file.txt");
std::string sin((std::istreambuf_iterator<char>(t)),
                 std::istreambuf_iterator<char>());

sau đó bạn có thể phù hợp với điều này thực sự có thể tùy chỉnh theo nhu cầu của bạn.

std::regex word_regex(",\\s]+");
auto what = 
    std::sregex_iterator(sin.begin(), sin.end(), word_regex);
auto wend = std::sregex_iterator();

std::vector<std::string> v;
for (;what!=wend ; wend) {
    std::smatch match = *what;
    v.push_back(match.str());
}

1

Vì tôi không được sử dụng để tăng cường ngay bây giờ, tôi sẽ đề xuất một giải pháp đơn giản hơn. Giả sử rằng tệp .csv của bạn có 100 dòng với 10 số trong mỗi dòng được phân tách bằng dấu ','. Bạn có thể tải dữ liệu này dưới dạng một mảng với mã sau:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
using namespace std;

int main()
{
    int A[100][10];
    ifstream ifs;
    ifs.open("name_of_file.csv");
    string s1;
    char c;
    for(int k=0; k<100; k++)
    {
        getline(ifs,s1);
        stringstream stream(s1);
        int j=0;
        while(1)
        {
            stream >>A[k][j];
            stream >> c;
            j++;
            if(!stream) {break;}
        }
    }


}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.