Как я могу читать и анализировать файлы CSV на C ++?
Автор вопроса: User1Ответов: 12
Мне нужно загрузить и использовать данные файла CSV на C ++. На данный момент это действительно может быть просто синтаксический анализатор с разделителями-запятыми (т.е. не беспокойтесь об экранировании новых строк и запятых). Основная потребность - построчный синтаксический анализатор, который будет возвращать вектор для следующей строки при каждом вызове метода.
Я никогда не использовал 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
Если вас не волнует экранирование запятой и новой строки, И вы не можете вставлять запятую и новую строку в кавычки (если вы не можете избежать, то ...) тогда это всего лишь три строки код (ОК 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 <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) есть класс сетки токенов, который позволяет вам для загрузки данных из текстовых файлов, строк или буферов символов и для их синтаксического анализа / обработки в режиме "строка-столбец".
Вы можете указать разделители строк и столбцов или просто использовать значения по умолчанию.
#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 следует помнить о следующем:
Требуется escape-символ (обратная косая черта по умолчанию - \)
Требуется разделитель / разделитель-символ (запятая по умолчанию -,)
Требуется кавычка (кавычка по умолчанию - ")
Формат CSV, указанный в wiki, гласит, что поля данных могут содержать разделители в кавычках (поддерживается):
1997, Ford, E350, "Супер, роскошный грузовик"
Формат CSV, указанный в wiki, гласит, что одинарные кавычки следует обрабатывать с помощью двойных кавычек (escaped_list_separator удалит все символы кавычек):
1997, Ford, E350, "Супер" "роскошный" "грузовик"
Формат CSV не указывает, что любые символы обратной косой черты должны быть удалены (escaped_list_separator удалит все escape-символы).
Возможный обходной путь для исправления поведения по умолчанию boost escaped_list_separator:
Сначала замените все символы обратной косой черты (\) двумя символами обратной косой черты (\\), чтобы они не удалялись.
Во-вторых, замените все двойные кавычки ("") одинарным символом обратной косой черты и кавычкой (\ ").
Этот обходной путь имеет побочный эффект, заключающийся в том, что пустые поля данных, представленные двойными кавычками, будут преобразованы в токен с одинарными кавычками. При итерации по токенам необходимо проверить, является ли токен одинарной кавычкой, и рассматривать его как пустую строку.
Не очень красиво, но работает, если в кавычках нет новых строк.
Ответ 9 · рейтинг 10 · jxh
Поскольку все вопросы CSV, кажется, перенаправляются сюда, я решил опубликовать свой ответ здесь. Этот ответ не отвечает напрямую на вопрос автора. Я хотел иметь возможность читать в потоке, который, как известно, находится в формате CSV, а также уже были известны типы каждого поля. Конечно, описанный ниже метод можно использовать для обработки каждого поля как строкового типа.
В качестве примера того, как я хотел использовать поток ввода CSV, рассмотрим следующий ввод (взятый из страница Википедии в CSV):
Вы можете посмотреть мой проект 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
}
}