/* orxonox - the future of 3D-vertical-scrollers Copyright (C) 2004 orx This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. ### File Specific: main-programmer: Benjamin Grauer co-programmer: Christian Meyer 2005-08-14: complete reimplementation: now the File is parsed at the initialisation, and informations is gathered there. */ #include "ini_parser.h" #include #include #define PARSELINELENGHT 1024 //!< how many chars to read at once /// /// /// /// /// /// /// INI-PARSER NODE /// /// /// /// /// /// /// /** * @brief Constructs a Node * @param name The name of the Node * @param comment The comment of the Node. */ IniParser::Node::Node(const std::string& name, const std::string& comment) { this->_name = name; this->_comment = comment; } /// /// /// /// /// //// /// INI-PARSER ENTRY /// /// /// /// /// /// //// /** * @brief Constructs a new Entry * @param name the Name of the Entry * @param value The name of the Value * @param comment The Comment used for the Entry */ IniParser::Entry::Entry(const std::string& name, const std::string& value, const std::string& comment) : IniParser::Node(name, comment), _value(value) {} /** * @brief Displays some nice debug info. */ void IniParser::Entry::debug() const { printf(" %s = %s\n", this->name().c_str(), this->_value.c_str()); } /// /// /// /// /// /// /// /// INI-PARSER SECTION /// /// /// /// /// /// /// /// /** * @brief constructs a new Section * @param sectionName The name of the Section * @param comment The Comment for this section */ IniParser::Section::Section(const std::string& sectionName, const std::string& comment) : IniParser::Node(sectionName, comment) {} /** * @brief Adds a new Entry to this Section * @param entryName The name of the Entry * @param value The Value of the Section * @param comment The Comment * @returns Reference to the Entry added. * @see IniParser::Entry::Entry */ IniParser::Entry& IniParser::Section::addEntry(const std::string& entryName, const std::string& value, const std::string& comment) { Entry::iterator entry = std::find(this->_entries.begin(), this->_entries.end(), entryName); if (entry == this->_entries.end()) { this->_entries.push_back(Entry(entryName, value, comment)); entry = --this->_entries.end(); } return *entry; } /** * @brief edits an Entry's Value * @param entryName The Entry to edit * @param value The Value to change * @param createMissing If the Entry is missing it is created if true. * @return true on success. */ bool IniParser::Section::editEntry(const std::string& entryName, const std::string& value, bool createMissing) { Entry::iterator entry = std::find(this->_entries.begin(), this->_entries.end(), entryName); if (entry == this->_entries.end()) { if (createMissing) { this->addEntry(entryName, value); return true; } else return false; } (*entry).setValue(value); return true; } /** * @param entryName The name of the entry to search for * @param defaultValue The returned value, if the entry is not found * @return The Value of the Entry, or defaultValue, if not found. */ const std::string& IniParser::Section::getValue(const std::string& entryName, const std::string& defaultValue) const { Entry::const_iterator entry = std::find(this->_entries.begin(), this->_entries.end(), entryName); if (entry != this->_entries.end()) return (*entry).value(); else return defaultValue; } /** * @brief sets a Comment to an Entry * @param entryName The Name of the Entry to set the comment to. * @param comment the Comment. * @return true on success (entry found and setup). */ bool IniParser::Section::setEntryComment(const std::string& entryName, const std::string& comment) { Entry::iterator entry = std::find(this->_entries.begin(), this->_entries.end(), entryName); if (entry != this->_entries.end()) { (*entry).setComment(comment); return true; } return false; } /** * @brief retrieves a Comment of an Entry * @param entryName The Entry to get the comment of. * @return The Comment, or "" if the Entry was not found. */ const std::string& IniParser::Section::getEntryComment(const std::string& entryName) const { Entry::const_iterator entry = std::find(this->_entries.begin(), this->_entries.end(), entryName); if (entry != this->_entries.end()) { return (*entry).comment(); } return IniParser::_emptyString; } /** * @brief retrieves a pointer to an Entry * @param entryName The Name of the Entry. * @return the located Entry or NULL! * @note beware of NULL! */ IniParser::Entry* IniParser::Section::getEntry(const std::string& entryName) { Entry::iterator entry = std::find(this->_entries.begin(), this->_entries.end(), entryName); if (entry != this->_entries.end()) return &(*entry); else return NULL; } /** * @brief retrieves an Iterator to an Entry called entryName within this section * @param entryName the name of the Entry * @return The iterator to the position, or end(); * @see Section::end(); */ IniParser::Entry::const_iterator IniParser::Section::getEntryIt(const std::string& entryName) const { return std::find(this->_entries.begin(), this->_entries.end(), entryName); } /** * @brief clears the Section's entries (flushes them) */ void IniParser::Section::clear() { this->_entries.clear(); } /** * @brief print out some debug info */ void IniParser::Section::debug() const { printf(" [%s]\n", this->name().c_str()); for(Entry::const_iterator entry = this->_entries.begin(); entry != this->_entries.end(); ++entry) (*entry).debug(); } /// /// /// /// /// /// /// /// INI-PARSER DOCUMENT /// /// /// /// /// /// /// /// /** * @brief Constructs a new Document * @param fileName The Name of the Document. * @param comment Some Comment */ IniParser::Document::Document(const std::string& fileName, const std::string& comment) : IniParser::Node(fileName, comment) {} /** * @brief Adds a new Section to the Document * @param sectionName The Name of the Section to add * @param comment A comment for the section. * @return A Reference to the newly added section. */ IniParser::Section& IniParser::Document::addSection(const std::string& sectionName, const std::string& comment) { Section::iterator section = std::find(this->_sections.begin(), this->_sections.end(), sectionName); if (section == this->_sections.end()) { this->_sections.push_back(Section(sectionName, comment)); return *(--_sections.end()); } else return *section; } /** * @brief removes a Section from the Document. * @param sectionName The section to remove * @return true on success (section was found). */ bool IniParser::Document::removeSection(const std::string& sectionName) { Section::iterator section = std::find(this->_sections.begin(), this->_sections.end(), sectionName); if (section != this->_sections.end()) { this->_sections.erase(section); return true; } else return false; } /** * @brief Sets a comment to a section * @param sectionName The name of the section * @param comment The Comment. * @return True on success (section was found). */ bool IniParser::Document::setSectionComment(const std::string& sectionName, const std::string& comment) { Section::iterator section = std::find(this->_sections.begin(), this->_sections.end(), sectionName); if (section != this->_sections.end()) { (*section).setComment(comment); return true; } else return false; } /** * @brief Queries for a Section within the document returning a Pointer to it * @param sectionName The Section to search for. * @return A pointer to the section, of NULL if not found. * @brief beware of the NULL-pointer! */ IniParser::Section* IniParser::Document::getSection(const std::string& sectionName) { Section::iterator section = std::find(this->_sections.begin(), this->_sections.end(), sectionName); if (section != this->_sections.end()) { return &(*section); } else return NULL; } /** * @brief Queries for a Section within the document returning a Pointer to it * @param sectionName The Section to search for. * @return An iterator to the Section, or end() * @see Section::end(). */ IniParser::Section::const_iterator IniParser::Document::getSectionIt(const std::string& sectionName) const { Section::const_iterator section = std::find(this->_sections.begin(), this->_sections.end(), sectionName); return section; } /** * @brief adds a new Entry to a designated section. * @param sectionName The name of the Section * @param entryName The Name of the Entry to add * @param value The Value to set for the entry * @param comment optionally a comment. * @return true on success (always true) * * @note the section will also be created on the go, if it did not exists so far! */ bool IniParser::Document::addEntry(const std::string& sectionName, const std::string& entryName, const std::string& value, const std::string& comment) { // locating section Section::iterator section = std::find(this->_sections.begin(), this->_sections.end(), sectionName); if (section == this->_sections.end()) { // creating section if not found!! this->_sections.push_back(Section(sectionName)); section = --_sections.end(); } (*section).addEntry(entryName, value, comment); return true; } /** * @brief edits an Entry, and possibly creating it. * @param sectionName The Section's name to edit the entry in. * @param entryName The Name of the Entry to edit the value from * @param value The new value for the Entry. * @param createMissing if true, the Entry, (and the section) will be created. * @return true on success, false otherwise. */ bool IniParser::Document::editEntry(const std::string& sectionName, const std::string& entryName, const std::string& value, bool createMissing) { // locating section Section::iterator section = std::find(this->_sections.begin(), this->_sections.end(), sectionName); if (section == this->_sections.end()) { // creating section if not found!! if (createMissing) { this->_sections.push_back(Section(sectionName)); section = --_sections.end(); } else return false; } return (*section).editEntry(entryName, value, createMissing); } /** * @brief Retrieve a value from an Entry. * @param sectionName The Name of the Section the enrty is in * @param entryName The Name of the entry * @param defaultValue A default value, if the entry is not found * @return A string containing the value, or defaultValue, if the Section->Entry was not found. */ const std::string& IniParser::Document::getValue(const std::string& sectionName, const std::string& entryName, const std::string& defaultValue) const { // locating section Section::const_iterator section = std::find(this->_sections.begin(), this->_sections.end(), sectionName); if (section != this->_sections.end()) return (*section).getValue(entryName, defaultValue); return defaultValue; } /** * @brief Sets a Comment to an Entry. * @param sectionName The name of the Section. * @param entryName The name of the Entry. * @param comment The comment to set to this Entry * @return true on success (Section->Entry found). */ bool IniParser::Document::setEntryComment(const std::string& sectionName, const std::string& entryName, const std::string& comment) { // locating section Section::iterator section = std::find(this->_sections.begin(), this->_sections.end(), sectionName); if (section != this->_sections.end()) return (*section).setEntryComment(entryName, comment); else return false; } /** * @brief retrieved the comment of an Entry. * @param sectionName The section. * @param entryName The Entry to get the comment from * @return the Comment of the Entry. */ const std::string& IniParser::Document::getEntryComment(const std::string& sectionName, const std::string& entryName) const { Section::const_iterator section = std::find(this->_sections.begin(), this->_sections.end(), sectionName); if (section != this->_sections.end()) return (*section).getEntryComment(entryName); else return IniParser::_emptyString; } /** * @brief clears all sections. */ void IniParser::Document::clear() { this->_sections.clear(); } /** * @brief Print some nice debug output. */ void IniParser::Document::debug() const { for(Section::const_iterator section = this->_sections.begin(); section != this->_sections.end(); ++section) (*section).debug(); } /// /// /// /// /// /// // /// INI-PARSER ITSELF //// /// /// /// /// /// /// // const std::string IniParser::_emptyString = ""; /** * @brief constructs an IniParser using a file * @param fileName: the path and name of the file to parse */ IniParser::IniParser (const std::string& fileName) : _document(fileName) { this->_fileName = fileName; if (!fileName.empty()) this->readFile(fileName); } /** * @brief removes the IniParser from memory */ IniParser::~IniParser () {} /** * @brief opens a file to parse * @param fileName: path and name of the new file to parse * @param keepSettings if the Settings (if already some are set) should be kept (false by default) * @return true on success false otherwise; * * If there was already an opened file, the file will be closed, * and the new one will be opened. */ bool IniParser::readFile(const std::string& fileName, bool keepSettings) { FILE* stream; //< The stream we use to read the file. int lineCount = 0; //< The Count of lines read. std::list commentList; //< A list of Comments. Section* currentSection = NULL; if (!keepSettings) this->_document.clear(); if( (stream = fopen (fileName.c_str(), "r")) == NULL) { printf("ERROR:: IniParser could not open %s for reading\n", fileName.c_str()); return false; } else { this->_fileName = fileName; ///////////////////////////// // READING IN THE INI-FILE // ///////////////////////////// char lineBuffer[PARSELINELENGHT+1]; char buffer[PARSELINELENGHT+1]; const char* lineBegin; char* ptr; while( fgets (lineBuffer, PARSELINELENGHT, stream)) { lineBegin = lineBuffer; // remove newline char, and \0-terminate if( (ptr = strchr( lineBuffer, '\n')) != NULL) *ptr = 0; else lineBuffer[PARSELINELENGHT] = 0; // cut up to the beginning of the line. while((*lineBegin == ' ' || *lineBegin == '\t') && lineBegin < lineBuffer + strlen(lineBuffer)) ++lineBegin; if ( !strcmp( lineBegin, "" ) ) continue; // check if we have a FileComment if ( (*lineBegin == '#' || *lineBegin == ';')) { std::string newCommenLine = lineBegin; commentList.push_back(newCommenLine); continue; } if (lineCount == 0 && !commentList.empty()) { this->setNodeComment(&this->_document, &commentList); lineCount++; } // check for section identifyer else if( sscanf (lineBegin, "[%s", buffer) == 1) { if( (ptr = strchr( buffer, ']')) != NULL) { *ptr = 0; Section& node = this->_document.addSection(buffer); setNodeComment(&node, &commentList); currentSection = &node; } } // check for Entry identifier (Entry = Value) else if( (ptr = strchr( lineBegin, '=')) != NULL) { if (currentSection == NULL) { printf("WARNING:: Not in a Section yet for %s\n", lineBegin); lineCount++; continue; } if( ptr == lineBegin) { lineCount++; continue; } char* valueBegin = ptr+1; while ((*valueBegin == ' ' || *valueBegin == '\t') && valueBegin <= lineBegin + strlen(lineBegin)) ++valueBegin; char* valueEnd = valueBegin + strlen(valueBegin)-1; while ((*valueEnd == ' ' || *valueEnd == '\t') && valueEnd >= valueBegin) --valueEnd; valueEnd[1] = '\0'; char* nameEnd = ptr-1; while ((*nameEnd == ' ' || *nameEnd == '\t' ) && nameEnd >= lineBegin) --nameEnd; nameEnd[1] = '\0'; Entry& node = currentSection->addEntry(lineBegin, valueBegin); this->setNodeComment(&node, &commentList); lineCount++; } } } fclose(stream); return true; } /** * @brief opens a file and writes to it. * @param fileName: path and name of the new file to write to. If empty the internal value is used. * @return true on success false otherwise */ bool IniParser::writeFile(const std::string& fileName) const { std::string parseFile; FILE* stream; //!< The stream we use to read the file. if( fileName.empty()) parseFile = _fileName; else parseFile = fileName; if( (stream = fopen (parseFile.c_str(), "w")) == NULL) { printf("ERROR:: IniParser could not open %s for writing\n", parseFile.c_str()); return false; } else { if (!this->_document.comment().empty()) fprintf(stream, "%s\n\n", this->_document.comment().c_str()); Section::const_iterator section; for (section = this->_document.sections().begin(); section != this->_document.sections().end(); ++section) { if (!(*section).comment().empty()) fprintf(stream, "%s", (*section).comment().c_str()); fprintf(stream, "\n [%s]\n", (*section).name().c_str()); Entry::const_iterator entry; for (entry = (*section).entries().begin(); entry != (*section).entries().end(); ++entry) { if (!(*entry).comment().empty()) fprintf(stream, "%s", (*entry).comment().c_str()); fprintf(stream, " %s = %s\n", (*entry).name().c_str(), (*entry).value().c_str()); } } } fclose(stream); return true; } void IniParser::setFileComment(const std::string& fileComment) { this->_document.setComment(fileComment); } /** * @brief adds a section to the list of Sections, * if no Section list is availiable, it will create it * @param sectionName the Name of the section to add * @return true on success... there is only success or segfault :) */ IniParser::Section& IniParser::addSection(const std::string& sectionName) { return this->_document.addSection(sectionName); } /** * @brief adds a new Entry to either the currentSection or the section called by sectionName * @param entryName the Name of the Entry to add * @param value the value to assign to this entry * @param sectionName if NULL then this entry will be set to the currentSection * otherwise to the section refered to by sectionName. * If both are NULL no entry will be added * @return The Entry, that was added. */ bool IniParser::addEntry(const std::string& sectionName, const std::string& entryName, const std::string& value, const std::string& comment) { return this->_document.addEntry(sectionName, entryName, value, comment); } /** * @brief edits the entry speciefied by entryName in sectionName/currentSection or creates it if it doesn't exist * @param entryName the Name of the Entry to add * @param value the value to assign to this entry * @param sectionName if NULL then this entry will be set to the currentSection * otherwise to the section refered to by sectionName. * If both are NULL no entry will be added * @return true if everything is ok false on error */ bool IniParser::editEntry(const std::string& sectionName, const std::string& entryName, const std::string& value, bool createMissing) { return this->_document.editEntry(sectionName, entryName, value, createMissing); } /** * @brief directly acesses an entry in a section * @param sectionName: the section where the entry is to be found * @param entryName: the name of the entry to find * @param defaultValue: what should be returned in case the entry cannot be found * @return The Value of the Entry. */ const std::string& IniParser::getValue(const std::string& sectionName, const std::string& entryName, const std::string& defaultValue) const { return this->_document.getValue(sectionName, entryName, defaultValue); } /** * Set the Comment of a specified Entry. * @param comment the Comment to set * @param entryName the Name of the Entry * @param sectionName the Name of the Section */ void IniParser::setEntryComment(const std::string& sectionName, const std::string& entryName, const std::string& comment) { this->_document.setEntryComment(sectionName, entryName, comment); } /** * @param entryName the Entry to query for * @param sectionName the Section to Query for * @returns the queried Comment. */ const std::string& IniParser::getEntryComment(const std::string& sectionName, const std::string& entryName) const { return this->_document.getEntryComment(sectionName, entryName); } /** * @brief output the whole tree in a nice and easy way. */ void IniParser::debug() const { printf("Iniparser '%s' - debug\n", this->_fileName.c_str()); if (!this->_document.comment().empty()) printf("FileComment:\n '%s'\n\n", this->_document.comment().c_str()); if (!this->_document.sections().empty()) this->_document.debug(); else printf("no Sections Defined in this ini-file (%s).\n", _fileName.c_str()); } /** * @brief takes lines together to form one NodeComment, ereasing the commentList * @param node the Node to apply the Comment to. * @param comments the CommentList to append. */ void IniParser::setNodeComment(Node* node, std::list* comments) { assert(node != NULL); assert(comments != NULL); std::string comment; if (comments->empty()) { comment = ""; } else { while (!comments->empty()) { if (!comment.empty()) comment += "\n"; comment += comments->front(); comments->pop_front(); } } node->setComment(comment); }