使用 Boost Spirit 将 INI 文件解析为结构时出现问题

问题描述 投票:0回答:1

我正在尝试使用以下代码将 INI 文件信息存储在结构中:

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iostream>
#include <map>
#include <vector>

using Key       = std::string;
using Value     = std::string;
using Section   = std::map<Key, Value>;

namespace qi = boost::spirit::qi;

namespace client 
{
    struct IniSection 
    {
        std::string name;
        Section     pair;
    };

    struct IniFile 
    {
        std::vector<IniSection> sections;
    };
};

namespace client 
{
    template <typename Iterator> 
    struct ini_grammar : qi::grammar<Iterator, IniFile()> 
    {
        ini_grammar() : ini_grammar::base_type(start) 
        {
            skipper = qi::blank | '#' >> *(qi::char_ - qi::eol);

            key     = +qi::char_("a-zA-Z_0-9");
            value   = *(qi::char_ - qi::eol);
            pair    = key >> '=' >> value;
            section = '[' >> key >> ']' >> +qi::eol >> *(pair >> +qi::eol);
            file    = *section;

            start   = qi::skip(copy(skipper))[file];

        }
        using Skipper = qi::rule<Iterator>;
        using KVP = std::pair<Key, Value>;
        Skipper skipper;

        
        qi::rule<Iterator, IniFile()>           start;
        qi::rule<Iterator, IniFile(), Skipper>  file;
        qi::rule<Iterator, IniSection()>        section;
        qi::rule<Iterator, KVP()>               pair;

        qi::rule<Iterator> value;
        qi::rule<Iterator> key;
    };
} 

int main() 
{
    std::string const ini_section = 
        R"([Section]
        key1 = value1
        key2 = value2
)";

    using It = std::string::const_iterator;
    client::ini_grammar<It> grammar;
    client::IniFile iniFile;

    It iter = ini_section.begin(), end = ini_section.end();
    bool r = parse(iter, end, grammar, iniFile);

    if (iter == end) 
    {
        std::cout << "-------------------------\n";
        std::cout << "Parsing succeeded\n";
        std::cout << "-------------------------\n";

        for (const auto& section : iniFile.sections)
        {
            std::cout << "[" << section.name << "]\n";
            for (const auto& kvp : section.pair)
            {
                std::cout << kvp.first << " = " << kvp.second << "\n";
            }
        }
    } 
    else 
    {
        std::cout << "-------------------------\n";
        std::cout << "Parsing failed\n";
        std::cout << "Stopped at position: " << std::distance(ini_section.begin(), iter) << std::endl;
        std::cout << "\nRemaining input unparsed:\n" << std::string(iter, end);
        std::cout << "-------------------------\n";
    }
}

问题是代码输出了一个巨大的错误,而我什么也不明白。我认为大部分错误与解决问题无关,因此我将给出我认为重要的部分。

错误

boost_1_84_0/boost/spirit/home/support/container.hpp:130:12: error: no type named ‘value_type’ in ‘struct client::IniFile’
  130 |     struct container_value
      |            ^~~~~~~~~~~~~~~

boost_1_84_0/boost/spirit/home/qi/detail/pass_container.hpp:320:66: error: no type named ‘type’ in ‘struct boost::spirit::traits::container_value<client::IniFile, void>’
  320 |             typedef typename traits::container_value<Attr>::type value_type;
      |                                                                  ^~~~~~~~~~
boost_1_84_0/boost/spirit/home/qi/detail/pass_container.hpp:333:15: error: no type named ‘type’ in ‘struct boost::spirit::traits::container_value<client::IniFile, void>’
  333 |             > predicate;
      |               ^~~~~~~~~

boost_1_84_0/boost/spirit/home/support/container.hpp:130:12: error: no type named ‘value_type’ in ‘struct client::IniSection’
  130 |     struct container_value
      |            ^~~~~~~~~~~~~~~

boost/boost_1_84_0/boost/spirit/home/qi/detail/pass_container.hpp:320:66: error: no type named ‘type’ in ‘struct boost::spirit::traits::container_value<client::IniSection, void>’
  320 |             typedef typename traits::container_value<Attr>::type value_type;
      |
boost/boost_1_84_0/boost/spirit/home/qi/detail/pass_container.hpp:333:15: error: no type named ‘type’ in ‘struct boost::spirit::traits::container_value<client::IniSection, void>’
  333 |             > predicate;
      |               ^~~~~~~~~

