Как я могу читать и анализировать файлы CSV на C ++?

Мне нужно загрузить и использовать данные файла CSV на C ++. На данный момент это действительно может быть просто синтаксический анализатор с разделителями-запятыми (т.е. не беспокойтесь об экранировании новых строк и запятых). Основная потребность - построчный синтаксический анализатор, который будет возвращать вектор для следующей строки при каждом вызове метода.

Я нашел эту статью, которая выглядит многообещающей: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

Я никогда не использовал Boost's Spirit, но хочу попробовать. Но только если нет более простого решения, на которое я не обращаю внимания.


person User1    schedule 13.07.2009    source источник
comment
Я посмотрел boost::spirit на разбор. Это больше для синтаксического анализа грамматик благодаря синтаксическому анализу простого формата файла. Кто-то из моей команды пытался использовать его для синтаксического анализа XML, и отлаживать это было сложно. По возможности держитесь подальше от boost::spirit.   -  person chrish    schedule 13.07.2009
comment
Извини, дружище, но это ужасный совет. Spirit не всегда является подходящим решением, но я использовал его - и продолжаю использовать - успешно в ряде проектов. По сравнению с аналогичными инструментами (Antlr, Lex / yacc и т. Д.) Он имеет значительные преимущества. Теперь для парсинга CSV это, наверное, излишество ...   -  person MattyT    schedule 14.07.2009
comment
@MattyT IMHO spirit довольно сложно использовать для библиотеки комбинатора синтаксического анализатора. Имея некоторый (очень приятный) опыт работы с библиотеками Haskells (atto)parsec, я ожидал, что он (дух) будет работать так же хорошо, но отказался от этого после борьбы с 600 ошибками компилятора строк.   -  person fho    schedule 14.07.2014
comment
C CSV Parser: sourceforge.net/projects/cccsvparser C CSV Writer: sourceforge.net/projects/cccsvwriter   -  person SomethingSomething    schedule 24.08.2014
comment
Почему бы вам не избежать запятых и новых строк! Каждый поиск ссылается на этот вопрос, и я не смог найти ни одного ответа, который учитывал бы побег! : |   -  person please_don't_hurt    schedule 18.05.2021


Ответы (39)


Если вас не волнует экранирование запятой и новой строки,
И вы не можете вставлять запятую и новую строку в кавычки (если вы не можете избежать, то ...)
тогда это всего лишь три строки код (ОК 14 - ›Но его всего 15, чтобы прочитать весь файл).

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

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

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

Я бы просто создал класс, представляющий строку.
Затем транслируйте в этот объект:

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

class CSVRow
{
    public:
        std::string_view operator[](std::size_t index) const
        {
            return std::string_view(&m_line[m_data[index] + 1], m_data[index + 1] -  (m_data[index] + 1));
        }
        std::size_t size() const
        {
            return m_data.size() - 1;
        }
        void readNextRow(std::istream& str)
        {
            std::getline(str, m_line);

            m_data.clear();
            m_data.emplace_back(-1);
            std::string::size_type pos = 0;
            while((pos = m_line.find(',', pos)) != std::string::npos)
            {
                m_data.emplace_back(pos);
                ++pos;
            }
            // This checks for a trailing comma with no data after it.
            pos   = m_line.size();
            m_data.emplace_back(pos);
        }
    private:
        std::string         m_line;
        std::vector<int>    m_data;
};

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

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

Но немного поработав, технически мы могли бы создать итератор:

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

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

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

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


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

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

Теперь, когда мы находимся в 2020 году, давайте добавим объект CSVRange:

