Недавно мне понадобилось парсить файл формата csv. При этом каждый элемент соответствует некоторому полю структуры, например:
Содержимое файла csv:
some string,1,some string 2,100500
hello, 2,good bye,111111
Структура, которую необходимо заполнить:
struct some_struct
{
std::string name1;
int count1;
std::string name2;
int count2;
}; |
_Winnie C++ Colorizer |
Используем для этих boost:
Нам понадобятся две библиотеки:
tokenizer и
lexical_cast
Итак, сначала определим нужные нам типы:
typedef boost::char_separator<char> sep_type;
typedef boost::tokenizer<sep_type> tok_type;
struct some_struct
{
std::string line1;
int count1;
std::string line2;
int count2;
};
typedef std::vector<some_struct> vec_type; |
_Winnie C++ Colorizer |
В первой строке мы определяем тип - сепаратор, который будет принимать решение о разделении строки. Во второй строке мы определяем тип - токенайзер, он будет разделять строки основываясь на данных приходящих от сепаратора.
Дальше мы определяем структуру и вектор, в который мы будем складывать результаты парсинга.
Посмотрим, каким образом tokenizer разбирает строку:
Допустим у нас есть строка, которая содержит данные в csv:
Объявляем сепаратор и токенайзер:
Теперь чтобы вывести элементы csv на экран, нам достаточно сделать что-то вроде:
for (tok_type::const_iterator it = tok.begin(); it != tok.end(); ++it)
{
std::cout << *it << std::endl;
} |
_Winnie C++ Colorizer |
С парсером разобрались, теперь мы хотим получить удобный механизм для инициализации структуры, при этом мы хотим диагностировать ошибку (например, в строке csv слишком мало элементов, или элемент неправильного типа: ожидалось число, а пришла строка).
Для этого напишем следующую шаблонную фукнцию:
template <typename InputIterator, typename ValueT>
void bindVariable(InputIterator pos, InputIterator end, ValueT & val)
{
if (pos == end)
{
throw std::runtime_error("bad csv format");
}
val = boost::lexical_cast<ValueT>(*pos);
} |
_Winnie C++ Colorizer |
Эта функция делает очень немного: она проверяет что мы не вышли за границы итерируемых токенов, а также присваивает переменной val соответствующее значение. Однако нам хватит этой функции чтобы организовать заполнение структуры. При этом lexical_cast бросит исключение, если типы входных и выходных параметров несовместимы. В принципе, можно расширить эту функцию, передавая в нее еще один параметр, например сообщение об ошибке, тогда мы сможем бросить исключение, передав в конструктор исключения сообщение об ошибке. Также мы можем ловить исключение bad_lexical_cast, и формировать свое исключение на основе пойманного и сообщения об ошибке.
Поехали дальше, теперь мы будем заполнять собственно нашу структуру:
template <typename InputIterator>
void parseCsvLine(InputIterator it, InputIterator end, some_struct & res)
{
bindVariable(it, end, res.line1); ++it;
bindVariable(it, end, res.count1); ++it;
bindVariable(it, end, res.line2); ++it;
bindVariable(it, end, res.count2); ++it;
} |
_Winnie C++ Colorizer |
Вот собственно и все! Так как функция bindVariable - шаблонная, то мы в нее можем передавать параметр любого типа, который корректно обрабатывается функцией lexical_cast. А функция lexical_cast в свою очередь требует чтобы для типа были реализованы операторы
std::ostream & operator << (std::ostream & strm, const Type & val);
std::istream & operator >> (std::istream & strm, Type & val); |
_Winnie C++ Colorizer |
Вот полный пример того как работает данный парсер:
В примере я добавил свою структуру - my_type, чтобы показать, что парсер может обрабатывать не только стандартные типы, но и определенные пользователем.
#include <boost/tokenizer.hpp>
#include <boost/lexical_cast.hpp>
#include <ostream>
#include <iostream>
#include <fstream>
#include <vector>
typedef boost::char_separator<char> sep_type;
typedef boost::tokenizer<sep_type> tok_type;
struct my_type
{
int val;
};
std::ostream & operator << (std::ostream & strm, const my_type & t)
{
strm << t.val;
return strm;
}
std::istream & operator >> (std::istream & strm, my_type & t)
{
strm >> t.val;
return strm;
}
struct some_struct
{
std::string line1;
int count1;
std::string line2;
//int count2;
my_type count2;
};
typedef std::vector<some_struct> vec_type;
template <typename InputIterator, typename ValueT>
void bindVariable(InputIterator pos, InputIterator end, ValueT & val)
{
if (pos == end)
{
throw std::runtime_error("bad csv format");
}
val = boost::lexical_cast<ValueT>(*pos);
}
template <typename InputIterator>
void parseCsvLine(InputIterator it, InputIterator end, some_struct & res)
{
bindVariable(it, end, res.line1); ++it;
bindVariable(it, end, res.count1); ++it;
bindVariable(it, end, res.line2); ++it;
bindVariable(it, end, res.count2); ++it;
}
int main(int argc, char * argv [])
{
if (argc < 2)
{
std::cerr << "usage: " << argv[0] << " path/to/csv/file" << std::endl;
return 1;
}
std::ifstream ifile(argv[1]);
if (!ifile.is_open())
{
std::cerr << "faild to open file: " << argv[1] << std::endl;
return 1;
}
std::string line;
sep_type sep(",");
vec_type vec;
while (!ifile.eof())
{
std::getline(ifile, line);
std::cout << "line: " << line << std::endl;
tok_type tok(line, sep);
some_struct tmp;
parseCsvLine(tok.begin(), tok.end(), tmp);
vec.push_back(tmp);
}
for (vec_type::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
std::cout <<
"line1: " << it->line1 <<
", count1: " << it->count1 <<
", line2: " << it->line2 <<
", count2: " << it->count2 << std::endl;
}
return 0;
} |
_Winnie C++ Colorizer |
CSV это же достаточно простой формат! Нахрена все так сложно было? Ведь достаточно для каждой строки вызвать split(DELIMITER_CHAR) и полученную разность проверить на кол-во столбцов, а если допустимое то работаем далее. И ненадо никаких токинизеров!
ОтветитьУдалитьСогласен, здесь реализация немного переусложнена. Здесь все же больший упор идет на использование именно библиотеки tokenizer, csv - не более чем один из вариантов применения. Соответственно это и хотелось показать в статье
Удалить2The NT...
ОтветитьУдалитьнуда, ну да, а как же экранирование кавычек, например?
или, собственно, сам символ кавычки.
всё время забывают о важных мелочах все.
Вот-вот. Тоже хотел написать. В экранировании — самое интересное, там наверное свой separator придётся писать.
Удалить