diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index ead2c9bb7..406efda73 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -969,7 +969,7 @@ set (PCH_SOURCES common/utility/zstrformat.cpp common/utility/utf8.cpp common/utility/superfasthash.cpp -#common/utility/configfile.cpp + common/utility/configfile.cpp ) if( MSVC ) diff --git a/source/common/utility/configfile.cpp b/source/common/utility/configfile.cpp new file mode 100644 index 000000000..cf7f44312 --- /dev/null +++ b/source/common/utility/configfile.cpp @@ -0,0 +1,966 @@ +/* +** configfile.cpp +** Implements the basic .ini parsing class +** +**--------------------------------------------------------------------------- +** Copyright 1998-2008 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include +#include +#include + +#include "configfile.h" +#include "files.h" + +#define READBUFFERSIZE 256 + +//==================================================================== +// +// FConfigFile Constructor +// +//==================================================================== + +FConfigFile::FConfigFile () +{ + Sections = CurrentSection = NULL; + LastSectionPtr = &Sections; + CurrentEntry = NULL; + PathName = ""; + OkayToWrite = true; + FileExisted = true; +} + +//==================================================================== +// +// FConfigFile Constructor +// +//==================================================================== + +FConfigFile::FConfigFile (const char *pathname) +{ + Sections = CurrentSection = NULL; + LastSectionPtr = &Sections; + CurrentEntry = NULL; + ChangePathName (pathname); + LoadConfigFile (); + OkayToWrite = true; + FileExisted = true; +} + +//==================================================================== +// +// FConfigFile Copy Constructor +// +//==================================================================== + +FConfigFile::FConfigFile (const FConfigFile &other) +{ + Sections = CurrentSection = NULL; + LastSectionPtr = &Sections; + CurrentEntry = NULL; + ChangePathName (other.PathName); + *this = other; + OkayToWrite = other.OkayToWrite; + FileExisted = other.FileExisted; +} + +//==================================================================== +// +// FConfigFile Destructor +// +//==================================================================== + +FConfigFile::~FConfigFile () +{ + FConfigSection *section = Sections; + + while (section != NULL) + { + FConfigSection *nextsection = section->Next; + FConfigEntry *entry = section->RootEntry; + + while (entry != NULL) + { + FConfigEntry *nextentry = entry->Next; + delete[] entry->Value; + delete[] (char *)entry; + entry = nextentry; + } + delete section; + section = nextsection; + } +} + +//==================================================================== +// +// FConfigFile Copy Operator +// +//==================================================================== + +FConfigFile &FConfigFile::operator = (const FConfigFile &other) +{ + FConfigSection *fromsection, *tosection; + FConfigEntry *fromentry; + + ClearConfig (); + fromsection = other.Sections; + while (fromsection != NULL) + { + fromentry = fromsection->RootEntry; + tosection = NewConfigSection (fromsection->SectionName); + while (fromentry != NULL) + { + NewConfigEntry (tosection, fromentry->Key, fromentry->Value); + fromentry = fromentry->Next; + } + fromsection = fromsection->Next; + } + return *this; +} + +//==================================================================== +// +// FConfigFile :: ClearConfig +// +// Removes all sections and entries from the config file. +// +//==================================================================== + +void FConfigFile::ClearConfig () +{ + CurrentSection = Sections; + while (CurrentSection != NULL) + { + FConfigSection *next = CurrentSection->Next; + ClearCurrentSection (); + delete CurrentSection; + CurrentSection = next; + } + Sections = NULL; + LastSectionPtr = &Sections; +} + +//==================================================================== +// +// FConfigFile :: ChangePathName +// +//==================================================================== + +void FConfigFile::ChangePathName (const char *pathname) +{ + PathName = pathname; +} + +//==================================================================== +// +// FConfigFile :: CreateSectionAtStart +// +// Creates the section at the start of the file if it does not exist. +// Otherwise, simply moves the section to the start of the file. +// +//==================================================================== + +void FConfigFile::CreateSectionAtStart (const char *name) +{ + NewConfigSection (name); + MoveSectionToStart (name); +} + +//==================================================================== +// +// FConfigFile :: MoveSectionToStart +// +// Moves the named section to the start of the file if it exists. +// Otherwise, does nothing. +// +//==================================================================== + +void FConfigFile::MoveSectionToStart (const char *name) +{ + FConfigSection *section = FindSection (name); + + if (section != NULL) + { + FConfigSection **prevsec = &Sections; + while (*prevsec != NULL && *prevsec != section) + { + prevsec = &((*prevsec)->Next); + } + *prevsec = section->Next; + section->Next = Sections; + Sections = section; + if (LastSectionPtr == §ion->Next) + { + LastSectionPtr = prevsec; + } + } +} + + +//==================================================================== +// +// FConfigFile :: SetSection +// +// Sets the current section to the named one, optionally creating it +// if it does not exist. Returns true if the section exists (even if +// it was newly created), false otherwise. +// +//==================================================================== + +bool FConfigFile::SetSection (const char *name, bool allowCreate) +{ + FConfigSection *section = FindSection (name); + if (section == NULL && allowCreate) + { + section = NewConfigSection (name); + } + if (section != NULL) + { + CurrentSection = section; + CurrentEntry = section->RootEntry; + return true; + } + return false; +} + +//==================================================================== +// +// FConfigFile :: SetFirstSection +// +// Sets the current section to the first one in the file. Returns +// false if there are no sections. +// +//==================================================================== + +bool FConfigFile::SetFirstSection () +{ + CurrentSection = Sections; + if (CurrentSection != NULL) + { + CurrentEntry = CurrentSection->RootEntry; + return true; + } + return false; +} + +//==================================================================== +// +// FConfigFile :: SetNextSection +// +// Advances the current section to the next one in the file. Returns +// false if there are no more sections. +// +//==================================================================== + +bool FConfigFile::SetNextSection () +{ + if (CurrentSection != NULL) + { + CurrentSection = CurrentSection->Next; + if (CurrentSection != NULL) + { + CurrentEntry = CurrentSection->RootEntry; + return true; + } + } + return false; +} + +//==================================================================== +// +// FConfigFile :: GetCurrentSection +// +// Returns the name of the current section. +// +//==================================================================== + +const char *FConfigFile::GetCurrentSection () const +{ + if (CurrentSection != NULL) + { + return CurrentSection->SectionName.GetChars(); + } + return NULL; +} + +//==================================================================== +// +// FConfigFile :: ClearCurrentSection +// +// Removes all entries from the current section. +// +//==================================================================== + +void FConfigFile::ClearCurrentSection () +{ + if (CurrentSection != NULL) + { + FConfigEntry *entry, *next; + + entry = CurrentSection->RootEntry; + while (entry != NULL) + { + next = entry->Next; + delete[] entry->Value; + delete[] (char *)entry; + entry = next; + } + CurrentSection->RootEntry = NULL; + CurrentSection->LastEntryPtr = &CurrentSection->RootEntry; + } +} + +//==================================================================== +// +// FConfigFile :: DeleteCurrentSection +// +// Completely removes the current section. The current section is +// advanced to the next section. Returns true if there is still a +// current section. +// +//==================================================================== + +bool FConfigFile::DeleteCurrentSection() +{ + if (CurrentSection != NULL) + { + FConfigSection *sec; + + ClearCurrentSection(); + + // Find the preceding section. + for (sec = Sections; sec != NULL && sec->Next != CurrentSection; sec = sec->Next) + { } + + sec->Next = CurrentSection->Next; + if (LastSectionPtr == &CurrentSection->Next) + { + LastSectionPtr = &sec->Next; + } + + delete CurrentSection; + + CurrentSection = sec->Next; + return CurrentSection != NULL; + } + return false; +} + +//==================================================================== +// +// FConfigFile :: ClearKey +// +// Removes a key from the current section, if found. If there are +// duplicates, only the first is removed. +// +//==================================================================== + +void FConfigFile::ClearKey(const char *key) +{ + if (CurrentSection->RootEntry == NULL) + { + return; + } + FConfigEntry **prober = &CurrentSection->RootEntry, *probe = *prober; + + while (probe != NULL && stricmp(probe->Key, key) != 0) + { + prober = &probe->Next; + probe = *prober; + } + if (probe != NULL) + { + *prober = probe->Next; + if (CurrentSection->LastEntryPtr == &probe->Next) + { + CurrentSection->LastEntryPtr = prober; + } + delete[] probe->Value; + delete[] (char *)probe; + } +} + +//==================================================================== +// +// FConfigFile :: SectionIsEmpty +// +// Returns true if the current section has no entries. If there is +// no current section, it is also considered empty. +// +//==================================================================== + +bool FConfigFile::SectionIsEmpty() +{ + return (CurrentSection == NULL) || (CurrentSection->RootEntry == NULL); +} + + +//==================================================================== +// +// FConfigFile :: NextInSection +// +// Provides the next key/value pair in the current section. Returns +// true if there was another, false otherwise. +// +//==================================================================== + +bool FConfigFile::NextInSection (const char *&key, const char *&value) +{ + FConfigEntry *entry = CurrentEntry; + + if (entry == NULL) + return false; + + CurrentEntry = entry->Next; + key = entry->Key; + value = entry->Value; + return true; +} + +//==================================================================== +// +// FConfigFile :: GetValueForKey +// +// Returns the value for the specified key in the current section, +// returning NULL if the key does not exist. +// +//==================================================================== + +const char *FConfigFile::GetValueForKey (const char *key) const +{ + FConfigEntry *entry = FindEntry (CurrentSection, key); + + if (entry != NULL) + { + return entry->Value; + } + return NULL; +} + +//==================================================================== +// +// FConfigFile :: SetValueForKey +// +// Sets they key/value pair as specified in the current section. If +// duplicates are allowed, it always creates a new pair. Otherwise, it +// will overwrite the value of an existing key with the same name. +// +//==================================================================== + +void FConfigFile::SetValueForKey (const char *key, const char *value, bool duplicates) +{ + if (CurrentSection != NULL) + { + FConfigEntry *entry; + + if (duplicates || (entry = FindEntry (CurrentSection, key)) == NULL) + { + NewConfigEntry (CurrentSection, key, value); + } + else + { + entry->SetValue (value); + } + } +} + +//==================================================================== +// +// FConfigFile :: FindSection +// +//==================================================================== + +FConfigFile::FConfigSection *FConfigFile::FindSection (const char *name) const +{ + FConfigSection *section = Sections; + + while (section != NULL && section->SectionName.CompareNoCase(name) != 0) + { + section = section->Next; + } + return section; +} + +//==================================================================== +// +// FConfigFile :: RenameSection +// +//==================================================================== + +void FConfigFile::RenameSection (const char *oldname, const char *newname) const +{ + FConfigSection *section = FindSection(oldname); + + if (section != NULL) + { + section->SectionName = newname; + } +} + +//==================================================================== +// +// FConfigFile :: FindEntry +// +//==================================================================== + +FConfigFile::FConfigEntry *FConfigFile::FindEntry ( + FConfigFile::FConfigSection *section, const char *key) const +{ + FConfigEntry *probe = section->RootEntry; + + while (probe != NULL && stricmp (probe->Key, key) != 0) + { + probe = probe->Next; + } + return probe; +} + +//==================================================================== +// +// FConfigFile :: NewConfigSection +// +//==================================================================== + +FConfigFile::FConfigSection *FConfigFile::NewConfigSection (const char *name) +{ + FConfigSection *section; + + section = FindSection (name); + if (section == NULL) + { + section = new FConfigSection; + section->RootEntry = NULL; + section->LastEntryPtr = §ion->RootEntry; + section->Next = NULL; + section->SectionName = name; + *LastSectionPtr = section; + LastSectionPtr = §ion->Next; + } + return section; +} + +//==================================================================== +// +// FConfigFile :: NewConfigEntry +// +//==================================================================== + +FConfigFile::FConfigEntry *FConfigFile::NewConfigEntry ( + FConfigFile::FConfigSection *section, const char *key, const char *value) +{ + FConfigEntry *entry; + size_t keylen; + + keylen = strlen (key); + entry = (FConfigEntry *)new char[sizeof(*section)+keylen]; + entry->Value = NULL; + entry->Next = NULL; + memcpy (entry->Key, key, keylen); + entry->Key[keylen] = 0; + *(section->LastEntryPtr) = entry; + section->LastEntryPtr = &entry->Next; + entry->SetValue (value); + return entry; +} + +//==================================================================== +// +// FConfigFile :: LoadConfigFile +// +//==================================================================== + +void FConfigFile::LoadConfigFile () +{ + FileReader file; + bool succ; + + FileExisted = false; + if (!file.OpenFile (PathName)) + { + return; + } + + succ = ReadConfig (&file); + FileExisted = succ; +} + +//==================================================================== +// +// FConfigFile :: ReadConfig +// +//==================================================================== + +bool FConfigFile::ReadConfig (void *file) +{ + uint8_t readbuf[READBUFFERSIZE]; + FConfigSection *section = NULL; + ClearConfig (); + + while (ReadLine ((char*)readbuf, READBUFFERSIZE, file) != NULL) + { + uint8_t *start = readbuf; + uint8_t *equalpt; + uint8_t *endpt; + + // Remove white space at start of line + while (*start && *start <= ' ') + { + start++; + } + // Remove comment lines + if (*start == '#' || (start[0] == '/' && start[1] == '/')) + { + continue; + } + // Do not process tail of long line + const bool longline = (READBUFFERSIZE - 1) == strlen((char*)readbuf) && '\n' != readbuf[READBUFFERSIZE - 2]; + if (longline) + { + endpt = start + READBUFFERSIZE - 2; + } + else + { + // Remove white space at end of line + endpt = start + strlen ((char*)start) - 1; + while (endpt > start && *endpt <= ' ') + { + endpt--; + } + // Remove line feed '\n' character + endpt[1] = 0; + if (endpt <= start) + continue; // Nothing here + } + + if (*start == '[') + { // Section header + if (*endpt == ']') + *endpt = 0; + section = NewConfigSection ((char*)start+1); + } + else if (section == NULL) + { + return false; + } + else + { // Should be key=value + equalpt = (uint8_t*)strchr ((char*)start, '='); + if (equalpt != NULL && equalpt > start) + { + // Remove white space in front of = + uint8_t *whiteprobe = equalpt - 1; + while (whiteprobe > start && isspace(*whiteprobe)) + { + whiteprobe--; + } + whiteprobe[1] = 0; + // Remove white space after = + whiteprobe = equalpt + 1; + while (*whiteprobe && isspace(*whiteprobe)) + { + whiteprobe++; + } + *(whiteprobe - 1) = 0; + // Check for multi-line value + if (whiteprobe[0] == '<' && whiteprobe[1] == '<' && whiteprobe[2] == '<' && whiteprobe[3] != '\0') + { + ReadMultiLineValue (file, section, (char*)start, (char*)whiteprobe + 3); + } + else if (longline) + { + const FString key = (char*)start; + FString value = (char*)whiteprobe; + + while (ReadLine ((char*)readbuf, READBUFFERSIZE, file) != NULL) + { + const size_t endpos = (0 == readbuf[0]) ? 0 : (strlen((char*)readbuf) - 1); + const bool endofline = '\n' == readbuf[endpos]; + + if (endofline) + { + readbuf[endpos] = 0; + } + + value += (char*)readbuf; + + if (endofline) + { + break; + } + } + + NewConfigEntry (section, key.GetChars(), value.GetChars()); + } + else + { + NewConfigEntry (section, (char*)start, (char*)whiteprobe); + } + } + } + } + return true; +} + +//==================================================================== +// +// FConfigFile :: ReadMultiLineValue +// +// Reads a multi-line value, with format as follows: +// +// key=<<>>ENDTAG +// +// The final ENDTAG must be on a line all by itself. +// +//==================================================================== + +FConfigFile::FConfigEntry *FConfigFile::ReadMultiLineValue(void *file, FConfigSection *section, const char *key, const char *endtag) +{ + char readbuf[READBUFFERSIZE]; + FString value; + size_t endlen = strlen(endtag); + + // Keep on reading lines until we reach a line that matches >>>endtag + while (ReadLine(readbuf, READBUFFERSIZE, file) != NULL) + { + // Does the start of this line match the endtag? + if (readbuf[0] == '>' && readbuf[1] == '>' && readbuf[2] == '>' && + strncmp(readbuf + 3, endtag, endlen) == 0) + { // Is there nothing but line break characters after the match? + size_t i; + for (i = endlen + 3; readbuf[i] != '\0'; ++i) + { + if (readbuf[i] != '\n' && readbuf[i] != '\r') + { // Not a line break character + break; + } + } + if (readbuf[i] == '\0') + { // We're done; strip the previous line's line breaks, since it's not part of the value. + value.StripRight("\n\r"); + } + break; + } + // Append this line to the value. + value << readbuf; + } + return NewConfigEntry(section, key, value); +} + +//==================================================================== +// +// FConfigFile :: ReadLine +// +//==================================================================== + +char *FConfigFile::ReadLine (char *string, int n, void *file) const +{ + return ((FileReader *)file)->Gets (string, n); +} + +//==================================================================== +// +// FConfigFile :: WriteConfigFile +// +//==================================================================== + +bool FConfigFile::WriteConfigFile () const +{ + if (!OkayToWrite && FileExisted) + { // Pretend it was written anyway so that the user doesn't get + // any "config not written" notifications, but only if the file + // already existed. Otherwise, let it write out a default one. + return true; + } + + FileWriter *file = FileWriter::Open (PathName); + FConfigSection *section; + FConfigEntry *entry; + + if (file == NULL) + return false; + + WriteCommentHeader (file); + + section = Sections; + while (section != NULL) + { + entry = section->RootEntry; + if (section->Note.IsNotEmpty()) + { + file->Write (section->Note.GetChars(), section->Note.Len()); + } + file->Printf ("[%s]\n", section->SectionName.GetChars()); + while (entry != NULL) + { + if (strpbrk(entry->Value, "\r\n") == NULL) + { // Single-line value + file->Printf ("%s=%s\n", entry->Key, entry->Value); + } + else + { // Multi-line value + const char *endtag = GenerateEndTag(entry->Value); + file->Printf ("%s=<<<%s\n%s\n>>>%s\n", entry->Key, + endtag, entry->Value, endtag); + } + entry = entry->Next; + } + section = section->Next; + file->Write ("\n", 1); + } + delete file; + return true; +} + +//==================================================================== +// +// FConfigFile :: GenerateEndTag +// +// Generates a terminator sequence for multi-line values that does +// not appear anywhere in the value. +// +//==================================================================== + +const char *FConfigFile::GenerateEndTag(const char *value) +{ + static const char Base64Table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._"; + static char EndTag[25] = "EOV-"; + + // Try different 20-character sequences until we find one that + // isn't in the value. We create the sequences by generating two + // 64-bit random numbers and Base64 encoding the first 15 bytes + // from them. + union { uint16_t rand_num[8]; uint8_t rand_bytes[16]; }; + do + { + for (auto &r : rand_num) r = (uint16_t)rand(); + + for (int i = 0; i < 5; ++i) + { + //uint32_t three_bytes = (rand_bytes[i*3] << 16) | (rand_bytes[i*3+1] << 8) | (rand_bytes[i*3+2]); // ??? + EndTag[4+i*4 ] = Base64Table[rand_bytes[i*3] >> 2]; + EndTag[4+i*4+1] = Base64Table[((rand_bytes[i*3] & 3) << 4) | (rand_bytes[i*3+1] >> 4)]; + EndTag[4+i*4+2] = Base64Table[((rand_bytes[i*3+1] & 15) << 2) | (rand_bytes[i*3+2] >> 6)]; + EndTag[4+i*4+3] = Base64Table[rand_bytes[i*3+2] & 63]; + } + } + while (strstr(value, EndTag) != NULL); + return EndTag; +} + +//==================================================================== +// +// FConfigFile :: WriteCommentHeader +// +// Override in a subclass to write a header to the config file. +// +//==================================================================== + +void FConfigFile::WriteCommentHeader (FileWriter *file) const +{ +} + +//==================================================================== +// +// FConfigFile :: FConfigEntry :: SetValue +// +//==================================================================== + +void FConfigFile::FConfigEntry::SetValue (const char *value) +{ + if (Value != NULL) + { + delete[] Value; + } + Value = new char[strlen (value)+1]; + strcpy (Value, value); +} + +//==================================================================== +// +// FConfigFile :: GetPosition +// +// Populates a struct with the current position of the parse cursor. +// +//==================================================================== + +void FConfigFile::GetPosition (FConfigFile::Position &pos) const +{ + pos.Section = CurrentSection; + pos.Entry = CurrentEntry; +} + +//==================================================================== +// +// FConfigFile :: SetPosition +// +// Sets the parse cursor to a previously retrieved position. +// +//==================================================================== + +void FConfigFile::SetPosition (const FConfigFile::Position &pos) +{ + CurrentSection = pos.Section; + CurrentEntry = pos.Entry; +} + +//==================================================================== +// +// FConfigFile :: SetSectionNote +// +// Sets a comment note to be inserted into the INI verbatim directly +// ahead of the section. Notes are lost when the INI is read so must +// be explicitly set to be maintained. +// +//==================================================================== + +void FConfigFile::SetSectionNote(const char *section, const char *note) +{ + SetSectionNote(FindSection(section), note); +} + +void FConfigFile::SetSectionNote(const char *note) +{ + SetSectionNote(CurrentSection, note); +} + +void FConfigFile::SetSectionNote(FConfigSection *section, const char *note) +{ + if (section != NULL) + { + if (note == NULL) + { + note = ""; + } + section->Note = note; + } +} diff --git a/source/common/utility/configfile.h b/source/common/utility/configfile.h new file mode 100644 index 000000000..ab6866430 --- /dev/null +++ b/source/common/utility/configfile.h @@ -0,0 +1,132 @@ +/* +** configfile.h +** +**--------------------------------------------------------------------------- +** Copyright 1998-2008 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#ifndef __CONFIGFILE_H__ +#define __CONFIGFILE_H__ + +#include +#include "files.h" +#include "zstring.h" + +class FConfigFile +{ +public: + FConfigFile (); + FConfigFile (const char *pathname); + FConfigFile (const FConfigFile &other); + virtual ~FConfigFile (); + + void ClearConfig (); + FConfigFile &operator= (const FConfigFile &other); + + bool HaveSections () { return Sections != NULL; } + void CreateSectionAtStart (const char *name); + void MoveSectionToStart (const char *name); + void SetSectionNote (const char *section, const char *note); + void SetSectionNote (const char *note); + bool SetSection (const char *section, bool allowCreate=false); + bool SetFirstSection (); + bool SetNextSection (); + const char *GetCurrentSection () const; + void ClearCurrentSection (); + bool DeleteCurrentSection (); + void ClearKey (const char *key); + + bool SectionIsEmpty (); + bool NextInSection (const char *&key, const char *&value); + const char *GetValueForKey (const char *key) const; + void SetValueForKey (const char *key, const char *value, bool duplicates=false); + + const char *GetPathName () const { return PathName.GetChars(); } + void ChangePathName (const char *path); + + void LoadConfigFile (); + bool WriteConfigFile () const; + +protected: + virtual void WriteCommentHeader (FileWriter *file) const; + + virtual char *ReadLine (char *string, int n, void *file) const; + bool ReadConfig (void *file); + static const char *GenerateEndTag(const char *value); + void RenameSection(const char *oldname, const char *newname) const; + + bool OkayToWrite; + bool FileExisted; + +private: + struct FConfigEntry + { + char *Value; + FConfigEntry *Next; + char Key[1]; // + length of key + + void SetValue (const char *val); + }; + struct FConfigSection + { + FString SectionName; + FConfigEntry *RootEntry; + FConfigEntry **LastEntryPtr; + FConfigSection *Next; + FString Note; + //char Name[1]; // + length of name + }; + + FConfigSection *Sections; + FConfigSection **LastSectionPtr; + FConfigSection *CurrentSection; + FConfigEntry *CurrentEntry; + FString PathName; + + FConfigSection *FindSection (const char *name) const; + FConfigEntry *FindEntry (FConfigSection *section, const char *key) const; + FConfigSection *NewConfigSection (const char *name); + FConfigEntry *NewConfigEntry (FConfigSection *section, const char *key, const char *value); + FConfigEntry *ReadMultiLineValue (void *file, FConfigSection *section, const char *key, const char *terminator); + void SetSectionNote (FConfigSection *section, const char *note); + +public: + class Position + { + friend class FConfigFile; + + FConfigSection *Section; + FConfigEntry *Entry; + }; + + void GetPosition (Position &pos) const; + void SetPosition (const Position &pos); +}; + +#endif //__CONFIGFILE_H__