class CSVRange
{
    std::istream&   stream;
    public:
        CSVRange(std::istream& str)
            : stream(str)
        {}
        CSVIterator begin() const {return CSVIterator{stream};}
        CSVIterator end()   const {return CSVIterator{};}
};

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

    for(auto& row: CSVRange(file))
    {
        std::cout << "4th Element(" << row[3] << ")\n";
    }
}
person Martin York    schedule 13.07.2009
comment
Это именно то, что я хотел! А теперь немного… как бы мне превратить это в класс с конструктором и двумя методами: firstLine () и nextLine (). std :: istream не имеет конструктора по умолчанию .. так что же мне использовать вместо этого? Спасибо за помощь!! - person User1; 14.07.2009
comment
первый () следующий (). Что это за Ява! Просто шучу. - person Martin York; 14.07.2009
comment
или вы можете использовать некоторые библиотеки ускорения для анализа csv ... см. ниже - person stefanB; 21.03.2010
comment
Этот код просто сэкономил мне часы. Обычно я не использую C ++, но мне пришлось прибегнуть к нему, чтобы написать быстрый парсер. Это отличный шаблон, и код даже компилируется. - person conradlee; 18.05.2011
comment
@conradlee: Я всегда стараюсь писать код, который будет компилироваться :-) Рад, что помог. Но, как предлагает stefanB, вы также можете посмотреть на boost. В Boost есть множество вещей, которые упрощают C ++, включая код парсера. - person Martin York; 18.05.2011
comment
одно из худших - переопределить операторы == и! =. это просто неправильно. - person DarthVader; 12.01.2012
comment
@DarthVader: широкое наложение, которое по своей широте выглядит глупо. Если вы хотите прояснить, почему это плохо и почему это зло применимо в данном контексте. - person Martin York; 13.01.2012
comment
так ты не думаешь, что это плохо? Ты думаешь, глупо думать, что это плохо? - person DarthVader; 13.01.2012
comment
@DarthVader: Я думаю, что делать широкие обобщения глупо. Приведенный выше код работает правильно, поэтому я действительно вижу в нем что-то не так. Но если у вас есть какие-то конкретные комментарии к вышеизложенному, я обязательно рассмотрю их в этом контексте. Но я понимаю, как вы можете прийти к такому выводу, бездумно следуя набору обобщенных правил для C # и применяя их к другому языку. - person Martin York; 13.01.2012
comment
+++ 1. Спасибо за пример с Итератором. CSV выполнен просто в несколько строк кода с хорошим дизайном. Можно взять это и обработать кавычки и другие диалекты CSV. - person Viet; 26.02.2013
comment
Кроме того, если вы столкнетесь со странными проблемами связывания с приведенным выше кодом, потому что где-то другая библиотека определяет istream::operator>> (например, Eigen), добавьте inline перед объявлением оператора, чтобы исправить это. - person sk29910; 28.06.2013
comment
@sebastian_k: Я думаю, вы не понимаете, что вы решили, добавив inline :-(. Конфликты пространства имен должны быть разрешены путем помещения материала в явное пространство имен. inline поможет компоновщику с проблемами, потому что вы помещаете определение в файл заголовка и включил его несколько раз. - person Martin York; 28.06.2013
comment
Часть синтаксического анализа отсутствует, одна по-прежнему заканчивается строками. Это просто перестроенный разделитель линий. - person Maxim Egorushkin; 03.07.2014
comment
Почему я не могу использовать getline(stringstream(line),cell,',') вместо stringstream lineStream(line); getline(lineStream,cell,',');? - person kirill_igum; 17.11.2014
comment
@kirill_igum: Хороший вопрос, заслуживающий отдельного набора ответов. Лучше спросить на основном сайте, чем в комментариях. - person Martin York; 17.11.2014
comment
Это самый простой и понятный пример создания класса итератора, который я когда-либо видел. - person Giancarlo Sportelli; 18.02.2015
comment
итератор не читает последнюю строку. какие изменения я должен сделать, чтобы была прочитана последняя строка? Благодарность - person tonytz; 03.06.2016
comment
@tonytz: У меня отлично работает. У меня есть чат. Разместите свой код, и я посмотрю, есть ли проблема chat.stackoverflow.com/rooms/113764/csv - person Martin York; 03.06.2016
comment
Спасибо! измените реализацию оператора ++ () на следующий код, чтобы исправить ошибку последней строки, не импортированной: CSVIterator& operator++() {if (m_str) { if (!((*m_str) >> m_row)){;m_str = NULL;}}return *this;} Если вам интересно, найдите объяснение в чате Локи. Спасибо за этот замечательный фрагмент кода! - person Stefan Woehrer; 30.09.2016
comment
@StefanWoe: Эта строка была помещена в код выше много месяцев назад. - person Martin York; 30.09.2016
comment
: D о, я использовал код несколько месяцев и только что обнаружил ошибку во время тестирования, поэтому я вернулся на эту страницу. Так что да, приведенный выше код безопасен! Отличная работа @LokiAstari! :) - person Stefan Woehrer; 03.10.2016
comment
Я обнаружил, что такая строка, как: a,b,, добавляет только два значения к m_data. Я думаю, следует добавить три (третья - пустая строка). Я работал над этим, добавляя m_data.push_back(""); после цикла while. Поскольку я обращаюсь к ячейкам по индексу, лишние значения меня не беспокоят. - person MatrixManAtYrService; 17.11.2016
comment
@MatrixManAtYrService: Без какого-либо тестирования я бы попробовал: if (!lineStream){m_data.push_back("");} Чтобы он выдвигал пустое значение только тогда, когда была запятая, после которой ничего не было. Если вы проверите это, и это сработает, я обновлю ответ выше. - person Martin York; 18.11.2016
comment
@LokiAstari Это выглядит намного лучше, чем мой хак. Я проверил это, и он работает. - person MatrixManAtYrService; 18.11.2016
comment
@LokiAstari Не могли бы вы немного объяснить if (!lineStream)? Проходит ли это условное выражение в случае отсутствия конечной запятой? Кроме того, для m_data.push_back(""), откуда взялся m_data? Разве это не должно быть result.push_back("")? - person Nicholas; 30.11.2016
comment
@ Николас Прости. Опечатка (при исправлении жалобы из комментариев). Да, if (!lineStream) нужно больше (исправлено). И m_data должно быть result (исправлено). - person Martin York; 30.11.2016
comment
@LokiAstari Может просто if (cell.empty())? Кажется, !lineStream верно и для нормальной линии. Кроме того, кажется, что std::vector result также добавит "" в конец вектора. Я думаю, это потому, что прямо перед достижением end-of-file condition в CSV-файле есть еще одна пустая ячейка между \n и end-of-file condition. - person Nicholas; 30.11.2016
comment
Итак, эта настройка работает для строковых символов? Если я хочу читать целые числа или числа с плавающей запятой? - person drizo; 13.03.2017
comment
@drizo: у вас есть строка, которую вы можете преобразовать в любой тип, десериализовав ее в объект соответствующего типа. Существует несколько способов десериализации объекта, поэтому вам следует задать этот вопрос в SO. - person Martin York; 13.03.2017
comment
@DarthVader неправильно не перегружать operator!= для класса, если у вас есть псевдоним участника typedef std::input_iterator_tag iterator_category; см. InputIterator - person Caleth; 15.01.2018
comment
См. Также интересную библиотеку, реализующую итераторы и диапазоны в виде класса, подобного iostream: github.com/roman- кашицын / текст-csv - person ferdymercury; 23.09.2020
comment
есть ли способ иметь функцию foo с аргументом n, которая читает файл csv до строки n? В частности, первый раз, когда foo вызывается с аргументом n1, он читает файл со строки 1 до строки n; второй раз он вызывается с аргументом n2, он читает файл от n1 + 1 до n2 и так далее ... - person Ryan; 19.11.2020
comment
@ Райан Это было бы тривиально. Просто держите объект ifstream file; на верхнем уровне. Передайте file и значение n функции. Тогда функция представляет собой просто цикл, который читает n строк. - person Martin York; 19.11.2020

Моя версия не использует ничего, кроме стандартной библиотеки C ++ 11. Он хорошо справляется с цитатой Excel CSV:

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

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

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

enum class CSVState {
    UnquotedField,
    QuotedField,
    QuotedQuote
};

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

/// Read CSV file, Excel dialect. Accept "quoted fields ""with quotes"""
std::vector<std::vector<std::string>> readCSV(std::istream &in) {
    std::vector<std::vector<std::string>> table;
    std::string row;
    while (!in.eof()) {
        std::getline(in, row);
        if (in.bad() || in.fail()) {
            break;
        }
        auto fields = readCSVRow(row);
        table.push_back(fields);
    }
    return table;
}
person sastanin    schedule 20.05.2015
comment
спасибо, я думаю, что это наиболее полный ответ, жаль, что он здесь похоронен. - person mihai; 24.06.2016
comment
этот вложенный вектор строк не подходит для современных процессоров. Выбрасывает их способность кэширования - person Nikolaos Giotis; 05.04.2018
comment
плюс у вас есть все эти переключатели - person Nikolaos Giotis; 05.04.2018
comment
Главный ответ не сработал для меня, так как я использую более старый компилятор. Этот ответ сработал, для инициализации вектора может потребоваться следующее: const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit)); - person dr_rk; 06.04.2018
comment
Похоже на отличное решение и лучшее решение. Спасибо. Я думаю, что вы могли бы избежать использования счетчика i, используя метод обратно в свой вектор, называемый полями. - person Mark S.; 09.06.2021

