/* =========================================================================== Copyright (C) 2015 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code 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; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code 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. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // configurator.cpp: Config class for INI files. #include "configurator.h" CLASS_DECLARATION(Class, Configurator, NULL) { {NULL, NULL} }; Configurator::Configurator(const char *filename) { Parse(filename); } Configurator::Configurator() { m_bNoWrite = false; m_bNeedWrite = false; } Configurator::~Configurator() { Close(); } size_t Configurator::GetLine(char *dest, const char *data, size_t size) { const char *p = data; size_t i = 0; while (*p == '\n') { p++; } if (p >= data + size) { return 0; } while (*p && *p != '\n' && i < size) { dest[i++] = *p++; } if (i < size) { dest[i++] = '\n'; } dest[i] = 0; return p - data; } void Configurator::RemoveSection(configSection_t *section) { configKey_t *key; int i; for (i = 0; i < section->key.NumObjects(); i++) { key = section->key[i]; RemoveKey(section, key); } m_reverseSections.RemoveObject(section); m_sections.remove(section->name); } void Configurator::RemoveKey(configSection_t *section, configKey_t *key) { section->key.RemoveObject(key); delete key; } configSection_t *Configurator::CreateSection(const char *section) { configSection_t *s = &m_sections.addKeyValue(section); if (s->key.NumObjects()) { RemoveSection(s); s = &m_sections.addKeyValue(section); } else { m_reverseSections.AddObject(s); } s->name = section; return s; } int Configurator::GetKeyArray(char *key) { int count; size_t len; int arrayindex = -1; char *p1, *p2; len = strlen(key) + 1; p1 = new char[len]; p2 = new char[len]; count = sscanf(key, "%[^[] [%[^]]", p1, p2); if (count == 2) { // Check for an array [num] strcpy(key, p1); arrayindex = atoi(p2); if (arrayindex < 0 || arrayindex > MAX_CONFIGURATOR_ARRAY_SIZE) { arrayindex = -2; } } delete[] p1; delete[] p2; return arrayindex; } int Configurator::GetKeyArray(str& key) { int count; size_t len; int arrayindex = -1; str arrayval; char *p1, *p2; len = key.length() + 1; p1 = new char[len]; p2 = new char[len]; count = sscanf(key, "%[^[] [%[^]]", p1, p2); if (count == 2) { // Check for an array [num] arrayval = p2; arrayindex = atoi(arrayval.c_str()); key = p1; if (arrayindex < 0 || arrayindex > MAX_CONFIGURATOR_ARRAY_SIZE) { arrayindex = -2; } } delete[] p1; delete[] p2; return arrayindex; } configKey_t *Configurator::CreateKey(configSection_t *section, const char *key, unsigned int *index) { configKey_t *k; str keyname; int arrayindex; if (index) { *index = 0; } keyname = key; arrayindex = GetKeyArray(keyname); if (arrayindex == -2) { return NULL; } else if (arrayindex >= 0) { if (index) { *index = arrayindex; } } k = FindKey(section, keyname); if (k) { if (arrayindex == -1 || !k->value.NumObjects()) { RemoveKey(section, k); k = NULL; } } if (!k) { k = new configKey_t; section->key.AddObject(k); k->name = keyname; k->m_bArray = arrayindex != -1; k->m_bNeedWrite = false; } if (arrayindex >= k->value.MaxObjects()) { k->value.Resize((arrayindex + 1) * 2); } return k; } configSection_t *Configurator::FindSection(const char *section) { configSection_t *s = m_sections.findKeyValue(section); return s; } configKey_t *Configurator::FindKey(configSection_t *section, const char *key) { configKey_t *k; int i; for (i = 0; i < section->key.NumObjects(); i++) { k = section->key[i]; if (!k->name.icmp(key)) { return k; } } return NULL; } int Configurator::CutLine(char *data) { char *p = data; while (*p == '\n') { *p++ = 0; } while (*p && *p != '\n') { *p++ = 0; } return p - data; } bool Configurator::SetupLine(char *line, int& lineno, size_t& len, size_t& last) { lineno++; len = (int)strlen(line) - 1; if (len <= 0) { return false; } if (line[len] != '\n') { last = 0; glbs.DPrintf("Configurator::SetupLine: input line too long in %s (line %d)\n", m_filename.c_str(), lineno); return false; } while ((len >= 0) && ((line[len] == '\n') || (isspace(line[len])))) { line[len] = 0; len--; } if (line[len] == '\\') { last = len; return false; } else { last = 0; } return true; } void Configurator::WriteData(char **data, size_t *size) { configSection_t *section; configKey_t *key; configValue_t *val; str value; char *oldData = *data; char *currentData = *data; size_t currentSize = *size; size_t len, tlen = 0; int i, j, k; size_t offset, offset2; bool bFound; for (i = 1; i <= m_reverseSections.NumObjects(); i++) { section = m_reverseSections.ObjectAt(i); if (!FindData(LINE_SECTION, section->name, NULL, &offset, currentData, currentSize)) { oldData = currentData; len = (offset ? 2 : 0) + 1 + section->name.length() + 1; // \n\n + [ + section + ] currentSize += len; currentData = new char[currentSize + 1]; strncpy(currentData, oldData, offset); Com_sprintf( currentData + offset, currentSize + 1 - offset, offset ? "\n\n[%s]" : "[%s]", section->name.c_str() ); Q_strncpyz(currentData + offset + len, oldData + offset, currentSize + 1 - offset - len); if (currentData != oldData) { delete[] oldData; } } for (j = 0; j < section->key.NumObjects(); j++) { key = section->key[j]; if (!key->m_bNeedWrite) { continue; } if (key->m_bArray) { for (k = 0; k < key->value.NumObjects(); k++) { val = &key->value[k]; if (!val->m_bNeedWrite) { continue; } value = val->value; bFound = FindData( LINE_VALUE, section->name, key->name + "[" + k + "]", &offset, currentData, currentSize ); { int digits, l; oldData = currentData; l = k; digits = 0; if (!l) { l = 1; } while (l) { l /= 10; digits++; } if (bFound) { len = CutLine(currentData + offset); currentSize -= len; offset2 = offset + len; } else { offset2 = offset; } len = 1 + key->name.length() + 1 + digits + 1 + 1 + value.length(); // \n + name + [ + digits + ] + = + value currentSize += len; currentData = new char[currentSize + 1]; strncpy(currentData, oldData, offset); Com_sprintf( currentData + offset, currentSize + 1 - offset, "\n%s[%d]=%s", key->name.c_str(), k, value.c_str() ); Q_strncpyz(currentData + offset + len, oldData + offset2, currentSize + 1 - offset - len); if (currentData != oldData) { delete[] oldData; } } } } else { val = &key->value[0]; if (!val->m_bNeedWrite) { continue; } value = val->value; bFound = FindData(LINE_VALUE, section->name, key->name, &offset, currentData, currentSize); { oldData = currentData; if (bFound) { len = CutLine(currentData + offset); currentSize -= len; offset2 = offset + len; } else { offset2 = offset; } len = 1 + key->name.length() + 1 + value.length(); // \n + name + = + value currentSize += len; currentData = new char[currentSize + 1]; strncpy(currentData, oldData, offset); Com_sprintf( currentData + offset, currentSize + 1 - offset "\n%s=%s", key->name.c_str(), value.c_str() ); Q_strncpyz(currentData + offset + len, oldData + offset2, currentSize + 1 - offset - len); if (currentData != oldData) { delete[] oldData; } } } } } if (currentData[currentSize - 1] != '\n') { currentData[currentSize] = '\n'; currentSize++; } *data = currentData; *size = currentSize; } void Configurator::WriteData2(char **data, size_t *size) { configSection_t *section; configKey_t *key; configValue_t *val; str value; char *oldData = *data; char *currentData = *data; size_t currentSize = *size; char *p = currentData; size_t tlen = 0; int i, j, k; for (i = 1; i <= m_reverseSections.NumObjects(); i++) { section = m_reverseSections.ObjectAt(i); if (i == 1) { tlen += 1 + section->name.length() + 1 + 1; // [ + name + ] + \n } else { tlen += 1 + 1 + section->name.length() + 1 + 1; // \n + [ name + ] + \n } if (tlen > currentSize) { oldData = currentData; currentSize = tlen + 1024; currentData = new char[currentSize]; p = currentData + (p - oldData); Q_strncpyz(currentData, oldData, currentSize); delete[] oldData; } if (i == 1) { p += Com_sprintf(p, currentSize - (p - currentData), "[%s]\n", section->name.c_str()); } else { p += Com_sprintf(p, currentSize - (p - currentData), "\n[%s]\n", section->name.c_str()); } for (j = 0; j < section->key.NumObjects(); j++) { key = section->key[j]; if (!key->m_bNeedWrite) { continue; } if (key->m_bArray) { for (k = 0; k < key->value.NumObjects(); k++) { val = &key->value[k]; if (!val->m_bNeedWrite) { continue; } value = val->value; { int digits, l; l = k; digits = 0; if (!l) { l = 1; } while (l) { l /= 10; digits++; } tlen += key->name.length() + 1 + digits + 1 + 1 + value.length() + 1; // name + [ + digits + ] + = + value + \n if (tlen > currentSize) { oldData = currentData; currentSize = tlen + 1024; currentData = new char[currentSize]; p = currentData + (p - oldData); strcpy(currentData, oldData); delete[] oldData; } p += Com_sprintf( p, currentSize - (p - currentData), "%s[%d]=%s\n", key->name.c_str(), k, value.c_str() ); } } } else { val = &key->value[0]; if (!val->m_bNeedWrite) { continue; } value = val->value; tlen += key->name.length() + 1 + value.length() + 1; // name + = + value + \n if (tlen > currentSize) { oldData = currentData; currentSize = tlen + 1024; currentData = new char[currentSize]; p = currentData + (p - oldData); strcpy(currentData, oldData); delete[] oldData; } p += Com_sprintf(p, currentSize - (p - currentData), "%s=%s\n", key->name.c_str(), value.c_str()); } } } *data = currentData; *size = tlen; } bool Configurator::FindData(int type, const char *s, const char *k, size_t *offset, const char *data, size_t size) { static char line[MAX_STRING_TOKENS + 1]; static char section[MAX_STRING_TOKENS + 1]; static char key[MAX_STRING_TOKENS + 1]; static char val[MAX_STRING_TOKENS + 1]; size_t last = 0; size_t len; size_t olen = 0; size_t tlen = 0; int lineno = 0; int arrayindex; bool bFoundSection = false; *offset = 0; while ((olen = GetLine(line + last, data + tlen, MAX_STRING_TOKENS - last)) != 0) { tlen += olen; if (!SetupLine(line + last, lineno, len, last)) { continue; } switch (ParseLine(line, section, key, val)) { case LINE_SECTION: if (bFoundSection) { // Return the last line of the section to append *offset = tlen - olen; return false; } if (!Q_stricmp(s, section)) { bFoundSection = true; if (type == LINE_SECTION) { *offset = tlen; return true; } } break; case LINE_VALUE: arrayindex = GetKeyArray(key); if (arrayindex >= 0) { Com_sprintf(key, sizeof(key), "%s[%d]", key, arrayindex); } if (type == LINE_VALUE && bFoundSection && !Q_stricmp(k, key)) { *offset = tlen - olen; return true; } break; default: break; } } *offset = tlen; return false; } void Configurator::ParseData(const char *data, size_t size) { static char line[MAX_STRING_TOKENS + 1]; static char section[MAX_STRING_TOKENS + 1]; static char key[MAX_STRING_TOKENS + 1]; static char val[MAX_STRING_TOKENS + 1]; size_t last = 0; size_t len; size_t tlen = 0; int lineno = 0; unsigned int index = 0; configSection_t *current; configKey_t *k; configValue_t v; while ((len = GetLine(line + last, data + tlen, MAX_STRING_TOKENS - last)) != 0) { tlen += len; if (!SetupLine(line + last, lineno, len, last)) { continue; } switch (ParseLine(line, section, key, val)) { case LINE_EMPTY: case LINE_COMMENT: break; case LINE_SECTION: current = CreateSection(section); break; case LINE_VALUE: k = CreateKey(current, key, &index); if (!k) { glbs.DPrintf("Configurator::ParseData: Could not create key:\n"); goto __error; } v.m_bNeedWrite = false; v.value = val; k->value.AddObjectAt(index + 1, v); break; case LINE_ERROR: glbs.DPrintf("Configurator::ParseData: syntax error:\n"); __error: glbs.DPrintf("(%s, %d):", m_filename.c_str(), lineno); glbs.DPrintf("%s\n", line); break; default: break; } } } int Configurator::ParseLine(char *line, char *section, char *key, char *value) { size_t len; int result; len = strlen(line); if (len < 1) { result = LINE_EMPTY; } else if (line[0] == ';') { result = LINE_COMMENT; } else if (line[0] == '[' && line[len - 1] == ']') { sscanf(line, "[%[^]]", section); strcpy(section, strstrip(section)); strlwc(section); result = LINE_SECTION; } else if ((sscanf(line, "%[^=] = \"%[^\"]\"", key, value)) == 2 || (sscanf(line, "%[^=] = %[^;]", key, value)) == 2) { strcpy(key, strstrip(key)); strlwc(key); strcpy(value, strstrip(value)); // sscanf cannot handle empty "" values if (!strcmp(value, "\"\"")) { value = ""; } result = LINE_VALUE; } else if ((sscanf(line, "%[^=] = %[;]", key, value)) == 2 || (sscanf(line, "%[^=] %[=]", key, value)) == 2) { strcpy(key, strstrip(key)); strlwc(key); value[0] = 0; result = LINE_VALUE; } else { result = LINE_ERROR; } return result; } str Configurator::GetValue(const char *section, const char *key, str defaultValue, int index) { configKey_t *k = GetKey(section, key, index); configValue_t val; if (!k) { return defaultValue; } if (index < 0) { index = 0; } if (k->m_bNeedWrite) { val.m_bNeedWrite = true; val.value = defaultValue; k->value.AddObjectAt(index + 1, val); } return k->value[index].value; } configKey_t *Configurator::GetKey(const char *section, const char *key, int index) { configSection_t *s; configKey_t *k; bool bArray = index >= 0; if (index < 0) { index = 0; } s = FindSection(section); if (!s) { if (m_bNoWrite) { return NULL; } s = CreateSection(section); s->m_bNeedWrite = true; m_bNeedWrite = true; } k = FindKey(s, key); if (!k) { if (m_bNoWrite) { return NULL; } str keyname; if (bArray) { keyname = str(key) + "[" + str(index) + "]"; } else { keyname = key; } k = CreateKey(s, keyname, NULL); k->m_bNeedWrite = true; m_bNeedWrite = true; } else if (index >= k->value.NumObjects()) { k->m_bNeedWrite = true; m_bNeedWrite = true; } return k; } //====================================== // Configurator::GetString //====================================== str Configurator::GetString(const char *section, const char *key, str defaultValue, int index) { str value = GetValue(section, key, defaultValue, index); return value; } //====================================== // Configurator::GetInteger //====================================== int Configurator::GetInteger(const char *section, const char *key, int defaultValue, int index) { str value = GetValue(section, key, defaultValue, index); return atoi(value.c_str()); } //====================================== // Configurator::GetFloat //====================================== float Configurator::GetFloat(const char *section, const char *key, float defaultValue, int index) { str value = GetValue(section, key, defaultValue, index); return atof(value.c_str()); } //====================================== // Configurator::SetString //====================================== void Configurator::SetString(const char *section, const char *key, str value, int index) { configKey_t *k = GetKey(section, key, index); configValue_t v; if (!k) { return; } if (index < 0) { index = 0; } if (index >= k->value.NumObjects() || k->value[index].value != value) { m_bNeedWrite = true; v.m_bNeedWrite = true; v.value = value; k->m_bNeedWrite = true; k->value.AddObjectAt(index + 1, v); } } //====================================== // Configurator::SetInteger //====================================== void Configurator::SetInteger(const char *section, const char *key, int value, int index) { return SetString(section, key, value, index); } //====================================== // Configurator::SetFloat //====================================== void Configurator::SetFloat(const char *section, const char *key, float value, int index) { return SetString(section, key, value, index); } //====================================== // Configurator::SetWrite //====================================== void Configurator::SetWrite(bool bWrite) { m_bNoWrite = !bWrite; } //====================================== // Configurator::Parse //====================================== void Configurator::Parse(const char *filename) { FILE *file; size_t size; char *buffer; m_bNoWrite = false; m_bNeedWrite = false; this->m_filename = filename; file = fopen(filename, "r"); if (!file) { return; } fseek(file, 0, SEEK_END); size = ftell(file); rewind(file); buffer = new char[size + 1]; size = fread(buffer, 1, size, file); buffer[size] = 0; fclose(file); ParseData(buffer, size); } //====================================== // Configurator::Close //====================================== void Configurator::Close(void) { FILE *file; char *buffer; int i; size_t size; if (!m_filename.length() || !m_bNeedWrite) { goto _cleanup; } file = fopen(m_filename.c_str(), "r+"); if (!file) { file = fopen(m_filename.c_str(), "w+"); if (!file) { return; } file = freopen(m_filename.c_str(), "r+", file); if (!file) { return; } } fseek(file, 0, SEEK_END); size = ftell(file); rewind(file); buffer = new char[size + 1]; size = fread(buffer, 1, size, file); buffer[size] = 0; glbs.DPrintf("Configurator::Close: Writing data to %s...\n", m_filename.c_str()); WriteData(&buffer, &size); glbs.DPrintf("Configurator::Close: Done.\n"); file = freopen(m_filename.c_str(), "w+", file); fwrite(buffer, size, 1, file); fclose(file); m_filename = ""; delete[] buffer; _cleanup: for (i = m_reverseSections.NumObjects(); i > 0; i--) { RemoveSection(m_reverseSections.ObjectAt(i)); } } void test_config(void) { Configurator config("test.ini"); str value; value = config.GetString("section1", "testvar1", "100"); glbs.DPrintf("value = %s\n", value.c_str()); value = config.GetString("section1", "testarray1", "100", 0); glbs.DPrintf("array1 = %s\n", value.c_str()); value = config.GetString("section1", "testarray1", "100", 1); glbs.DPrintf("array2 = %s\n", value.c_str()); value = config.GetString("section1", "testarray1", "100", 2); glbs.DPrintf("array3 = %s\n", value.c_str()); value = config.GetString("section1", "testforcewrite", "500"); glbs.DPrintf("testforcewrite = %s\n", value.c_str()); value = config.GetString("section2", "testforcewrite2", "500"); glbs.DPrintf("testforcewrite2 = %s\n", value.c_str()); //for( int i = 0; i < 2048; i++ ) //{ // value = config.GetString( "section1", "testforcewrite5", "2048", i ); //} config.SetString("section1", "testforcewrite5", "newstring", 2048); config.Close(); }