Como posso ler e analisar arquivos CSV em C ++?


264

Preciso carregar e usar dados do arquivo CSV em C ++. Nesse ponto, ele pode realmente ser apenas um analisador delimitado por vírgula (ou seja, não se preocupe em escapar de novas linhas e vírgulas). A principal necessidade é um analisador de linha por linha que retornará um vetor para a próxima linha sempre que o método for chamado.

Encontrei este artigo que parece bastante promissor: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

Eu nunca usei o Boost's Spirit, mas estou disposto a tentar. Mas somente se não houver uma solução mais direta que eu esteja ignorando.


11
Eu olhei boost::spiritpara analisar. É mais para analisar gramáticas, graças à análise de um formato de arquivo simples. Alguém da minha equipe estava tentando usá-lo para analisar o XML e foi difícil depurar. Fique longe, boost::spiritse possível.
ChrisH

50
Desculpe chrish, mas esse é um conselho terrível. O Spirit nem sempre é uma solução apropriada, mas eu a usei - e continuo a usá-lo - com sucesso em vários projetos. Comparado a ferramentas similares (Antlr, Lex / yacc etc), possui vantagens significativas. Agora, para analisar CSV é provavelmente um exagero ...
MattyT

4
@ MattyT IMHO spirité bastante difícil de usar para uma biblioteca combinadora de analisadores. Tendo tido alguma experiência (muito agradável) com as (atto)parsecbibliotecas Haskells, eu esperava que (espírito) funcionasse da mesma forma, mas desisti depois de lutar com erros do compilador de 600 linhas.
fho 14/07

Respostas:


296

Se você não se importa com a fuga de vírgula e nova linha,
E você não pode incorporar vírgula e nova linha entre aspas (se não puder escapar, então ...)
, são apenas três linhas de código (OK 14 -> Mas é apenas 15 para ler o arquivo inteiro).

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

Eu apenas criaria uma classe representando uma linha.
Em seguida, transmita para esse objeto:

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

Porém, com um pouco de trabalho, poderíamos criar tecnicamente um iterador:

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
primeiro () próximo (). O que é esse Java! Apenas brincando.
Martin Iorque

4
@ DarthVader: Uma declaração ampla sobreposta que, por sua largura, é boba. Se você deseja esclarecer por que isso é ruim e por que essa maldade se aplica nesse contexto.
Martin York

12
@ DarthVader: Eu acho que é bobagem fazer amplas generalizações. O código acima funciona corretamente para que eu possa ver algo de errado com ele. Mas se você tiver algum comentário específico sobre o exposto, definitivamente considerarei neste contexto. Mas posso ver como você pode chegar a essa conclusão seguindo um conjunto de regras generalizadas para C # e aplicando-o a outro idioma.
Martin York

5
Além disso, se você encontrar problemas estranhos de vinculação com o código acima porque outra biblioteca define em algum lugar istream::operator>>(como Eigen), adicione uma inlinedeclaração antes da operadora para corrigi-la.
sebastian_k

3
Este é o exemplo mais simples e limpo de como criar uma classe de iterador que eu já vi.
Giancarlo Sportelli

46

Solução usando o 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
O tokenizer de impulso não oferece suporte completo ao padrão CSV completo, mas existem algumas soluções rápidas. Veja stackoverflow.com/questions/1120140/csv-parser-in-c/…
Rolf Kristensen

3
Você precisa ter toda a biblioteca de reforço em sua máquina ou pode usar apenas um subconjunto do código deles para fazer isso? 256mb parece muito para a análise de CSV ..
NPike:

6
@NPike: Você pode usar o utilitário bcp que vem com boost para extrair apenas os cabeçalhos que você realmente precisa.
Ildjarn

46

Minha versão não está usando nada além da biblioteca C ++ 11 padrão. Ele lida bem com a cotação do Excel CSV:

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

O código é escrito como uma máquina de estado finito e consome um caractere por vez. Eu acho que é mais fácil argumentar.