Решение с использованием 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);
}
person dtw    schedule 13.07.2009
comment
Токенизатор ускорения не полностью поддерживает полный стандарт CSV, но есть несколько быстрых обходных путей. См. stackoverflow.com/questions/1120140/csv-parser -in-c / - person Rolf Kristensen; 14.04.2010
comment
У вас должна быть вся библиотека ускорения на вашем компьютере, или вы можете просто использовать для этого подмножество их кода? 256 Мб вроде бы много для парсинга CSV .. - person NPike; 28.04.2011
comment
@NPike: вы можете использовать утилиту bcp, поставляется с ускорением для извлечения только тех заголовков, которые вам действительно нужны. - person ildjarn; 25.05.2011

В C ++ String Toolkit Library (StrTk) есть класс сетки токенов, который позволяет вам для загрузки данных из текстовых файлов, строк или буферов символов и для их синтаксического анализа / обработки в режиме "строка-столбец".

Вы можете указать разделители строк и столбцов или просто использовать значения по умолчанию.

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

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

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

Дополнительные примеры можно найти здесь

person Community    schedule 25.09.2009
comment
Хотя strtk поддерживает поля в двойных кавычках и даже удаляет окружающие кавычки (через options.trim_dquotes = true), он не поддерживает удаление двойных двойных кавычек (например, поле "She said ""oh no"", and left." как c-строку "She said \"oh no\", and left."). Вам придется сделать это самому. - person rampion; 28.08.2017
comment
При использовании strtk вам также придется вручную обрабатывать поля с двойными кавычками, которые содержат символы новой строки. - person rampion; 29.08.2017

Вы можете использовать Boost Tokenizer с escaped_list_separator.

escaped_list_separator анализирует расширенный набор CSV. Boost :: tokenizer

При этом используются только файлы заголовков токенизатора Boost, связывание с библиотеками Boost не требуется.

Вот пример (см. Parse CSV-файл с Boost Tokenizer на C ++ для подробностей или Boost::tokenizer):

#include <iostream>     // cout, endl
#include <fstream>      // fstream
#include <vector>
#include <string>
#include <algorithm>    // copy
#include <iterator>     // ostream_operator
#include <boost/tokenizer.hpp>

int main()
{
    using namespace std;
    using namespace boost;
    string data("data.csv");

    ifstream in(data.c_str());
    if (!in.is_open()) return 1;

    typedef tokenizer< escaped_list_separator<char> > Tokenizer;
    vector< string > vec;
    string line;

    while (getline(in,line))
    {
        Tokenizer tok(line);
        vec.assign(tok.begin(),tok.end());

        // vector now contains strings from one row, output to cout here
        copy(vec.begin(), vec.end(), ostream_iterator<string>(cout, "|"));

        cout << "\n----------------------" << endl;
    }
}
person stefanB    schedule 24.02.2010
comment
И если вы хотите иметь возможность анализировать встроенные новые строки mybyteofcode.blogspot.com/2010/11/. - person stefanB; 13.01.2011
comment
Хотя этот метод работает, я обнаружил, что он имеет очень низкую производительность. Анализ CSV-файла на 90000 строк с десятью полями на строку занимает около 8 секунд на моем Xeon с частотой 2 ГГц. Модуль csv стандартной библиотеки Python анализирует тот же файл примерно за 0,3 секунды. - person Rob Smallshire; 27.06.2012
comment
@Rob, это интересно - что по-другому делает Python csv? - person tofutim; 12.07.2012
comment
@RobSmallshire - это простой пример кода, а не высокопроизводительный. Этот код делает копии всех полей в каждой строке. Для более высокой производительности вы должны использовать другие параметры и возвращать только ссылки на поля в буфере вместо создания копий. - person stefanB; 16.07.2012

Использование Spirit для разбора CSV - не излишество. Spirit хорошо подходит для задач микропарсинга. Например, с Spirit 2.1 это так же просто, как:

bool r = phrase_parse(first, last,

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

    space, v);

Вектор v заполняется значениями. Существует серия руководств касаясь этого в новой документации Spirit 2.1, которая только что была выпущена с Boost 1.41.

Учебник прогрессирует от простого к сложному. Парсеры CSV представлены где-то посередине и затрагивают различные методы использования Spirit. Сгенерированный код настолько же сложен, как и рукописный код. Проверьте сгенерированный ассемблер!

