/* 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 #if HAVE_CONFIG_H #include #endif #ifdef DEBUG_LEVEL #include "../../../defs/debug.h" #else #define PRINTF(x) printf #define PRINT(x) printf #endif /** * @brief constructs an IniParser using a file * @param fileName: the path and name of the file to parse */ IniParser::IniParser (const std::string& fileName) { this->fileName = ""; this->comment = ""; if (!fileName.empty()) this->readFile(fileName); } /** * @brief removes the IniParser from memory */ IniParser::~IniParser () { this->deleteSections(); this->setFileName(""); } const std::string IniParser::emptyString = ""; /** * @brief removes all the sections. This is like delete, but even cooler :) */ void IniParser::deleteSections() { // in all sections this->sections.clear(); this->currentSection = this->sections.end(); this->setFileName(""); } /** * @brief sets the Name of the input-file * @param fileName The new FileName to set to the IniParser * If fileName is NULL the new Name will be set to NULL too. */ void IniParser::setFileName(const std::string& fileName) { this->comment = ""; this->fileName = fileName; } /** * @brief opens a file to parse * @param fileName: path and name of the new file to parse * @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) { FILE* stream; //< The stream we use to read the file. int lineCount = 0; //< The Count of lines. if (!this->fileName.empty()) this->deleteSections(); if( fileName.empty()) return false; if( (stream = fopen (fileName.c_str(), "r")) == NULL) { PRINTF(1)("IniParser could not open %s for reading\n", fileName.c_str()); return false; } else { this->setFileName(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; this->commentList.push_back(newCommenLine); continue; } if (lineCount == 0 && !this->commentList.empty()) { this->setFileComment(); lineCount++; } // check for section identifyer else if( sscanf (lineBegin, "[%s", buffer) == 1) { if( (ptr = strchr( buffer, ']')) != NULL) { *ptr = 0; this->addSection(buffer); this->setSectionComment(); } } // check for Entry identifier (Entry = Value) else if( (ptr = strchr( lineBegin, '=')) != NULL) { if (currentSection == NULL) { PRINTF(2)("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'; this->addVar(lineBegin, valueBegin); this->setEntryComment(); lineCount++; } } } this->currentSection = this->sections.begin(); if (!this->sections.empty()) this->currentEntry = (*this->currentSection).entries.begin(); fclose(stream); return true; } /** * @brief opens a file and writes to it * @param fileName: path and name of the new file to write to * @return true on success false otherwise */ bool IniParser::writeFile(const std::string& fileName) const { FILE* stream; //!< The stream we use to read the file. if( fileName.empty()) return false; if( (stream = fopen (fileName.c_str(), "w")) == NULL) { PRINTF(1)("IniParser could not open %s for writing\n", fileName.c_str()); return false; } else { if (!this->comment.empty()) fprintf(stream, "%s\n\n", this->comment.c_str()); std::list::const_iterator section; for (section = this->sections.begin(); section != this->sections.end(); section++) { if (!(*section).comment.empty()) fprintf(stream, "%s", (*section).comment.c_str()); fprintf(stream, "\n [%s]\n", (*section).name.c_str()); std::list::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->comment = 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 :) */ bool IniParser::addSection(const std::string& sectionName) { if (sectionName.empty()) return false; IniSection newSection; newSection.name = sectionName; newSection.comment = ""; this->sections.push_back(newSection); this->currentSection = --this->sections.end(); if (!this->sections.empty()) this->currentEntry = (*this->currentSection).entries.begin(); PRINTF(5)("Added Section %s\n", sectionName.c_str()); return true; } /** * @brief Set the parsing cursor to the specified section * @param sectionName: the name of the section to set the cursor to * @return true on success or false if the section could not be found */ bool IniParser::getSection(const std::string& sectionName) { this->currentSection = this->getSectionIT(sectionName); if (this->currentSection != this->sections.end()) { this->currentEntry = (*this->currentSection).entries.begin(); return true; } else return false; } /** * */ void IniParser::setSectionComment(const std::string& comment, const std::string& sectionName) { std::list::iterator section = this->getSectionIT(sectionName); if (section == this->sections.end()) return; (*section).comment = comment; } /** * @param sectionName the Section to query for * @returns the Comment, or NULL on error. */ const std::string& IniParser::getSectionComment(const std::string& sectionName) const { std::list::const_iterator section = this->getSectionIT(sectionName); if (section != this->sections.end()) return (*section).comment; else return IniParser::emptyString; } /** * @brief moves to the first section */ void IniParser::firstSection() { this->currentSection = this->sections.begin(); if (!this->sections.empty()) this->currentEntry = (*this->currentSection).entries.begin(); } /** * @brief searches the next section * @returns the name of the section if found, NULL otherwise */ const std::string& IniParser::nextSection() { if (this->currentSection == this->sections.end()) return IniParser::emptyString; this->currentSection++; if (this->currentSection != this->sections.end()) { this->currentEntry = (*this->currentSection).entries.begin(); return this->currentSection->name; } else return IniParser::emptyString; } /** * @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 true if everything is ok false on error */ bool IniParser::addVar(const std::string& entryName, const std::string& value, const std::string& sectionName) { std::list::iterator section; if (!sectionName.empty()) { for (section = this->sections.begin(); section != this->sections.end(); section++) if ((*section).name == sectionName) break; } else section = this->currentSection; if (section == this->sections.end()) return false; if (section == this->sections.end()) { PRINTF(2)("section '%s' not found for value '%s'\n", sectionName.c_str(), entryName.c_str()); return false; } else { (*section).entries.push_back(IniEntry()); (*section).entries.back().comment = ""; (*section).entries.back().name = entryName; (*section).entries.back().value = value; PRINTF(5)("Added Entry %s with Value '%s' to Section %s\n", (*section).entries.back().name.c_str(), (*section).entries.back().value.c_str(), (*section).name.c_str()); this->currentEntry = --(*section).entries.end(); return true; } } /** * @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::editVar(const std::string& entryName, const std::string& value, const std::string& sectionName, bool createMissing) { std::list::iterator section; if (!sectionName.empty()) { for (section = this->sections.begin(); section != this->sections.end(); section++) if ((*section).name == sectionName) break; } else section = this->currentSection; if (section == this->sections.end()) { this->addSection(sectionName); for (section = this->sections.begin(); section != this->sections.end(); section++) if ((*section).name == sectionName) break; } //try find item std::list::iterator entry; for (entry = section->entries.begin(); entry!=section->entries.end(); entry++) if (entry->name == entryName ) break; //found it? if ( entry != section->entries.end() ) { entry->value = value; return true; } else { //not found -> create it (*section).entries.push_back(IniEntry()); (*section).entries.back().comment = ""; (*section).entries.back().name = entryName; (*section).entries.back().value = value; PRINTF(5)("Added Entry '%s' with Value '%s' to Section '%s'\n", (*section).entries.back().name.c_str(), (*section).entries.back().value.c_str(), (*section).name.c_str()); this->currentEntry = --(*section).entries.end(); return true; } return false; } /** * @brief directly acesses an entry in a section * @param entryName: the name of the entry to find * @param sectionName: the section where the entry is to be found * @param defaultValue: what should be returned in case the entry cannot be found * @return a pointer to a buffer conatining the value of the specified entry. This buffer will contain the data specified in defvalue in case the entry wasn't found * * The returned pointer points to an internal buffer, so do not free it on your own. Do not give a NULL pointer to defvalue, this will certainly * lead to unwanted behaviour. */ const std::string& IniParser::getVar(const std::string& entryName, const std::string& sectionName, const std::string& defaultValue) const { if (!this->fileName.empty()) { std::list::const_iterator entry = this->getEntryIT(entryName, sectionName); if (entry != NULL && (*entry).name == entryName) return (*entry).value; PRINTF(2)("Entry '%s' in section '%s' not found.\n", entryName.c_str(), sectionName.c_str()); } else PRINTF(2)("no File opened\n"); return 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& comment, const std::string& entryName, const std::string& sectionName) { std::list::iterator entry = this->getEntryIT(entryName, sectionName); (*entry).comment = 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& entryName, const std::string& sectionName) const { std::list::const_iterator entry = this->getEntryIT(entryName, sectionName); return (*entry).comment; } /** * @brief moves to the first Variable of the current Section */ void IniParser::firstVar() { if (!this->sections.empty() && this->currentSection != this->sections.end()) this->currentEntry = (*this->currentSection).entries.begin(); } /** * @brief gets the next VarName = VarValue pair from the parsing stream * @return true on success, false otherwise (in the latter case name and value will be NULL) */ bool IniParser::nextVar() { if ( this->sections.empty() || this->currentSection == this->sections.end() || this->currentEntry == (*this->currentSection).entries.end()) return false; this->currentEntry++; if (this->currentEntry == (*this->currentSection).entries.end()) return false; else return true; } /** * @returns the name of the Current selected Section */ const std::string& IniParser::getCurrentSection() const { if (!this->sections.empty() && this->currentSection != this->sections.end()) return this->currentSection->name; else return IniParser::emptyString ; } /** * @returns the current entries Name, or NULL if we havn't selected a Entry */ const std::string& IniParser::getCurrentName() const { if (!this->sections.empty() && this->currentSection != this->sections.end() && this->currentEntry != (*this->currentSection).entries.end()) return (*this->currentEntry).name; else return emptyString; } /** * @returns the current entries Value, or NULL if we havn't selected a Entry */ const std::string& IniParser::getCurrentValue() const { if (!this->sections.empty() && this->currentSection != this->sections.end() && this->currentEntry != (*this->currentSection).entries.end()) return (*this->currentEntry).value; else return IniParser::emptyString; } /** * Finds the Section Iterator of the Section Called sectionName * @param sectionName the Name of the Section to get the Iterator from */ std::list::const_iterator IniParser::getSectionIT(const std::string& sectionName) const { std::list::const_iterator section = this->currentSection; if (sectionName.empty()) return this->currentSection; else for (section = this->sections.begin(); section != this->sections.end(); section++) if ((*section).name == sectionName) break; return section; } /** * Finds the Section Iterator of the Section Called sectionName * @param sectionName the Name of the Section to get the Iterator from */ std::list::iterator IniParser::getSectionIT(const std::string& sectionName) { std::list::iterator section = this->currentSection; if (sectionName.empty()) return this->currentSection; else for (section = this->sections.begin(); section != this->sections.end(); section++) if ((*section).name == sectionName) break; return section; } /** * Finds the Entry Iterator of the Section Called sectionName and entry called EntryName * @param entryName the Name of the Entry to get the Iterator from * @param sectionName the Name of the Section to get the Iterator from */ std::list::const_iterator IniParser::getEntryIT(const std::string& entryName, const std::string& sectionName) const { if (entryName.empty()) return this->currentEntry; std::list::const_iterator section = this->getSectionIT(sectionName); std::list::const_iterator entry = this->currentEntry; if (section != this->sections.end()) for (entry = (*section).entries.begin(); entry != (*section).entries.end(); entry++) if ((*entry).name == entryName) break; if (entry == (*section).entries.end()) return NULL; else return entry; } /** * Finds the Entry Iterator of the Section Called sectionName and entry called EntryName * @param entryName the Name of the Entry to get the Iterator from * @param sectionName the Name of the Section to get the Iterator from */ std::list::iterator IniParser::getEntryIT(const std::string& entryName, const std::string& sectionName) { if (entryName.empty()) return this->currentEntry; std::list::iterator section = this->getSectionIT(sectionName); std::list::iterator entry = this->currentEntry; if (section != this->sections.end()) for (entry = (*section).entries.begin(); entry != (*section).entries.end(); entry++) if ((*entry).name == entryName) break; if (entry == (*section).entries.end()) return NULL; else return entry; } /** * takes lines together to form one FileComment, ereasing the commentList */ void IniParser::setFileComment() { if (this->commentList.empty()) { this->comment = ""; return; } std::list::iterator comment; while (!this->commentList.empty()) { if (this->comment[0] != '\0') this->comment += "\n"; this->comment += this->commentList.front(); this->commentList.pop_front(); } } /** * takes lines together to form one SectionComment, ereasing the commentList */ void IniParser::setSectionComment() { (*this->currentSection).comment = ""; if (this->commentList.empty()) return; while (!this->commentList.empty()) { if ((*this->currentSection).comment[0] != '\0') (*this->currentSection).comment += "\n"; (*this->currentSection).comment += this->commentList.front(); this->commentList.pop_front(); } } /** * takes lines together to form one EntryComment, ereasing the commentList */ void IniParser::setEntryComment() { (*this->currentEntry).comment = ""; if (this->commentList.empty()) return; while (!this->commentList.empty()) { if ((*this->currentEntry).comment[0] != '\0') (*this->currentEntry).comment += "\n"; (*this->currentEntry).comment += this->commentList.front(); this->commentList.pop_front(); } } /** * @brief output the whole tree in a nice and easy way. */ void IniParser::debug() const { PRINT(0)("Iniparser '%s' - debug\n", this->fileName.c_str()); if (!this->comment.empty()) PRINT(0)("FileComment:\n '%s'\n\n", this->comment.c_str()); if (!this->fileName.empty()) { if (sections.empty()) PRINT(0)("No Sections defined\n"); std::list::const_iterator section; for (section = this->sections.begin(); section != this->sections.end(); section++) { if (!(*section).comment.empty()) PRINTF(0)(" %s\n", (*section).comment.c_str()); PRINTF(0)(" [%s]\n", (*section).name.c_str()); if ((*section).entries.empty()) PRINT(0)("No Entries defined within Section '%s'\n", (*section).name.c_str()); std::list::const_iterator entry; for (entry = (*section).entries.begin(); entry != (*section).entries.end(); entry++) { if (!(*entry).comment.empty()) PRINTF(0)(" %s\n", (*entry).comment.c_str()); PRINTF(0)(" '%s' -> '%s'\n", (*entry).name.c_str(), (*entry).value.c_str()); } } } else PRINTF(0)("no opened ini-file.\n"); }