#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
obrigado, acho que esta é a resposta mais completa, pena que esteja enterrada aqui.
Mihai

esse vetor aninhado de strings não é obrigatório para processadores modernos. Joga fora sua capacidade de armazenamento em cache
Nikolaos Giotis

Além disso, você tem todas essas declarações switch
Nikolaos Giotis

A resposta principal não funcionou para mim, pois estou em um compilador mais antigo. Esta resposta funcionou, a inicialização do vetor pode exigir o seguinte:const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
dr_rk

31

A Biblioteca de C ++ String Toolkit (StrTk) possui uma classe de grade de tokens que permite carregar dados de arquivos de texto, seqüências de caracteres ou buffers de caractere e analisá-los / processá-los da maneira de coluna de linha.

Você pode especificar os delimitadores de linha e de coluna ou apenas usar os padrões.

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

Mais exemplos podem ser encontrados aqui


1
Embora o strtk suporte campos com aspas duplas e até mesmo retire as aspas circundantes (via options.trim_dquotes = true), ele não suporta a remoção de aspas duplas duplicadas (por exemplo, o campo "She said ""oh no"", and left."como c-string "She said \"oh no\", and left."). Você terá que fazer isso sozinho.
Rampion

1
Ao usar strtk, você também precisará manipular manualmente campos com aspas duplas que contenham caracteres de nova linha.
Rampion

29

Você pode usar o Boost Tokenizer com escaped_list_separator.

escaped_list_separator analisa um superconjunto do csv. Boost :: tokenizer

Isso usa apenas os arquivos de cabeçalho do tokenizer Boost, sem necessidade de vinculação para aumentar as bibliotecas.

Aqui está um exemplo (consulte Analisar arquivo CSV com o Boost Tokenizer no C ++ para obter detalhes ou 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;
    }
}

E se você deseja analisar novas linhas incorporadas, mybyteofcode.blogspot.com/2010/11/… .
stefanB

Enquanto essa técnica funciona, eu achei que ela tinha um desempenho muito ruim. A análise de um arquivo CSV de 90000 linhas com dez campos por linha leva cerca de 8 segundos no meu Xeon de 2 GHz. O módulo csv da biblioteca padrão do Python analisa o mesmo arquivo em cerca de 0,3 segundos.
Rob Smallshire

@ Rob isso é interessante - o que o CSV Python faz diferente?
tofutim

1
@RobSmallshire, é um exemplo de código simples e não de alto desempenho. Este código faz cópias de todos os campos por linha. Para um desempenho superior, você usaria opções diferentes e retornaria apenas referências a campos no buffer, em vez de fazer cópias.
stefanB

29

Não é um exagero usar o Spirit para analisar CSVs. O Spirit é adequado para tarefas de micro-análise. Por exemplo, com o Spirit 2.1, é tão fácil quanto:

bool r = phrase_parse(first, last,

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

    space, v);

O vetor, v, é preenchido com os valores. Há uma série de tutoriais abordando isso nos novos documentos do Spirit 2.1 que foram lançados com o Boost 1.41.

O tutorial progride do simples ao complexo. Os analisadores de CSV são apresentados em algum lugar no meio e abordam várias técnicas no uso do Spirit. O código gerado é tão rígido quanto o código escrito à mão. Confira o assembler gerado!


18
Na verdade, é um exagero, o tempo de compilação é enorme e torna irracional o uso do Spirit para simples "tarefas de micro-análise".
Gerdiner 2/12/12

13
Também gostaria de salientar que o código acima não analisa CSV, apenas analisa um intervalo do tipo do vetor delimitado por vírgulas. Ele não lida com aspas, tipos variados de colunas etc. Em suma, 19 votos para algo que realmente responde à pergunta me parece um pouco suspeito.
Gerdiner 2/12/12

