2015-03-08 161 views
1

读空值我想读一个CSV到一个结构:与升压::精神

struct data 
{ 
    std::string a; 
    std::string b; 
    std::string c; 
} 

不过,我想读甚至空字符串,以确保所有值都各得其所。 我适应该结构到一个boost ::融合,所以下面的工作:

// Our parser (using a custom skipper to skip comments and empty lines) 
template <typename Iterator, typename skipper = comment_skipper<Iterator> > 
    struct google_parser : qi::grammar<Iterator, addressbook(), skipper> 
{ 
    google_parser() : google_parser::base_type(contacts, "contacts") 
    { 
    using qi::eol; 
    using qi::eps; 
    using qi::_1; 
    using qi::_val; 
    using qi::repeat; 
    using standard_wide::char_; 
    using phoenix::at_c; 
    using phoenix::val; 

    value = *(char_ - ',' - eol) [_val += _1]; 

    // This works but only for small structs 
    entry %= value >> ',' >> value >> ',' >> value >> eol; 
    } 

    qi::rule<Iterator, std::string()> value; 
    qi::rule<Iterator, data()> entry; 
}; 

不幸的是,repeat存储在向量中的所有非空值,从而属性的值可以被混合在一起(即,如果场为b为空,也可能包含来自c内容):

entry %= repeat(2)[ value >> ','] >> value >> eol; 

我想用类似repeat短规则为我的结构在实践60个属性!不仅是编写60条规则乏味,但似乎Boost不喜欢长规则...

+0

