提升精神解析 CSV,列顺序可变
boost spirit parsing CSV with columns in variable order
我正在尝试使用boost spirit解析CSV文件(带有标题行)。csv 不是恒定格式。有时有一些额外的列或列的顺序混合。我对几列感兴趣,其标题名称是众所周知的。
例如,我的 CSV 可能如下所示:
姓名,姓氏,年龄约翰,多伊,32
或:
年龄,姓名32,约翰
我只想解析名称和年龄的内容(注意年龄是整数类型)。 目前,我提出了一个非常丑陋的解决方案,其中Spirit解析了第一行并创建了一个向量,该向量在我感兴趣的位置包含一个枚举。然后我必须手动解析终端符号......
enum LineItems {
NAME, AGE, UNUSED
};
struct CsvLine {
string name;
int age;
};
using Column = std::string;
using CsvFile = std::vector<CsvLine>;
template<typename It>
struct CsvGrammar: qi::grammar<It, CsvFile(), qi::locals<std::vector<LineItems>>, qi::blank_type> {
CsvGrammar() :
CsvGrammar::base_type(start) {
using namespace qi;
static const char colsep = ',';
start = qi::omit[header[qi::_a = qi::_1]] >> eol >> line(_a) % eol;
header = (lit("Name")[phx::push_back(phx::ref(qi::_val), LineItems::NAME)]
| lit("Age")[phx::push_back(phx::ref(qi::_val), LineItems::AGE)]
| column[phx::push_back(phx::ref(qi::_val), LineItems::UNUSED)]) % colsep;
line = (column % colsep)[phx::bind(&CsvGrammar<It>::convertFunc, this, qi::_1, qi::_r1,
qi::_val)];
column = quoted | *~char_(",n");
quoted = '"' >> *("""" | ~char_(""n")) >> '"';
}
void convertFunc(std::vector<string>& columns, std::vector<LineItems>& positions, CsvLine &csvLine) {
//terminal symbol parsing here, and assign to csvLine struct.
...
}
private:
qi::rule<It, CsvFile(), qi::locals<std::vector<LineItems>>, qi::blank_type> start;
qi::rule<It, std::vector<LineItems>(), qi::blank_type> header;
qi::rule<It, CsvLine(std::vector<LineItems>), qi::blank_type> line;
qi::rule<It, Column(), qi::blank_type> column;
qi::rule<It, std::string()> quoted;
qi::rule<It, qi::blank_type> empty;
};
这是完整的来源。
如果标头解析器可以准备一个vector<rule<...>*>
,而"行解析器"只是使用这个向量来解析自己怎么办? 一种高级的纳比亚莱克技巧(我一直在尝试,但我无法做到)。
或者有没有更好的方法来解析这种CSV与Spirit?(任何帮助不胜感激,提前谢谢)
我会同意你的想法,
我认为它非常优雅(齐当地人甚至允许重复使用这个)。
为了减少规则中的缺陷(Boost Spirit:"语义行为是邪恶的"?),您可以将"转换函数"移至属性转换自定义点。
哎呀。正如评论的那样,这太简单了。但是,您仍然可以大大减少粗糙度。通过两个简单的调整,语法如下:
item.add("Name", NAME)("Age", AGE);
start = omit[ header[_a=_1] ] >> eol >> line(_a) % eol;
header = (item | omit[column] >> attr(UNUSED)) % colsep;
line = (column % colsep) [convert];
column = quoted | *~char_(",n");
quoted = '"' >> *("""" | ~char_(""n")) >> '"';
调整:
- 使用
qi::symbols
从标头映射到LineItem
使用直接访问上下文的原始语义操作(
[convert]
)(请参阅增强精神语义操作参数):struct final { using Ctx = typename decltype(line)::context_type; void operator()(Columns const& columns, Ctx &ctx, bool &pass) const { auto& csvLine = boost::fusion::at_c<0>(ctx.attributes); auto& positions = boost::fusion::at_c<1>(ctx.attributes); int i =0; for (LineItems position : positions) { switch (position) { case NAME: csvLine.name = columns[i]; break; case AGE: csvLine.age = atoi(columns[i].c_str()); break; default: break; } i++; } pass = true; // returning false fails the `line` rule } } convert;
可以说,结果类似于做auto convert = phx::bind(&CsvGrammar<It>::convertFunc, this, qi::_1, qi::_r1, qi::_val)
但是将auto
与原始/凤凰/精神表达式一起使用是出了名的容易出错(UB 由于从表达式模板中悬挂对临时的引用),所以我当然更喜欢上面显示的方式。
住在科里鲁
//#define BOOST_SPIRIT_DEBUG
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <iostream>
#include <boost/fusion/include/at_c.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <string>
#include <vector>
namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;
using std::string;
enum LineItems { NAME, AGE, UNUSED };
struct CsvLine {
string name;
int age;
};
using Column = std::string;
using Columns = std::vector<Column>;
using CsvFile = std::vector<CsvLine>;
template<typename It>
struct CsvGrammar: qi::grammar<It, CsvFile(), qi::locals<std::vector<LineItems>>, qi::blank_type> {
CsvGrammar() : CsvGrammar::base_type(start) {
using namespace qi;
static const char colsep = ',';
item.add("Name", NAME)("Age", AGE);
start = qi::omit[ header[_a=_1] ] >> eol >> line(_a) % eol;
header = (item | omit[column] >> attr(UNUSED)) % colsep;
line = (column % colsep) [convert];
column = quoted | *~char_(",n");
quoted = '"' >> *("""" | ~char_(""n")) >> '"';
BOOST_SPIRIT_DEBUG_NODES((header)(column)(quoted));
}
private:
qi::rule<It, std::vector<LineItems>(), qi::blank_type> header;
qi::rule<It, CsvFile(), qi::locals<std::vector<LineItems>>, qi::blank_type> start;
qi::rule<It, CsvLine(std::vector<LineItems> const&), qi::blank_type> line;
qi::rule<It, Column(), qi::blank_type> column;
qi::rule<It, std::string()> quoted;
qi::rule<It, qi::blank_type> empty;
qi::symbols<char, LineItems> item;
struct final {
using Ctx = typename decltype(line)::context_type;
void operator()(Columns const& columns, Ctx &ctx, bool &pass) const {
auto& csvLine = boost::fusion::at_c<0>(ctx.attributes);
auto& positions = boost::fusion::at_c<1>(ctx.attributes);
int i =0;
for (LineItems position : positions) {
switch (position) {
case NAME: csvLine.name = columns[i]; break;
case AGE: csvLine.age = atoi(columns[i].c_str()); break;
default: break;
}
i++;
}
pass = true; // returning false fails the `line` rule
}
} convert;
};
int main() {
const std::string s = "Surname,Name,Age,nJohn,Doe,32nMark,Smith,43";
auto f(begin(s)), l(end(s));
CsvGrammar<std::string::const_iterator> p;
CsvFile parsed;
bool ok = qi::phrase_parse(f, l, p, qi::blank, parsed);
if (ok) {
for (CsvLine line : parsed) {
std::cout << '[' << line.name << ']' << '[' << line.age << ']';
std::cout << std::endl;
}
} else {
std::cout << "Parse failedn";
}
if (f != l)
std::cout << "Remaining unparsed: '" << std::string(f, l) << "'n";
}
指纹
[Doe][32]
[Smith][43]
- CMake-按正确顺序将项目与C运行时对象文件链接
- 函数调用中参数的顺序重要吗
- 为什么不;名字在地图上是按顺序排列的吗
- 将Integer转换为4字节的unsined字符矢量(按大端字节顺序)
- 数到第n个楼梯的路(顺序无关紧要)
- 正在将csv文件读取为双精度矢量
- 优先顺序:智能指针和类析构函数
- 在循环中按顺序遍历成员变量
- 独立读取-修改-写入顺序
- QML按钮点击功能执行顺序
- C++中数据类型修饰符的顺序
- 当比特(而不是字节)的顺序至关重要时的持久性
- C++从其他 constexpr 创建 lambda 不能按顺序执行 Constexpr
- 通过选项卡的文本设置QTabWidget顺序
- c++11评估顺序(未定义的行为)
- 如何在C++中递归地按相反顺序打印集合
- 给定顺序中的事件处理
- 具有包含其他对象的类的对象创建顺序
- 如何通过替换顺序代码的while循环来添加OpenMP for循环
- 提升精神解析 CSV,列顺序可变