【问题标题】:Read empty values with boost::spirit使用 boost::spirit 读取空值
【发布时间】:2015-03-08 20:25:59
【问题描述】:

我想将 CSV 读入结构:

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

但是,我什至想读取空字符串,以确保所有值都在正确的位置。 我将结构调整为 boost::fusion,所以以下工作:

// 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 似乎不喜欢长规则......

【问题讨论】:

标签: c++ boost boost-spirit


【解决方案1】:

您只想确保您也解析“空”字符串的值。

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&lt;std::string&gt; 没有定义(因为,​​它会做什么?)所以我抓住了这个演示的机会:

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

更新

容器方法

这很微妙。实际上是两个。所以这里有一个演示:

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";
    }
}

微妙之处在于:

【讨论】:

  • 添加了让 Qi 从那里适应的融合结构生成适当解析器的方法:entry = skip(skipper() | ',') [auto_] &gt;&gt; eol;
  • 我想保留我的结构以供以后使用,所以我在容器和您的 auto_ 方法之间犹豫不决。不幸的是,关于容器的文档有点吓人并且缺乏示例。所以我更倾向于去auto_,但你所有的代码对我来说似乎都是黑魔法:)
  • 我个人建议通过将 AST 类型与您希望使用的语法相匹配来简化。此处显示的 auto_ 方法已经存在一些限制(相邻分隔符呢?),这将结束自动生成解析器的梦想。
  • 使用容器属性的方法几乎在所有方面都更简单。
  • @alex_reader 我昨天在手机上。我在答案中添加了一个容器示例,因为仍然存在一些细微之处(请参阅说明文本)。即使有了这些,它也比 create_parser 的“hack”简单!
猜你喜欢
  • 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
相关资源
最近更新 更多