ini_load.cpp

Go to the documentation of this file.
00001 /* $Id: ini_load.cpp 26541 2014-04-29 18:18:52Z frosch $ */
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   if (this->name != NULL) str_validate(this->name, this->name + len);
00030 
00031   *parent->last_item = this;
00032   parent->last_item = &this->next;
00033 }
00034 
00036 IniItem::~IniItem()
00037 {
00038   free(this->name);
00039   free(this->value);
00040   free(this->comment);
00041 
00042   delete this->next;
00043 }
00044 
00049 void IniItem::SetValue(const char *value)
00050 {
00051   free(this->value);
00052   this->value = strdup(value);
00053 }
00054 
00061 IniGroup::IniGroup(IniLoadFile *parent, const char *name, size_t len) : next(NULL), type(IGT_VARIABLES), item(NULL), comment(NULL)
00062 {
00063   if (len == 0) len = strlen(name);
00064 
00065   this->name = strndup(name, len);
00066   if (this->name == NULL) error("not enough memory to allocate group name");
00067   str_validate(this->name, this->name + len);
00068 
00069   this->last_item = &this->item;
00070   *parent->last_group = this;
00071   parent->last_group = &this->next;
00072 
00073   if (parent->list_group_names != NULL) {
00074     for (uint i = 0; parent->list_group_names[i] != NULL; i++) {
00075       if (strcmp(this->name, parent->list_group_names[i]) == 0) {
00076         this->type = IGT_LIST;
00077         return;
00078       }
00079     }
00080   }
00081   if (parent->seq_group_names != NULL) {
00082     for (uint i = 0; parent->seq_group_names[i] != NULL; i++) {
00083       if (strcmp(this->name, parent->seq_group_names[i]) == 0) {
00084         this->type = IGT_SEQUENCE;
00085         return;
00086       }
00087     }
00088   }
00089 }
00090 
00092 IniGroup::~IniGroup()
00093 {
00094   free(this->name);
00095   free(this->comment);
00096 
00097   delete this->item;
00098   delete this->next;
00099 }
00100 
00108 IniItem *IniGroup::GetItem(const char *name, bool create)
00109 {
00110   for (IniItem *item = this->item; item != NULL; item = item->next) {
00111     if (strcmp(item->name, name) == 0) return item;
00112   }
00113 
00114   if (!create) return NULL;
00115 
00116   /* otherwise make a new one */
00117   return new IniItem(this, name, strlen(name));
00118 }
00119 
00123 void IniGroup::Clear()
00124 {
00125   delete this->item;
00126   this->item = NULL;
00127   this->last_item = &this->item;
00128 }
00129 
00135 IniLoadFile::IniLoadFile(const char * const *list_group_names, const char * const *seq_group_names) :
00136     group(NULL),
00137     comment(NULL),
00138     list_group_names(list_group_names),
00139     seq_group_names(seq_group_names)
00140 {
00141   this->last_group = &this->group;
00142 }
00143 
00145 IniLoadFile::~IniLoadFile()
00146 {
00147   free(this->comment);
00148   delete this->group;
00149 }
00150 
00159 IniGroup *IniLoadFile::GetGroup(const char *name, size_t len, bool create_new)
00160 {
00161   if (len == 0) len = strlen(name);
00162 
00163   /* does it exist already? */
00164   for (IniGroup *group = this->group; group != NULL; group = group->next) {
00165     if (!strncmp(group->name, name, len) && group->name[len] == 0) {
00166       return group;
00167     }
00168   }
00169 
00170   if (!create_new) return NULL;
00171 
00172   /* otherwise make a new one */
00173   IniGroup *group = new IniGroup(this, name, len);
00174   group->comment = strdup("\n");
00175   return group;
00176 }
00177 
00182 void IniLoadFile::RemoveGroup(const char *name)
00183 {
00184   size_t len = strlen(name);
00185   IniGroup *prev = NULL;
00186   IniGroup *group;
00187 
00188   /* does it exist already? */
00189   for (group = this->group; group != NULL; prev = group, group = group->next) {
00190     if (strncmp(group->name, name, len) == 0) {
00191       break;
00192     }
00193   }
00194 
00195   if (group == NULL) return;
00196 
00197   if (prev != NULL) {
00198     prev->next = prev->next->next;
00199     if (this->last_group == &group->next) this->last_group = &prev->next;
00200   } else {
00201     this->group = this->group->next;
00202     if (this->last_group == &group->next) this->last_group = &this->group;
00203   }
00204 
00205   group->next = NULL;
00206   delete group;
00207 }
00208 
00215 void IniLoadFile::LoadFromDisk(const char *filename, Subdirectory subdir)
00216 {
00217   assert(this->last_group == &this->group);
00218 
00219   char buffer[1024];
00220   IniGroup *group = NULL;
00221 
00222   char *comment = NULL;
00223   uint comment_size = 0;
00224   uint comment_alloc = 0;
00225 
00226   size_t end;
00227   FILE *in = this->OpenFile(filename, subdir, &end);
00228   if (in == NULL) return;
00229 
00230   end += ftell(in);
00231 
00232   /* for each line in the file */
00233   while ((size_t)ftell(in) < end && fgets(buffer, sizeof(buffer), in)) {
00234     char c, *s;
00235     /* trim whitespace from the left side */
00236     for (s = buffer; *s == ' ' || *s == '\t'; s++) {}
00237 
00238     /* trim whitespace from right side. */
00239     char *e = s + strlen(s);
00240     while (e > s && ((c = e[-1]) == '\n' || c == '\r' || c == ' ' || c == '\t')) e--;
00241     *e = '\0';
00242 
00243     /* Skip comments and empty lines outside IGT_SEQUENCE groups. */
00244     if ((group == NULL || group->type != IGT_SEQUENCE) && (*s == '#' || *s == ';' || *s == '\0')) {
00245       uint ns = comment_size + (e - s + 1);
00246       uint a = comment_alloc;
00247       /* add to comment */
00248       if (ns > a) {
00249         a = max(a, 128U);
00250         do a *= 2; while (a < ns);
00251         comment = ReallocT(comment, comment_alloc = a);
00252       }
00253       uint pos = comment_size;
00254       comment_size += (e - s + 1);
00255       comment[pos + e - s] = '\n'; // comment newline
00256       memcpy(comment + pos, s, e - s); // copy comment contents
00257       continue;
00258     }
00259 
00260     /* it's a group? */
00261     if (s[0] == '[') {
00262       if (e[-1] != ']') {
00263         this->ReportFileError("ini: invalid group name '", buffer, "'");
00264       } else {
00265         e--;
00266       }
00267       s++; // skip [
00268       group = new IniGroup(this, s, e - s);
00269       if (comment_size != 0) {
00270         group->comment = strndup(comment, comment_size);
00271         comment_size = 0;
00272       }
00273     } else if (group != NULL) {
00274       if (group->type == IGT_SEQUENCE) {
00275         /* A sequence group, use the line as item name without further interpretation. */
00276         IniItem *item = new IniItem(group, buffer, e - buffer);
00277         if (comment_size) {
00278           item->comment = strndup(comment, comment_size);
00279           comment_size = 0;
00280         }
00281         continue;
00282       }
00283       char *t;
00284       /* find end of keyname */
00285       if (*s == '\"') {
00286         s++;
00287         for (t = s; *t != '\0' && *t != '\"'; t++) {}
00288         if (*t == '\"') *t = ' ';
00289       } else {
00290         for (t = s; *t != '\0' && *t != '=' && *t != '\t' && *t != ' '; t++) {}
00291       }
00292 
00293       /* it's an item in an existing group */
00294       IniItem *item = new IniItem(group, s, t - s);
00295       if (comment_size != 0) {
00296         item->comment = strndup(comment, comment_size);
00297         comment_size = 0;
00298       }
00299 
00300       /* find start of parameter */
00301       while (*t == '=' || *t == ' ' || *t == '\t') t++;
00302 
00303       bool quoted = (*t == '\"');
00304       /* remove starting quotation marks */
00305       if (*t == '\"') t++;
00306       /* remove ending quotation marks */
00307       e = t + strlen(t);
00308       if (e > t && e[-1] == '\"') e--;
00309       *e = '\0';
00310 
00311       /* If the value was not quoted and empty, it must be NULL */
00312       item->value = (!quoted && e == t) ? NULL : strndup(t, e - t);
00313       if (item->value != NULL) str_validate(item->value, item->value + strlen(item->value));
00314     } else {
00315       /* it's an orphan item */
00316       this->ReportFileError("ini: '", buffer, "' outside of group");
00317     }
00318   }
00319 
00320   if (comment_size > 0) {
00321     this->comment = strndup(comment, comment_size);
00322     comment_size = 0;
00323   }
00324 
00325   free(comment);
00326   fclose(in);
00327 }
00328