处理INI文件中重复的节名 [英] Deal with duplicate section names in INI files

查看:91
本文介绍了处理INI文件中重复的节名的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  numColors = 4boardSize = 11numSnails = 2[初始化]id = 0行= 3col = 4方向= 0[初始化]id = 1行= 5col = 0方向= 1[颜色]id = 0nextColor = 1deltaOrientation = +2[颜色]id = 1nextColor = 2deltaOrientation = +1[颜色]id = 2nextColor = 3deltaOrientation = -2[颜色]id = 3nextColor = 0deltaOrientation = -1 

解决方案

不是什么

简而言之,这根本不是INI格式.它与它非常松散地相似.很好.

这是什么?

您没有指定太多,所以我要做个假设.

为简单起见,我假设

  • 初始化部分在颜色部分之前
  • 相似部分中的
  • 键始终具有相同的顺序
  • 所有显示的键都是必填项,
  • 增量是有符号整数值(正号是可选的)
  • 所有其他值均为非负整数
  • 空格不重要
  • 情况很重要
  • 所有数字均为十进制形式(与前导零无关)

非必要扣除(可用于添加更多验证):

  • 初始化次数= numSnails
  • 木板尺寸决定行和列在[0,boardSize)

数据结构

为表示文件,我要做:

 命名空间Ast {struct初始化{无符号ID,行,列,方向;};结构颜色{未签名的id,nextColor;int deltaOrientation;};结构文件{无符号numColors,boardSize,numSnails;std :: vector< Initialization>初始化;std :: vector< Color>颜色;};} 

这是我能想到的最简单的

解析

对于Boost Spirit来说是一项不错的工作.如果我们将数据结构调整为融合序列:

  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,numColors,boardSize,numSnails,初始化,颜色) 

