Bagaimana saya bisa membaca dan mem-parsing file CSV di C ++?


264

Saya perlu memuat dan menggunakan data file CSV di C ++. Pada titik ini, ia hanya bisa menjadi parser yang dipisah koma (mis. Jangan khawatir akan keluar dari baris dan koma baru). Kebutuhan utama adalah pengurai baris demi baris yang akan mengembalikan vektor untuk baris berikutnya setiap kali metode dipanggil.

Saya menemukan artikel ini yang terlihat cukup menjanjikan: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

Saya tidak pernah menggunakan Boost's Spirit, tetapi saya bersedia mencobanya. Tetapi hanya jika tidak ada solusi yang lebih mudah saya hadapi.


11
Saya telah mencari boost::spiritparsing. Ini lebih untuk parsing tata bahasa, terima parsing format file sederhana. Seseorang di tim saya mencoba menggunakannya untuk mem-parsing XML dan itu menyusahkan untuk debug. Jauhi boost::spiritjika memungkinkan.
chrish

50
Maaf chrish, tapi itu saran yang mengerikan. Spirit tidak selalu merupakan solusi yang tepat tetapi saya telah menggunakannya - dan terus menggunakannya - berhasil di sejumlah proyek. Dibandingkan dengan alat serupa (Antlr, Lex / yacc dll) ini memiliki kelebihan yang signifikan. Sekarang, untuk parsing CSV itu mungkin berlebihan ...
MattyT

4
@MattyT IMHO spiritcukup sulit digunakan untuk pustaka kombinator perpustakaan. Setelah memiliki beberapa pengalaman (sangat menyenangkan) dengan (atto)parsecperpustakaan Haskells saya berharap (semangat) untuk bekerja dengan baik, tetapi menyerah setelah berjuang dengan 600 baris kompiler kesalahan.
untuk

Jawaban:


296

Jika Anda tidak peduli tentang keluar dari koma dan baris baru,
DAN Anda tidak dapat menanamkan koma dan baris baru dalam tanda kutip (Jika Anda tidak dapat melarikan diri maka ...)
maka hanya sekitar tiga baris kode (OK 14 -> Tapi itu hanya 15 untuk membaca seluruh file).

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

Saya hanya akan membuat kelas yang mewakili satu baris.
Kemudian streaming ke objek itu:

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

Tetapi dengan sedikit kerja kita secara teknis bisa membuat iterator:

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
pertama () selanjutnya (). Apa ini Java! Hanya bercanda.
Martin York

4
@DarthVader: Pernyataan luas hamparan yang oleh luasnya konyol. Jika Anda ingin menjelaskan mengapa itu buruk dan mengapa kejahatan ini berlaku dalam konteks ini.
Martin York

12
@DarthVader: Saya pikir konyol untuk membuat generalisasi yang luas. Kode di atas berfungsi dengan benar sehingga saya benar-benar dapat melihat sesuatu yang salah dengannya. Tetapi jika Anda memiliki komentar spesifik di atas, saya pasti akan mempertimbangkan dalam konteks ini. Tapi saya bisa melihat bagaimana Anda bisa sampai pada kesimpulan itu dengan tanpa berpikir mengikuti seperangkat aturan umum untuk C # dan menerapkannya ke bahasa lain.
Martin York

5
juga, jika Anda mengalami masalah penautan yang aneh dengan kode di atas karena perpustakaan lain di suatu tempat mendefinisikan istream::operator>>(seperti Eigen), tambahkan inlinesebelum deklarasi operator untuk memperbaikinya.
sebastian_k

3
Ini adalah contoh paling sederhana dan paling bersih tentang cara membuat kelas iterator yang pernah saya lihat.
Giancarlo Sportelli

46

Solusi menggunakan 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
Tokenizer boost tidak sepenuhnya mendukung standar CSV lengkap, tetapi ada beberapa solusi cepat. Lihat stackoverflow.com/questions/1120140/csv-parser-in-c/…
Rolf Kristensen

3
Apakah Anda harus memiliki seluruh pustaka boost di mesin Anda, atau bisakah Anda menggunakan subset kode mereka untuk melakukan ini? 256mb sepertinya banyak untuk penguraian CSV ..
NPike

6
@NPike: Anda dapat menggunakan utilitas bcp yang hadir dengan dorongan untuk mengekstrak hanya header yang benar-benar Anda butuhkan.
ildjarn

46

Versi saya tidak menggunakan apa pun kecuali pustaka C ++ 11 standar. Ini cocok dengan kutipan Excel CSV:

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

Kode ini ditulis sebagai mesin negara-terbatas dan mengkonsumsi satu karakter pada suatu waktu. Saya pikir itu lebih mudah untuk dipikirkan.

#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
terima kasih, saya pikir ini adalah jawaban yang paling lengkap, sayang sekali terkubur di sini.
mihai

vektor string bersarang ini tidak boleh digunakan untuk prosesor modern. Membuang kemampuan caching mereka
Nikolaos Giotis

ditambah Anda mendapatkan semua pernyataan peralihan itu
Nikolaos Giotis

Jawaban teratas tidak berfungsi untuk saya, karena saya menggunakan kompiler yang lebih lama. Jawaban ini berhasil, inisialisasi vektor mungkin memerlukan ini:const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
dr_rk

31

The C ++ String Toolkit Perpustakaan (StrTk) memiliki kelas jaringan token yang memungkinkan Anda untuk memuat data baik dari file teks, string atau buffer arang , dan untuk mengurai / proses mereka dengan cara baris-kolom.

Anda dapat menentukan pembatas baris dan pembatas kolom atau cukup gunakan default.

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

Lebih banyak contoh dapat ditemukan di sini


1
Meskipun strtk mendukung bidang kutip ganda , dan bahkan options.trim_dquotes = truemenghapus tanda kutip di sekitarnya (via ), strtk tidak mendukung penghapusan kutip ganda digandakan (misalnya bidang "She said ""oh no"", and left."sebagai c-string "She said \"oh no\", and left."). Anda harus melakukannya sendiri.
rampion

1
Saat menggunakan strtk, Anda juga harus menangani secara manual bidang yang dikutip ganda yang berisi karakter baris baru.
rampion

29

Anda dapat menggunakan Boost Tokenizer dengan escaped_list_separator.

escaped_list_separator mem-parsing superset dari csv. Boost :: tokenizer

Ini hanya menggunakan file header tokenizer Boost, tidak perlu menautkan untuk meningkatkan perpustakaan.

Berikut ini sebuah contoh, (lihat Parse CSV File With Boost Tokenizer Di C ++ untuk detailnya atau 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;
    }
}

Dan jika Anda ingin dapat mengurai baris baru yang disematkan mybyteofcode.blogspot.com/2010/11/… .
stefanB

Sementara teknik ini berhasil, saya merasa kinerjanya sangat buruk. Mengurai file CSV 90000 baris dengan sepuluh bidang per baris membutuhkan waktu sekitar 8 detik pada Xeon 2 GHz saya. Modul Python Standard Library csv mem-parsing file yang sama dalam waktu sekitar 0,3 detik.
Rob Smallshire

@Rob itu menarik - apa yang dilakukan Python csv berbeda?
tofutim

1
@RobSmallshire itu adalah contoh kode sederhana bukan yang berkinerja tinggi. Kode ini membuat salinan dari semua bidang per baris. Untuk kinerja yang lebih tinggi, Anda akan menggunakan opsi yang berbeda dan mengembalikan referensi yang adil ke bidang dalam buffer alih-alih membuat salinan.
stefanB

29

Tidaklah berlebihan untuk menggunakan Spirit untuk mem-parsing CSV. Spirit cocok untuk tugas penguraian mikro. Misalnya, dengan Spirit 2.1, semudah:

bool r = phrase_parse(first, last,

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

    space, v);

Vektor, v, diisi dengan nilai-nilai. Ada serangkaian tutorial yang menyentuh ini di Spirit 2.1 docs baru yang baru saja dirilis dengan Boost 1.41.

Tutorial berkembang dari yang sederhana ke yang kompleks. Parser CSV disajikan di suatu tempat di tengah dan menyentuh berbagai teknik dalam menggunakan Spirit. Kode yang dihasilkan seketat kode tulisan tangan. Lihat assembler yang dihasilkan!


18
Sebenarnya itu berlebihan, kompilasi waktu yang dihabiskan sangat besar dan membuat menggunakan Spirit untuk "tugas parsing mikro" yang sederhana tidak masuk akal.
Gerdiner

13
Saya juga ingin menunjukkan bahwa kode di atas tidak mem-parsing CSV, hanya mem-parsing berbagai jenis vektor yang dibatasi oleh koma. Itu tidak menangani kutipan, berbagai jenis kolom dll. Singkatnya 19 suara untuk sesuatu yang menjawab pertanyaan sama sekali tampak agak mencurigakan bagi saya.
Gerdiner

9
@Gerdiner Nonsense. Waktu kompilasi untuk parser kecil tidak terlalu besar, tetapi juga tidak relevan karena Anda memasukkan kode ke unit kompilasi sendiri dan mengompilasinya sekali . Maka Anda hanya perlu menautkannya dan itu seefisien mungkin. Dan untuk komentar Anda yang lain, ada banyak dialek CSV karena ada prosesor untuk itu. Yang satu ini tentu saja bukan dialek yang sangat berguna tetapi bisa diperpanjang untuk menangani nilai-nilai yang dikutip.
Konrad Rudolph

11
@konrad: Cukup sertakan "#include <boost / spirit / include / qi.hpp>" dalam file kosong dengan hanya file utama dan tidak ada yang lain. Ini mengasapi yang tidak perlu. Jawaban yang diterima mengkompilasi di bawah 2 detik pada mesin yang sama, saya benci membayangkan berapa lama Boost 'tepat'. Contoh Roh akan mengambil untuk mengkompilasi.
Gerdiner

11
@ Gerdiner Saya harus setuju dengan Anda biaya overhead dalam menggunakan semangat untuk sesuatu yang sederhana seperti pemrosesan cvs terlalu besar.

18

Jika Anda DO peduli tentang parsing CSV dengan benar, ini akan melakukannya ... relatif lambat karena bekerja satu char pada suatu waktu.

 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 ini tidak akan menangani tanda kutip yang disematkan dengan benar (mis. "String ini memiliki" "tanda kutip yang disematkan" "", "foo", 1))
Jeremy Friesner

14