person Joel de Guzman    schedule 19.11.2009
comment
На самом деле это перебор, время компиляции огромно, и использование Spirit для простых задач микропарсинга нецелесообразно. - person Gerdiner; 02.12.2012
comment
Также я хотел бы отметить, что приведенный выше код не анализирует CSV, он просто анализирует диапазон типа вектора, разделенного запятыми. Он не обрабатывает кавычки, различные типы столбцов и т. Д. Короче говоря, 19 голосов за то, что вообще отвечает на вопрос, кажется мне немного подозрительным. - person Gerdiner; 02.12.2012
comment
@Gerdiner Чепуха. Время компиляции для небольших синтаксических анализаторов не так велико, но это также не имеет значения, потому что вы помещаете код в его собственный модуль компиляции и компилируете его один раз. Затем вам нужно только связать его, и это максимально эффективно. А что касается вашего другого комментария, диалектов CSV столько же, сколько и процессоров для него. Это, конечно, не очень полезный диалект, но его можно тривиально расширить для обработки цитируемых значений. - person Konrad Rudolph; 06.12.2012
comment
@konrad: Простое включение #include ‹boost / spirit / include / qi.hpp› в пустой файл только с основным и ничем другим занимает 9,7 секунды с MSVC 2012 на corei7, работающем на частоте 2 ГГц. Это ненужное раздувание. Принятый ответ компилируется менее чем за 2 секунды на той же машине, мне не хотелось бы представлять, сколько времени потребуется для компиляции «правильного» примера Boost.Spirit. - person Gerdiner; 11.01.2013
comment
@Gerdiner Для меня это занимает значительно меньше времени (~ 4 секунды), но, как я уже сказал, это не имеет значения, поскольку вам нужно скомпилировать этот TU только один раз. Время, сэкономленное на реализации парсера, легко компенсирует затраты на компиляцию. Что касается «правильных» грамматик Boost.Sprit: для компиляции большой грамматики может потребоваться несколько минут. Но еще раз: эти затраты легко компенсируются простотой написания синтаксического анализатора, и это не постоянные затраты, поскольку вам не нужно перекомпилировать синтаксический анализатор каждый раз, когда вы перекомпилируете клиентский код. - person Konrad Rudolph; 11.01.2013
comment
@Gerdiner Я должен согласиться с вами, что накладные расходы на использование Spirit для чего-то столь же простого, как обработка cvs, слишком велики. - person ; 25.02.2014
comment
Я, например, думаю, что это полностью зависит от приложения, которое вы пишете. В моем случае я мог бы использовать boost, потому что он у нас уже скомпилирован, и даже если Spirit не ориентирован на производительность, я хочу, чтобы он загружал исходные данные для тестов. Это что, всего 1-3 строчки кода? Считайте меня, мне не нужно писать парсер, даже если это будет несложно. - person ArthurChamz; 22.03.2014
comment
@ArthurChamz На самом деле Spirit смехотворно эффективен. Да, компиляция занимает много времени, но скорость выполнения тривиально превосходит рукописные синтаксические анализаторы, даже не наивные реализации. Чтобы проиллюстрировать, синтаксический анализатор boost::qi::int_ - это , безусловно, самый эффективный метод из всех существующие библиотеки, включая рукописный код. - person Konrad Rudolph; 25.06.2014
comment
@KonradRudolph Вау, это так? Я впечатлен! В итоге я использовал его для синтаксического анализа csv в тестах, он отлично работает! - person ArthurChamz; 04.07.2014
comment
Согласитесь с @KonradRudolph - здесь люди, озабоченные производительностью, озабочены производительностью компиляции, которая противоположна намерениям Spirit и тем, чем обычно занимаются кодеры. Это проблема шаблонов C ++ в целом. Пересматривая статью, было бы неплохо включить более полный пример синтаксического анализа csv, а не просто ссылку на учебник ... - person fuzzyTew; 21.03.2016
comment
В этом примере показан список двойников, разделенных запятыми, а не CSV. - person rytis; 28.04.2017

Если вы ОБЯЗАТЕЛЬНО позаботитесь о правильном синтаксическом анализе CSV, это будет делать это ... относительно медленно, так как работает по одному символу за раз.

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

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

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

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

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

          aChar++;
       }

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

       if (line.size())
          lines.push_back(line);
    }
person Michael    schedule 19.03.2010
comment
AFAICT, это не будет правильно обрабатывать встроенные кавычки (например, эта строка имеет встроенные кавычки, foo, 1)) - person Jeremy Friesner; 19.06.2014

При использовании Boost Tokenizer escaped_list_separator для файлов CSV следует помнить о следующем:

  1. Требуется escape-символ (обратная косая черта по умолчанию - \)
  2. Требуется разделитель / разделитель-символ (запятая по умолчанию -,)
  3. Требуется кавычка (кавычка по умолчанию - ")

Формат CSV, указанный в wiki, гласит, что поля данных могут содержать разделители в кавычках (поддерживается):

1997, Ford, E350, «Супер, роскошный грузовик»

Формат CSV, указанный в wiki, гласит, что одинарные кавычки следует обрабатывать с помощью двойных кавычек (escaped_list_separator удалит все символы кавычек):

1997, Ford, E350, "Супер" "роскошный" "грузовик"

Формат CSV не указывает, что любые символы обратной косой черты должны быть удалены (escaped_list_separator удалит все escape-символы).

Возможный обходной путь для исправления поведения по умолчанию boost escaped_list_separator:

  1. Сначала замените все символы обратной косой черты (\) двумя символами обратной косой черты (\\), чтобы они не удалялись.
  2. Во-вторых, замените все двойные кавычки ("") одинарным символом обратной косой черты и кавычкой (\ ").

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

Не очень красиво, но работает, если в кавычках нет новых строк.

person Rolf Kristensen    schedule 20.10.2009

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

В качестве примера того, как я хотел использовать поток ввода CSV, рассмотрим следующий ввод (взятый из страница Википедии в 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"
;

Затем я хотел иметь возможность читать такие данные:

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

Это было решение, к которому я пришел.

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

Со следующими помощниками, которые можно упростить с помощью новых шаблонов интегральных признаков в 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) };
};

Попробуйте в Интернете!

person jxh    schedule 18.07.2012

Вы можете посмотреть мой проект FOSS CSVfix ( обновленная ссылка), который представляет собой редактор потока CSV, написанный на C ++. Парсер CSV не является призом, но выполняет свою работу, и весь пакет может делать то, что вам нужно, без написания кода.

См. alib / src / a_csv .cpp для парсера CSV и csvlib / src / csved_ioman.cpp (IOManager::ReadCSV) для примера использования.

person Community    schedule 13.07.2009
comment
Вроде бы здорово ... А как насчет статуса бета / продакшн? - person neuro; 13.07.2009
comment
Статус находится в разработке, как следует из номеров версий. Мне действительно нужно больше отзывов от пользователей, прежде чем перейти к версии 1.0. Кроме того, у меня есть еще пара функций, которые я хочу добавить, связанные с производством XML из CSV. - person ; 13.07.2009
comment
Добавьте его в закладки, и я попробую в следующий раз, когда мне придется иметь дело с этими замечательными стандартными файлами CSV ... - person neuro; 13.07.2009

Я написал синтаксический анализатор CSV C ++ 11 только для заголовков. Он хорошо протестирован, быстр, поддерживает всю спецификацию CSV (поля в кавычках, разделитель / терминатор в кавычках, экранирование кавычек и т. Д.) И настраивается для учета CSV, которые не соответствуют спецификации.

Конфигурация выполняется через свободный интерфейс:

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

Разбор - это просто цикл на основе диапазона:

#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;
  }
}
person m0meni    schedule 29.05.2017
comment
Хорошая работа, но вам нужно добавить еще три вещи: (1) прочитать заголовок (2) обеспечить индексацию полей по имени (3) не перераспределять память в цикле путем повторного использования одного и того же вектора строк - person Maksym Ganenko; 23.07.2017
comment
@MaksymGanenko Я делаю №3. Не могли бы вы подробнее рассказать о № 2? - person m0meni; 23.07.2017
comment
Очень полезно получать поля не по позиции в строке, а по имени, указанному в заголовке (в первой строке таблицы CSV). Например, я ожидаю, что таблица CSV с полем даты, но я не знаю, какой индекс поля даты находится в строке. - person Maksym Ganenko; 24.07.2017
comment
@MaksymGanenko а, я понимаю, о чем вы. Там github.com/ben-strasser/fast-cpp-csv-parser, когда вы знаете столбцы вашего CSV во время компиляции, и это, вероятно, лучше, чем у меня. Мне нужен синтаксический анализатор CSV для случаев, когда вы хотите использовать один и тот же код для множества разных CSV и заранее не знаете, как они выглядят. Так что я, вероятно, не буду добавлять №2, но я добавлю №1 когда-нибудь в будущем. - person m0meni; 24.07.2017

Еще одну библиотеку ввода-вывода CSV можно найти здесь:

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
  }
}
person Heygard Flisch    schedule 17.12.2012
comment
Хорошо, но это заставляет вас выбирать количество столбцов во время компиляции. Не очень полезно для многих приложений. - person quant_dev; 26.12.2015

Другое решение, аналогичное ответу Локи Астари на C ++ 11. Строки здесь std::tuple определенного типа. Код сканирует одну строку, затем сканирует до каждого разделителя, а затем преобразует и выгружает значение непосредственно в кортеж (с небольшим количеством кода шаблона).

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

Преимущества:

  • довольно чистый и простой в использовании, только C ++ 11.
  • автоматическое преобразование типа в std::tuple<t1, ...> через operator>>.

Чего не хватает:

  • экранирование и цитирование
  • нет обработки ошибок в случае искаженного CSV.

Основной код:

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

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

person Pietro Saccardi    schedule 05.12.2015
comment
Вы можете не заботиться о встраивании, потому что большинство компиляторов решают это самостоятельно. По крайней мере, я уверен в Visual C ++. Он может встроить метод независимо от спецификации вашего метода. - person MrPisarik; 29.01.2016
comment
Именно поэтому я явно их пометил. Gcc и Clang, которые я чаще всего использую, также имеют свои собственные соглашения. Встроенное ключевое слово должно быть просто стимулом. - person Pietro Saccardi; 29.01.2016

Вот еще одна реализация парсера Unicode CSV (работает с wchar_t). Я написал его часть, а Джонатан Леффлер написал остальное.

Примечание. Этот синтаксический анализатор предназначен для максимально точного воспроизведения поведения Excel, особенно при импорте поврежденных или искаженных файлов CSV.

Это исходный вопрос - Анализ CSV-файла с многострочными полями и экранированные двойные кавычки

Это код как SSCCE (короткий, автономный, правильный пример).

#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;
}
person sashoalm    schedule 15.10.2013

Это старый поток, но он все еще находится в верхней части результатов поиска, поэтому я добавляю свое решение, используя std :: stringstream и простой метод замены строки от Yves Baumes, который я нашел здесь.

В следующем примере файл будет считываться построчно, строки комментариев, начинающиеся с //, будут игнорироваться, а другие строки будут разбиты на комбинацию строк, целых и двойных чисел. Stringstream выполняет синтаксический анализ, но ожидает, что поля будут разделены пробелами, поэтому я использую stringreplace, чтобы сначала превратить запятые в пробелы. Он нормально обрабатывает вкладки, но не работает со строками в кавычках.

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

#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));
      }
   }
}
person marcp    schedule 26.06.2013

Мне нужна была простая в использовании библиотека C ++ для анализа файлов CSV, но я не мог найти ни одной из доступных, поэтому в итоге я ее создал. Rapidcsv - это библиотека C ++ 11 только для заголовков, которая дает прямой доступ к анализируемым столбцам (или строкам). ) как векторы в выбранном типе данных. Например:

#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;
}
person d99kris    schedule 29.05.2017
comment
Хорошая работа, но библиотека не работает должным образом, если в заголовке есть пустые метки. Это типично для таблицы Excel / LibreOffice NxN. Кроме того, он может пропустить последнюю строку данных. К сожалению, ваша библиотека не надежна. - person Maksym Ganenko; 23.07.2017
comment
Спасибо за отзыв @MaksymGanenko. Я исправил ошибку последней строки данных для конечных строк без разрыва конечной строки. Что касается другой упомянутой проблемы - заголовков с пустыми метками - я не уверен, к чему это относится? Библиотека должна обрабатывать пустые метки (как в кавычках, так и без кавычек). Он также может читать CSV без строки / столбца заголовка, но тогда он требует, чтобы пользователь указывал это (идентификатор заголовка столбца -1 и идентификатор заголовка строки -1). Пожалуйста, предоставьте более подробную информацию или сообщите об ошибке на странице GitHub, если у вас есть конкретный вариант использования, который вы хотели бы видеть поддерживаемым. Спасибо! - person d99kris; 24.07.2017

Извините, но все это похоже на сложный синтаксис, скрывающий несколько строк кода.

Почему не это:

/**

  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;
}
person ravenspoint    schedule 14.07.2009
comment
Эээ, а почему в строке должно быть ",\n"? - person Timmmm; 18.11.2014
comment
@Timmmm найдите метод substr класса String, и вы увидите, что он принимает несколько символов, \ n - это символ новой строки, поэтому в этом случае он считается одним символом. Он не ищет всю ценность в целом. Он ищет каждого отдельного персонажа; а именно запятая или новая строка. substr вернет позицию первого найденного символа и -1, если не найдет ни одного, что означает, что чтение строки завершено. fp отслеживает позицию в файле внутри, поэтому каждый вызов readCSV перемещает его по одной строке за раз. - person Martyn Shutt; 05.06.2015

Вот код для чтения матрицы, обратите внимание, что у вас также есть функция csvwrite в 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;
    }
}
person Jim M.    schedule 19.02.2013

Вы можете открыть и прочитать файл .csv с помощью функций fopen, fscanf, но важно проанализировать данные. Самый простой способ проанализировать данные с помощью разделителя. В случае .csv разделителем является ','.

Предположим, ваш файл data1.csv выглядит следующим образом:

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

вы можете токенизировать данные и хранить их в массиве символов, а затем использовать функцию atoi () и т. д. для соответствующих преобразований.

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

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

[^,], ^ -it инвертирует логику, означает соответствие любой строке, которая не содержит запятой, затем последней, говорит, что соответствует запятой, которая завершала предыдущую строку.

person Amruta Ghodke    schedule 10.11.2014

Первое, что вам нужно сделать, это убедиться, что файл существует. Для этого вам просто нужно попробовать открыть файловый поток по пути. После того, как вы открыли файловый поток, используйте stream.fail (), чтобы узнать, работает ли он должным образом или нет.

bool fileExists(string fileName)
{

ifstream test;

test.open(fileName.c_str());

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

Вы также должны убедиться, что предоставленный файл является файлом правильного типа. Для этого вам нужно просмотреть указанный путь к файлу, пока не найдете расширение файла. Если у вас есть расширение файла, убедитесь, что это файл .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;
}

Эта функция вернет расширение файла, которое будет использоваться позже в сообщении об ошибке.

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

Эта функция фактически вызовет созданные выше проверки ошибок, а затем проанализирует файл.

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;
    }
}
person Elizabeth Card    schedule 06.10.2015

Поскольку сейчас я не привык к бусту, я предложу более простое решение. Предположим, что ваш файл .csv имеет 100 строк с 10 числами в каждой строке, разделенными символом ",". Вы можете загрузить эти данные в виде массива с помощью следующего кода:

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


}
person nikos_k    schedule 16.10.2016

Вы можете использовать эту библиотеку: https://github.com/vadamsky/csvworker

Например, код:

#include <iostream>
#include "csvworker.h"

using namespace std;

int main()
{
    //
    CsvWorker csv;
    csv.loadFromFile("example.csv");
    cout << csv.getRowsNumber() << "  " << csv.getColumnsNumber() << endl;

    csv.getFieldRef(0, 2) = "0";
    csv.getFieldRef(1, 1) = "0";
    csv.getFieldRef(1, 3) = "0";
    csv.getFieldRef(2, 0) = "0";
    csv.getFieldRef(2, 4) = "0";
    csv.getFieldRef(3, 1) = "0";
    csv.getFieldRef(3, 3) = "0";
    csv.getFieldRef(4, 2) = "0";

    for(unsigned int i=0;i<csv.getRowsNumber();++i)
    {
        //cout << csv.getRow(i) << endl;
        for(unsigned int j=0;j<csv.getColumnsNumber();++j)
        {
            cout << csv.getField(i, j) << ".";
        }
        cout << endl;
    }

    csv.saveToFile("test.csv");

    //
    CsvWorker csv2(4,4);

    csv2.getFieldRef(0, 0) = "a";
    csv2.getFieldRef(0, 1) = "b";
    csv2.getFieldRef(0, 2) = "r";
    csv2.getFieldRef(0, 3) = "a";
    csv2.getFieldRef(1, 0) = "c";
    csv2.getFieldRef(1, 1) = "a";
    csv2.getFieldRef(1, 2) = "d";
    csv2.getFieldRef(2, 0) = "a";
    csv2.getFieldRef(2, 1) = "b";
    csv2.getFieldRef(2, 2) = "r";
    csv2.getFieldRef(2, 3) = "a";

    csv2.saveToFile("test2.csv");

    return 0;
}
person vadamsky    schedule 29.06.2017
comment
Еще одна интересная библиотека - github.com/roman-kashitsyn/text-csv. - person ferdymercury; 23.09.2020

Вы должны гордиться, когда используете что-то настолько красивое, как boost::spirit

Вот моя попытка парсера (почти) соответствующего спецификациям CSV по этой ссылке CSV specs (Мне не нужны были разрывы строк внутри полей. Также пропущены пробелы вокруг запятых).

После того, как вы преодолеете шокирующий опыт ожидания компиляции этого кода в течение 10 секунд :), вы можете расслабиться и наслаждаться.

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

Скомпилировать:

make csvparser

Тест (пример, украденный из Википедии):

./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
person jav    schedule 31.07.2017

Это решение обнаруживает эти 4 случая

полный класс в

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

Он считывает файл посимвольно и считывает по одной строке за раз в вектор (из строк), поэтому подходит для очень больших файлов.

Использование

Итерировать до тех пор, пока не будет возвращена пустая строка (конец файла). Строка - это вектор, где каждая запись представляет собой столбец 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;
  }
}

объявление класса

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

реализация

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;
}
person Pedro Vicente    schedule 01.09.2017

Анализ строк файла CSV с помощью Stream

Я написал небольшой пример синтаксического анализа строк файла CSV, при желании его можно разработать с помощью циклов for и while:

#include <iostream>
#include <fstream>
#include <string.h>

using namespace std;

int main() {


ifstream fin("Infile.csv");
ofstream fout("OutFile.csv");
string strline, strremain, strCol1 , strout;

string delimeter =";";

int d1;

чтобы продолжить до конца файла:

while (!fin.eof()){ 

получить первую строку из InFile:

    getline(fin,strline,'\n');      

найти позицию разделителя в строке:

    d1 = strline.find(';');

и проанализируем первый столбец:

    strCol1 = strline.substr(0,d1); // parse first Column
    d1++;
    strremain = strline.substr(d1); // remaining line

создать строку вывода в формате CSV:

    strout.append(strCol1);
    strout.append(delimeter);

записать строку в Out File:

    fout << strout << endl; //out file line

} 

fin.close();
fout.close();

return(0);
}

Этот код скомпилирован и запущен. Удачи!

person Olikay Gokce    schedule 24.01.2020

Вы также можете ознакомиться с возможностями библиотеки Qt.

Он поддерживает регулярные выражения, а класс QString имеет хорошие методы, например. split() возвращает QStringList, список строк, полученных путем разделения исходной строки с помощью заданного разделителя. Должно хватить для файла csv ..

Чтобы получить столбец с заданным именем заголовка, я использую следующее: C ++ наследование Qt проблема qstring

person MadH    schedule 18.09.2009
comment
это не обрабатывает запятые в кавычках - person Ezee; 26.03.2015

Если вы не хотите иметь дело с включением ускорения в свой проект (он значительно велик, если все, для чего вы собираетесь его использовать, это синтаксический анализ CSV ...)

Мне повезло с синтаксическим анализом CSV здесь:

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

Он обрабатывает поля в кавычках, но не обрабатывает строчные символы \ n (что, вероятно, подходит для большинства случаев).

person NPike    schedule 29.04.2011
comment
Разве компилятор не должен исключить все несущественное? - person tofutim; 12.07.2012

Вот моя реализация. Он имеет дело с вводом wstring, но его можно легко настроить на строку. Он не обрабатывает новую строку в полях (как и мое приложение, но добавить его поддержку не так уж сложно) и не соответствует концу строки "\ r \ n" согласно RFC (при условии, что вы используете std :: getline), но он правильно обрабатывает обрезку пробелов и двойные кавычки (надеюсь).

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();
}
person Fabien    schedule 19.07.2013

Вот готовая к использованию функция, если все, что вам нужно, это загрузить файл данных с двойными числами (без целых чисел, без текста).

#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;
}
person Antonello    schedule 24.01.2014

Еще один быстрый и простой способ - использовать _ 1_:

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

Выходы:

(abc 123 true 3.14)
(def 456 false 2.718)
person Maxim Egorushkin    schedule 03.07.2014

Я написал хороший способ синтаксического анализа файлов CSV и подумал, что должен добавить его в качестве ответа:

#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;
}
person scap3y    schedule 18.11.2015

Можно использовать std::regex.

В зависимости от размера вашего файла и доступной вам памяти его можно читать построчно или целиком в std::string.

Чтобы прочитать файл, можно использовать:

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

тогда вы можете сопоставить его с этим, который действительно настраивается в соответствии с вашими потребностями.

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());
}
person g24l    schedule 02.12.2015

Вы можете использовать только заголовочную библиотеку Csv :: Parser.

  • Он полностью поддерживает RFC 4180, включая значения в кавычках, экранированные кавычки и новые строки в значениях полей.
  • Для этого требуется только стандартный C ++ (C ++ 17).
  • Он поддерживает чтение данных CSV из std::string_view во время компиляции.
  • Он тщательно протестирован с использованием Catch2.
person Alexander    schedule 22.02.2021

Если вы используете Visual Studio / MFC, следующее решение может облегчить вам жизнь. Он поддерживает как Unicode, так и MBCS, имеет комментарии, не имеет других зависимостей, кроме CString, и для меня работает достаточно хорошо. Он не поддерживает разрывы строк, встроенные в строку в кавычках, но меня это не волнует, если в этом случае он не выйдет из строя, а это не так.

Общая стратегия состоит в том, чтобы обрабатывать кавычки и пустые строки как особые случаи, а в остальном использовать Tokenize. Для строк в кавычках стратегия заключается в том, чтобы найти настоящую заключительную цитату, отслеживая, встречались ли пары последовательных кавычек. Если да, используйте «Заменить», чтобы преобразовать пары в одиночные. Несомненно, есть более эффективные методы, но в моем случае производительность не была достаточно критичной, чтобы оправдать дальнейшую оптимизацию.

class CParseCSV {
public:
// Construction
    CParseCSV(const CString& sLine);

// Attributes
    bool    GetString(CString& sDest);

protected:
    CString m_sLine;    // line to extract tokens from
    int     m_nLen;     // line length in characters
    int     m_iPos;     // index of current position
};

CParseCSV::CParseCSV(const CString& sLine) : m_sLine(sLine)
{
    m_nLen = m_sLine.GetLength();
    m_iPos = 0;
}

bool CParseCSV::GetString(CString& sDest)
{
    if (m_iPos < 0 || m_iPos > m_nLen)  // if position out of range
        return false;
    if (m_iPos == m_nLen) { // if at end of string
        sDest.Empty();  // return empty token
        m_iPos = -1;    // really done now
        return true;
    }
    if (m_sLine[m_iPos] == '\"') {  // if current char is double quote
        m_iPos++;   // advance to next char
        int iTokenStart = m_iPos;
        bool    bHasEmbeddedQuotes = false;
        while (m_iPos < m_nLen) {   // while more chars to parse
            if (m_sLine[m_iPos] == '\"') {  // if current char is double quote
                // if next char exists and is also double quote
                if (m_iPos < m_nLen - 1 && m_sLine[m_iPos + 1] == '\"') {
                    // found pair of consecutive double quotes
                    bHasEmbeddedQuotes = true;  // request conversion
                    m_iPos++;   // skip first quote in pair
                } else  // next char doesn't exist or is normal
                    break;  // found closing quote; exit loop
            }
            m_iPos++;   // advance to next char
        }
        sDest = m_sLine.Mid(iTokenStart, m_iPos - iTokenStart);
        if (bHasEmbeddedQuotes) // if string contains embedded quote pairs
            sDest.Replace(_T("\"\""), _T("\""));    // convert pairs to singles
        m_iPos += 2;    // skip closing quote and trailing delimiter if any
    } else if (m_sLine[m_iPos] == ',') {    // else if char is comma
        sDest.Empty();  // return empty token
        m_iPos++;   // advance to next char
    } else {    // else get next comma-delimited token
        sDest = m_sLine.Tokenize(_T(","), m_iPos);
    }
    return true;
}

// calling code should look something like this:

    CStdioFile  fIn(pszPath, CFile::modeRead);
    CString sLine, sToken;
    while (fIn.ReadString(sLine)) { // for each line of input file
        if (!sLine.IsEmpty()) { // ignore blank lines
            CParseCSV   csv(sLine);
            while (csv.GetString(sToken)) {
                // do something with sToken here
            }
        }
    }
person victimofleisure    schedule 19.12.2018

У меня есть способ более быстрое решение, изначально предназначенное для этого вопроса:

Как вытащить определенную часть из разных строк?

Но он был явно закрыт. Я не собираюсь выбрасывать это:

#include <iostream>
#include <string>
#include <regex>

std::string text = "\"4,\"\"3\"\",\"\"Mon May 11 03:17:40 UTC 2009\"\",\"\"kindle2\"\",\"\"tpryan\"\",\"\"TEXT HERE\"\"\";;;;";

int main()
{
    std::regex r("(\".*\")(\".*\")(\".*\")(\".*\")(\".*\")(\".*\")(\".*\")(\".*\")(\".*\")(\".*\")");
    std::smatch m;
    std::regex_search(text, m, r);
    std::cout<<"FOUND: "<<m[9]<<std::endl;

    return 0;
}

Просто выберите любое совпадение из подборки по индексу. Регулярное выражение - это блаженство.

person Jack Of Blades    schedule 19.12.2018
comment
На ум приходит хайко: у вас проблема. Вы решаете это с помощью рег. Теперь есть две проблемы. :-D - person Luke Usherwood; 03.04.2019

Как и все, кто ставит свое решение, вот мое, использующее шаблон, лямбда и кортеж.

Он может преобразовать любой CSV с нужными столбцами в вектор кортежа C ++.

Он работает, определяя каждый тип элемента строки CSV в кортеже.

Вам также необходимо определить лямбда std::string в type преобразования Formatter для каждого элемента (например, используя std::atod).

Затем у вас есть вектор этой структуры, соответствующий вашим данным CSV.

Вы можете легко использовать его повторно для соответствия любой структуре CSV.

StringsHelpers.hpp

#include <string>
#include <fstream>
#include <vector>
#include <functional>

namespace StringHelpers
{
    template<typename Tuple>
    using Formatter = std::function<Tuple(const std::vector<std::string> &)>;

    std::vector<std::string> split(const std::string &string, const std::string &delimiter);

    template<typename Tuple>
    std::vector<Tuple> readCsv(const std::string &path, const std::string &delimiter, Formatter<Tuple> formatter);
};

StringsHelpers.cpp

#include "StringHelpers.hpp"

namespace StringHelpers
{
    /**
     * Split a string with the given delimiter into several strings
     *
     * @param string - The string to extract the substrings from
     * @param delimiter - The substrings delimiter
     *
     * @return The substrings
     */
    std::vector<std::string> split(const std::string &string, const std::string &delimiter)
    {
        std::vector<std::string> result;
        size_t                   last = 0,
                                 next = 0;

        while ((next = string.find(delimiter, last)) != std::string::npos) {
            result.emplace_back(string.substr(last, next - last));
            last = next + 1;
        }

        result.emplace_back(string.substr(last));

        return result;
    }

    /**
     * Read a CSV file and store its values into the given structure (Tuple with Formatter constructor)
     *
     * @tparam Tuple - The CSV line structure format
     *
     * @param path - The CSV file path
     * @param delimiter - The CSV values delimiter
     * @param formatter - The CSV values formatter that take a vector of strings in input and return a Tuple
     *
     * @return The CSV as vector of Tuple
     */
    template<typename Tuple>
    std::vector<Tuple> readCsv(const std::string &path, const std::string &delimiter, Formatter<Tuple> formatter)
    {
        std::ifstream      file(path, std::ifstream::in);
        std::string        line;
        std::vector<Tuple> result;

        if (file.fail()) {
            throw std::runtime_error("The file " + path + " could not be opened");
        }

        while (std::getline(file, line)) {
            result.emplace_back(formatter(split(line, delimiter)));
        }

        file.close();

        return result;
    }

    // Forward template declarations

    template std::vector<std::tuple<double, double, double>> readCsv<std::tuple<double, double, double>>(const std::string &, const std::string &, Formatter<std::tuple<double, double, double>>);
} // End of StringHelpers namespace

