blob: 1f8c19eddfd1f65e390995c878e9c7bef3f49323 [file] [log] [blame]
/*
* Modified INI Parser from: https://github.com/nmandery/ini-parser
* support spacing beside equal mark
*/
#ifndef INI_HPP
#define INI_HPP
#include <cassert>
#include <map>
#include <list>
#include <stdexcept>
#include <string>
#include <cstring>
#include <iostream>
#include <fstream>
namespace INI {
struct Level {
Level() : parent(NULL), depth(0) {
}
Level(Level* p) : parent(p), depth(0) {
}
typedef std::map<std::string, std::string> value_map_t;
typedef std::map<std::string, Level> section_map_t;
typedef std::list<value_map_t::const_iterator> values_t;
typedef std::list<section_map_t::const_iterator> sections_t;
value_map_t values;
section_map_t sections;
values_t ordered_values; // original order in the ini file
sections_t ordered_sections;
Level* parent;
size_t depth;
const std::string& operator[](const std::string& name) {
return values[name];
}
Level& operator()(const std::string& name) {
return sections[name];
}
};
class Parser {
public:
Parser(const char* fn);
Parser(std::istream& f) : f_(&f), ln_(0) {
parse(top_);
}
Level& top() {
return top_;
}
void dump(std::ostream& s) {
dump(s, top(), "");
}
private:
void dump(std::ostream& s, const Level& l, const std::string& sname);
void parse(Level& l);
void parseSLine(std::string& sname, size_t& depth);
void err(const char* s);
private:
Level top_;
std::ifstream f0_;
std::istream* f_;
std::string line_;
size_t ln_;
};
inline void
Parser::err(const char* s) {
char buf[256];
snprintf(buf, sizeof buf, "%s on line #%ld", s, ln_);
throw std::runtime_error(buf);
}
inline std::string trim(const std::string& s) {
char p[] = " \t\r\n";
long sp = 0;
long ep = s.length() - 1;
for (; sp <= ep; ++sp)
if (!strchr(p, s[sp])) break;
for (; ep >= 0; --ep)
if (!strchr(p, s[ep])) break;
return s.substr(sp, ep - sp + 1);
}
inline
Parser::Parser(const char* fn) : f0_(fn), f_(&f0_), ln_(0) {
if (!f_)
throw std::runtime_error(std::string("failed to open file: ") + fn);
parse(top_);
}
inline void
Parser::parseSLine(std::string& sname, size_t& depth) {
depth = 0;
for (; depth < line_.length(); ++depth)
if (line_[depth] != '[') break;
sname = line_.substr(depth, line_.length() - 2 * depth);
}
inline void
Parser::parse(Level& l) {
while (std::getline(*f_, line_)) {
++ln_;
if (line_[0] == '#' || line_[0] == ';') continue;
line_ = trim(line_);
if (line_.empty()) continue;
if (line_[0] == '[') {
size_t depth;
std::string sname;
parseSLine(sname, depth);
Level* lp = NULL;
Level* parent = &l;
if (depth > l.depth + 1)
err("section with wrong depth");
if (l.depth == depth - 1)
lp = &l.sections[sname];
else {
lp = l.parent;
size_t n = l.depth - depth;
for (size_t i = 0; i < n; ++i) lp = lp->parent;
parent = lp;
lp = &lp->sections[sname];
}
if (lp->depth != 0)
err("duplicate section name on the same level");
if (!lp->parent) {
lp->depth = depth;
lp->parent = parent;
}
parent->ordered_sections.push_back(parent->sections.find(sname));
parse(*lp);
} else {
size_t n = line_.find('=');
if (n == std::string::npos)
err("no '=' found");
size_t lend = n - 1, rstart = n + 1;
char p[] = " \t\r\n";
while (lend > 0 && strchr(p, line_[lend])) --lend;
while (rstart < line_.length() && strchr(p, line_[rstart])) ++rstart;
if (strchr(p, line_[lend]) || rstart == line_.length())
err("lvalue or rvalue is empty");
std::pair < Level::value_map_t::const_iterator, bool> res =
l.values.insert(std::make_pair(line_.substr(0, lend + 1),
line_.substr(rstart, line_.length() - rstart)));
if (!res.second)
err("duplicated key found");
l.ordered_values.push_back(res.first);
}
}
}
inline void
Parser::dump(std::ostream& s, const Level& l, const std::string& sname) {
if (!sname.empty()) s << '\n';
for (size_t i = 0; i < l.depth; ++i) s << '[';
if (!sname.empty()) s << sname;
for (size_t i = 0; i < l.depth; ++i) s << ']';
if (!sname.empty()) s << std::endl;
for (Level::values_t::const_iterator it = l.ordered_values.begin(); it != l.ordered_values.end(); ++it)
s << (*it)->first << '=' << (*it)->second << std::endl;
for (Level::sections_t::const_iterator it = l.ordered_sections.begin(); it != l.ordered_sections.end(); ++it) {
assert((*it)->second.depth == l.depth + 1);
dump(s, (*it)->second, (*it)->first);
}
}
}
#endif // INI_HPP