Saat menggunakan Boost Tokenizer escaped_list_separator untuk file CSV, maka orang harus mengetahui hal berikut:

  1. Ini membutuhkan karakter pelarian (default back-slash - \)
  2. Membutuhkan karakter splitter / seperator (koma default -,)
  3. Membutuhkan karakter kutipan (kutipan default - ")

Format CSV yang ditentukan oleh wiki menyatakan bahwa bidang data dapat berisi pemisah dalam tanda kutip (didukung):

1997, Ford, E350, "Truk super, mewah"

Format CSV yang ditentukan oleh wiki menyatakan bahwa tanda kutip tunggal harus ditangani dengan tanda kutip ganda (escaped_list_separator akan menghapus semua karakter kutipan):

1997, Ford, E350, "Super" "mewah" "truk"

Format CSV tidak menentukan bahwa karakter back-slash apa pun harus dihapus (escaped_list_separator akan menghapus semua karakter escape).

Kemungkinan solusi untuk memperbaiki perilaku default dari boost escaped_list_separator:

  1. Pertama-tama gantilah semua karakter back-slash (\) dengan dua karakter back-slash (\\) agar mereka tidak dihapus.
  2. Kedua ganti semua tanda kutip ganda ("") dengan karakter back-slash tunggal dan tanda kutip (\ ")

Work-around ini memiliki efek samping yang mengosongkan bidang data yang diwakili oleh kuotasi ganda, akan ditransformasikan menjadi token kuotasi tunggal. Ketika melakukan iterasi melalui token, maka seseorang harus memeriksa apakah token itu merupakan tanda kutip tunggal, dan memperlakukannya seperti string kosong.

Tidak cantik tetapi berfungsi, selama tidak ada baris baru di dalam tanda kutip.


8

Anda mungkin ingin melihat CSVfix proyek FOSS saya ( tautan yang diperbarui ), yang merupakan editor aliran CSV yang ditulis dalam C ++. Pengurai CSV bukanlah hadiah, tetapi melakukan pekerjaan dan seluruh paket dapat melakukan apa yang Anda butuhkan tanpa Anda menulis kode apa pun.

Lihat alib / src / a_csv.cpp untuk parser CSV, dan csvlib / src / csved_ioman.cpp ( IOManager::ReadCSV) untuk contoh penggunaan.


Tampak hebat ... Bagaimana dengan status beta / produksi?
neuro

Statusnya "dalam pengembangan", seperti yang disarankan oleh nomor versi. Saya benar-benar membutuhkan lebih banyak umpan balik dari pengguna sebelum masuk ke versi 1.0. Plus saya memiliki beberapa fitur yang ingin saya tambahkan, berkaitan dengan produksi XML dari CSV.

Bookmark, dan akan mencobanya lain kali saya harus berurusan dengan orang-orang yang indah file CSV standar ...
neuro

8

Karena semua pertanyaan CSV sepertinya dialihkan ke sini, saya pikir saya akan memposting jawaban saya di sini. Jawaban ini tidak secara langsung menjawab pertanyaan si penanya. Saya ingin dapat membaca dalam aliran yang dikenal dalam format CSV, dan juga jenis masing-masing bidang sudah diketahui. Tentu saja, metode di bawah ini dapat digunakan untuk memperlakukan setiap bidang menjadi tipe string.

Sebagai contoh bagaimana saya ingin dapat menggunakan aliran input CSV, pertimbangkan input berikut (diambil dari halaman wikipedia di 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"
;

Kemudian, saya ingin dapat membaca data seperti ini:

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

Ini adalah solusi yang akhirnya saya dapatkan.

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

Dengan bantuan berikut yang dapat disederhanakan oleh templat ciri integral baru di 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) };
};

Cobalah online!


6

Saya menulis parser CSV header-only, C ++ 11 . Telah diuji dengan baik, cepat, mendukung seluruh spesifikasi CSV (bidang yang dikutip, pembatas / terminator dalam tanda kutip, pelolosan kutipan, dll.), Dan dapat dikonfigurasi untuk memperhitungkan CSV yang tidak mematuhi spesifikasi.

Konfigurasi dilakukan melalui antarmuka yang lancar:

// 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

Parsing hanya rentang berdasarkan untuk loop:

#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
Kerja bagus, tetapi Anda perlu menambahkan tiga hal lagi: (1) baca tajuk (2) memberikan bidang pengindeksan berdasarkan nama (3) jangan mengalokasikan kembali memori dalam lingkaran dengan menggunakan kembali vektor string yang sama
Maksym Ganenko

@ MaksymGanenko saya lakukan # 3. Bisakah Anda menguraikan # 2?
m0meni

1
Sangat berguna untuk mendapatkan bidang bukan dengan posisi di baris, tetapi dengan nama yang diberikan di header (di baris pertama tabel CSV). Misalnya, saya mengharapkan tabel CSV dengan bidang "Tanggal", tapi saya tidak tahu apa itu indeks bidang "Tanggal" dalam satu baris.
Maksym Ganenko