我们基本上可以让解析器自己编写":

 模板< typename It>struct GameParser:qi :: grammar< It,Ast :: File()>{GameParser():GameParser :: base_type(start){使用命名空间qi;开始=跳过(空白)[文件];自动部分= [](std :: string名称){return copy('['> lexeme [lit(name)]>>']'>>(+ eol | eoi));};自动要求= [](std :: string name){return copy(lexeme [eps> lit(name)]>'='> auto_>(+ eol | eoi));};文件=required("numColors")>必需的("boardSize")>必需的("numSnails")>*初始化>*颜色>oi//必须到达输入的结尾初始化=部分(初始化")>required("id")>必需的(行")>required("col")>必需的(方向");颜色= section(颜色")>required("id")>必需的("nextColor")>required("deltaOrientation");BOOST_SPIRIT_DEBUG_NODES((文件)(初始化)(颜色))}私人的:使用Skipper = qi :: blank_type;qi :: rule< It,Ast :: File()>开始;qi :: rule< It,Ast :: File(),Skipper>文件;qi :: rule< It,Ast :: Initialization(),Skipper>初始化;qi :: rule< It,Ast :: Color(),Skipper>颜色;}; 

由于我们做出了许多假设,我们用期望点( operator> 序列代替了 operator>> ).这意味着我们得到帮助".输入无效的错误消息,例如

 预期:nextColor预期:=预期:< eoi> 

另请参阅下面的奖金"部分,该内容可以大大改善

测试/现场演示

进行测试,我们将首先读取文件,然后使用该解析器对其进行解析:

  std :: string read_file(std :: string name){std :: ifstream ifs(name);返回std :: string(std :: istreambuf_iterator< char>(ifs),{});}静态Ast :: File parse_game(std :: string_view输入){使用SVI = std :: string_view :: const_iterator;静态const GameParser< SVI>解析器{};尝试 {Ast :: File已解析;如果(qi :: parse(input.begin(),input.end(),解析器,已解析)){返回解析}抛出std :: runtime_error(无法解析游戏");} catch(qi :: expectation_failure< SVI> const& ef){std :: ostringstream oss;oss<<"预期:"<<ef.what_;抛出std :: runtime_error(oss.str());}} 

有很多可以改进的地方,但是现在它可以工作并解析您的输入了

I need to load these values from INI file and print them in the application using C++ Boost Library. The sections have duplicate names. I have been restricted to using C++ Boost Library only.

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

解决方案

What It Isn't

In short, this is not INI format at all. It just very loosely resembles it. Which is nice.

What Is It Instead?

You don't specify a lot, so I'm going to make assumptions.

I'm going to, for simplicity, assume that

  • initialization sections precede color sections
  • keys in like sections have the same order always
  • all keys shown are mandatory in like sections
  • the deltas are signed integral values (positive sign being optional)
  • all other values are non-negative integral numbers
  • whitespace is not significant
  • case is significant
  • all numbers are in decimal form (regardless of leading zeros)

Non-essential deductions (could be used to add more validation):

  • the number of of initializations = numSnails
  • the board size dictates row and col are in [0, boardSize)

Data Structures

To represent the file, I'd make:

namespace Ast {
    struct Initialization {
        unsigned id, row, col, orientation;
    };

    struct Color {
        unsigned id, nextColor;
        int deltaOrientation;
    };

    struct File {
        unsigned numColors, boardSize, numSnails;

        std::vector<Initialization> initializations;
        std::vector<Color>          colors;
    };
}

That's the simplest I can think of.

Parsing It

Is a nice job for Boost Spirit. If we adapt the data structures as Fusion Sequences:

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, numColors, boardSize, numSnails,
                          initializations, colors)

We can basically let the parser "write itself":

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

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

        file =
            required("numColors") >
            required("boardSize") >
            required("numSnails") >
            *initialization >
            *color >
            eoi; // must reach end of input

        initialization = section("initialization") >
            required("id") >
            required("row") >
            required("col") >
            required("orientation");
            
        color = section("color") >
            required("id") >
            required("nextColor") >
            required("deltaOrientation");

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

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

Because of the many assumptions we've made we littered the place with expectation points (operator> sequences, instead of operator>>). This means we get "helpful" error messages on invalid input, like

Expected: nextColor
Expected: =
Expected: <eoi>

See also BONUS section below that improves this a lot

Testing/Live Demo

Testing it, we will read the file first and then parse it using that parser:

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

static Ast::File 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;
        oss << "Expected: " << ef.what_;
        throw std::runtime_error(oss.str());
    }
}

A lot could be improved, but for now it works and parses your input:

Live On Coliru

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

    Ast::File data = parse_game(game_save);
}

The absense of output means success.

BONUS

Some improvements, instead of using auto_ to generate the right parser for the type, we can make that explicit:

namespace Ast {
    using Id          = unsigned;
    using Size        = uint8_t;
    using Coord       = Size;
    using ColorNumber = Size;
    using Orientation = Size;
    using Delta       = signed;

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

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

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

        std::vector<Initialization> initializations;
        std::vector<Color>          colors;
    };
}  // namespace Ast

And then in the parser define the analogous:

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;

Which we then use e.g.:

initialization = section("initialization") >
    required("id", _id) >
    required("row", _coord) >
    required("col", _coord) >
    required("orientation", _orientation);

Now we can improve the error messages to be e.g.:

input.txt:2:13 Expected: <unsigned-integer>
 note: boardSize = (11)
 note:             ^--- here

Or

input.txt:16:19 Expected: <alternative><eol><eoi>
 note:     nextColor = 1 deltaOrientation = +2
 note:                   ^--- here

Full Code, Live On Coliru

//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/home/qi.hpp>
#include <fstream>
#include <sstream>
#include <iomanip>
namespace qi = boost::spirit::qi;

namespace Ast {
    using Id          = unsigned;
    using Size        = uint8_t;
    using Coord       = Size;
    using ColorNumber = Size;
    using Orientation = Size;
    using Delta       = signed;

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

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

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

        std::vector<Initialization> initializations;
        std::vector<Color>          colors;
    };
}  // namespace Ast

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, numColors, boardSize, numSnails,
                          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 =
            required("numColors", _size) >
            required("boardSize", _size) >
            required("numSnails", _size) >
            *initialization >
            *color >
            eoi; // must reach end of input

        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)(initialization)(color))
    }

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

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

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

static Ast::File 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());
    }
}

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

    try {
        Ast::File data = parse_game(game_save);
    } catch (std::exception const& e) {
        std::cerr << e.what() << "\n";
    }
}

Look here for various failure modes and BOOST_SPIRIT_DEBUG ouput:

这篇关于处理INI文件中重复的节名的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