我注意到 - 在编写答案之后 - 你认为输入是CSV。请参阅[如何使用Spirit解析CSV](http://stackoverflow.com/questions/18365463/h/18366335#18366335)和[this other answer](http://stackoverflow.com/questions/7436481/h/) 7462539#7462539)(以及[适用于映射文件的零拷贝分析](http://stackoverflow.com/questions/23699731/s/23703810#23703810))。还有一个映射列:[提高精神解析CSV与变量顺序列](http://stackoverflow.com/questions/27967195/b/27967473#27967473)。为了您的灵感 – sehe 2015-03-09 00:55:03

+0

sehe,感谢您的深入解答。你不仅非常清楚,而且你不厌其烦地写出完整的例子。像你这样的人让stackoverflow值得。 – 2015-03-09 20:35:04

回答

2

你只是想确保你解析“空”字符串的值。

value = +(char_ - ',' - eol) | attr("(unspecified)"); 
entry = value >> ',' >> value >> ',' >> value >> eol; 

观看演示:

Live On Coliru

//#define BOOST_SPIRIT_DEBUG 
#include <boost/fusion/adapted/struct.hpp> 
#include <boost/spirit/include/qi.hpp> 

namespace qi = boost::spirit::qi; 

struct data { 
    std::string a; 
    std::string b; 
    std::string c; 
}; 

BOOST_FUSION_ADAPT_STRUCT(data, (std::string, a)(std::string, b)(std::string, c)) 

template <typename Iterator, typename skipper = qi::blank_type> 
struct google_parser : qi::grammar<Iterator, data(), skipper> { 
    google_parser() : google_parser::base_type(entry, "contacts") { 
     using namespace qi; 

     value = +(char_ - ',' - eol) | attr("(unspecified)"); 
     entry = value >> ',' >> value >> ',' >> value >> eol; 

     BOOST_SPIRIT_DEBUG_NODES((value)(entry)) 
    } 
    private: 
    qi::rule<Iterator, std::string()> value; 
    qi::rule<Iterator, data(), skipper> entry; 
}; 

int main() { 
    using It = std::string::const_iterator; 
    google_parser<It> p; 

    for (std::string input : { 
      "something, awful, is\n", 
      "fine,,just\n", 
      "like something missing: ,,\n", 
     }) 
    { 
     It f = input.begin(), l = input.end(); 

     data parsed; 
     bool ok = qi::phrase_parse(f,l,p,qi::blank,parsed); 

     if (ok) 
      std::cout << "Parsed: '" << parsed.a << "', '" << parsed.b << "', '" << parsed.c << "'\n"; 
     else 
      std::cout << "Parse failed\n"; 

     if (f!=l) 
      std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n"; 
    } 
} 

打印:

Parsed: 'something', 'awful', 'is' 
Parsed: 'fine', '(unspecified)', 'just' 
Parsed: 'like something missing: ', '(unspecified)', '(unspecified)' 

但是,你有一个更大的问题。假设qi::repeat(2) [ value ]将解析为2个字符串不起作用。

repeat,像operator*,operator+operator%解析成容器属性。在这种情况下,容器属性(字符串)将接收输入从第二value还有:

Live On Coliru

Parsed: 'somethingawful', 'is', '' 
Parsed: 'fine(unspecified)', 'just', '' 
Parsed: 'like something missing: (unspecified)', '(unspecified)', '' 

由于这是不是你想要的,考虑你的数据类型:

auto_方法:

如果您教Qi如何提取单个值,您可以使用一个简单的规则,如

entry = skip(skipper() | ',') [auto_] >> eol; 

这样一来,Spirit本身就会为给定的Fusion序列生成正确数量的值提取!

这里有一个快速的脏方法:

CAVEAT专业为std::string直接像这可能不是最好的主意(它可能并不总是合适的,并可能与其他解析器严重交互)。然而,在默认情况下create_parser<std::string>未定义(因为,​​它会做什么?),所以我抓住了机会,这个演示的目的:

namespace boost { namespace spirit { namespace traits { 
    template <> struct create_parser<std::string> { 
     typedef proto::result_of::deep_copy< 
      BOOST_TYPEOF(
       qi::lexeme [+(qi::char_ - ',' - qi::eol)] | qi::attr("(unspecified)") 
      ) 
     >::type type; 

     static type call() { 
      return proto::deep_copy(
       qi::lexeme [+(qi::char_ - ',' - qi::eol)] | qi::attr("(unspecified)") 
      ); 
     } 
    }; 
}}} 

再次,看演示输出:

Live On Coliru

Parsed: 'something', 'awful', 'is' 
Parsed: 'fine', 'just', '(unspecified)' 
Parsed: 'like something missing: ', '(unspecified)', '(unspecified)' 

备注有些先进的魔法让船长“恰到好处”工作(见skip()[]lexeme[])。一些一般性的解释可以在这里找到:Boost spirit skipper issues

UPDATE

容器方法

有一个微妙了这一点。其实两个。所以这里有一个演示:

Live On Coliru

//#define BOOST_SPIRIT_DEBUG 
#include <boost/fusion/adapted/struct.hpp> 
#include <boost/spirit/include/qi.hpp> 

namespace qi = boost::spirit::qi; 

struct data { 
    std::vector<std::string> parts; 
}; 

BOOST_FUSION_ADAPT_STRUCT(data, (std::vector<std::string>, parts)) 

template <typename Iterator, typename skipper = qi::blank_type> 
struct google_parser : qi::grammar<Iterator, data(), skipper> { 
    google_parser() : google_parser::base_type(entry, "contacts") { 
     using namespace qi; 
     qi::as<std::vector<std::string> > strings; 

     value = +(char_ - ',' - eol) | attr("(unspecified)"); 
     entry = strings [ repeat(2) [ value >> ',' ] >> value ] >> eol; 

     BOOST_SPIRIT_DEBUG_NODES((value)(entry)) 
    } 
    private: 
    qi::rule<Iterator, std::string()> value; 
    qi::rule<Iterator, data(), skipper> entry; 
}; 

int main() { 
    using It = std::string::const_iterator; 
    google_parser<It> p; 

    for (std::string input : { 
      "something, awful, is\n", 
      "fine,,just\n", 
      "like something missing: ,,\n", 
     }) 
    { 
     It f = input.begin(), l = input.end(); 

     data parsed; 
     bool ok = qi::phrase_parse(f,l,p,qi::blank,parsed); 

     if (ok) { 
      std::cout << "Parsed: "; 
      for (auto& part : parsed.parts) 
       std::cout << "'" << part << "' "; 
      std::cout << "\n"; 
     } 
     else 
      std::cout << "Parse failed\n"; 

     if (f!=l) 
      std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n"; 
    } 
} 

的精妙之处是:

+0

添加了让Qi从适应融合结构中生成适当解析器的方法:** ['entry = skip(skipper()|',')[auto_] >> eol;'](http:// coliru。 stacked-crooked.com/a/30541461bab7e1a1)** – sehe 2015-03-09 00:48:51

+0

我想保留我的结构以备后用,所以我在容器和'auto_'方法之间犹豫不决。不幸的是,关于容器的文档有点令人恐惧,缺乏示例。所以我更喜欢'auto_',但所有代码对我来说都像是黑魔法:) – 2015-03-09 20:36:14

+0

我个人建议通过将AST类型与您希望使用的语法进行匹配来简化。在这里显示的auto_方法中已经存在一些限制(关于相邻分隔符呢?),这将终结梦想的自动语法分析器生成。 – sehe 2015-03-09 20:49:22