1
@ MaksymGanenko ah saya mengerti maksud Anda. Ada github.com/ben-strasser/fast-cpp-csv-parser untuk ketika Anda mengetahui kolom CSV Anda pada waktu kompilasi, dan mungkin lebih baik daripada saya. Apa yang saya inginkan adalah parser CSV untuk kasus-kasus di mana Anda ingin menggunakan kode yang sama untuk banyak CSV yang berbeda dan tidak tahu seperti apa mereka sebelumnya. Jadi saya mungkin tidak akan menambahkan # 2, tapi saya akan menambahkan # 1 di masa depan.
m0meni

5

Pustaka I / O CSV lain dapat ditemukan di sini:

http://code.google.com/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
Bagus, tetapi memaksa Anda untuk memilih jumlah kolom pada waktu kompilasi. Tidak terlalu berguna untuk banyak aplikasi.
quant_dev

5

Solusi lain yang mirip dengan jawaban Loki Astari , di C ++ 11. Baris di sini adalah std::tupledari tipe yang diberikan. Kode memindai satu baris, kemudian memindai hingga setiap pembatas, dan kemudian mengkonversi dan membuang nilai langsung ke tuple (dengan sedikit kode templat).

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

Penganjur:

  • cukup bersih dan mudah digunakan, hanya C ++ 11.
  • konversi tipe otomatis ke std::tuple<t1, ...>via operator>>.

Apa yang hilang:

  • melarikan diri dan mengutip
  • tidak ada penanganan kesalahan dalam kasus CSV cacat.

Kode utama:

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

Saya memberikan contoh kerja yang kecil di GitHub ; Saya telah menggunakannya untuk mem-parsing beberapa data numerik dan melayani tujuannya.


1
Anda mungkin tidak peduli tentang inlining, karena sebagian besar kompiler memutuskan sendiri. Setidaknya saya yakin di Visual C ++. Itu dapat inline metode secara independen dari spesifikasi metode Anda.
MrPisarik

1
Itulah mengapa saya menandai mereka secara eksplisit. Gcc dan Dentang, yang paling sering saya gunakan, memiliki juga konvensi mereka sendiri. Kata kunci "sebaris" seharusnya hanya insentif.
Spak

4

Berikut ini adalah implementasi lain dari parser Unicode CSV (berfungsi dengan wchar_t). Saya menulis sebagian darinya, sementara Jonathan Leffler menulis sisanya.

Catatan: Parser ini bertujuan mereplikasi perilaku Excel sedekat mungkin, khususnya saat mengimpor file CSV yang rusak atau salah .

Ini adalah pertanyaan asli - Parsing file CSV dengan bidang multiline dan lolos dari tanda kutip ganda

Ini adalah kode sebagai SSCCE (Short, Self-Contained, Correct Example).

#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

Saya membutuhkan pustaka C ++ yang mudah digunakan untuk mem-parsing file CSV tetapi tidak dapat menemukan yang tersedia, jadi saya akhirnya membangun satu. Rapidcsv adalah pustaka hanya header C ++ 11 yang memberikan akses langsung ke kolom yang diurai (atau baris) sebagai vektor, dalam tipe data pilihan. Sebagai contoh:

#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
Kerja bagus, tetapi perpustakaan tidak berfungsi dengan baik jika header memiliki label kosong. Itu tipikal untuk tabel Excel / LibreOffice NxN. Juga, mungkin melewati baris data terakhir. Sayangnya, lib Anda tidak kuat.
Maksym Ganenko