9
@Gerdiner Nonsense. O tempo de compilação atingido para pequenos analisadores não é tão grande, mas também é irrelevante porque você coloca o código em sua própria unidade de compilação e o compila uma vez . Então você só precisa vinculá-lo e isso é o mais eficiente possível. E quanto ao seu outro comentário, existem tantos dialetos de CSV quanto processadores. Este certamente não é um dialeto muito útil, mas pode ser estendido trivialmente para lidar com valores citados.
Konrad Rudolph

11
@konrad: Basta incluir "#include <boost / spirit / include / qi.hpp>" em um arquivo vazio com apenas um main e nada mais leva 9,7 segundos com o MSVC 2012 em um corei7 rodando a 2.ghz. É inchaço desnecessário. A resposta aceita é compilada em menos de 2 segundos na mesma máquina. Detestaria imaginar quanto tempo o exemplo 'Boost.Spirit' adequado 'levaria para compilar.
Gerdiner 11/01

11
@ Gerdiner Eu tenho que concordar com você a sobrecarga no uso do espírito para algo tão simples quanto o processamento cvs é muito grande.

18

Se você FAZER cuidado sobre como analisar CSV corretamente, isso vai fazê-lo ... de forma relativamente lenta, já que funciona um caractere de cada vez.

 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 isso não vai lidar com incorporado aspas corretamente (por exemplo, "Essa frase tem "" marcas incorporadas quote" "", "foo", 1))
Jeremy Friesner

14

Ao usar o Boost Tokenizer escaped_list_separator para arquivos CSV, é necessário estar ciente do seguinte:

  1. Requer um caractere de escape (barra invertida padrão - \)
  2. Requer um caractere separador / separador (vírgula padrão -,)
  3. Requer um caractere de citação (citação padrão - ")

O formato CSV especificado pelo wiki afirma que os campos de dados podem conter separadores entre aspas (suportado):

1997, Ford, E350, "Super, caminhão luxuoso"

O formato CSV especificado pelo wiki declara que aspas simples devem ser tratadas com aspas duplas (escaped_list_separator removerá todos os caracteres de aspas):

1997, Ford, E350, "Super" "luxuoso" "caminhão"

O formato CSV não especifica que nenhum caractere de barra invertida seja removido (escaped_list_separator removerá todos os caracteres de escape).

Uma possível solução alternativa para corrigir o comportamento padrão do impulso escaped_list_separator:

  1. Primeiro substitua todos os caracteres de barra invertida (\) por dois caracteres de barra invertida (\\) para que não sejam removidos.
  2. Em segundo lugar, substitua todas as aspas duplas ("") por um único caractere de barra invertida e uma aspas (\ ")

Essa solução alternativa tem o efeito colateral de que os campos de dados vazios representados por aspas duplas serão transformados em um token de aspas simples. Ao iterar pelos tokens, é necessário verificar se o token é de aspas simples e tratá-lo como uma string vazia.

Não é bonito, mas funciona, desde que não haja novas linhas entre aspas.


8

Você pode olhar para o meu projeto FOSS CSVfix ( link atualizado ), que é um editor de fluxo CSV escrito em C ++. O analisador de CSV não é um prêmio, mas faz o trabalho e todo o pacote pode fazer o que você precisa sem escrever nenhum código.

Consulte alib / src / a_csv.cpp para o analisador CSV e csvlib / src / csved_ioman.cpp ( IOManager::ReadCSV) para obter um exemplo de uso.


Parece ótimo ... E o status beta / produção?
1300 neuro

O status é "em desenvolvimento", conforme sugerido pelos números de versão. Eu realmente preciso de mais feedback dos usuários antes de ir para a versão 1.0. Além disso, tenho mais alguns recursos que quero adicionar, relacionados à produção de XML em CSV.

Bookmarking-lo, e vai dar-lhe uma tentativa a próxima vez eu tenho que lidar com essas maravilhosas arquivos CSV padrão ...
neuro

8

Como todas as perguntas sobre CSV parecem ser redirecionadas aqui, pensei em postar minha resposta aqui. Esta resposta não aborda diretamente a pergunta do solicitante. Queria poder ler em um fluxo conhecido por estar no formato CSV, e também os tipos de cada campo já eram conhecidos. Obviamente, o método abaixo poderia ser usado para tratar cada campo como um tipo de string.

Como um exemplo de como eu queria poder usar um fluxo de entrada CSV, considere a seguinte entrada (retirada da página da wikipedia em 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"
;

Então, eu queria poder ler os dados assim:

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

Esta foi a solução que eu acabei com.

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

Com os seguintes auxiliares que podem ser simplificados pelos novos modelos de características integrais no 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) };
};

