【发布时间】:2018-06-12 16:31:37
【问题描述】:
我想高效地解析类似 CSV 的大型文件,这些文件的列顺序是我在运行时获得的。使用 Spirit Qi,我将使用 lazy 辅助解析器解析每个字段,该解析器将在运行时选择将哪个特定于列的解析器应用于每个列。但是X3好像没有lazy(虽然是listed in documentation)。在阅读了关于 SO 的建议后,我决定编写一个自定义解析器。
结果非常好,但现在我注意到我真的不需要在自定义解析器本身之外的任何地方公开pos 变量。我尝试将其放入自定义解析器本身并开始收到编译器错误,指出column_value_parser 对象是只读的。我可以以某种方式将pos 放入解析器结构吗?
得到编译时错误的简化代码,并注释掉了我的工作版本的部分内容:
#include <iostream>
#include <variant>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/support.hpp>
namespace helpers {
// https://bitbashing.io/std-visit.html
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
}
auto const unquoted_text_field = *(boost::spirit::x3::char_ - ',' - boost::spirit::x3::eol);
struct text { };
struct integer { };
struct real { };
struct skip { };
typedef std::variant<text, integer, real, skip> column_variant;
struct column_value_parser : boost::spirit::x3::parser<column_value_parser> {
typedef boost::spirit::unused_type attribute_type;
std::vector<column_variant>& columns;
// size_t& pos;
size_t pos;
// column_value_parser(std::vector<column_variant>& columns, size_t& pos)
column_value_parser(std::vector<column_variant>& columns)
: columns(columns)
// , pos(pos)
, pos(0)
{ }
template<typename It, typename Ctx, typename Other, typename Attr>
bool parse(It& f, It l, Ctx& ctx, Other const& other, Attr& attr) const {
auto const saved_f = f;
bool successful = false;
visit(
helpers::overloaded {
[&](skip const&) {
successful = boost::spirit::x3::parse(f, l, boost::spirit::x3::omit[unquoted_text_field]);
},
[&](text& c) {
std::string value;
successful = boost::spirit::x3::parse(f, l, unquoted_text_field, value);
if(successful) {
std::cout << "Text: " << value << '\n';
}
},
[&](integer& c) {
int value;
successful = boost::spirit::x3::parse(f, l, boost::spirit::x3::int_, value);
if(successful) {
std::cout << "Integer: " << value << '\n';
}
},
[&](real& c) {
double value;
successful = boost::spirit::x3::parse(f, l, boost::spirit::x3::double_, value);
if(successful) {
std::cout << "Real: " << value << '\n';
}
}
},
columns[pos]);
if(successful) {
pos = (pos + 1) % columns.size();
return true;
} else {
f = saved_f;
return false;
}
}
};
int main(int argc, char *argv[])
{
std::string input = "Hello,1,13.7,XXX\nWorld,2,1e3,YYY";
// Comes from external source.
std::vector<column_variant> columns = {text{}, integer{}, real{}, skip{}};
size_t pos = 0;
boost::spirit::x3::parse(
input.begin(), input.end(),
// (column_value_parser(columns, pos) % ',') % boost::spirit::x3::eol);
(column_value_parser(columns) % ',') % boost::spirit::x3::eol);
}
XY:我的目标是在合理的时间内在 RAM 很少的机器上解析 ~500 GB 的伪 CSV 文件,转换为(大致)[行号、列名、值]的列表,然后入库。格式实际上比 CSV 复杂一点:数据库转储以……人性化的方式格式化,列值实际上是几个小的子语言(例如日期,或者,呃,类似于填充到单个字段中的整个 apache 日志行),我经常只提取每列的一个特定部分。不同的文件可能有不同的列和不同的顺序,我只能通过解析另一组包含原始查询的文件来学习。值得庆幸的是,Spirit 让它变得轻而易举……
【问题讨论】:
-
房间里的大象:为什么不导出 XML 或使用数据库而不是转储?
-
@sehe,哦,我希望它以一种更理智的方式完成……作为一个业余爱好项目,我只是说服该数据集的所有者给我他们可以轻松手动检查的内容,只需很少的技术技能.它仍然比他们自己使用的 XLS 好。
-
我会首先解析自定义 AST 节点(例如在共享/映射内存容器中)并从那里实现所有逻辑。
标签: c++ c++17 boost-spirit boost-spirit-x3