/* =========================================================================== 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(); }