Работа с CSV в C++: от ручного парсинга до библиотеки libcsv
Формат CSV (Comma-Separated Values) остаётся одним из самых популярных способов обмена табличными данными. В C++ нет встроенной поддержки CSV, но существует несколько эффективных подходов: ручной парсинг с использованием стандартных средств и специализированные библиотеки, такие как libcsv. В этой статье мы разберём оба метода, их преимущества и недостатки, а также приведём рабочие примеры кода.
Что такое CSV и его особенности
CSV — это текстовый формат, где каждая строка соответствует записи, а поля разделяются запятыми. Основные сложности:
- Поля могут содержать запятые, если они заключены в кавычки (
"Hello, world"); - Кавычки внутри поля экранируются дублированием (
""); - Разделитель может быть другим символом (точка с запятой, табуляция);
- Строки могут содержать символы новой строки внутри кавычек.
Поэтому простое разбиение строки по запятым часто приводит к ошибкам. Рассмотрим два надёжных способа.
Метод 1: Ручной парсинг с помощью std::stringstream
Подходит для простых CSV без вложенных кавычек и переносов строк. Реализация состоит из двух функций: чтения одного поля и разбора всей строки.
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
// Разделение строки CSV на поля с учётом кавычек
std::vector<std::string> parseCSVLine(const std::string& line) {
std::vector<std::string> fields;
std::string field;
bool inQuotes = false;
for (size_t i = 0; i < line.size(); ++i) {
char c = line[i];
if (c == '"' && !inQuotes) {
inQuotes = true;
} else if (c == '"' && inQuotes) {
// Проверка на экранированные кавычки
if (i + 1 < line.size() && line[i + 1] == '"') {
field += '"';
++i; // пропускаем следующую кавычку
} else {
inQuotes = false;
}
} else if (c == ',' && !inQuotes) {
fields.push_back(field);
field.clear();
} else {
field += c;
}
}
fields.push_back(field); // последнее поле
return fields;
}
int main() {
std::ifstream file("data.csv");
std::string line;
while (std::getline(file, line)) {
auto fields = parseCSVLine(line);
for (const auto& f : fields) {
std::cout << "[" << f << "] ";
}
std::cout << std::endl;
}
return 0;
}
Плюсы: нет внешних зависимостей, полный контроль над логикой.
Минусы: не обрабатывает многострочные поля (значение, содержащее \n), требует ручной обработки ошибок.
Метод 2: Библиотека libcsv
libcsv — легковесная C-библиотека для парсинга CSV, написанная на C, но отлично работающая из C++. Она корректно обрабатывает все стандартные случаи: кавычки, экранирование, многострочные поля, пользовательские разделители.
Установка
Скачайте исходники с SourceForge или установите через пакетный менеджер:
# Linux (Debian/Ubuntu)
sudo apt-get install libcsv-dev
# macOS
brew install libcsv
Пример чтения CSV с libcsv
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstring>
#include <csv.h>
// Структура для хранения данных
struct CSVData {
std::vector<std::vector<std::string>> rows;
std::vector<std::string> currentRow;
};
// Callback при обнаружении нового поля
void cb1(void *s, size_t len, void *data) {
CSVData *csvData = static_cast<CSVData*>(data);
csvData->currentRow.push_back(std::string(static_cast<char*>(s), len));
}
// Callback при завершении строки
void cb2(int c, void *data) {
CSVData *csvData = static_cast<CSVData*>(data);
csvData->rows.push_back(csvData->currentRow);
csvData->currentRow.clear();
}
int main() {
std::ifstream file("data