smatz@9999: /* $Id$ */ smatz@9999: smatz@9999: /** @file ini.cpp Definition of the IniItem class, related to reading/writing '*.ini' files. */ rubidium@9996: rubidium@9996: #include "stdafx.h" rubidium@9996: #include "core/alloc_func.hpp" rubidium@9996: #include "core/math_func.hpp" rubidium@9996: #include "debug.h" rubidium@9996: #include "ini_type.h" rubidium@9996: #include "string_func.h" rubidium@10039: #include "fileio_func.h" rubidium@9996: rubidium@9996: IniItem::IniItem(IniGroup *parent, const char *name, size_t len) : next(NULL), value(NULL), comment(NULL) rubidium@9996: { rubidium@9996: if (len == 0) len = strlen(name); rubidium@9996: rubidium@9996: this->name = strndup(name, len); rubidium@9996: *parent->last_item = this; rubidium@9996: parent->last_item = &this->next; rubidium@9996: } rubidium@9996: rubidium@9996: IniItem::~IniItem() rubidium@9996: { rubidium@9996: free(this->name); rubidium@9996: free(this->value); rubidium@9996: free(this->comment); rubidium@9996: rubidium@9996: delete this->next; rubidium@9996: } rubidium@9996: rubidium@10008: void IniItem::SetValue(const char *value) rubidium@10008: { rubidium@10008: free(this->value); rubidium@10008: this->value = strdup(value); rubidium@10008: } rubidium@10008: rubidium@9996: IniGroup::IniGroup(IniFile *parent, const char *name, size_t len) : next(NULL), type(IGT_VARIABLES), item(NULL), comment(NULL) rubidium@9996: { rubidium@9996: if (len == 0) len = strlen(name); rubidium@9996: rubidium@9996: this->name = strndup(name, len); rubidium@9996: this->last_item = &this->item; rubidium@9996: *parent->last_group = this; rubidium@9996: parent->last_group = &this->next; rubidium@9996: rubidium@9996: if (parent->list_group_names == NULL) return; rubidium@9996: rubidium@9996: for (uint i = 0; parent->list_group_names[i] != NULL; i++) { rubidium@9996: if (strcmp(this->name, parent->list_group_names[i]) == 0) { rubidium@9996: this->type = IGT_LIST; rubidium@9996: return; rubidium@9996: } rubidium@9996: } rubidium@9996: } rubidium@9996: rubidium@9996: IniGroup::~IniGroup() rubidium@9996: { rubidium@9996: free(this->name); rubidium@9996: free(this->comment); rubidium@9996: rubidium@9996: delete this->item; rubidium@9996: delete this->next; rubidium@9996: } rubidium@9996: rubidium@9996: IniItem *IniGroup::GetItem(const char *name, bool create) rubidium@9996: { rubidium@9996: IniItem *item; rubidium@9996: size_t len = strlen(name); rubidium@9996: rubidium@9996: for (item = this->item; item != NULL; item = item->next) { rubidium@9996: if (strcmp(item->name, name) == 0) return item; rubidium@9996: } rubidium@9996: rubidium@9996: if (!create) return NULL; rubidium@9996: rubidium@9996: /* otherwise make a new one */ rubidium@9996: return new IniItem(this, name, len); rubidium@9996: } rubidium@9996: rubidium@9996: IniFile::IniFile(const char **list_group_names) : group(NULL), comment(NULL), list_group_names(list_group_names) rubidium@9996: { rubidium@9996: this->last_group = &this->group; rubidium@9996: } rubidium@9996: rubidium@9996: IniFile::~IniFile() rubidium@9996: { rubidium@9996: free(this->comment); rubidium@9996: delete this->group; rubidium@9996: } rubidium@9996: rubidium@9996: IniGroup *IniFile::GetGroup(const char *name, size_t len) rubidium@9996: { rubidium@9996: IniGroup *group; rubidium@9996: rubidium@9996: if (len == 0) len = strlen(name); rubidium@9996: rubidium@9996: /* does it exist already? */ rubidium@9996: for (group = this->group; group != NULL; group = group->next) { rubidium@9996: if (!memcmp(group->name, name, len) && group->name[len] == 0) { rubidium@9996: return group; rubidium@9996: } rubidium@9996: } rubidium@9996: rubidium@9996: /* otherwise make a new one */ rubidium@9996: group = new IniGroup(this, name, len); rubidium@9996: group->comment = strdup("\n"); rubidium@9996: return group; rubidium@9996: } rubidium@9996: rubidium@9996: void IniFile::RemoveGroup(const char *name) rubidium@9996: { rubidium@9996: size_t len = strlen(name); rubidium@9996: IniGroup *prev = NULL; rubidium@9996: IniGroup *group; rubidium@9996: rubidium@9996: /* does it exist already? */ rubidium@9996: for (group = this->group; group != NULL; prev = group, group = group->next) { rubidium@9996: if (memcmp(group->name, name, len) == 0) { rubidium@9996: break; rubidium@9996: } rubidium@9996: } rubidium@9996: rubidium@9996: if (group == NULL) return; rubidium@9996: rubidium@9996: if (prev != NULL) { rubidium@9996: prev->next = prev->next->next; rubidium@9996: } else { rubidium@9996: this->group = this->group->next; rubidium@9996: prev = this->group; rubidium@9996: } rubidium@9996: rubidium@9996: group->next = NULL; rubidium@9996: delete group; rubidium@9996: } rubidium@9996: rubidium@9996: void IniFile::LoadFromDisk(const char *filename) rubidium@9996: { rubidium@9996: assert(this->last_group == &this->group); rubidium@9996: rubidium@9996: char buffer[1024], c, *s, *t, *e; rubidium@9996: IniGroup *group = NULL; rubidium@9996: IniItem *item = NULL; rubidium@9996: rubidium@9996: char *comment = NULL; rubidium@9996: uint comment_size = 0; rubidium@9996: uint comment_alloc = 0; rubidium@9996: rubidium@10003: size_t end; rubidium@10064: /* rubidium@10064: * Now we are going to open a file that contains no more than simple rubidium@10064: * plain text. That would raise the question: "why open the file as rubidium@10064: * if it is a binary file?". That's simple... Microsoft, in all rubidium@10064: * their greatness and wisdom decided it would be useful if ftell rubidium@10064: * is aware of '\r\n' and "sees" that as a single character. The rubidium@10064: * easiest way to test for that situation is by searching for '\n' rubidium@10064: * and decrease the value every time you encounter a '\n'. This will rubidium@10064: * thus also make ftell "see" the '\r' when it is not there, so the rubidium@10064: * result of ftell will be highly unreliable. So to work around this rubidium@10064: * marvel of wisdom we have to open in as a binary file. rubidium@10064: */ rubidium@10064: FILE *in = FioFOpenFile(filename, "rb", DATA_DIR, &end); rubidium@9996: if (in == NULL) return; rubidium@9996: rubidium@10003: end += ftell(in); rubidium@10003: rubidium@9996: /* for each line in the file */ rubidium@10003: while ((size_t)ftell(in) < end && fgets(buffer, sizeof(buffer), in)) { rubidium@9996: /* trim whitespace from the left side */ rubidium@9996: for (s = buffer; *s == ' ' || *s == '\t'; s++) {} rubidium@9996: rubidium@9996: /* trim whitespace from right side. */ rubidium@9996: e = s + strlen(s); rubidium@9996: while (e > s && ((c = e[-1]) == '\n' || c == '\r' || c == ' ' || c == '\t')) e--; rubidium@9996: *e = '\0'; rubidium@9996: rubidium@9996: /* skip comments and empty lines */ rubidium@9996: if (*s == '#' || *s == ';' || *s == '\0') { rubidium@9996: uint ns = comment_size + (e - s + 1); rubidium@9996: uint a = comment_alloc; rubidium@9996: uint pos; rubidium@9996: /* add to comment */ rubidium@9996: if (ns > a) { rubidium@9996: a = max(a, 128U); rubidium@9996: do a *= 2; while (a < ns); rubidium@9996: comment = ReallocT(comment, comment_alloc = a); rubidium@9996: } rubidium@9996: pos = comment_size; rubidium@9996: comment_size += (e - s + 1); rubidium@9996: comment[pos + e - s] = '\n'; // comment newline rubidium@9996: memcpy(comment + pos, s, e - s); // copy comment contents rubidium@9996: continue; rubidium@9996: } rubidium@9996: rubidium@9996: /* it's a group? */ rubidium@9996: if (s[0] == '[') { rubidium@9996: if (e[-1] != ']') { rubidium@9996: ShowInfoF("ini: invalid group name '%s'", buffer); rubidium@9996: } else { rubidium@9996: e--; rubidium@9996: } rubidium@9996: s++; // skip [ rubidium@9996: group = new IniGroup(this, s, e - s); rubidium@9996: if (comment_size) { rubidium@9996: group->comment = strndup(comment, comment_size); rubidium@9996: comment_size = 0; rubidium@9996: } rubidium@9996: } else if (group) { rubidium@9996: /* find end of keyname */ rubidium@9996: if (*s == '\"') { rubidium@9996: s++; rubidium@9996: for (t = s; *t != '\0' && *t != '\"'; t++) {} rubidium@9996: if (*t == '\"') *t = ' '; rubidium@9996: } else { rubidium@9996: for (t = s; *t != '\0' && *t != '=' && *t != '\t' && *t != ' '; t++) {} rubidium@9996: } rubidium@9996: rubidium@9996: /* it's an item in an existing group */ rubidium@9996: item = new IniItem(group, s, t-s); rubidium@9996: if (comment_size) { rubidium@9996: item->comment = strndup(comment, comment_size); rubidium@9996: comment_size = 0; rubidium@9996: } rubidium@9996: rubidium@9996: /* find start of parameter */ rubidium@9996: while (*t == '=' || *t == ' ' || *t == '\t') t++; rubidium@9996: rubidium@9996: rubidium@9996: /* remove starting quotation marks */ rubidium@9996: if (*t == '\"') t++; rubidium@9996: /* remove ending quotation marks */ rubidium@9996: e = t + strlen(t); rubidium@9996: if (e > t && e[-1] == '\"') e--; rubidium@9996: *e = '\0'; rubidium@9996: rubidium@9996: item->value = strndup(t, e - t); rubidium@9996: } else { rubidium@9996: /* it's an orphan item */ rubidium@9996: ShowInfoF("ini: '%s' outside of group", buffer); rubidium@9996: } rubidium@9996: } rubidium@9996: rubidium@9996: if (comment_size > 0) { rubidium@9996: this->comment = strndup(comment, comment_size); rubidium@9996: comment_size = 0; rubidium@9996: } rubidium@9996: rubidium@9996: free(comment); rubidium@9996: fclose(in); rubidium@9996: } rubidium@9996: rubidium@9996: bool IniFile::SaveToDisk(const char *filename) rubidium@9996: { rubidium@9996: FILE *f; rubidium@9996: IniGroup *group; rubidium@9996: IniItem *item; rubidium@9996: rubidium@9996: f = fopen(filename, "w"); rubidium@9996: if (f == NULL) return false; rubidium@9996: rubidium@9996: for (group = this->group; group != NULL; group = group->next) { rubidium@9996: if (group->comment) fputs(group->comment, f); rubidium@9996: fprintf(f, "[%s]\n", group->name); rubidium@9996: for (item = group->item; item != NULL; item = item->next) { rubidium@9996: assert(item->value != NULL); rubidium@9996: if (item->comment != NULL) fputs(item->comment, f); rubidium@9996: rubidium@9996: /* protect item->name with quotes if needed */ rubidium@9996: if (strchr(item->name, ' ') != NULL) { rubidium@9996: fprintf(f, "\"%s\"", item->name); rubidium@9996: } else { rubidium@9996: fprintf(f, "%s", item->name); rubidium@9996: } rubidium@9996: rubidium@9996: fprintf(f, " = %s\n", item->value); rubidium@9996: } rubidium@9996: } rubidium@9996: if (this->comment) fputs(this->comment, f); rubidium@9996: rubidium@9996: fclose(f); rubidium@9996: return true; rubidium@9996: }