我使用此问题的答案中提供的代码作为参考。我尝试使用

BOOST_FUSION_ADAPT_STRUCT
来解决该问题,但我认为这与问题无关,它只是造成了更多错误。

c++ parsing boost ini boost-spirit
1个回答
0
投票

看起来自从上次以来你一直在改变事情。一些转变引起了混乱。

例如:

using Section = std::map<Key, Value>;

struct IniSection {
    std::string name;
    Section     pair;
};

成员名称

pair
与其类型
Section
之间存在严重脱节。

现在我将 100% 使用像我之前展示的关联容器,而不是

std::vector<IniSection>

using IniFile = std::multimap<Key, Section>;

但是我们暂时假设您确实想保留部分的输入顺序(显然不是键)。逻辑上的改变是

using IniFile = std::pair<Key, Section>;

引入你自己的类型

IniSection
IniFile
意味着你必须教Qi如何向它们传播属性。我可以向您展示这一点,但首先让我们保持简单:

namespace client {
    using Key     = std::string;
    using Value   = std::string;
    using Entries = std::map<Key, Value>;
    using Section = std::pair<Key, Entries>;
    using IniFile = std::vector<Section>;
}; // namespace client

但是,不知何故,你的

value
key
规则已经放弃了它们的属性。这很简单意味着他们根本无法暴露自己的属性:

    qi::rule<Iterator> value;
    qi::rule<Iterator> key;

因此不会再发生自动传播,因为没有任何东西可以传播。根据定义,所有数据结构都与“无属性”不兼容。首先从我之前的回答中恢复这些:

    // lexemes
    qi::rule<Iterator, Key()>   key;
    qi::rule<Iterator, Value()> value;

现在你包括

#include <boost/fusion/include/adapt_struct.hpp>

这也意味着您停止了包括

#include <boost/fusion/include/std_pair.hpp>

我们也从我之前的回答中恢复一下:

#include <boost/fusion/adapted.hpp> // includes both

此时再次编译,但它不再解析整个部分。仔细观察,那是因为您将船长从

IniSection
KVP
规则中删除了。 (为什么?)

    qi::rule<Iterator, IniSection()>        section;
    qi::rule<Iterator, KVP()>               pair;

从我之前的答案中恢复,我们得到了完整的解析:

住在Coliru

#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iostream>
#include <map>

namespace client {
    using Key     = std::string;
    using Value   = std::string;
    using Entries = std::map<Key, Value>;
    using Section = std::pair<Key, Entries>;
    using IniFile = std::vector<Section>;
}; // namespace client

namespace client {
    namespace qi = boost::spirit::qi;

    template <typename Iterator> struct ini_grammar : qi::grammar<Iterator, IniFile()> {
        ini_grammar() : ini_grammar::base_type(start) {
            skipper = qi::blank | '#' >> *(qi::char_ - qi::eol);

            key     = +qi::char_("a-zA-Z_0-9");
            value   = *(qi::char_ - qi::eol);
            entry   = key >> '=' >> value;
            section = '[' >> key >> ']' >> +qi::eol >> *(entry >> +qi::eol);
            file    = *section;

            start   = qi::skip(copy(skipper))[file];
        }
        using Skipper = qi::rule<Iterator>;
        using Entry   = std::pair<Key, Value>;
        Skipper skipper;

        qi::rule<Iterator, IniFile()>          start;
        qi::rule<Iterator, IniFile(), Skipper> file;
        qi::rule<Iterator, Section(), Skipper> section;
        qi::rule<Iterator, Entry(), Skipper>   entry;

        qi::rule<Iterator, Key()>   value;
        qi::rule<Iterator, Value()> key;
    };
} // namespace client

int main() {
    for (std::string const input : {
             R"([Section]
                key1 = value1
                key2 = value2

                [Section10]
                key3 = value3

                [Section3] # let's see that order is preserved
                key4 = value4
        )",
         }) {

        std::cout << "-------------------------\n";
        using It = std::string::const_iterator;
        client::ini_grammar<It> grammar;
        client::IniFile         iniFile;

        It   iter = input.begin(), end = input.end();
        bool r = parse(iter, end, grammar, iniFile);

        if (r) {
            std::cout << "Parsing succeeded\n";

            for (auto const& [name, entries] : iniFile) {
                std::cout << "[" << name << "]\n";
                for (auto const& [k,v] : entries) {
                    std::cout << k << " = " << v << "\n";
                }
            }
        } else {
            std::cout << "Parsing failed\n";
        }

        if (iter!=end) {
            std::cout << "Stopped at position: " << std::distance(input.begin(), iter) << std::endl;
            std::cout << "\nRemaining input unparsed:\n" << std::string(iter, end);
        }
    }
}

印刷

-------------------------
Parsing succeeded
[Section]
key1 = value1
key2 = value2
[Section10]
key3 = value3
[Section3]
key4 = value4

旁注

您甚至不再检查 main 中的解析结果(

r
)。您也不会将 检查
eoi
合并到解析器中。添加最后的润色:

住在Coliru

#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iostream>
#include <map>

namespace client {
    using Key     = std::string;
    using Value   = std::string;
    using Entries = std::map<Key, Value>;
    using Section = std::pair<Key, Entries>;
    using IniFile = std::vector<Section>;
}; // namespace client

namespace client {
    namespace qi = boost::spirit::qi;

    template <typename Iterator> struct ini_grammar : qi::grammar<Iterator, IniFile()> {
        ini_grammar() : ini_grammar::base_type(start) {
            skipper = qi::blank | '#' >> *(qi::char_ - qi::eol);

            key     = +qi::char_("a-zA-Z_0-9");
            value   = *(qi::char_ - qi::eol);
            entry   = key >> '=' >> value >> (+qi::eol | qi::eoi);
            section = '[' >> key >> ']' >> (+qi::eol | qi::eoi) >> *entry;
            file    = *section;

            start = qi::skip(copy(skipper))[ //
                file >> qi::eoi              //
            ];
        }

      private:
        using Skipper = qi::rule<Iterator>;
        using Entry   = std::pair<Key, Value>;
        Skipper skipper;

        qi::rule<Iterator, IniFile()>          start;
        qi::rule<Iterator, IniFile(), Skipper> file;
        qi::rule<Iterator, Section(), Skipper> section;
        qi::rule<Iterator, Entry(), Skipper>   entry;

        qi::rule<Iterator, Key()>   value;
        qi::rule<Iterator, Value()> key;
    };
} // namespace client

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

    for (std::string const input :
         {
             R"([Section]
                key1 = value1
                key2 = value2

                [Section10]
                key3 = value3

                [Section3] # let's see that order is preserved
                key4 = value4
        )",
             R"()",
             R"(oops=bad)",
         }) //
    {
        std::cout << "-------------------------\n";
        client::IniFile         iniFile;

        if (parse(input.begin(), input.end(), grammar, iniFile)) {
            std::cout << "Parsing succeeded\n";

            for (auto const& [name, entries] : iniFile) {
                std::cout << "[" << name << "]\n";
                for (auto const& [k,v] : entries) {
                    std::cout << k << " = " << v << "\n";
                }
            }
        } else {
            std::cout << "Parsing failed\n";
        }
    }
}

打印预期输出

-------------------------
Parsing succeeded
[Section]
key1 = value1
key2 = value2
[Section10]
key3 = value3
[Section3]
key4 = value4
-------------------------
Parsing succeeded
-------------------------
Parsing failed

总结

您很好地修改了代码以满足您的需求,并“使其成为您自己的”,即完全理解它。然而,一路走来,你已经积累了许多干扰性变化的组合,这些变化一起使你自己无法看到哪里出了问题。我有一个怀疑,一件事导致了另一件事,你就在这里。

教训是:无论如何都要去调整代码。如果程序员害怕接触代码,他们就无法正常工作。但是,请遵循两个基本原则帮助自己:

  1. 有测试用例
  2. 一次只改变一件事
  3. 检查每个案例中的测试用例。

这意味着您“永远不会”发生无法编译的更改(您只需撤消该更改),并且当您破坏某些内容时,您将“立即”发现。这并不一定意味着您必须撤消更改。也许您需要补偿代码中其他地方的更改。无论如何,在所有测试再次通过之前,不要更改任何不相关的内容。 这就是开明程序员之道。

¹

也许您开始接受副驾驶的一些建议?


© www.soinside.com 2019 - 2024. All rights reserved.