使用 boost-spirit 解析简单的 csv table

Parsing simple csv table with boost-spirit

我试图使用 boost-spirit 来解析一个相当简单的 cvs 文件格式。

我的 csv 文件如下所示:

Test.txt

2
5. 3. 2.
6. 3. 6.

第一个整数表示要读取的行数,每行恰好包含三个双精度值。

这是我目前得到的。

main.cpp

#include <vector>
#include <fstream>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/phoenix/object/construct.hpp>

std::vector<std::vector<double>> parseFile(std::string& content)
{
    std::vector<std::vector<double>> ret;
    using namespace boost::phoenix;
    using namespace boost::spirit::qi;
    using ascii::space;

    int no;
    phrase_parse(content.begin(), content.end(),
        int_[ref(no) = _1] >> repeat(ref(no))[(double_ >> double_ >> double_)[
            std::cout << _1 << _2 << _3
        ]], space
    );
    return ret;
}

int main(int arg, char **args) {
    auto ifs = std::ifstream("Test.txt");
    std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
    parseFile(content);
}

现在,我需要添加包含三个值的 std::vector<double>,而不是行 std::cout << _1 << _2 << _3

我已经尝试过 _val=construct<std::vector<double>>({_1,_2,_3}),但它不起作用。那我做错了什么?

它比你想象的要简单得多¹

std::vector<std::vector<double>> parseFile(std::string const& content) {
    namespace px = boost::phoenix; 
    using namespace boost::spirit::qi;

    int no;
    std::vector<std::vector<double>> data;

    bool ok = phrase_parse(content.begin(), content.end(),
        int_ [ px::ref(no) = _1 ] >> eol 
        >> repeat(px::ref(no)) [ repeat(3) [double_] >> (eol|eoi)],
        blank,
        data
    );

    if (!ok)
        throw std::runtime_error("Parse failure");

    return data;
}

看到了Live On Coliru。它使用自动属性传播——Spirit 最有用的一个功能——和 blank skipper 而不是 space 所以我们仍然可以解析 eol

现在,我建议让它更简单:

Live On Coliru

bool ok = phrase_parse(
    content.begin(), content.end(),
    int_ >> eol >> *(+double_ >> (eol|eoi)) >> *eol >> eoi, 
    blank, 
    no, data
);

if (!ok && (no == data.size()))
    throw std::runtime_error("Parse failure");

或者,事实上,仅使用标准库更简单:

Live On Coliru

#include <vector>
#include <iostream>
#include <fstream>
#include <iterator>

std::vector<std::vector<double>> parseFile(std::string const& fname) {
    std::vector<std::vector<double>> data;

    auto ifs = std::ifstream(fname);

    size_t no = -1;
    if (ifs >> no && ifs.ignore(1024, '\n')) {
        double a, b, c;
        while (ifs >> a >> b >> c)
            data.push_back({a, b, c});
    }

    if (!(ifs.eof() && (no == data.size())))
        throw std::runtime_error("Parse failure");

    return data;
}

int main() {
    for (auto& row : parseFile("input.txt"))
        std::copy(row.begin(), row.end(), std::ostream_iterator<double>(std::cout << "\n", " "));
}

全部成功解析打印:

5 3 2 
6 3 6 

¹ Boost Spirit: "Semantic actions are evil"?

奖励:自动自定义属性类型

为什么不使用像

这样的结构而不是不透明的向量
struct Point {
    double x,y,z;
};

并解析为std::vector<Point>。作为奖励,您基本上可以免费获得输出和解析器:

BOOST_FUSION_ADAPT_STRUCT(Point, x, y, z)

// parser:
auto_ >> eol >> *(auto_ >> (+eol|eoi)) >> eoi,

看到了Live On Coliru