【问题标题】:boost spirit parsing CSV with columns in variable order使用可变顺序的列来提升精神解析 CSV
【发布时间】:2015-01-15 15:40:43
【问题描述】:

我正在尝试使用 boost spirit 解析一个 CSV 文件(带有标题行)。 csv 不是固定格式。有时有一些额外的列或列的顺序是混合的。我对几列感兴趣,它们的标题名称是众所周知的。

例如,我的 CSV 可能如下所示:

姓名、姓氏、年龄 约翰,能源部,32

或者:

年龄、姓名 32,约翰

我只想解析 NameAge 的内容(注意,Age 是整数类型)。目前我提出了一个非常丑陋的解决方案,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;

};

这里是full source

如果头解析器可以准备一个vector&lt;rule&lt;...&gt;*&gt; 而“行解析器”只使用这个向量来解析自己呢?一种高级的nabialek trick(我一直在尝试,但我做不到)。

或者有没有更好的方法来用 Spirit 解析这种 CSV? (任何帮助表示赞赏,在此先感谢您)

【问题讨论】:

    标签: c++ csv boost boost-spirit


    【解决方案1】:

    我会接受你的概念,

    我认为它非常优雅(qi 当地人甚至允许重入使用它)。

    为了减少规则中的繁琐(Boost Spirit: "Semantic actions are evil"?),您可以将“转换功能”移到属性转换中自定义点 .

    哎呀。正如所评论的那样,这太简单了。但是,您仍然可以大大减少粗糙度。通过两个简单的调整,语法如下:

    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])(参见 boost spirit semantic action parameters):

      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(&amp;CsvGrammar&lt;It&gt;::convertFunc, this, qi::_1, qi::_r1, qi::_val),但将auto 与Proto/Phoenix/Spirit 表达式一起使用是出了名的容易出错(UB 由于悬空引用到表达式模板中的临时变量),所以我当然更喜欢方式如上。

    Live On Coliru

    //#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,32\nMark,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 failed\n";
        }
    
        if (f != l)
            std::cout << "Remaining unparsed: '" << std::string(f, l) << "'\n";
    }
    

    打印

    [Doe][32]
    [Smith][43]
    

    【讨论】:

    • 抱歉,我找不到将额外参数传递给assign_to_attribute_from_value 的方法。为了成功地将vector&lt;string&gt; 解析到结构中,我还必须知道字段在行中的位置(std::vector&lt;LineItems&gt;&amp; positions 参数)。
    • 你可以绑定一个仿函数。我稍后会告诉你。遗憾的是,至少在接下来的 6 个小时内没有时间(您尝试已经非常令人沮丧,而且您已经走到了这一步。我很乐意提供帮助)
    • 好的,我回来了。我刚刚意识到我确实做了一个 thinko(忘记了转换特征是纯静态的)。您可以使用“原始语义角色”对phx::bind 加糖,但当然您总是需要规则上下文。一会儿我会发帖。
    • 查看更新。很抱歉之前提出了太多期望:S
    • 非常感谢您的回答和链接。这是对语义动作的有趣使用。虽然我将继续研究 Spirit 是否可用于解析文件,其中(复杂)标题描述了文件其余部分中要使用的语法。我缺乏处理这种情况的能力是项目经理对 Spirit 严厉打击的一点。为了您的好奇心,我正在编写一个用于数值模拟的语言之间的转换器(Abaqus Nastran Samcef Code Aster)。我们将不得不再等一下“Sehe 把戏”。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多