处理INI文件中重复的节名 [英] Deal with duplicate section names in INI files
问题描述
我需要从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:
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屋!