main.cpp (некоторое использование)

#include "StringHelpers.hpp"

/**
 * Example of use with a CSV file which have (number,Red,Green,Blue) as line values. We do not want to use the 1st value
 * of the line.
 */
int main(int argc, char **argv)
{
    // Declare CSV line type, formatter and template type
    typedef std::tuple<double, double, double>                          CSV_format;
    typedef std::function<CSV_format(const std::vector<std::string> &)> formatterT;

    enum RGB { Red = 1, Green, Blue };

    const std::string COLOR_MAP_PATH = "/some/absolute/path";

    // Load the color map
    auto colorMap = StringHelpers::readCsv<CSV_format>(COLOR_MAP_PATH, ",", [](const std::vector<std::string> &values) {
        return CSV_format {
                // Here is the formatter lambda that convert each value from string to what you want
                std::strtod(values[Red].c_str(), nullptr),
                std::strtod(values[Green].c_str(), nullptr),
                std::strtod(values[Blue].c_str(), nullptr)
        };
    });

    // Use your colorMap as you  wish...
}
person Romain Laneuville    schedule 06.02.2020

Незначительное издание решения @sastanin, чтобы оно могло работать с символами новой строки в кавычках.

std::vector<std::vector<std::string>> readCSV(std::istream &in) {
    std::vector<std::vector<std::string>> table;

    while (!in.eof()) {
        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;
                        case '\n': // newline
                                  table.push_back(fields);
                                  state = CSVState::UnquotedField;
                                  fields = vector<string>{""};
                                  i = 0;
                        default:  // end of quote
                                  state = CSVState::UnquotedField;
                                  break; }
                    break;
            }
        }
    }
    return table;
}
person user2891006    schedule 07.02.2020

