ini_load.cpp

Go to the documentation of this file.
00001 /* $Id: ini_load.cpp 23741 2012-01-03 21:47:01Z rubidium $ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD 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, version 2.
00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include "core/alloc_func.hpp"
00014 #include "core/mem_func.hpp"
00015 #include "ini_type.h"
00016 #include "string_func.h"
00017 
00024 IniItem::IniItem(IniGroup *parent, const char *name, size_t len) : next(NULL), value(NULL), comment(NULL)
00025 {
00026   if (len == 0) len = strlen(name);
00027 
00028   this->name = strndup(name, len);
00029   *parent->last_item = this;
00030   parent->last_item = &this->next;
00031 }
00032 
00034 IniItem::~IniItem()
00035 {
00036   free(this->name);
00037   free(this->value);
00038   free(this->comment);
00039 
00040   delete this->next;
00041 }
00042 
00047 void IniItem::SetValue(const char *value)
00048 {
00049   free(this->value);
00050   this->value = strdup(value);
00051 }
00052 
00059 IniGroup::IniGroup(IniLoadFile *parent, const char *name, size_t len) : next(NULL), type(IGT_VARIABLES), item(NULL), comment(NULL)
00060 {
00061   if (len == 0) len = strlen(name);
00062 
00063   this->name = strndup(name, len);
00064   this->last_item = &this->item;
00065   *parent->last_group = this;
00066   parent->last_group = &this->next;
00067 
00068   if (parent->list_group_names != NULL) {
00069     for (uint i = 0; parent->list_group_names[i] != NULL; i++) {
00070       if (strcmp(this->name, parent->list_group_names[i]) == 0) {
00071         this->type = IGT_LIST;
00072         return;
00073       }
00074     }
00075   }
00076   if (parent->seq_group_names != NULL) {
00077     for (uint i = 0; parent->seq_group_names[i] != NULL; i++) {
00078       if (strcmp(this->name, parent->seq_group_names[i]) == 0) {
00079         this->type = IGT_SEQUENCE;
00080         return;
00081       }
00082     }
00083   }
00084 }
00085 
00087 IniGroup::~IniGroup()
00088 {
00089   free(this->name);
00090   free(this->comment);
00091 
00092   delete this->item;
00093   delete this->next;
00094 }
00095 
00103 IniItem *IniGroup::GetItem(const char *name, bool create)
00104 {
00105   for (IniItem *item = this->item; item != NULL; item = item->next) {
00106     if (strcmp(item->name, name) == 0) return item;
00107   }
00108 
00109   if (!create) return NULL;
00110 
00111   /* otherwise make a new one */
00112   return new IniItem(this, name, strlen(name));
00113 }
00114 
00118 void IniGroup::Clear()
00119 {
00120   delete this->item;
00121   this->item = NULL;
00122   this->last_item = &this->item;
00123 }
00124 
00130 IniLoadFile::IniLoadFile(const char * const *list_group_names, const char * const *seq_group_names) :
00131     group(NULL),
00132     comment(NULL),
00133     list_group_names(list_group_names),
00134     seq_group_names(seq_group_names)
00135 {
00136   this->last_group = &this->group;
00137 }
00138 
00140 IniLoadFile::~IniLoadFile()
00141 {
00142   free(this->comment);
00143   delete this->group;
00144 }
00145 
00154 IniGroup *IniLoadFile::GetGroup(const char *name, size_t len, bool create_new)
00155 {
00156   if (len == 0) len = strlen(name);
00157 
00158   /* does it exist already? */
00159   for (IniGroup *group = this->group; group != NULL; group = group->next) {
00160     if (!strncmp(group->name, name, len) && group->name[len] == 0) {
00161       return group;
00162     }
00163   }
00164 
00165   if (!create_new) return NULL;
00166 
00167   /* otherwise make a new one */
00168   IniGroup *group = new IniGroup(this, name, len);
00169   group->comment = strdup("\n");
00170   return group;
00171 }
00172 
00177 void IniLoadFile::RemoveGroup(const char *name)
00178 {
00179   size_t len = strlen(name);
00180   IniGroup *prev = NULL;
00181   IniGroup *group;
00182 
00183   /* does it exist already? */
00184   for (group = this->group; group != NULL; prev = group, group = group->next) {
00185     if (strncmp(group->name, name, len) == 0) {
00186       break;
00187     }
00188   }
00189 
00190   if (group == NULL) return;
00191 
00192   if (prev != NULL) {
00193     prev->next = prev->next->next;
00194     if (this->last_group == &group->next) this->last_group = &prev->next;
00195   } else {
00196     this->group = this->group->next;
00197     if (this->last_group == &group->next) this->last_group = &this->group;
00198   }
00199 
00200   group->next = NULL;
00201   delete group;
00202 }
00203 
00210 void IniLoadFile::LoadFromDisk(const char *filename, Subdirectory subdir)
00211 {
00212   assert(this->last_group == &this->group);
00213 
00214   char buffer[1024];
00215   IniGroup *group = NULL;
00216 
00217   char *comment = NULL;
00218   uint comment_size = 0;
00219   uint comment_alloc = 0;
00220 
00221   size_t end;
00222   FILE *in = this->OpenFile(filename, subdir, &end);
00223   if (in == NULL) return;
00224 
00225   end += ftell(in);
00226 
00227   /* for each line in the file */
00228   while ((size_t)ftell(in) < end && fgets(buffer, sizeof(buffer), in)) {
00229     char c, *s;
00230     /* trim whitespace from the left side */
00231     for (s = buffer; *s == ' ' || *s == '\t'; s++) {}
00232 
00233     /* trim whitespace from right side. */
00234     char *e = s + strlen(s);
00235     while (e > s && ((c = e[-1]) == '\n' || c == '\r' || c == ' ' || c == '\t')) e--;
00236     *e = '\0';
00237 
00238     /* Skip comments and empty lines outside IGT_SEQUENCE groups. */
00239     if ((group == NULL || group->type != IGT_SEQUENCE) && (*s == '#' || *s == ';' || *s == '\0')) {
00240       uint ns = comment_size + (e - s + 1);
00241       uint a = comment_alloc;
00242       /* add to comment */
00243       if (ns > a) {
00244         a = max(a, 128U);
00245         do a *= 2; while (a < ns);
00246         comment = ReallocT(comment, comment_alloc = a);
00247       }
00248       uint pos = comment_size;
00249       comment_size += (e - s + 1);
00250       comment[pos + e - s] = '\n'; // comment newline
00251       memcpy(comment + pos, s, e - s); // copy comment contents
00252       continue;
00253     }
00254 
00255     /* it's a group? */
00256     if (s[0] == '[') {
00257       if (e[-1] != ']') {
00258         this->ReportFileError("ini: invalid group name '", buffer, "'");
00259       } else {
00260         e--;
00261       }
00262       s++; // skip [
00263       group = new IniGroup(this, s, e - s);
00264       if (comment_size != 0) {
00265         group->comment = strndup(comment, comment_size);
00266         comment_size = 0;
00267       }
00268     } else if (group != NULL) {
00269       if (group->type == IGT_SEQUENCE) {
00270         /* A sequence group, use the line as item name without further interpretation. */
00271         IniItem *item = new IniItem(group, buffer, e - buffer);
00272         if (comment_size) {
00273           item->comment = strndup(comment, comment_size);
00274           comment_size = 0;
00275         }
00276         continue;
00277       }
00278       char *t;
00279       /* find end of keyname */
00280       if (*s == '\"') {
00281         s++;
00282         for (t = s; *t != '\0' && *t != '\"'; t++) {}
00283         if (*t == '\"') *t = ' ';
00284       } else {
00285         for (t = s; *t != '\0' && *t != '=' && *t != '\t' && *t != ' '; t++) {}
00286       }
00287 
00288       /* it's an item in an existing group */
00289       IniItem *item = new IniItem(group, s, t - s);
00290       if (comment_size != 0) {
00291         item->comment = strndup(comment, comment_size);
00292         comment_size = 0;
00293       }
00294 
00295       /* find start of parameter */
00296       while (*t == '=' || *t == ' ' || *t == '\t') t++;
00297 
00298       bool quoted = (*t == '\"');
00299       /* remove starting quotation marks */
00300       if (*t == '\"') t++;
00301       /* remove ending quotation marks */
00302       e = t + strlen(t);
00303       if (e > t && e[-1] == '\"') e--;
00304       *e = '\0';
00305 
00306       /* If the value was not quoted and empty, it must be NULL */
00307       item->value = (!quoted && e == t) ? NULL : strndup(t, e - t);
00308     } else {
00309       /* it's an orphan item */
00310       this->ReportFileError("ini: '", buffer, "' outside of group");
00311     }
00312   }
00313 
00314   if (comment_size > 0) {
00315     this->comment = strndup(comment, comment_size);
00316     comment_size = 0;
00317   }
00318 
00319   free(comment);
00320   fclose(in);
00321 }
00322