Bezdepbonusu7Программирование: статьи и ответы

Вопрос

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

Автор вопроса: User1Ответов: 12

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

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

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

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

c++csvtextparsing

Ответы

Ответ 1 · рейтинг 328 · Martin York

Если вас не волнует экранирование запятой и новой строки,
И вы не можете вставлять запятую и новую строку в кавычки (если вы не можете избежать, то ...)
тогда это всего лишь три строки код (ОК 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";
    }
}

Ответ 2 · рейтинг 68 · sastanin

Моя версия не использует ничего, кроме стандартной библиотеки 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;
}

Ответ 3 · рейтинг 48 · dtw

Решение с использованием 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);
}

Ответ 4 · рейтинг 33 · Community

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

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

Ответ 5 · рейтинг 29 · stefanB

Вы можете использовать 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;
    }
}

Ответ 6 · рейтинг 28 · Joel de Guzman

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

Ответ 7 · рейтинг 18 · Michael

Если вы ОБЯЗАТЕЛЬНО позаботитесь о правильном синтаксическом анализе 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);
    }

Ответ 8 · рейтинг 14 · Rolf Kristensen

При использовании 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. Во-вторых, замените все двойные кавычки ("") одинарным символом обратной косой черты и кавычкой (\ ").

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

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

Ответ 9 · рейтинг 10 · jxh

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

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

Ответ 10 · рейтинг 8 · Community

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

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

Ответ 11 · рейтинг 8 · m0meni

Я написал синтаксический анализатор 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;
  }
}

Ответ 12 · рейтинг 6 · Heygard Flisch

Еще одну библиотеку ввода-вывода 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
  }
}

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы