处理 INI 文件中重复的节名称

2023-12-10

我需要从 INI 文件加载这些值并使用 C++ Boost 库在应用程序中打印它们。这些部分具有重复的名称。我被限制只能使用 C++ Boost 库。

numColors = 4
boardSize = 11
numSnails = 2
[initialization]
id = 0
row = 3
col = 4
orientation = 0
[initialization]
id = 1
row = 5
col = 0
orientation = 1
[color]
id = 0
nextColor = 1
deltaOrientation = +2
[color]
id = 1   
nextColor = 2
deltaOrientation = +1
[color]
id = 2
nextColor = 3
deltaOrientation = -2
[color]
id = 3
nextColor = 0
deltaOrientation = -1

我想对于一个随机的路人来说提升精神答案可能看起来很复杂/矫枉过正。

我想再试一次,因为现在是 2021 年了,无论是使用 C++17,我们都可以仅使用标准库来完成合理的工作。

事实证明还有很多工作要做。 Qi 实现需要 86 行代码,但标准库实现需要 136 行。另外,我花了很长时间(几个小时)来调试/编写。尤其是很难得到'=', '[', ']'作为令牌边界std::istream&。我用的是ctype来自这个答案的方面方法:如何在 C++ 中逐行迭代 cin?

我确实离开了DebugPeeker(20行)所以你也许可以自己理解。

简短的博览会

顶级解析函数看起来很正常,并显示了我想要实现的目标:自然std::istream萃取:

static Ast::File std_parse_game(std::string_view input) {
    std::istringstream iss{std::string(input)};

    using namespace Helpers;
    if (Ast::File parsed; iss >> parsed)
        return parsed;
    throw std::runtime_error("Unable to parse game");
}

其余的都位于命名空间中Helpers:

static inline std::istream& operator>>(std::istream& is, Ast::File& v) {

    for (section s; is >> s;) {
        if (s.name == "parameters")
            is >> v.parameters;
        else if (s.name == "initialization")
            is >> v.initializations.emplace_back();
        else if (s.name == "color")
            is >> v.colors.emplace_back();
        else
            is.setstate(std::ios::failbit);
    }
    if (is.eof())
        is.clear();
    return is;
}

到目前为止,这一举措的回报良好。不同的部分类型是相似的:

static inline std::istream& operator>>(std::istream& is, Ast::Parameters& v) {
    return is
        >> entry{"numColors", v.numColors}
        >> entry{"boardSize", v.boardSize}
        >> entry{"numSnails", v.numSnails};
}

static inline std::istream& operator>>(std::istream& is, Ast::Initialization& v) {
    return is
        >> entry{"id", v.id}
        >> entry{"row", v.row}
        >> entry{"col", v.col}
        >> entry{"orientation", v.orientation};
}

static inline std::istream& operator>>(std::istream& is, Ast::Color& v) {
    return is
        >> entry{"id", v.id}
        >> entry{"nextColor", v.nextColor}
        >> entry{"deltaOrientation", v.deltaOrientation};
}

现在,如果一切都像这样一帆风顺,我就不会推荐 Spirit。现在我们进入条件解析。 这entry{"name", value}配方使用“操纵器类型”:

template <typename T> struct entry {
    entry(std::string name, T& into) : _name(name), _into(into) {}
    std::string _name;
    T& _into;
    friend std::istream& operator>>(std::istream& is, entry e) {
        return is >> expect{e._name} >> expect{'='} >> e._into;
    }
};

同样,部分正在使用expect and token:

struct section {
    std::string name;
    friend std::istream& operator>>(std::istream& is, section& s) {
        if (is >> expect('['))
            return is >> token{s.name} >> expect{']'};
        return is;
    }
};

条件对于能够检测 EOF 而不将流置于硬失败模式(is.bad() != is.fail()).

expect是建立在token:

template <typename T> struct expect {
    expect(T expected) : _expected(expected) {}
    T _expected;
    friend std::istream& operator>>(std::istream& is, expect const& e) {
        if (T actual; is >> token{actual})
            if (actual != e._expected)
                is.setstate(std::ios::failbit);
        return is;
    }
};

您会注意到错误信息少了很多。我们只是做 溪流fail()如果没有找到预期的令牌。

这就是真正的复杂性所在。我不想解析字符 特点。但读书std::string using operator>>只会停止于 空格,意味着节名会“吃掉”括号:parameters]代替parameters,钥匙可能会吃掉=如果没有字符 分隔空间。

在上面链接的答案中,我们学习如何塑造自己的性格 分类区域设置方面:

// make sure =,[,] break tokens
struct mytoken_ctype : std::ctype<char> {
    static auto const* get_table() {
        static std::vector rc(table_size, std::ctype_base::mask());

        rc[' '] = rc['\f'] = rc['\v'] = rc['\t'] = rc['\r'] = rc['\n'] =
            std::ctype_base::space;
        // crucial for us:
        rc['='] = rc['['] = rc[']'] = std::ctype_base::space;
        return rc.data();
    }

    mytoken_ctype() : std::ctype<char>(get_table()) {}
};

然后我们需要使用它,但前提是我们解析一个std::string令牌。那 方式,如果我们expect('=')它不会跳过'='因为我们的方面称其为空白...

template <typename T> struct token {
    token(T& into) : _into(into) {}
    T& _into;
    friend std::istream& operator>>(std::istream& is, token const& t) {
        std::locale loc = is.getloc();
        if constexpr (std::is_same_v<std::decay_t<T>, std::string>) {
            loc = is.imbue(std::locale(std::locale(), new mytoken_ctype()));
        }

        try { is >> t._into; is.imbue(loc); }
        catch (...) { is.imbue(loc); throw; }
        return is;
    }
};

我试图保持它的简洁。如果我使用正确的格式,我们会 还有更多行代码:)

演示和测试

我使用了相同的 Ast 类型,因此测试这两种实现和 比较结果是否相等。

NOTES:

  1. 在 Compiler Explorer 上我们可以享受libfmt方便输出
  2. 为了进行比较,我使用了一项 C++20 功能来生成编译器operator==

实时编译器资源管理器

#include <boost/spirit/home/qi.hpp>
#include <boost/fusion/include/io.hpp>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <fmt/ranges.h>
#include <fmt/ostream.h>
namespace qi = boost::spirit::qi;

namespace Ast {
    using Id          = unsigned;
    using Size        = uint16_t; // avoiding char types for easy debug/output
    using Coord       = Size;
    using ColorNumber = Size;
    using Orientation = Size;
    using Delta       = signed;

    struct Parameters {
        Size numColors{}, boardSize{}, numSnails{};

        bool operator==(Parameters const&) const = default;
    };

    struct Initialization {
        Id          id;
        Coord       row;
        Coord       col;
        Orientation orientation;

        bool operator==(Initialization const&) const = default;
    };

    struct Color {
        Id          id;
        ColorNumber nextColor;
        Delta       deltaOrientation;

        bool operator==(Color const&) const = default;
    };

    struct File {
        Parameters                  parameters;
        std::vector<Initialization> initializations;
        std::vector<Color>          colors;

        bool operator==(File const&) const = default;
    };

    using boost::fusion::operator<<;

    template <typename T>
    static inline std::ostream& operator<<(std::ostream& os, std::vector<T> const& v) {
        return os << fmt::format("vector<{}>{}",
                                 boost::core::demangle(typeid(T).name()), v);
    }
}  // namespace Ast

BOOST_FUSION_ADAPT_STRUCT(Ast::Parameters, numColors, boardSize, numSnails)
BOOST_FUSION_ADAPT_STRUCT(Ast::Initialization, id, row, col, orientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::Color, id, nextColor, deltaOrientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::File, parameters, initializations, colors)

template <typename It>
struct GameParser : qi::grammar<It, Ast::File()> {
    GameParser() : GameParser::base_type(start) {
        using namespace qi;
        start = skip(blank)[file];

        auto section = [](const std::string& name) {
            return copy('[' >> lexeme[lit(name)] >> ']' >> (+eol | eoi));
        };
        auto required = [](const std::string& name, auto value) {
            return copy(lexeme[eps > lit(name)] > '=' > value >
                        (+eol | eoi));
        };

        file = parameters >
            *initialization >
            *color >
            eoi; // must reach end of input

        parameters = section("parameters") >
            required("numColors", _size) >
            required("boardSize", _size) >
            required("numSnails", _size);

        initialization = section("initialization") >
            required("id", _id) >
            required("row", _coord) >
            required("col", _coord) >
            required("orientation", _orientation);
            
        color = section("color") >
            required("id", _id) >
            required("nextColor", _colorNumber) >
            required("deltaOrientation", _delta);

        BOOST_SPIRIT_DEBUG_NODES((file)(parameters)(initialization)(color))
    }

  private:
    using Skipper = qi::blank_type;
    qi::rule<It, Ast::File()>                    start;
    qi::rule<It, Ast::File(), Skipper>           file;
    // sections
    qi::rule<It, Ast::Parameters(), Skipper>     parameters;
    qi::rule<It, Ast::Initialization(), Skipper> initialization;
    qi::rule<It, Ast::Color(), Skipper>          color;

    // value types
    qi::uint_parser<Ast::Id>          _id;
    qi::uint_parser<Ast::Size>        _size;
    qi::uint_parser<Ast::Coord>       _coord;
    qi::uint_parser<Ast::ColorNumber> _colorNumber;
    qi::uint_parser<Ast::Orientation> _orientation;
    qi::int_parser<Ast::Delta>        _delta;
};

static Ast::File qi_parse_game(std::string_view input) {
    using SVI = std::string_view::const_iterator;
    static const GameParser<SVI> parser{};

    try {
        Ast::File parsed;
        if (qi::parse(input.begin(), input.end(), parser, parsed)) {
            return parsed;
        }
        throw std::runtime_error("Unable to parse game");
    } catch (qi::expectation_failure<SVI> const& ef) {
        std::ostringstream oss;

        auto where  = ef.first - input.begin();
        auto sol    = 1 + input.find_last_of("\r\n", where);
        auto lineno = 1 + std::count(input.begin(), input.begin() + sol, '\n');
        auto col    = 1 + where - sol;
        auto llen   = input.substr(sol).find_first_of("\r\n");

        oss << "input.txt:" << lineno << ":" << col << " Expected: " << ef.what_ << "\n"
            << " note: " << input.substr(sol, llen) << "\n"
            << " note:"  << std::setw(col) << "" << "^--- here";
        throw std::runtime_error(oss.str());
    }
}

namespace Helpers {
    struct DebugPeeker {
        DebugPeeker(std::istream& is, int line) : is(is), line(line) { dopeek(); }
        ~DebugPeeker() { dopeek(); }

      private:
        std::istream& is;
        int line;

        void dopeek() const {
            std::char_traits<char> t;
            auto ch = is.peek();
            std::cerr << "DEBUG " << line << " Peek: ";
            if (std::isgraph(ch))
                std::cerr << "'" << t.to_char_type(ch) << "'";
            else 
                std::cerr << "<" << ch << ">";
            std::cerr << " " << std::boolalpha << is.good() << "\n";
        }
    };

#define DEBUG_PEEK(is) // Peeker _peek##__LINE__(is, __LINE__);

    // make sure =,[,] break tokens
    struct mytoken_ctype : std::ctype<char> {
        static auto const* get_table() {
            static std::vector rc(table_size, std::ctype_base::mask());

            rc[' '] = rc['\f'] = rc['\v'] = rc['\t'] = rc['\r'] = rc['\n'] =
                std::ctype_base::space;
            // crucial for us:
            rc['='] = rc['['] = rc[']'] = std::ctype_base::space;
            return rc.data();
        }

        mytoken_ctype() : std::ctype<char>(get_table()) {}
    };

    template <typename T> struct token {
        token(T& into) : _into(into) {}
        T& _into;
        friend std::istream& operator>>(std::istream& is, token const& t) {
            DEBUG_PEEK(is);
            std::locale loc = is.getloc();
            if constexpr (std::is_same_v<std::decay_t<T>, std::string>) {
                loc = is.imbue(std::locale(std::locale(), new mytoken_ctype()));
            }

            try { is >> t._into; is.imbue(loc); }
            catch (...) { is.imbue(loc); throw; }
            return is;
        }
    };

    template <typename T> struct expect {
        expect(T expected) : _expected(expected) {}
        T _expected;
        friend std::istream& operator>>(std::istream& is, expect const& e) {
            DEBUG_PEEK(is);
            if (T actual; is >> token{actual})
                if (actual != e._expected)
                    is.setstate(std::ios::failbit);
            return is;
        }
    };

    template <typename T> struct entry {
        entry(std::string name, T& into) : _name(name), _into(into) {}
        std::string _name;
        T& _into;
        friend std::istream& operator>>(std::istream& is, entry e) {
            DEBUG_PEEK(is);
            return is >> expect{e._name} >> expect{'='} >> e._into;
        }
    };

    struct section {
        std::string name;
        friend std::istream& operator>>(std::istream& is, section& s) {
            DEBUG_PEEK(is);
            if (is >> expect('['))
                return is >> token{s.name} >> expect{']'};
            return is;
        }
    };

    static inline std::istream& operator>>(std::istream& is, Ast::Parameters& v) {
        DEBUG_PEEK(is);
        return is
            >> entry{"numColors", v.numColors}
            >> entry{"boardSize", v.boardSize}
            >> entry{"numSnails", v.numSnails};
    }

    static inline std::istream& operator>>(std::istream& is, Ast::Initialization& v) {
        DEBUG_PEEK(is);
        return is
            >> entry{"id", v.id}
            >> entry{"row", v.row}
            >> entry{"col", v.col}
            >> entry{"orientation", v.orientation};
    }

    static inline std::istream& operator>>(std::istream& is, Ast::Color& v) {
        DEBUG_PEEK(is);
        return is
            >> entry{"id", v.id}
            >> entry{"nextColor", v.nextColor}
            >> entry{"deltaOrientation", v.deltaOrientation};
    }

    static inline std::istream& operator>>(std::istream& is, Ast::File& v) {
        DEBUG_PEEK(is);

        for (section s; is >> s;) {
            if (s.name == "parameters")
                is >> v.parameters;
            else if (s.name == "initialization")
                is >> v.initializations.emplace_back();
            else if (s.name == "color")
                is >> v.colors.emplace_back();
            else
                is.setstate(std::ios::failbit);
        }
        if (is.eof())
            is.clear();
        return is;
    }
}

static Ast::File std_parse_game(std::string_view input) {
    std::istringstream iss{std::string(input)};

    using namespace Helpers;
    if (Ast::File parsed; iss >> parsed)
        return parsed;
    throw std::runtime_error("Unable to parse game");
}

std::string read_file(const std::string& name) {
    std::ifstream ifs(name);
    return std::string(std::istreambuf_iterator<char>(ifs), {});
}

int main() {
    std::string const game_save = read_file("input.txt");

    Ast::File g1, g2;
    try {
        std::cout << "Qi:    " << (g1 = qi_parse_game(game_save)) << "\n";
    } catch (std::exception const& e) { std::cerr << e.what() << "\n"; }
    try {
        std::cout << "std:   " << (g2 = std_parse_game(game_save)) << "\n";
    } catch (std::exception const& e) { std::cerr << e.what() << "\n"; }

    std::cout << "Equal: " << std::boolalpha << (g1 == g2) << "\n";
}

让我松了口气的是,解析器对数据达成了一致:

Qi:    ((4 11 2)
        vector<Ast::Initialization>{(0 3 4 0), (1 5 0 1)}
        vector<Ast::Color>{(0 1 2), (1 2 1), (2 3 -2), (3 0 -1)})
std:   ((4 11 2)
        vector<Ast::Initialization>{(0 3 4 0), (1 5 0 1)}
        vector<Ast::Color>{(0 1 2), (1 2 1), (2 3 -2), (3 0 -1)})
Equal: true

总结/结论

虽然这个答案是“标准的”和“便携式的”,但它有一些缺点。

例如,它肯定不容易正确,它几乎没有调试选项或错误报告,它不验证输入格式。例如。它仍然会阅读这个邪恶的混乱并接受它:

[parameters] numColors=999 boardSize=999 numSnails=999
[color] id=0 nextColor=1 deltaOrientation=+2 [color] id=1 nextColor=2
                         deltaOrientation=+1 [
initialization] id=1 row=5 col=0 orientation=1
[color] id=2 nextColor=3 deltaOrientation=-2
[parameters] numColors=4 boardSize=11 numSnails=2
[color] id=3 nextColor=0 deltaOrientation=-1
[initialization] id=0 row=3 col=4 orientation=0

如果您的输入格式不稳定并且是计算机编写的,我强烈建议不要使用标准库方法,因为它将导致难以诊断的问题和可怕的用户体验(不要让您的用户想要扔掉他们的计算机窗口的原因是无用的错误消息,例如“无法解析游戏数据”)。

否则,你可能会。一方面,编译速度会更快。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

处理 INI 文件中重复的节名称 的相关文章

随机推荐

  • 递归地反向链接列表

    我在链表中 定义了一个节点 typedef struct abc int id struct abc next node 我想递归地反转链表 我将头指针传递给函数 我的函数定义如下 node reverseLinkedListRecursi
  • 使用 telnet 从一个 Android 模拟器拨打另一个 Android 模拟器的电话?

    是否可以通过 telnet 从一个 Android 模拟器拨打另一个 Android 模拟器 例如 我在屏幕上打开了两个模拟器 5554 和 5556 我可以使用命令 gsm call 5554通过 telnet 接听来自 5554 的来电
  • 如何在 C# 中无需更多实例即可实现登录表单和主表单

    我在单击登录按钮时创建主表单的实例 并在单击注销按钮时再次创建登录表单的实例 我的代码是 if txtUsrNm Text Admin txtPswd Text Admin mainForm mainFm new mainForm main
  • 如何在文本下显示图像作为背景?

    我想在文本下显示图像 我用 Photoshop 完成了这个 但它是一个图像 所以每次文本更改时我都必须在 Photoshop 中进行更改 我想使用 jQuery 或 CSS3 或任何其他网络技术来实现相同的目标 我想要与此类似 但不是每次文
  • python argparse 可选参数的默认值

    usage h foo FOO bar 如何确保 FOO 的默认值是abc如果我像下面一样执行我的脚本 myscript py foo bar gt bar这里是位置参数 但args foo正在考虑bar作为论点 foo 我想args fo
  • 使用一种上下文更新一个实体,并使用另一种上下文插入新实体?

    问候并感谢您阅读我的帖子 我正在使用中更新条目 照片 using context new PhotoEntities context Entry photo State EntityState Modified 问题是当我使用保存此条目时
  • 如何让 ggplot ecdf 绘制填充背景

    我试图让我的经验累积密度曲线填充它们的背景 但似乎无法实现 我尝试了以下两种方法 第一种方法似乎改变了曲线的 alpha 而不是填充 ggplot myDataFrame aes x myVariable fill myFactor geo
  • Dreamweaver CS5.5 中的 Phonegap

    版本是什么Phonegap被使用过Dreamweaver CS5 5 我尝试过替换默认的phonegap js最新版本的文件给出了错误 更换现有的是个好主意吗phonegap js最新版本的文件 升级adobe dreamweaver cs
  • Mongo 查找数组包含给定数组的 x 值的文档

    我有一个收藏 其中有类似的文件 实体字段并不是在每个文档中都设置的 并且具有不同的值 id ObjectId 5388cfbdec82ba7cd5438635 name Name1 entity Entity1 Entity2 Entity
  • Flask-sqlalchemy 中多对多多...关系的多辅助表

    许多问题都是关于多对多的问题 可以使用辅助表来解决 但是多 多 多怎么样 如果存在一种更优雅的方法来处理这个问题 我试图提出一个问题https github com pallets flask sqlalchemy issues 710 但
  • Python 多个 telnet 会话

    我需要构建一个脚本来获取尽可能多的主机的 telnet 输出 并将它们保存到每个主机的单独文件中 该脚本应作为守护进程运行 目前我有一个函数封装了为单个主机执行此操作的逻辑telnetlib 但我不知道如何进行 我计划打开一个进程 mult
  • Symfony2+Doctrine:如何将 iso8859-1 转换为 utf-8,反之亦然?

    我们正在构建一个使用 Oracle 数据库的 Symfony2 应用程序 DB中的所有数据编码为WE8ISO8859P15 iso 8859 1 所有网站编码为utf 8 有没有办法将从数据库接收到的所有数据转换为utf8 并将发送到数据库
  • GNU Radio:使用声音输出作为输入源

    In gnuradio 伴侣我使用音频源块作为下一个块的输入信号 一切工作几乎都很好 唯一的小问题是我从麦克风收到信号 这是正常行为 我宁愿直接播放音频信号 而不必通过我的扬声器 我房间的空气和麦克风 所有这些都会产生信号损失并增加噪声 我
  • 如何填充 MVC4 剃刀视图的下拉列表 (C#)

    用户档案模型 Table Users public class UserProfiles Key DatabaseGeneratedAttribute DatabaseGeneratedOption Identity public int
  • 使用 pyodbc 将 Python 连接到 mac 中的 Teradata

    我成功安装了 python 2 7 的 pyodbc 模块 但是 当输入以下代码连接到teradata时 import pyodbc conn pyodbc connect DRIVER Teradata DBCNAME
  • C++ 抑制自动初始化和销毁

    如何抑制类型的自动初始化和销毁 虽然这很美妙T buffer 100 自动初始化所 有元素buffer 并在它们超出范围时销毁它们 这不是我想要的行为 include
  • 从 AsyncTask 获取返回的 JSON

    所以我有这个扩展 AsyncTask 的加载器类 那我就做new loader execute 但我想用JSONArray 响应我的加载器类returns我怎么做 因为我在几个不同的地方都需要它 或者我应该将代码移至 onPostExecu
  • 如何将多个输入的行保存在数据库的同一列中?

    数据库表 id title reading writing speaking 表单 blade php table tr th Language th th Reading th th Writing th th Speaking th t
  • 使用 docker-compose 自动创建数据库和表

    我正在使用 docker compose 上传环境 但我想在 docker compose 中自动创建一个表 但是它不起作用 docker 撰写 mysql image mysql 5 7 stdin open true tty true
  • 处理 INI 文件中重复的节名称

    我需要从 INI 文件加载这些值并使用 C Boost 库在应用程序中打印它们 这些部分具有重复的名称 我被限制只能使用 C Boost 库 numColors 4 boardSize 11 numSnails 2 initializati