Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | File List | Namespace Members | Class Members | File Members | Related Pages

Tools/IniFile.cpp

Go to the documentation of this file.
00001 /**
00002  * \class IniFile  
00003  * Key/Value based .INI-File handling.
00004  *
00005  * The values are stored in a double layered map.  The first
00006  * layer contains the section names, the second layer contains
00007  * the actual key/value pairs. All names are case-insensitive,
00008  * and are kept in sorted order. Insertion, lookup and removal
00009  * of keys should take logarithmic time.
00010  *
00011  * \author  Thomas Kindler, thomas.kindler@gmx.de
00012  */
00013 
00014 #include "IniFile.h"
00015 #include "Streams/InStreams.h" 
00016 #include "Streams/OutStreams.h" 
00017 #include <strstream>
00018 
00019 /**
00020  * Construct an empty IniFile.
00021  */
00022 IniFile::IniFile() 
00023 {
00024 }
00025 
00026 
00027 /**
00028  * Construct an IniFile and load settings.
00029  *
00030  * \param  filename  name of .ini file to load
00031  */
00032 IniFile::IniFile(const char *filename) 
00033 {
00034   load(filename);  
00035 }
00036 
00037 
00038 /**
00039  * Remove a key from a section.
00040  * \note    section and key names are case-insensitive.
00041  *
00042  * \param  section  section of key
00043  * \param  key      name of key to remove
00044  */
00045 void IniFile::removeKey(const char *section, const char *key)
00046 {
00047   Sections::iterator  s = sections.find(section);
00048   if (s != sections.end())
00049     s->second.erase(key);
00050 }
00051 
00052 
00053 /**
00054  * Load settings from an .ini file.
00055  *
00056  * Lines can be arbitrarily long and may use \-escaping
00057  * and quoted whitespace. Comments must start with an ';'.
00058  * 
00059  * \note  
00060  *   The contents of the file are <i>added</i> to the current
00061  *   list of settings. You can safely load multiple .ini-files
00062  *   into one IniFile instance.
00063  *
00064  * \param   filename  name of .ini file to load
00065  * \return  true if successful, false otherwise
00066  */
00067 bool IniFile::load(const char *filename)
00068 {
00069   InBinaryFile  ini(filename);
00070   if (!ini.exists())
00071     return false;
00072 
00073   // ini-parser state machine
00074   //
00075   enum {
00076     INITIAL, IGNORE,
00077     SECTION, KEY, AFTER_KEY, 
00078     BEFORE_VALUE, VALUE
00079   } state = INITIAL;
00080   
00081   string  s, section, key, value;
00082   bool  escape = false, quote = false;
00083 
00084   for (;;) {
00085     // read one character
00086     // (i hate the GT2004 stream functions!)
00087     //  
00088     unsigned char uc;
00089     ini.read(&uc, 1);
00090     int c = ini.getEof() ? EOF : uc;
00091 
00092     // check for special characters
00093     //
00094     if (escape)    { s+=c; escape=false; continue; } 
00095     if (c == '\\') { escape = true;      continue; }
00096     if (c == '"')  { quote  = !quote;    continue; }
00097 
00098     // end of line (or file) reached
00099     // 
00100     if (c == '\n' || c == '\r' || c == EOF) {
00101       if (state == VALUE)
00102         value = s;       
00103       if (key != "")
00104         sections[section][key] = value;
00105 
00106       state = INITIAL;
00107       quote = false;
00108 
00109       if (c == EOF)  
00110         break;
00111       else
00112         continue;
00113     }
00114 
00115     // parse normal characters
00116     //
00117     switch (state) {
00118       case INITIAL:  // wait for first char
00119         s = key = value = "";
00120         if      (c == '[')    { state = SECTION; }
00121         else if (c == ';')    { state = IGNORE;  }
00122         else if (!isspace(c)) { state = KEY;     }
00123         break;
00124 
00125       case SECTION:  // read section
00126         if (quote) break;
00127         if (c == ']') { 
00128           section = s.substr(1);
00129           state   = IGNORE;
00130         }
00131         break;
00132       
00133       case KEY:  // read key
00134         if (quote)  break;
00135         if (isspace(c)) {
00136           key   = s;
00137           s     = "";
00138           state = AFTER_KEY;
00139         } else if (c == '=') {
00140           key   = s;
00141           s     = "";
00142           state = BEFORE_VALUE;
00143         }
00144         break;
00145 
00146       case AFTER_KEY:  // wait for '='
00147         if (c == '=') {
00148           state = BEFORE_VALUE;
00149         } else if (!isspace(c)) {
00150           state = IGNORE;
00151         }
00152         break;
00153 
00154       case BEFORE_VALUE:  // wait for value
00155         if (!(isspace(c))) {
00156           s = "";
00157           state = VALUE;
00158         }
00159         break;
00160 
00161       case VALUE:  // read value
00162         if (quote)  break;
00163         if (isspace(c)) {
00164           value = s;
00165           state = IGNORE;
00166         }
00167         break;   
00168 
00169       case IGNORE:
00170         break;
00171     }
00172     s += c;
00173   }  
00174     
00175   return true;
00176 }
00177 
00178 
00179 /**
00180  * Escape special characters and add quotes if necessary. 
00181  *
00182  * \param   s  string to escape
00183  * \return  ini-safe string
00184  */
00185 static const string esc(const string s)
00186 {
00187   if (s.size() == 0)
00188     return "\"\"";
00189   
00190   string t = "";
00191   bool  mustQuote = false;
00192 
00193   for (unsigned i=0; i<s.size(); i++) {
00194     if (isspace(s[i]) || 
00195         s[i] == '=' || s[i] == ';' || 
00196         s[i] == '[' || s[i] == ']'  )
00197       mustQuote = true;
00198 
00199     if (!isprint(s[i]) || s[i] == '\\' || s[i] == '"')
00200       t += '\\';
00201 
00202     t += s[i];
00203   }
00204     
00205   return mustQuote ? "\"" + t + "\"" : t;
00206 }
00207 
00208 
00209 /**
00210  * Save setting to an .ini-File.
00211  *
00212  * \note  
00213  *   Sections and keys are saved in undefined order,
00214  *   formatting and comments are not preserved.
00215  *
00216  * \param  filename  name of .ini file to save into
00217  */
00218 bool IniFile::save(const char *filename) const
00219 {
00220   OutTextRawFile  ini(filename, false);
00221   if (!ini.exists())
00222     return false;
00223 
00224   Sections::const_iterator s;
00225   for (s = sections.begin(); s != sections.end(); s++) {
00226     ini << "[" << esc(s->first).c_str() << "]\n";
00227     
00228     Keys::const_iterator k;
00229     for (k = s->second.begin(); k != s->second.end(); k++)
00230       ini << esc(k->first).c_str()  << " = "
00231           << esc(k->second).c_str() << "\n";
00232 
00233     ini << "\n";
00234   }
00235   return true;
00236 }
00237 
00238 
00239 /**
00240  * Set value of key.
00241  * \note    section and key names are case-insensitive.
00242  *
00243  * \param   section  section of key
00244  * \param   key      name of key to set
00245  * \param   val      value to set
00246  */
00247 void IniFile::setString(const char *section, const char *key, const char *val)
00248 {
00249   sections[section][key] = val;
00250 }
00251 
00252 
00253 /**
00254  * Get value of key.
00255  * \note    section and key names are case-insensitive.
00256  *
00257  * \param   section  section of key
00258  * \param   key      name of key to retrieve
00259  * \param   def      default value
00260  *
00261  * \return  value of key, or def if key doesn't exist
00262  */
00263 const char *IniFile::getString(const char *section, const char *key, const char *def) const
00264 {
00265   Sections::const_iterator  s = sections.find(section);
00266   if (s != sections.end()) {
00267     Keys::const_iterator  k = s->second.find(key);
00268     if (k != s->second.end())
00269       return  k->second.c_str();
00270   } 
00271   return  def;
00272 }
00273 
00274 
00275 int  IniFile::getInt(const char *section, const char *key, int def) const
00276 {
00277   string s = getString(section, key, "");
00278   return s == "" ? def : strtol(s.c_str(), NULL, 0);
00279 }
00280 
00281 
00282 double  IniFile::getDouble(const char *section, const char *key, double def) const
00283 {
00284   string s = getString(section, key, "");
00285   return s == "" ? def : strtod(s.c_str(), NULL);
00286 }
00287 
00288 
00289 bool  IniFile::getBool(const char *section, const char *key, bool def) const
00290 {
00291   const char *s = getString(section, key, "");
00292   if (*s == '\0')
00293     return def;
00294   
00295   if (!stricmp(s, "true") || !stricmp(s, "yes") ||
00296       !stricmp(s, "on")   || strtol(s, NULL, 0) != 0)
00297     return true;
00298   else
00299     return false;
00300 }
00301     
00302 
00303 void  IniFile::setInt(const char *section, const char *key, int val)
00304 {
00305   strstream s; s << val;
00306   setString(section, key, s.str());
00307 }
00308 
00309 
00310 void  IniFile::setDouble(const char *section, const char *key, double val)
00311 {
00312   strstream s; s << val;
00313   setString(section, key, s.str());
00314 }
00315 
00316 
00317 void  IniFile::setBool(const char *section, const char *key, bool val)
00318 {
00319   setString(section, key, val ? "true" : "false");
00320 }
00321 
00322 
00323 /**
00324  * Change log:
00325  *
00326  * $Log: IniFile.cpp,v $
00327  * Revision 1.1.1.1  2004/05/22 17:35:51  cvsadm
00328  * created new repository GT2004_WM
00329  *
00330  * Revision 1.1  2004/04/13 23:35:16  kindler
00331  * initial import
00332  *
00333  */

Generated on Thu Sep 23 19:57:38 2004 for GT2004 by doxygen 1.3.6