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

264

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

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

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

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

Ответы:

296

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

    for(CSVIterator loop(file); loop != CSVIterator(); ++loop)
    {
        std::cout << "4th Element(" << (*loop)[3] << ")\n";
    }
}
Мартин Йорк
источник
20
первый () следующий (). Что это за Java! Только пошутил.
Мартин Йорк,
4
@DarthVader: Наложение широкого утверждения, которое по своей широте глупо. Если вы хотите уточнить, почему это плохо, а затем, почему эта плохость применяется в этом контексте.
Мартин Йорк
12
@DarthVader: Я думаю, что глупо делать широкие обобщения. Приведенный выше код работает правильно, поэтому я могу видеть что-то не так с ним. Но если у вас есть какие-либо конкретные комментарии по поводу вышеизложенного, я обязательно рассмотрим в этом контексте. Но я вижу, как вы можете прийти к такому выводу, бездумно следуя набору обобщенных правил для C # и применяя его к другому языку.
Мартин Йорк,
5
Кроме того, если вы столкнетесь со странными проблемами связывания с приведенным выше кодом, потому что другая библиотека где-то определяет istream::operator>>(например, Eigen), добавьте inlineперед оператором объявление, чтобы исправить это.
sebastian_k
3
Это самый простой и чистый пример того, как создать класс итераторов, который я когда-либо видел.
Джанкарло Спортелли
46

Решение с использованием 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);
}
DTW
источник
9
Повышающий токенизатор не полностью поддерживает полный стандарт CSV, но есть несколько быстрых решений. См. Stackoverflow.com/questions/1120140/csv-parser-in-c/…
Рольф Кристенсен,
3
У вас должна быть целая библиотека boost на вашем компьютере, или вы можете просто использовать подмножество их кода для этого? 256мб кажется много для разбора CSV ..
NPike
6
@NPike: Вы можете использовать BCP утилита , которая поставляется с усилением , чтобы извлечь только заголовки вы на самом деле нужно.
ildjarn
46

Моя версия не использует ничего, кроме стандартной библиотеки 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;
}
sastanin
источник
6
спасибо, я думаю, что это самый полный ответ, очень жаль, что он похоронен здесь.
Михай
этот вложенный вектор строк не нужен современным процессорам. Выбрасывает их способность кеширования
Николаос Джотис
Кроме того, вы получили все эти заявления о смене
Николаос Джотис
Главный ответ не работал для меня, так как я работаю на старом компиляторе. Этот ответ сработал, для инициализации вектора может потребоваться следующее:const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
dr_rk
31

++ Строка Инструментарий Библиотека C (StrTk) имеет символический класс сетки , что позволяет загружать данные как из текстовых файлов, строк или символьных буферов и разбор / обрабатывать их в моде ряда столбцов.

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

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

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

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

Больше примеров можно найти здесь

J Mkdjion
источник
1
Хотя strtk поддерживает поля с двойными кавычками и даже удаляет окружающие кавычки (через options.trim_dquotes = true), он не поддерживает удаление двойных кавычек (например, поле в "She said ""oh no"", and left."виде c-строки "She said \"oh no\", and left."). Тебе придется сделать это самостоятельно.
Рэмпион
1
При использовании strtkвам также придется вручную обрабатывать поля в двойных кавычках, содержащие символы новой строки.
Рэмпион
29

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

escaped_list_separator анализирует надмножество CSV. Повышение :: токенизатор

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

Вот пример (см Анализировать CSV файл с форсировкой 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;
    }
}
stefanB
источник
И если вы хотите иметь возможность разбирать встроенные новые строки mybyteofcode.blogspot.com/2010/11/… .
stefanB
Хотя эта техника работает, я обнаружил, что она имеет очень плохую производительность. На моем Xeon с частотой 2 ГГц анализ файла CSV 90000 с десятью полями на строку занимает около 8 секунд. CSV-модуль стандартной библиотеки Python анализирует тот же файл примерно за 0,3 секунды.
Роб Смолшир
@Rob это интересно - что делает Python CSV по-другому?
тофутим
1
@RobSmallshire это простой пример кода, а не высокопроизводительный. Этот код делает копии всех полей в строке. Для более высокой производительности вы будете использовать разные опции и возвращать только ссылки на поля в буфере, а не делать копии.
stefanB
29

Не лишним будет использовать Дух для разбора CSV. 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. Сгенерированный код такой же жесткий, как и рукописный код. Проверьте созданный ассемблер!

Джоэл де Гусман
источник
18
На самом деле это излишне, время компиляции огромно и делает использование Spirit для простых «задач микропарсинга» неразумным.
Гердинер
13
Также я хотел бы отметить, что приведенный выше код не анализирует CSV, он просто анализирует диапазон типа вектора, разделенного запятыми. Он не обрабатывает кавычки, различные типы столбцов и т. Д. Короче 19 голосов за то, что действительно отвечает на вопрос, кажется мне немного подозрительным.
Гердинер
9
@ Гердинер Ерунда. Время компиляции для небольших анализаторов не так уж велико, но оно также не имеет значения, потому что вы помещаете код в его собственный модуль компиляции и компилируете его один раз . Тогда вам нужно всего лишь связать это, и это настолько эффективно, насколько это возможно. И что касается вашего другого комментария, существует столько диалектов CSV, сколько есть процессоров для него. Этот, конечно, не очень полезный диалект, но его можно тривиально расширить для обработки указанных значений.
Конрад Рудольф
11
@konrad: Просто включите «#include <boost / spirit / include / qi.hpp>» в пустой файл, содержащий только основной файл, и ничего больше не займет 9,7 с в MSVC 2012 на corei7, работающем с тактовой частотой 2 ГГц. Это ненужное раздувание. Принятый ответ компилируется в 2secs на той же машине, я бы не хотел представить, сколько времени займет «правильный» пример Boost.Spirit для компиляции.
Гердинер
11
@Gerdiner Я должен согласиться с вами, что использование духа для чего-то такого простого, как обработка cvs, слишком велико.
18

Если вы DO заботиться о разборе CSV правильно, это будет сделать ... относительно медленно , так как он работает один символ за один раз.

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

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

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

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

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

          aChar++;
       }

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

       if (line.size())
          lines.push_back(line);
    }
Майкл
источник
AFAICT это не будет правильно обрабатывать встроенные кавычки (например, «Эта строка имеет» «встроенные кавычки» »,« foo », 1))
Джереми Фризнер
14

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

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

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

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

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

1997, форд, е350, "супер", "роскошный", "грузовик"

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

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

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

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

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

Рольф Кристенсен
источник
8

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

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

CXW
источник
Кажется великолепным ... Как насчет статуса бета / производство?
Нейро
Статус «в разработке», как указано в номерах версий. Мне действительно нужно больше отзывов от пользователей, прежде чем перейти к версии 1.0. Кроме того, я хочу добавить еще пару функций, связанных с производством XML из CSV.
Добавьте в закладки, и в следующий раз я попытаюсь разобраться с этими замечательными стандартными файлами CSV ...
Нейро
8

Поскольку все вопросы о 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) };
};

Попробуйте онлайн!

jxh
источник
6

Я написал только для заголовка, C ++ 11 CSV-парсер . Он хорошо протестирован, быстр, поддерживает всю спецификацию 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;
  }
}
m0meni
источник
1
Хорошая работа, но вам нужно добавить еще три вещи: (1) чтение заголовка (2) обеспечение индексации полей по имени (3) не перераспределять память в цикле путем повторного использования одного и того же вектора строк
Максим Ганенко,
@MaksymGanenko Я делаю # 3. Не могли бы вы остановиться на № 2?
m0meni
1
Очень полезно получать поля не по позиции в строке, а по имени, указанному в заголовке (в первой строке таблицы CSV). Например, я ожидаю таблицу CSV с полем «Дата», но я не знаю, что такое индекс поля «Дата» в строке.
Максим Ганенко
1
@MaksymGanenko ах, я понимаю, что вы имеете в виду. Есть github.com/ben-strasser/fast-cpp-csv-parser, когда вы знаете столбцы вашего CSV во время компиляции, и это, вероятно, лучше, чем у меня. Мне нужен был анализатор CSV для тех случаев, когда вы хотели использовать один и тот же код для разных CSV и не знали, как они выглядят заранее. Поэтому я, вероятно, не добавлю # 2, но добавлю # 1 в будущем.
m0meni
5

Другая библиотека ввода / вывода 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
  }
}
Хейгард Флиш
источник
2
Хорошо, но это заставляет вас выбирать количество столбцов во время компиляции. Не очень полезно для многих приложений.
Quant_dev
5

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

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

Advanges:

  • довольно чистый и простой в использовании, только C ++ 11.
  • автоматическое преобразование типов в std::tuple<t1, ...>via 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 ; Я использовал его для разбора некоторых числовых данных, и он выполнил свою задачу.

Spak
источник
1
Вы можете не заботиться о вставке, потому что большинство компиляторов решают это самостоятельно. По крайней мере, я уверен в Visual C ++. Он может встроить метод независимо от спецификации вашего метода.
MrPisarik
1
Именно поэтому я отметил их явно. Gcc и Clang, которые я в основном использую, также имеют свои собственные соглашения. «Встроенное» ключевое слово должно быть просто стимулом.
Спек
4

Вот еще одна реализация парсера 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;
}
sashoalm
источник
3

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

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

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

/**

  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;
}
ravenspoint
источник
Хм, а почему бы ",\n"в строке?
Тимммм
@Timmmm ищет метод substr класса String, и вы увидите, что он принимает несколько символов, \ n - символ новой строки, поэтому в данном случае он считается одним символом. Он не ищет все значение в целом. Он ищет каждого персонажа; а именно запятая или перевод строки. substr вернет позицию первого найденного символа и -1, если не найдет ни одного, что означает, что он завершил чтение строки. fp отслеживает положение в файле внутри, поэтому каждый вызов readCSV перемещает его по одной строке за раз.
Мартын Шутт
2

Вот код для чтения матрицы, обратите внимание, у вас также есть функция 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;
    }
}
Джим М.
источник
2

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

Предположим, ваш файл 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 инвертирует логику, означает совпадение с любой строкой, которая не содержит запятую, затем последняя, ​​говорит, что совпадает с запятой, оканчивающейся предыдущей строкой.

Амрута Гёдке
источник
2

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

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

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

После преодоления шокирующего опыта ожидания 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
яв
источник
2

Это решение обнаруживает эти 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,",

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

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

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

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

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

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

Мад
источник
это не будет обрабатывать запятые в кавычках
Ezee
1

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

Мне повезло с разбором CSV здесь:

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

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

NPike
источник
1
Разве компилятор не должен убрать все, что несущественно?
тофутим
1

Это старая ветка, но она все еще находится в верхней части результатов поиска, поэтому я добавляю свое решение, используя 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));
      }
   }
}
marcp
источник
1

Для чего это стоит, вот моя реализация. Он имеет дело с вводом 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();
}
Фабьен
источник
1

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

#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

Другой быстрый и простой способ заключается в использовании 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';
}

Выходы:

(abc 123 true 3.14)
(def 456 false 2.718)
Максим Егорушкин
источник
1

Я написал хороший способ парсинга 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;
}
scap3y
источник
1

Можно использовать 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());
}
g24l
источник
1

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


}
nikos_k
источник