# ini.cpp

Go to the documentation of this file.

    00001 /* $Id: ini.cpp 15085 2009-01-14 20:23:45Z peter1138 $ */
    00002
    00005 #include "stdafx.h"
    00006 #include "core/alloc_func.hpp"
    00007 #include "core/math_func.hpp"
    00008 #include "debug.h"
    00009 #include "ini_type.h"
    00010 #include "string_func.h"
    00011 #include "fileio_func.h"
    00012
    00013 IniItem::IniItem(IniGroup *parent, const char *name, size_t len) : next(NULL), value(NULL), comment(NULL)
    00014 {
    00015   if (len == 0) len = strlen(name);
    00016
    00017   this->name = strndup(name, len);
    00018   *parent->last_item = this;
    00019   parent->last_item = &this->next;
    00020 }
    00021
    00022 IniItem::~IniItem()
    00023 {
    00024   free(this->name);
    00025   free(this->value);
    00026   free(this->comment);
    00027
    00028   delete this->next;
    00029 }
    00030
    00031 void IniItem::SetValue(const char *value)
    00032 {
    00033   free(this->value);
    00034   this->value = strdup(value);
    00035 }
    00036
    00037 IniGroup::IniGroup(IniFile *parent, const char *name, size_t len) : next(NULL), type(IGT_VARIABLES), item(NULL), comment(NULL)
    00038 {
    00039   if (len == 0) len = strlen(name);
    00040
    00041   this->name = strndup(name, len);
    00042   this->last_item = &this->item;
    00043   *parent->last_group = this;
    00044   parent->last_group = &this->next;
    00045
    00046   if (parent->list_group_names == NULL) return;
    00047
    00048   for (uint i = 0; parent->list_group_names[i] != NULL; i++) {
    00049     if (strcmp(this->name, parent->list_group_names[i]) == 0) {
    00050       this->type = IGT_LIST;
    00051       return;
    00052     }
    00053   }
    00054 }
    00055
    00056 IniGroup::~IniGroup()
    00057 {
    00058   free(this->name);
    00059   free(this->comment);
    00060
    00061   delete this->item;
    00062   delete this->next;
    00063 }
    00064
    00065 IniItem *IniGroup::GetItem(const char *name, bool create)
    00066 {
    00067   IniItem *item;
    00068   size_t len = strlen(name);
    00069
    00070   for (item = this->item; item != NULL; item = item->next) {
    00071     if (strcmp(item->name, name) == 0) return item;
    00072   }
    00073
    00074   if (!create) return NULL;
    00075
    00076   /* otherwise make a new one */
    00077   return new IniItem(this, name, len);
    00078 }
    00079
    00080 void IniGroup::Clear()
    00081 {
    00082   delete this->item;
    00083   this->item = NULL;
    00084   this->last_item = &this->item;
    00085 }
    00086
    00087 IniFile::IniFile(const char **list_group_names) : group(NULL), comment(NULL), list_group_names(list_group_names)
    00088 {
    00089   this->last_group = &this->group;
    00090 }
    00091
    00092 IniFile::~IniFile()
    00093 {
    00094   free(this->comment);
    00095   delete this->group;
    00096 }
    00097
    00098 IniGroup *IniFile::GetGroup(const char *name, size_t len)
    00099 {
    00100   IniGroup *group;
    00101
    00102   if (len == 0) len = strlen(name);
    00103
    00104   /* does it exist already? */
    00105   for (group = this->group; group != NULL; group = group->next) {
    00106     if (!memcmp(group->name, name, len) && group->name[len] == 0) {
    00107       return group;
    00108     }
    00109   }
    00110
    00111   /* otherwise make a new one */
    00112   group = new IniGroup(this, name, len);
    00113   group->comment = strdup("\n");
    00114   return group;
    00115 }
    00116
    00117 void IniFile::RemoveGroup(const char *name)
    00118 {
    00119   size_t len = strlen(name);
    00120   IniGroup *prev = NULL;
    00121   IniGroup *group;
    00122
    00123   /* does it exist already? */
    00124   for (group = this->group; group != NULL; prev = group, group = group->next) {
    00125     if (memcmp(group->name, name, len) == 0) {
    00126       break;
    00127     }
    00128   }
    00129
    00130   if (group == NULL) return;
    00131
    00132   if (prev != NULL) {
    00133     prev->next = prev->next->next;
    00134     if (this->last_group == &group->next) this->last_group = &prev->next;
    00135   } else {
    00136     this->group = this->group->next;
    00137     if (this->last_group == &group->next) this->last_group = &this->group;
    00138   }
    00139
    00140   group->next = NULL;
    00141   delete group;
    00142 }
    00143
    00144 void IniFile::LoadFromDisk(const char *filename)
    00145 {
    00146   assert(this->last_group == &this->group);
    00147
    00148   char buffer[1024], c, *s, *t, *e;
    00149   IniGroup *group = NULL;
    00150   IniItem *item = NULL;
    00151
    00152   char *comment = NULL;
    00153   uint comment_size = 0;
    00154   uint comment_alloc = 0;
    00155
    00156   size_t end;
    00157   /*
    00158    * Now we are going to open a file that contains no more than simple
    00159    * plain text. That would raise the question: "why open the file as
    00160    * if it is a binary file?". That's simple... Microsoft, in all
    00161    * their greatness and wisdom decided it would be useful if ftell
    00162    * is aware of '\r\n' and "sees" that as a single character. The
    00163    * easiest way to test for that situation is by searching for '\n'
    00164    * and decrease the value every time you encounter a '\n'. This will
    00165    * thus also make ftell "see" the '\r' when it is not there, so the
    00166    * result of ftell will be highly unreliable. So to work around this
    00167    * marvel of wisdom we have to open in as a binary file.
    00168    */
    00169   FILE *in = FioFOpenFile(filename, "rb", DATA_DIR, &end);
    00170   if (in == NULL) return;
    00171
    00172   end += ftell(in);
    00173
    00174   /* for each line in the file */
    00175   while ((size_t)ftell(in) < end && fgets(buffer, sizeof(buffer), in)) {
    00176     /* trim whitespace from the left side */
    00177     for (s = buffer; *s == ' ' || *s == '\t'; s++) {}
    00178
    00179     /* trim whitespace from right side. */
    00180     e = s + strlen(s);
    00181     while (e > s && ((c = e[-1]) == '\n' || c == '\r' || c == ' ' || c == '\t')) e--;
    00182     *e = '\0';
    00183
    00184     /* skip comments and empty lines */
    00185     if (*s == '#' || *s == ';' || *s == '\0') {
    00186       uint ns = comment_size + (e - s + 1);
    00187       uint a = comment_alloc;
    00188       uint pos;
    00189       /* add to comment */
    00190       if (ns > a) {
    00191         a = max(a, 128U);
    00192         do a *= 2; while (a < ns);
    00193         comment = ReallocT(comment, comment_alloc = a);
    00194       }
    00195       pos = comment_size;
    00196       comment_size += (e - s + 1);
    00197       comment[pos + e - s] = '\n'; // comment newline
    00198       memcpy(comment + pos, s, e - s); // copy comment contents
    00199       continue;
    00200     }
    00201
    00202     /* it's a group? */
    00203     if (s[0] == '[') {
    00204       if (e[-1] != ']') {
    00205         ShowInfoF("ini: invalid group name '%s'", buffer);
    00206       } else {
    00207         e--;
    00208       }
    00209       s++; // skip [
    00210       group = new IniGroup(this, s, e - s);
    00211       if (comment_size) {
    00212         group->comment = strndup(comment, comment_size);
    00213         comment_size = 0;
    00214       }
    00215     } else if (group) {
    00216       /* find end of keyname */
    00217       if (*s == '\"') {
    00218         s++;
    00219         for (t = s; *t != '\0' && *t != '\"'; t++) {}
    00220         if (*t == '\"') *t = ' ';
    00221       } else {
    00222         for (t = s; *t != '\0' && *t != '=' && *t != '\t' && *t != ' '; t++) {}
    00223       }
    00224
    00225       /* it's an item in an existing group */
    00226       item = new IniItem(group, s, t-s);
    00227       if (comment_size) {
    00228         item->comment = strndup(comment, comment_size);
    00229         comment_size = 0;
    00230       }
    00231
    00232       /* find start of parameter */
    00233       while (*t == '=' || *t == ' ' || *t == '\t') t++;
    00234
    00235
    00236       /* remove starting quotation marks */
    00237       if (*t == '\"') t++;
    00238       /* remove ending quotation marks */
    00239       e = t + strlen(t);
    00240       if (e > t && e[-1] == '\"') e--;
    00241       *e = '\0';
    00242
    00243       item->value = strndup(t, e - t);
    00244     } else {
    00245       /* it's an orphan item */
    00246       ShowInfoF("ini: '%s' outside of group", buffer);
    00247     }
    00248   }
    00249
    00250   if (comment_size > 0) {
    00251     this->comment = strndup(comment, comment_size);
    00252     comment_size = 0;
    00253   }
    00254
    00255   free(comment);
    00256   fclose(in);
    00257 }
    00258
    00259 bool IniFile::SaveToDisk(const char *filename)
    00260 {
    00261   FILE *f;
    00262   IniGroup *group;
    00263   IniItem *item;
    00264
    00265   f = fopen(filename, "w");
    00266   if (f == NULL) return false;
    00267
    00268   for (group = this->group; group != NULL; group = group->next) {
    00269     if (group->comment) fputs(group->comment, f);
    00270     fprintf(f, "[%s]\n", group->name);
    00271     for (item = group->item; item != NULL; item = item->next) {
    00272       assert(item->value != NULL);
    00273       if (item->comment != NULL) fputs(item->comment, f);
    00274
    00275       /* protect item->name with quotes if needed */
    00276       if (strchr(item->name, ' ') != NULL) {
    00277         fprintf(f, "\"%s\"", item->name);
    00278       } else {
    00279         fprintf(f, "%s", item->name);
    00280       }
    00281
    00282       fprintf(f, " = %s\n", item->value);
    00283     }
    00284   }
    00285   if (this->comment) fputs(this->comment, f);
    00286
    00287   fclose(f);
    00288   return true;
    00289 }