c++ - 为什么用 Spirit 解析一个空行会在 map 中产生一个空的键值对?

标签 c++ parsing boost key-value boost-spirit

我正在尝试使用 Spirit.Qi 来解析一种简单的文件格式,该文件格式具有以等号分隔的键值对。该文件还支持注释和空行,以及引用值。

我几乎可以让所有这些按预期工作,但是,任何空白行或注释都会导致将空键值对添加到 map 中。将 map 换成 vector 时,不会产生空白条目。

示例程序:

#include <fstream> 
#include <iostream> 
#include <string> 
#include <map> 

#include "boost/spirit/include/qi.hpp" 
#include "boost/spirit/include/karma.hpp" 
#include "boost/fusion/include/std_pair.hpp" 

using namespace boost::spirit; 
using namespace boost::spirit::qi; 

//////////////////////////////////////////////////////////////////////////////// 
int main(int argc, char** argv) 
{ 
   std::ifstream ifs("file"); 
   ifs >> std::noskipws; 

   std::map< std::string, std::string > vars; 

   auto value = as_string[*print]; 
   auto quoted_value = as_string[lexeme['"' >> *(print-'"') >> '"']]; 
   auto key = as_string[alpha >> *(alnum | char_('_'))]; 
   auto kvp = key >> '=' >> (quoted_value | value); 

   phrase_parse( 
      istream_iterator(ifs), 
      istream_iterator(), 
      -kvp % eol, 
      ('#' >> *(char_-eol)) | blank, 
      vars); 

   std::cout << "vars[" << vars.size() << "]:" << std::endl; 
   std::cout << karma::format(*(karma::string << " -> " << karma::string << karma::eol), vars); 

   return 0; 
}

输入文件:

one=two
three=four

# Comment
five=six

输出:

vars[4]:
 ->
one -> two
three -> four
five -> six

空键值对从何而来?我怎样才能防止它被生成?

最佳答案

首先,你的程序有未定义的行为(事实上它在我的系统上崩溃了)。原因是您不能使用 auto 表达式来存储有状态的解析器表达式。

See Assigning parsers to auto variables, boost spirit V2 qi bug associated with optimization level and others. See e.g. these answers for useful strategies to get around this limitation.

其次,空行是因为语法。

有区别

  (-kvp) % qi::eol

  -(kvp % qi::eol)

第一个将导致“可选地解析 kvp”,然后是“将结果插入属性容器”。

后者将可选地“将 1 个或多个 kvp 解析到一个容器中”。请注意,如果不匹配,这不会推送空值。

固定/演示

我建议

  • 同时制作 keyvalue 词位(实际上只需将 Skipper 放在规则声明中);您可能不希望将 'key name 1=value 1 解析为 "keyname1"-> "value1"。您可能也不想允许 key # no value\n
  • 使用 BOOST_SPIRIT_DEBUG 查看发生了什么
  • 不是一揽子使用命名空间 boost::spirit。这是个坏主意。相信我:/
  • 规则声明可能看起来很冗长,但它们确实减少了规则定义中的麻烦
  • 使用 +eol 而不是 eol 允许空行,这似乎是你想要的

Live On Coliru

#define BOOST_SPIRIT_DEBUG
#include "boost/spirit/include/qi.hpp" 
#include "boost/spirit/include/karma.hpp" 
#include "boost/fusion/include/std_pair.hpp" 
#include <fstream> 
#include <map> 

namespace qi    = boost::spirit::qi;
namespace karma = boost::spirit::karma;

template <typename It, typename Skipper, typename Data>
struct kvp_grammar : qi::grammar<It, Data(), Skipper> {
    kvp_grammar() : kvp_grammar::base_type(start) {
        using namespace qi;

        value        = raw [*print];
        quoted_value = '"' >> *~char_('"') >> '"';
        key          = raw [ alpha >> *(alnum | '_') ];

        kvp          = key >> '=' >> (quoted_value | value);
        start        = -(kvp % +eol);

        BOOST_SPIRIT_DEBUG_NODES((value)(quoted_value)(key)(kvp))
    }
  private:
    using Pair = std::pair<std::string, std::string>;
    qi::rule<It, std::string(), Skipper> value;
    qi::rule<It, Pair(),        Skipper> kvp;
    qi::rule<It, Data(),        Skipper> start;
    // lexeme:
    qi::rule<It, std::string()> quoted_value, key;
};

