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 😃


Log in to reply