Experimente online!


6

Eu escrevi um analisador C ++ 11 CSV somente de cabeçalho . É bem testado, rápido, suporta toda a especificação CSV (campos entre aspas, delimitador / terminador entre aspas, escape de cotação etc.) e é configurável para contabilizar os CSVs que não aderem à especificação.

A configuração é feita através de uma interface fluente:

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

A análise é apenas um intervalo baseado em 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
Bom trabalho, mas você precisa adicionar mais três coisas: (1) ler o cabeçalho (2) fornecer campos de indexação por nome (3) que a memória não realocar em malha, reutilizando o mesmo vetor de strings
Maksym Ganenko

@MaksymGanenko eu faço # 3. Você poderia elaborar o # 2?
M0meni

1
É muito útil obter campos não por posição em uma linha, mas pelo nome fornecido no cabeçalho (na primeira linha da tabela CSV). Por exemplo, espero uma tabela CSV com o campo "Data", mas não sei o que é o índice do campo "Data" em uma linha.
Maksym Ganenko

1
@MaksymGanenko ah eu entendo o que você quer dizer. Existe o github.com/ben-strasser/fast-cpp-csv-parser para quando você conhece as colunas do seu CSV em tempo de compilação, e provavelmente é melhor que o meu. O que eu queria era um analisador de CSV para os casos em que você queria usar o mesmo código para muitos CSVs diferentes e não sabe como eles são antes do tempo. Portanto, provavelmente não adicionarei o nº 2, mas adicionarei o nº 1 em algum momento no futuro.
M0meni

5

Outra biblioteca de E / S CSV pode ser encontrada aqui:

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
Bom, mas força você a escolher o número de colunas em tempo de compilação. Não é muito útil para muitas aplicações.
26415 quant_dev

5

Outra solução semelhante à resposta de Loki Astari , em C ++ 11. Linhas aqui são std::tuples de um determinado tipo. O código varre uma linha, depois varre até cada delimitador e, em seguida, converte e despeja o valor diretamente na tupla (com um pouco de código de modelo).

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

Advanges:

  • bastante limpo e simples de usar, apenas C ++ 11.
  • conversão automática de tipos em std::tuple<t1, ...>via operator>>.

O que está a faltar:

  • escapando e citando
  • nenhum tratamento de erros no caso de CSV mal formado.

O código principal:

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

Eu coloquei um pequeno exemplo de trabalho no GitHub ; Eu tenho usado para analisar alguns dados numéricos e serviu a seu propósito.


1
Você pode não se importar com a inclusão de linhas, porque a maioria dos compiladores decide isso por conta própria. Pelo menos eu tenho certeza no Visual C ++. Ele pode incorporar o método independentemente da especificação do método.
MrPisarik

1
Foi exatamente por isso que os marquei explicitamente. Gcc e Clang, os que eu mais uso, também têm suas próprias convenções. Uma palavra-chave "inline" deve ser apenas um incentivo.
Spak

4

Aqui está outra implementação de um analisador Unicode CSV (funciona com wchar_t). Eu escrevi parte, enquanto Jonathan Leffler escreveu o resto.