template <typename Map>
bool parse_vars(std::istream& is, Map& data) {
    using It = boost::spirit::istream_iterator;
    using Skipper = qi::rule<It>;

    kvp_grammar<It, Skipper, Map> grammar;
    It f(is >> std::noskipws), l;

    Skipper skipper = ('#' >> *(qi::char_-qi::eol)) | qi::blank;
    return qi::phrase_parse(f, l, grammar, skipper, data); 
}

int main() { 
    std::ifstream ifs("input.txt"); 

    std::map<std::string, std::string> vars; 

    if (parse_vars(ifs, vars)) {
        std::cout << "vars[" << vars.size() << "]:" << std::endl; 
        std::cout << karma::format(*(karma::string << " -> " << karma::string << karma::eol), vars); 
    }
}

输出(目前在 Coliru 上损坏):

vars[3]:
five -> six
one -> two
three -> four

带有调试信息:

<kvp>
  <try>one=two\nthree=four\n\n</try>
  <key>
    <try>one=two\nthree=four\n\n</try>
    <success>=two\nthree=four\n\n# C</success>
    <attributes>[[o, n, e]]</attributes>
  </key>
  <quoted_value>
    <try>two\nthree=four\n\n# Co</try>
    <fail/>
  </quoted_value>
  <value>
    <try>two\nthree=four\n\n# Co</try>
    <success>\nthree=four\n\n# Comme</success>
    <attributes>[[t, w, o]]</attributes>
  </value>
  <success>\nthree=four\n\n# Comme</success>
  <attributes>[[[o, n, e], [t, w, o]]]</attributes>
</kvp>
<kvp>
  <try>three=four\n\n# Commen</try>
  <key>
    <try>three=four\n\n# Commen</try>
    <success>=four\n\n# Comment\nfiv</success>
    <attributes>[[t, h, r, e, e]]</attributes>
  </key>
  <quoted_value>
    <try>four\n\n# Comment\nfive</try>
    <fail/>
  </quoted_value>
  <value>
    <try>four\n\n# Comment\nfive</try>
    <success>\n\n# Comment\nfive=six</success>
    <attributes>[[f, o, u, r]]</attributes>
  </value>
  <success>\n\n# Comment\nfive=six</success>
  <attributes>[[[t, h, r, e, e], [f, o, u, r]]]</attributes>
</kvp>
<kvp>
  <try>five=six\n</try>
  <key>
    <try>five=six\n</try>
    <success>=six\n</success>
    <attributes>[[f, i, v, e]]</attributes>
  </key>
  <quoted_value>
    <try>six\n</try>
    <fail/>
  </quoted_value>
  <value>
    <try>six\n</try>
    <success>\n</success>
    <attributes>[[s, i, x]]</attributes>
  </value>
  <success>\n</success>
  <attributes>[[[f, i, v, e], [s, i, x]]]</attributes>
</kvp>
<kvp>
  <try></try>
  <key>
    <try></try>
    <fail/>
  </key>
  <fail/>
</kvp>

关于c++ - 为什么用 Spirit 解析一个空行会在 map 中产生一个空的键值对?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29506665/

相关文章:

c++ - 如何在一行上连接多个 C++ 字符串?

php - 检查 php 是否有解析错误而不进行处理

python - 一个方法如何调用它在 python 中所属的类?

Python:将带有 '|'的文本文件解析到MySql表

c++ - 如何使用 Boost Graph Library 将对象附加到图形的节点和边缘?

c++ - boost 宣传 - 需要帮助

c++ - 要链接到哪个 boost 库?

c++ - 更新标签文本 GTK+ C++

C++ std::string 和 NULL const char*

c++ - Lua:加载第二个字符串后无法获取字段;