CSV-файл - это текстовые файлы, состоящие из строк, каждая строка состоит из токенов, разделенных запятыми. Хотя есть кое-что, что вы должны знать при разборе:

(0) Файл закодирован с помощью кодовой страницы CP_ACP. вы должны использовать ту же страницу кодирования для декодирования содержимого файла.

(1) CSV потерял информацию о составных ячейках (например, rowspan ›1), поэтому, когда он считывается обратно в Excel, информация о составных ячейках теряется.

(2) текст ячейки может быть заключен в кавычки в начале и в конце, а буквальный символ кавычки станет двойными кавычками. поэтому закрывающий соответствующий символ цитаты должен быть символом цитаты, за которым не следует другой символ цитаты. например, если в ячейке есть запятая, она должна быть заключена в кавычки в CSV, потому что запятая имеет смысл в CSV.

(3) когда содержимое ячейки имеет несколько строк, оно будет цитироваться в CSV, в этом случае синтаксический анализатор должен продолжать читать следующие отдельные строки в файле CSV, пока вы не получите заключительный символ цитаты, соответствующий первому символу цитаты, убедитесь, что текущая логическая строка считывается завершенной перед анализом токенов строки.

например: в файле csv следующие 3 физические строки представляют собой одну логическую строку, состоящую из 3 токенов:

    --+----------
    1 |a,"b-first part
    2 |b-second part
    3 |b-third part",c
    --+----------
person Exlife    schedule 29.03.2021

Поскольку разбор CSV в формате Excel - нетривиальная задача, я хотел бы добавить сюда свой код:

http://fgw.ddnss.de/CSV_Endlicher_Automat.html

Это детерминированный конечный автомат, который обрабатывает все случаи (простые, кавычки, экранирование кавычек) явно и без каких-либо странных хаков.

Я действительно считаю, что некоторые из вышеперечисленных решений неверны, а некоторые слишком сложны.

person user8784486    schedule 25.09.2020
comment
Это может быть (а может и не быть) хорошим решением, но в любом случае его очень трудно читать программистам, не говорящим по-немецки. Даже API на немецком языке! - person Alexander; 02.02.2021