INI-File Parser
-
Hi!
Aus Spaß hab ich mich an einen rangesetzt und versucht, es möglichst ordentlich zu machen.
Heer ist ein kleines Test Programm: http://ideone.com/thQal (Edit: was verbessert)
Die File könnte so aussehen:test.ini (bitte den Inhalt ignorieren :D):
[General] ;General file paths images = Images SVGs = ../textures/svg [OS_specific] ;Windows exe = bin/win32/test.exe ;Mac (since OS.X Leopard) app = bin/os.x/test.app [Graphics] resolutionX = 1600 resolutionY = 900 AntiAliasing = 8 MIPmaps = 5
Mein GCC 4.8 kompiliert es Fehlerfrei.
#include <vector> #include <string> #include <iterator> #include <sstream> #include <algorithm> #include <stdexcept> namespace INIParser { namespace CharacterGroups { const std::string lowercase_letters{"abcdefghijklmnopqrstuvwxyz"}, uppercase_letters{"ABCDEFGHIJKLMNOPQRSTUVWXYZ"}, all_letters{lowercase_letters + uppercase_letters}, digits{"0123456789"}, real{digits + "+-."}, directory_path{"\\/:."}; } struct ParseException : public std::logic_error { ParseException(std::string const& w): std::logic_error(w){} }; ///Intern genutzt! void throw_parse_err(std::string const& what, std::string const& line) //throw (ParseException) { throw ParseException(what + "\nLine: " + line); } struct Node { std::string key, value; }; bool operator==(Node const& a, Node const& b) noexcept { return a.key == b.key; } std::ostream& operator<<(std::ostream& os, Node const& t) { return os << t.key << " = " << t.value; } std::istream& operator>>(std::istream& is, Node& toread); // throw (ParseException); struct Section { std::vector<Node> Nodes; std::string const Name; explicit Section(std::string const& name_): Name(name_) { } Node& operator[](std::string const& key) { auto iter = std::find_if(Nodes.begin(), Nodes.end(), [&key](Node const& s){return s.key == key;}); if(iter == Nodes.end()) throw std::invalid_argument("INIFile::getSection: No such node in section \"" + Name + "\" with key \"" + key + "\"!"); return *iter; } }; std::ostream& operator<<(std::ostream& os, Section const& t) { os << '[' << t.Name << "]\n"; for(auto const& n : t.Nodes) os << n << '\n'; return os; } class INIFile { std::vector<Section> mSections; static uint8_t mMaxCharsKey, mMaxCharsVal; public: static const std::string allowed_whitespaces; std::string const allowed_chars_key, allowed_chars_value; explicit INIFile(std::string const& allowed_chars_key_, std::string const& allowed_chars_value_): allowed_chars_key(allowed_chars_key_), allowed_chars_value(allowed_chars_value_) {} explicit INIFile(std::istream &is, std::string const& allch_key_, std::string const& allch_value_): //throw (ParseException) (in operator>>) INIFile(allch_key_, allch_value_) { is >> *this; } INIFile(INIFile const&) = delete; INIFile(INIFile&&) = delete; std::vector<Section> const& sections() const noexcept { return mSections; } static uint8_t maxCharactersKey() noexcept {return mMaxCharsKey;} static uint8_t maxCharactersValue() noexcept {return mMaxCharsVal;} static void setMaxCharactersKey(uint8_t const u) noexcept {mMaxCharsKey = u;} static void setMaxCharactersValue(uint8_t const u) noexcept {mMaxCharsVal = u;} friend std::istream& operator>>(std::istream &is, INIFile& inifile) // throw (ParseException) { inifile.mSections.clear(); for(std::string str;std::getline(is, str);) { auto pos = str.find_first_not_of(INIFile::allowed_whitespaces); if(pos == std::string::npos) continue; switch(str[pos]) { case ';': break; case '[': { auto pos2 = str.find(']'); if(pos2 == std::string::npos ///There is no corresponding ] or str.substr(pos2 + 1).find_first_not_of(INIFile::allowed_whitespaces) != std::string::npos)///Or there are characters after the ] throw_parse_err("A section naming has inappropriate syntax!", str); auto Name = str.substr(pos + 1, pos2 - pos - 1); if(Name.find_first_not_of(CharacterGroups::all_letters + "_") != std::string::npos)///If there are illegal characters in the Name throw_parse_err("There are illegal characters in the Name of the section!", str); inifile.mSections.push_back(Section(Name)); } break; default: if(!inifile.mSections.size()) ///Assignment without section throw_parse_err("A assignment occured before a section started!", str); else { std::istringstream stream(str); auto next = *std::istream_iterator<Node>(stream); if(next.key.find_first_not_of(inifile.allowed_chars_key) != std::string::npos ///Wrong characters in key or next.value.find_first_not_of(inifile.allowed_chars_value) != std::string::npos) /// ... or in value throw_parse_err("Invalid characters in key->value assignment!", str); else if(std::find(inifile.mSections.back().Nodes.begin(), ///Or the key exists already inifile.mSections.back().Nodes.end(), next) != inifile.mSections.back().Nodes.end()) throw_parse_err("A key was assigned a second time!", str); inifile.mSections.back().Nodes.push_back(next); } } } is.clear(); return is; } friend std::ostream& operator<<(std::ostream& os, INIFile const& inifile) { std::copy(inifile.sections().begin(), inifile.sections().end(), std::ostream_iterator<Section>(os, "\n")); return os; } }; const std::string INIFile::allowed_whitespaces(" \t"); uint8_t INIFile::mMaxCharsKey(32), INIFile::mMaxCharsVal(32); std::istream& operator>>(std::istream& is, Node& toread) // throw (ParseException) { static auto remove_whitesp = [](std::string &str) -> std::string const& { auto posFirst = str.find_first_not_of(INIFile::allowed_whitespaces); return str = str.substr(posFirst, str.find_last_not_of(INIFile::allowed_whitespaces) - posFirst + 1); }; std::getline(is, toread.key, '='); std::getline(is, toread.value); if(!is or !toread.key.length() ///Key Is empty or !toread.value.length() ///Value is empty or !remove_whitesp(toread.key).length() ///After whitespace-removement empty or !remove_whitesp(toread.value).length()) throw_parse_err("Syntactically invalid assignment!", toread.key + "=" + toread.value); return is; } }
Ich hab selbstverständlich die ODR missachtet - wenn man es in einen Header packt, müssen die Definitionen alle woanders hin.
Bitte Verbesserungsvorschläge!
MfG
P.S.: Das ist natürlich noch stark verbesserungsfähig, vielleicht muss da auch vom Design her viel refactoring gemacht werden.
Mal sehen