1
Terima kasih atas umpan balik @MaksymGanenko Saya telah memperbaiki bug "data baris terakhir" untuk garis akhir tanpa garis istirahat. Adapun masalah lain yang disebutkan - "header dengan label kosong" - Saya tidak yakin apa yang dimaksud? Perpustakaan harus menangani label kosong (baik yang dikutip maupun tidak). Ia juga dapat membaca CSV tanpa baris / kolom tajuk, tetapi kemudian mengharuskan pengguna untuk menentukan ini (id judul kolom -1 dan id judul baris -1). Berikan beberapa detail lebih lanjut atau laporkan bug di halaman GitHub jika Anda memiliki kasus penggunaan khusus yang ingin Anda lihat didukung. Terima kasih!
d99kris

2

Maaf, tapi ini semua sepertinya banyak sintaksis yang rumit untuk menyembunyikan beberapa baris kode.

Kenapa tidak ini:

/**

  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, mengapa ada ",\n"string?
Timmmm

@Timmmm mencari metode substr dari kelas String, dan Anda akan melihat bahwa dibutuhkan beberapa karakter, \ n adalah karakter baris baru, jadi ini dihitung sebagai karakter tunggal, dalam contoh ini. Itu tidak mencari seluruh nilai secara keseluruhan. Sedang mencari setiap karakter individu; yaitu koma atau baris baru. Substr akan mengembalikan posisi karakter pertama yang ditemukannya, dan -1 jika tidak menemukan keduanya, yang berarti sudah selesai membaca baris. fp melacak posisi dalam file secara internal, sehingga setiap panggilan ke readCSV memindahkannya satu baris sekaligus.
Martyn Shutt

2

Berikut ini adalah kode untuk membaca matriks, perhatikan Anda juga memiliki fungsi csvwrite di 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

Anda dapat membuka dan membaca file .csv menggunakan fopen, fungsi fscanf, tetapi yang penting adalah mem-parsing data. Cara termudah untuk mem-parsing data menggunakan pembatas. Dalam kasus .csv, pembatas adalah ','.

Misalkan file data1.csv Anda adalah sebagai berikut:

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

Anda dapat mengidentifikasi data dan menyimpannya dalam array char dan kemudian menggunakan fungsi atoi () dll untuk konversi yang sesuai

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

[^,], ^ -itu membalikkan logika, berarti cocok dengan string apa pun yang tidak mengandung koma lalu terakhir, mengatakan untuk mencocokkan koma yang mengakhiri string sebelumnya.


2

Hal pertama yang perlu Anda lakukan adalah memastikan file itu ada. Untuk mencapai ini, Anda hanya perlu mencoba dan membuka aliran file di jalur. Setelah Anda membuka aliran file gunakan stream.fail () untuk melihat apakah itu berfungsi seperti yang diharapkan, atau tidak.

bool fileExists(string fileName)
{

ifstream test;

test.open(fileName.c_str());

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

Anda juga harus memverifikasi bahwa file yang disediakan adalah jenis file yang benar. Untuk mencapai ini, Anda perlu melihat jalur file yang disediakan sampai Anda menemukan ekstensi file. Setelah Anda memiliki ekstensi file pastikan itu file .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;
}

Fungsi ini akan mengembalikan ekstensi file yang digunakan kemudian dalam pesan kesalahan.

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

Fungsi ini sebenarnya akan memanggil pemeriksaan kesalahan yang dibuat di atas dan kemudian mem-parsing melalui file.

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

Anda harus merasa bangga ketika Anda menggunakan sesuatu yang sangat indah boost::spirit

Di sini upaya saya untuk parser (hampir) mematuhi spesifikasi CSV pada tautan ini. Spesifikasi CSV (saya tidak perlu jeda baris di dalam bidang. Juga ruang di sekitar koma diberhentikan).

Setelah Anda mengatasi pengalaman mengejutkan dari menunggu 10 detik untuk menyusun kode ini :), Anda dapat duduk dan menikmati.

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

Menyusun:

make csvparser

Tes (contoh dicuri dari 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

Solusi ini mendeteksi 4 kasus ini

kelas lengkap ada di

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,",

Itu membaca karakter file dengan karakter, dan membaca 1 baris sekaligus ke vektor (string), oleh karena itu cocok untuk file yang sangat besar.

Penggunaan adalah

Iterasi sampai baris kosong dikembalikan (akhir file). Baris adalah vektor di mana setiap entri adalah kolom 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;
  }
}

deklarasi kelas

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

pelaksanaan

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

Anda juga bisa melihat kapabilitas Qtperpustakaan.

Ini memiliki dukungan ekspresi reguler dan kelas QString memiliki metode yang bagus, misalnya split()mengembalikan QStringList, daftar string yang diperoleh dengan memisahkan string asli dengan pembatas yang disediakan. Harus mencukupi untuk file csv ..

Untuk mendapatkan kolom dengan nama header yang diberikan, saya menggunakan yang berikut ini: c ++ inheritance Qt problem qstring


ini tidak akan menangani koma dalam tanda kutip
Ezee

1

Jika Anda tidak ingin berurusan dengan memasukkan peningkatan dalam proyek Anda (itu sangat besar jika semua yang akan Anda gunakan adalah penguraian CSV ...)

Saya beruntung dengan penguraian CSV di sini:

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

Ini menangani kolom yang dikutip - tetapi tidak menangani karakter sebaris \ n (yang mungkin bagus untuk sebagian besar penggunaan).


1
Bukankah kompiler harus menghapus semua yang tidak penting?
tofutim

1

Ini adalah utas lama tetapi masih di bagian atas hasil pencarian, jadi saya menambahkan solusi saya menggunakan std :: stringstream dan metode penggantian string sederhana oleh Yves Baumes yang saya temukan di sini.

Contoh berikut akan membaca file baris demi baris, abaikan baris komentar yang dimulai dengan // dan parsing baris lainnya ke dalam kombinasi string, int, dan ganda. Stringstream melakukan parsing, tetapi mengharapkan bidang dibatasi oleh spasi, jadi saya menggunakan stringreplace untuk mengubah koma menjadi spasi terlebih dahulu. Ini menangani tab ok, tetapi tidak berurusan dengan string yang dikutip.

Masukan yang salah atau hilang diabaikan begitu saja, yang mungkin atau mungkin tidak baik, tergantung pada keadaan Anda.

#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

Untuk apa nilainya, inilah implementasi saya. Ini berkaitan dengan input wstring, tetapi dapat disesuaikan dengan string dengan mudah. Ini tidak menangani baris baru di bidang (karena aplikasi saya juga tidak, tetapi menambahkan dukungannya tidak terlalu sulit) dan tidak mematuhi "\ r \ n" akhir baris sesuai RFC (dengan asumsi Anda menggunakan std :: getline), tetapi tidak menangani pemotongan spasi putih dan tanda kutip ganda dengan benar (mudah-mudahan).

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

Berikut adalah fungsi siap pakai jika yang Anda butuhkan adalah memuat file data ganda (tanpa bilangan bulat, tanpa teks).

#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

Cara cepat dan mudah lainnya adalah dengan menggunakan 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';
}

Keluaran:

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

1

Saya menulis cara yang bagus untuk mem-parsing file CSV dan saya pikir saya harus menambahkannya sebagai jawaban:

#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

Dimungkinkan untuk digunakan std::regex.

Bergantung pada ukuran file Anda dan memori yang tersedia untuk Anda, dimungkinkan membacanya baik baris demi baris atau seluruhnya dalam std::string.

Untuk membaca file, seseorang dapat menggunakan:

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

maka Anda dapat mencocokkan dengan ini yang sebenarnya disesuaikan dengan kebutuhan Anda.

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

Karena saya tidak terbiasa meningkatkan sekarang, saya akan menyarankan solusi yang lebih sederhana. Mari kita anggap bahwa file .csv Anda memiliki 100 baris dengan 10 angka di setiap baris yang dipisahkan oleh ','. Anda bisa memuat data ini dalam bentuk array dengan kode berikut:

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


}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.