使用 Boost::Spirit 的未知键解析 'key = value' 的列表

Parsing list of 'key = value' with unknown keys with Boost::Spirit

我有一个像下面这样的字符串:

[GENERAL]
FMax Antenna = 3000
FMin Antenna = 2000
Invalid key  = Invalid value
EMin Antenna = -50
EMax Antenna = 80

我想解析它以便将 FMin AntennaFMax AntennaEMin AntennaEMax Antenna 的值保存在一个结构中。我创建了一个 Spirit 解析器,但它只能部分工作。 由于文件可以有很多 key = value 行,我只需要解析我需要的(我必须读取的键值)而忽略其他对。

keyvalue 都可以是带空格和制表符的字母数字字符串。

我已经在解析器中定义了我想要读取的键,但是当我遇到一个未知键时,我无法读取后面的键(在示例中,我无法读取 EMin AntennaEMax Antenna 因为是在未知键之后定义的)。

我试过下面的代码:如果我解析 file1,它只包含我想读取的键,它可以工作,但是如果我在中间添加未知的 key = value 对该文件,如 file2 中一样,它会停止读取所有后续行。

如何修复它并在未知键值对后继续解析文件?

#include <boost/optional/optional_io.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;

const std::string file1 = R"xx(
[GENERAL]
FMax Antenna = 3000
FMin Antenna = 2000
EMin Antenna = -50
EMax Antenna = 80
)xx";

const std::string file2 = R"xx(
[GENERAL]
FMax Antenna = 3000
FMin Antenna = 2000
EMin Antenna = -50
pappa pio = po po
EMax Antenna = 80
Ciao = 55
)xx";

struct Data {
  double minFrequency = 0.0;
  double maxFrequency = 0.0;
  double minElevation = 0.0;
  double maxElevation = 0.0;
};

BOOST_FUSION_ADAPT_STRUCT(
  Data,
  (double, minFrequency)
  (double, maxFrequency)
  (double, minElevation)
  (double, maxElevation)
)

template <typename It, typename Skipper = qi::space_type>
struct grammar : qi::grammar<It, Data(), Skipper> {

  grammar() : grammar::base_type(start) {

    auto minFrequency = bind(&Data::minFrequency, qi::_val);
    auto maxFrequency = bind(&Data::maxFrequency, qi::_val);
    auto minElevation = bind(&Data::minElevation, qi::_val);
    auto maxElevation = bind(&Data::maxElevation, qi::_val);

    start = qi::no_case["[GENERAL]"] >> *(
      ("FMin Antenna" >> qi::lit('=') >> qi::int_)[minFrequency = qi::_1] |
      ("FMax Antenna" >> qi::lit('=') >> qi::int_)[maxFrequency = qi::_1] |
      ("EMin Antenna" >> qi::lit('=') >> qi::int_)[minElevation = qi::_1] |
      ("EMax Antenna" >> qi::lit('=') >> qi::int_)[maxElevation = qi::_1] |
      (+(qi::alnum | qi::blank) >> qi::lit('=') >> +(qi::alnum | qi::blank)) // Issue here?
    );
  }

private:

  qi::rule<It, Data(), Skipper> start;
};

int main() {
  using It = std::string::const_iterator;
  Data parsed1, parsed2;
  bool ok = qi::phrase_parse(file1.begin(), file1.end(), grammar<It>(), qi::space, parsed1);
  std::cout << "--- File 1 ---" << std::endl;
  std::cout << "parsed   = " << std::boolalpha << ok << std::endl;
  std::cout << "min freq = " << parsed1.minFrequency << std::endl;
  std::cout << "max freq = " << parsed1.maxFrequency << std::endl;
  std::cout << "min elev = " << parsed1.minElevation << std::endl;
  std::cout << "max elev = " << parsed1.maxElevation << std::endl;
  std::cout << "--- File 2 ---" << std::endl;
  ok = qi::phrase_parse(file2.begin(), file2.end(), grammar<It>(), qi::space, parsed2);
  std::cout << "parsed   = " << std::boolalpha << ok << std::endl;
  std::cout << "min freq = " << parsed2.minFrequency << std::endl;
  std::cout << "max freq = " << parsed2.maxFrequency << std::endl;
  std::cout << "min elev = " << parsed2.minElevation << std::endl;
  std::cout << "max elev = " << parsed2.maxElevation << std::endl;
  return 0;
}

输出:

--- File 1 ---
parsed   = true
min freq = 2000
max freq = 3000
min elev = -50
max elev = 80
--- File 2 ---
parsed   = true
min freq = 2000
max freq = 3000
min elev = -50
max elev = 0  <-- This should be 80 like in the first parsing

你对船长感到困惑。

  • 换行符在你的语法中很重要,这就是为什么你需要一个不吃它们的船长
  • 在你的规则中,你匹配 +(alnum|blank) 这永远不会起作用,因为无论如何船长都会吃掉所有匹配 blank 的东西

(背景见Boost spirit skipper issues

其他说明:

  • 除非你想要自动魔法属性传播,否则你不需要融合适应。您现在没有使用它。

解决它

我会把事情说得非常明确:

known =
    ("FMin Antenna" >> lit('=') >> int_)[minFrequency = _1] |
    ("FMax Antenna" >> lit('=') >> int_)[maxFrequency = _1] |
    ("EMin Antenna" >> lit('=') >> int_)[minElevation = _1] |
    ("EMax Antenna" >> lit('=') >> int_)[maxElevation = _1]
    ;

unknown = +alnum >> '=' >> +alnum;

setting = (known(_r1) | unknown) >> +eol;

start =
    no_case["[GENERAL]"] >> eol 
    >> *setting(_val);

按规则拆分有点棘手,因为 *setting 会尝试合成容器属性,从而无法传播到实际的 Data 属性。

我通过在继承属性中通过引用传递属性来解决它,这会禁用自动属性传播。

Alternatively, you could add a semantic action of any kind to inhibit automatic attribute propagation

演示

Live On Coliru

#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>

namespace qi = boost::spirit::qi;

struct Data {
    double minFrequency = 0.0;
    double maxFrequency = 0.0;
    double minElevation = 0.0;
    double maxElevation = 0.0;
};

template <typename It, typename Skipper = qi::blank_type>
struct grammar : qi::grammar<It, Data(), Skipper> {

    grammar() : grammar::base_type(start) {

        using namespace qi;
        auto minFrequency = bind(&Data::minFrequency, _r1);
        auto maxFrequency = bind(&Data::maxFrequency, _r1);
        auto minElevation = bind(&Data::minElevation, _r1);
        auto maxElevation = bind(&Data::maxElevation, _r1);

        known =
            ("FMin Antenna" >> lit('=') >> int_)[minFrequency = _1] |
            ("FMax Antenna" >> lit('=') >> int_)[maxFrequency = _1] |
            ("EMin Antenna" >> lit('=') >> int_)[minElevation = _1] |
            ("EMax Antenna" >> lit('=') >> int_)[maxElevation = _1]
            ;

        unknown = +alnum >> '=' >> +alnum;

        setting = (known(_r1) | unknown) >> +eol;

        start =
            no_case["[GENERAL]"] >> eol 
            >> *setting(_val);
    }

  private:
    qi::rule<It, Data(), Skipper> start;
    qi::rule<It, void(Data&), Skipper> setting, known;
    qi::rule<It, Skipper> unknown;
};

int main() {
    using It = std::string::const_iterator;
    grammar<It> const g;

    for (std::string const file : {
            "[GENERAL]\nFMax Antenna = 3000\nFMin Antenna = 2000\nEMin Antenna = -50\nEMax Antenna = 80\n",
            "[GENERAL]\nFMax Antenna = 3000\nFMin Antenna = 2000\nEMin Antenna = -50\npappa pio = po po\nEMax Antenna = 80\nCiao = 55\n",
        })
    {
        Data parsed;
        It f = begin(file), l = end(file);
        bool ok = qi::phrase_parse(f, l, g, qi::blank, parsed);

        std::cout << "--- File ---" << "\n";
        std::cout << "parsed   = " << std::boolalpha << ok << "\n";
        if (ok) {
            std::cout << "min freq = " << parsed.minFrequency << "\n";
            std::cout << "max freq = " << parsed.maxFrequency << "\n";
            std::cout << "min elev = " << parsed.minElevation << "\n";
            std::cout << "max elev = " << parsed.maxElevation << "\n";
        }

        if (f!=l) {
            std::cout << "Remaining unparsed: ";
            while (f!=l) {
                char c = *f++;
                if (isprint(c)) std::cout << c;
                else std::cout << "\x" << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(c);
            }
        }
    }
}

版画

--- File ---
parsed   = true
min freq = 2000
max freq = 3000
min elev = -50
max elev = 80
--- File ---
parsed   = true
min freq = 2000
max freq = 3000
min elev = -50
max elev = 80