Nota: este analisador visa replicar o comportamento do Excel o mais próximo possível, especificamente ao importar arquivos CSV quebrados ou malformados .

Esta é a pergunta original - Analisando arquivo CSV com campos de múltiplas linhas e aspas duplas escapadas

Este é o código como um SSCCE (exemplo curto, independente e correto).

#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

Eu precisava de uma biblioteca C ++ fácil de usar para analisar arquivos CSV, mas não consegui encontrar nenhum disponível, então acabei criando um. O Rapidcsv é uma biblioteca somente de cabeçalho C ++ 11 que fornece acesso direto a colunas (ou linhas) analisadas como vetores, no tipo de dados de sua escolha. Por exemplo:

#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
Bom trabalho, mas a biblioteca não funciona corretamente se o cabeçalho tiver rótulos vazios. Isso é típico para a tabela Excel / LibreOffice NxN. Além disso, pode pular a última linha de dados. Infelizmente, sua biblioteca não é robusta.
Maksym Ganenko

1
Obrigado pelo feedback @MaksymGanenko Corrigi o bug da "última linha de dados" para as linhas finais sem quebras de linha à direita. Quanto à outra questão mencionada - "cabeçalhos com rótulos vazios" - não sei ao que se refere? A biblioteca deve manipular rótulos vazios (entre aspas e não entre aspas). Ele também pode ler CSV sem a linha / coluna do cabeçalho, mas exige que o usuário especifique isso (ID do título da coluna -1 e ID do título da linha -1). Forneça mais detalhes ou relate um erro na página do GitHub se você tiver algum caso de uso específico que gostaria de ver suportado. Obrigado!
d99kris

2

Com licença, mas tudo isso parece uma grande sintaxe elaborada para ocultar algumas linhas de código.

Por que não isso:

/**

  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, por que haveria ",\n"na corda?
Timmmm

O @Timmmm consulta o método substr da classe String e verá que ele usa vários caracteres, \ n é o caractere de nova linha, portanto conta como um único caractere, neste caso. Ele não procura o valor inteiro como um todo. Está procurando por cada personagem individual; ou seja, vírgula ou nova linha. substr retornará a posição do primeiro caractere que encontrar e -1 se não encontrar nenhum, o que significa que terminou de ler a linha. O fp controla a posição no arquivo internamente, portanto, cada chamada para readCSV a move uma linha por vez.
Martyn Shutt

2

Aqui está o código para ler uma matriz, observe que você também tem uma função csvwrite no 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

Você pode abrir e ler o arquivo .csv usando as funções fopen, fscanf, mas o importante é analisar os dados. A maneira mais simples de analisar os dados usando o delimitador. No caso de .csv, o delimitador é ','.

Suponha que seu arquivo data1.csv seja o seguinte:

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

você pode tokenizar dados e armazenar na matriz char e, posteriormente, usar a função atoi () etc para as conversões apropriadas

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

[^,], ^ - inverte a lógica, significa corresponder a qualquer sequência que não contenha vírgula e depois a última, diz corresponder à vírgula que terminou a sequência anterior.


2

A primeira coisa que você precisa fazer é garantir que o arquivo exista. Para fazer isso, basta tentar abrir o fluxo de arquivos no caminho. Depois de abrir o fluxo de arquivos, use stream.fail () para verificar se funcionou conforme o esperado ou não.

bool fileExists(string fileName)
{

ifstream test;

test.open(fileName.c_str());

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

Você também deve verificar se o arquivo fornecido é o tipo correto de arquivo. Para fazer isso, você precisa examinar o caminho do arquivo fornecido até encontrar a extensão do arquivo. Depois de ter a extensão do arquivo, verifique se é um arquivo .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;
}

Esta função retornará a extensão do arquivo que será usada posteriormente em uma mensagem de erro.

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

Essa função realmente chama as verificações de erro criadas acima e depois analisa o arquivo.

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

Você precisa se sentir orgulhoso quando usa algo tão bonito quanto boost::spirit

Aqui, minha tentativa de um analisador (quase) em conformidade com as especificações CSV neste link Especificações CSV (eu não precisava de quebras de linha nos campos. Também os espaços ao redor das vírgulas são descartados).

Depois de superar a experiência chocante de esperar 10 segundos para compilar esse código :), você pode relaxar e aproveitar.

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

Compilar:

make csvparser

Teste (exemplo roubado da 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

Esta solução detecta esses 4 casos

aula completa é a

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

Ele lê o arquivo caractere por caractere e lê 1 linha por vez em um vetor (de seqüências de caracteres), portanto, adequado para arquivos muito grandes.

O uso é

Repita até que uma linha vazia seja retornada (final do arquivo). Uma linha é um vetor em que cada entrada é uma coluna 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;
  }
}

a declaração de classe

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

a implementação

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

Você também pode dar uma olhada nos recursos da Qtbiblioteca.

Ele possui suporte para expressões regulares e a classe QString possui métodos agradáveis, por exemplo, split()retornando QStringList, lista de strings obtidas dividindo a string original com um delimitador fornecido. Deve ser suficiente para o arquivo csv ..

Para obter uma coluna com um nome de cabeçalho especificado, use o seguinte: herança c ++ Qt problem qstring


isso não vai lidar com vírgulas entre aspas
Ezee

1

Se você não quiser lidar com a inclusão de aumento no seu projeto (é consideravelmente grande se tudo o que você vai usar para analisar CSV ...)

Eu tive sorte com o CSV analisando aqui:

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

Ele lida com campos entre aspas - mas não lida com caracteres \ n embutidos (o que provavelmente é bom para a maioria dos usos).


1
O compilador não deve remover tudo o que não é essencial?
tofutim 12/07/12

1

Este é um tópico antigo, mas ainda está no topo dos resultados de pesquisa, por isso estou adicionando minha solução usando std :: stringstream e um método simples de substituição de string por Yves Baumes que encontrei aqui.

O exemplo a seguir lerá um arquivo linha por linha, ignorará as linhas de comentário iniciadas por // e analisará as outras linhas em uma combinação de cadeias, ints e duplas. Stringstream faz a análise, mas espera que os campos sejam delimitados por espaços em branco, então eu uso stringreplace para transformar vírgulas em espaços primeiro. Ele lida com guias ok, mas não lida com seqüências de caracteres citadas.

Entrada incorreta ou ausente é simplesmente ignorada, o que pode ou não ser bom, dependendo da sua circunstância.

#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

Pelo que vale, aqui está a minha implementação. Ele lida com a entrada do wstring, mas pode ser ajustado facilmente para o string. Ele não lida com a nova linha nos campos (como meu aplicativo também não, mas adicionar seu suporte não é muito difícil) e não está em conformidade com o fim da linha "\ r \ n" conforme RFC (assumindo que você use std :: getline), mas lida com o recorte de espaço em branco e as aspas duplas corretamente (espero).

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

Aqui está uma função pronta para uso, se tudo o que você precisa é carregar um arquivo de dados com dobra (sem números inteiros, sem texto).

#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

Outra maneira rápida e fácil é usar 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';
}

Saídas:

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

1

Eu escrevi uma ótima maneira de analisar arquivos CSV e achei que deveria adicioná-lo como resposta:

#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

É possível usar std::regex.

Dependendo do tamanho do seu arquivo e da memória disponível, é possível lê-lo linha por linha ou inteiramente em um arquivo std::string.

Para ler o arquivo, pode-se usar:

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

então você pode combinar com isso, que é realmente personalizável para suas necessidades.

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

Como não estou acostumado a impulsionar agora, vou sugerir uma solução mais simples. Vamos supor que seu arquivo .csv tenha 100 linhas com 10 números em cada linha separados por um ','. Você pode carregar esses dados na forma de uma matriz com o seguinte código:

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


}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.