2023-05-23 00:22:04 +02:00
/* minIni - Multi-Platform INI file parser, suitable for embedded systems
*
* These routines are in part based on the article " Multiplatform .INI Files "
* by Joseph J . Graf in the March 1994 issue of Dr . Dobb ' s Journal .
*
* Copyright ( c ) CompuPhase , 2008 - 2021
*
* Licensed under the Apache License , Version 2.0 ( the " License " ) ; you may not
* use this file except in compliance with the License . You may obtain a copy
* of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS , WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied . See the
* License for the specific language governing permissions and limitations
* under the License .
*
* Version : $ Id : minIni . c 53 2015 - 01 - 18 13 : 35 : 11 Z thiadmer . riemersma @ gmail . com $
*/
# if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY
# if !defined UNICODE /* for Windows */
# define UNICODE
# endif
# if !defined _UNICODE /* for C library */
# define _UNICODE
# endif
# endif
# define MININI_IMPLEMENTATION
# include "minIni.h"
# if defined NDEBUG
# define assert(e)
# else
# include <assert.h>
# endif
# if !defined __T || defined INI_ANSIONLY
# include <ctype.h>
# include <string.h>
# include <stdlib.h>
# define TCHAR char
# define __T(s) s
# define _tcscat strcat
# define _tcschr strchr
# define _tcscmp strcmp
# define _tcscpy strcpy
# define _tcsicmp stricmp
# define _tcslen strlen
# define _tcsncmp strncmp
# define _tcsnicmp strnicmp
# define _tcsrchr strrchr
# define _tcstol strtol
# define _tcstod strtod
# define _totupper toupper
# define _stprintf sprintf
# define _tfgets fgets
# define _tfputs fputs
# define _tfopen fopen
# define _tremove remove
# define _trename rename
# endif
# if defined __linux || defined __linux__
# define __LINUX__
# elif defined FREEBSD && !defined __FreeBSD__
# define __FreeBSD__
# elif defined(_MSC_VER)
# pragma warning(disable: 4996) /* for Microsoft Visual C/C++ */
# endif
# if !defined strnicmp && !defined PORTABLE_STRNICMP
# if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ || defined __NetBSD__ || defined __DragonFly__ || defined __GNUC__
# define strnicmp strncasecmp
# endif
# endif
# if !defined _totupper
# define _totupper toupper
# endif
# if !defined INI_LINETERM
# if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ || defined __NetBSD__ || defined __DragonFly__
# define INI_LINETERM __T("\n")
# else
# define INI_LINETERM __T("\r\n")
# endif
# endif
# if !defined INI_FILETYPE
# error Missing definition for INI_FILETYPE.
# endif
# if !defined sizearray
# define sizearray(a) (sizeof(a) / sizeof((a)[0]))
# endif
enum quote_option {
QUOTE_NONE ,
QUOTE_ENQUOTE ,
QUOTE_DEQUOTE ,
} ;
# if defined PORTABLE_STRNICMP
int strnicmp ( const TCHAR * s1 , const TCHAR * s2 , size_t n )
{
while ( n - - ! = 0 & & ( * s1 | | * s2 ) ) {
register int c1 , c2 ;
c1 = * s1 + + ;
if ( ' a ' < = c1 & & c1 < = ' z ' )
c1 + = ( ' A ' - ' a ' ) ;
c2 = * s2 + + ;
if ( ' a ' < = c2 & & c2 < = ' z ' )
c2 + = ( ' A ' - ' a ' ) ;
if ( c1 ! = c2 )
return c1 - c2 ;
}
return 0 ;
}
# endif /* PORTABLE_STRNICMP */
static TCHAR * skipleading ( const TCHAR * str )
{
assert ( str ! = NULL ) ;
while ( ' \0 ' < * str & & * str < = ' ' )
str + + ;
return ( TCHAR * ) str ;
}
static TCHAR * skiptrailing ( const TCHAR * str , const TCHAR * base )
{
assert ( str ! = NULL ) ;
assert ( base ! = NULL ) ;
while ( str > base & & ' \0 ' < * ( str - 1 ) & & * ( str - 1 ) < = ' ' )
str - - ;
return ( TCHAR * ) str ;
}
static TCHAR * striptrailing ( TCHAR * str )
{
TCHAR * ptr = skiptrailing ( _tcschr ( str , ' \0 ' ) , str ) ;
assert ( ptr ! = NULL ) ;
* ptr = ' \0 ' ;
return str ;
}
static TCHAR * ini_strncpy ( TCHAR * dest , const TCHAR * source , size_t maxlen , enum quote_option option )
{
size_t d , s ;
assert ( maxlen > 0 ) ;
assert ( source ! = NULL & & dest ! = NULL ) ;
assert ( ( dest < source | | ( dest = = source & & option ! = QUOTE_ENQUOTE ) ) | | dest > source + strlen ( source ) ) ;
if ( option = = QUOTE_ENQUOTE & & maxlen < 3 )
option = QUOTE_NONE ; /* cannot store two quotes and a terminating zero in less than 3 characters */
switch ( option ) {
case QUOTE_NONE :
for ( d = 0 ; d < maxlen - 1 & & source [ d ] ! = ' \0 ' ; d + + )
dest [ d ] = source [ d ] ;
assert ( d < maxlen ) ;
dest [ d ] = ' \0 ' ;
break ;
case QUOTE_ENQUOTE :
d = 0 ;
dest [ d + + ] = ' " ' ;
for ( s = 0 ; source [ s ] ! = ' \0 ' & & d < maxlen - 2 ; s + + , d + + ) {
if ( source [ s ] = = ' " ' ) {
if ( d > = maxlen - 3 )
break ; /* no space to store the escape character plus the one that follows it */
dest [ d + + ] = ' \\ ' ;
}
dest [ d ] = source [ s ] ;
}
dest [ d + + ] = ' " ' ;
dest [ d ] = ' \0 ' ;
break ;
case QUOTE_DEQUOTE :
for ( d = s = 0 ; source [ s ] ! = ' \0 ' & & d < maxlen - 1 ; s + + , d + + ) {
if ( ( source [ s ] = = ' " ' | | source [ s ] = = ' \\ ' ) & & source [ s + 1 ] = = ' " ' )
s + + ;
dest [ d ] = source [ s ] ;
}
dest [ d ] = ' \0 ' ;
break ;
default :
assert ( 0 ) ;
}
return dest ;
}
static TCHAR * cleanstring ( TCHAR * string , enum quote_option * quotes )
{
int isstring ;
TCHAR * ep ;
assert ( string ! = NULL ) ;
assert ( quotes ! = NULL ) ;
/* Remove a trailing comment */
isstring = 0 ;
for ( ep = string ; * ep ! = ' \0 ' & & ( ( * ep ! = ' ; ' & & * ep ! = ' # ' ) | | isstring ) ; ep + + ) {
if ( * ep = = ' " ' ) {
if ( * ( ep + 1 ) = = ' " ' )
ep + + ; /* skip "" (both quotes) */
else
isstring = ! isstring ; /* single quote, toggle isstring */
} else if ( * ep = = ' \\ ' & & * ( ep + 1 ) = = ' " ' ) {
ep + + ; /* skip \" (both quotes */
}
}
assert ( ep ! = NULL & & ( * ep = = ' \0 ' | | * ep = = ' ; ' | | * ep = = ' # ' ) ) ;
* ep = ' \0 ' ; /* terminate at a comment */
striptrailing ( string ) ;
/* Remove double quotes surrounding a value */
* quotes = QUOTE_NONE ;
if ( * string = = ' " ' & & ( ep = _tcschr ( string , ' \0 ' ) ) ! = NULL & & * ( ep - 1 ) = = ' " ' ) {
string + + ;
* - - ep = ' \0 ' ;
* quotes = QUOTE_DEQUOTE ; /* this is a string, so remove escaped characters */
}
return string ;
}
static int getkeystring ( INI_FILETYPE * fp , const TCHAR * Section , const TCHAR * Key ,
int idxSection , int idxKey , TCHAR * Buffer , int BufferSize ,
INI_FILEPOS * mark )
{
TCHAR * sp , * ep ;
int len , idx ;
enum quote_option quotes ;
TCHAR LocalBuffer [ INI_BUFFERSIZE ] ;
assert ( fp ! = NULL ) ;
/* Move through file 1 line at a time until a section is matched or EOF. If
* parameter Section is NULL , only look at keys above the first section . If
* idxSection is positive , copy the relevant section name .
*/
len = ( Section ! = NULL ) ? ( int ) _tcslen ( Section ) : 0 ;
if ( len > 0 | | idxSection > = 0 ) {
assert ( idxSection > = 0 | | Section ! = NULL ) ;
idx = - 1 ;
do {
do {
if ( ! ini_read ( LocalBuffer , INI_BUFFERSIZE , fp ) )
return 0 ;
sp = skipleading ( LocalBuffer ) ;
ep = _tcsrchr ( sp , ' ] ' ) ;
} while ( * sp ! = ' [ ' | | ep = = NULL ) ;
/* When arrived here, a section was found; now optionally skip leading and
* trailing whitespace .
*/
assert ( sp ! = NULL & & * sp = = ' [ ' ) ;
sp = skipleading ( sp + 1 ) ;
assert ( ep ! = NULL & & * ep = = ' ] ' ) ;
ep = skiptrailing ( ep , sp ) ;
} while ( ( ( ( int ) ( ep - sp ) ! = len | | Section = = NULL | | _tcsnicmp ( sp , Section , len ) ! = 0 ) & & + + idx ! = idxSection ) ) ;
if ( idxSection > = 0 ) {
if ( idx = = idxSection ) {
assert ( ep ! = NULL ) ;
* ep = ' \0 ' ; /* the end of the section name was found earlier */
ini_strncpy ( Buffer , sp , BufferSize , QUOTE_NONE ) ;
return 1 ;
}
return 0 ; /* no more section found */
}
}
/* Now that the section has been found, find the entry.
* Stop searching upon leaving the section ' s area .
*/
assert ( Key ! = NULL | | idxKey > = 0 ) ;
len = ( Key ! = NULL ) ? ( int ) _tcslen ( Key ) : 0 ;
idx = - 1 ;
do {
if ( mark ! = NULL )
ini_tell ( fp , mark ) ; /* optionally keep the mark to the start of the line */
if ( ! ini_read ( LocalBuffer , INI_BUFFERSIZE , fp ) | | * ( sp = skipleading ( LocalBuffer ) ) = = ' [ ' )
return 0 ;
sp = skipleading ( LocalBuffer ) ;
ep = _tcschr ( sp , ' = ' ) ; /* Parse out the equal sign */
if ( ep = = NULL )
ep = _tcschr ( sp , ' : ' ) ;
} while ( * sp = = ' ; ' | | * sp = = ' # ' | | ep = = NULL
| | ( ( len = = 0 | | ( int ) ( skiptrailing ( ep , sp ) - sp ) ! = len | | _tcsnicmp ( sp , Key , len ) ! = 0 ) & & + + idx ! = idxKey ) ) ;
if ( idxKey > = 0 ) {
if ( idx = = idxKey ) {
assert ( ep ! = NULL ) ;
assert ( * ep = = ' = ' | | * ep = = ' : ' ) ;
* ep = ' \0 ' ;
striptrailing ( sp ) ;
ini_strncpy ( Buffer , sp , BufferSize , QUOTE_NONE ) ;
return 1 ;
}
return 0 ; /* no more key found (in this section) */
}
/* Copy up to BufferSize chars to buffer */
assert ( ep ! = NULL ) ;
assert ( * ep = = ' = ' | | * ep = = ' : ' ) ;
sp = skipleading ( ep + 1 ) ;
sp = cleanstring ( sp , & quotes ) ; /* Remove a trailing comment */
ini_strncpy ( Buffer , sp , BufferSize , quotes ) ;
return 1 ;
}
/** ini_gets()
* \ param Section the name of the section to search for
* \ param Key the name of the entry to find the value of
* \ param DefValue default string in the event of a failed read
* \ param Buffer a pointer to the buffer to copy into
* \ param BufferSize the maximum number of characters to copy
* \ param Filename the name and full path of the . ini file to read from
*
* \ return the number of characters copied into the supplied buffer
*/
int ini_gets ( const TCHAR * Section , const TCHAR * Key , const TCHAR * DefValue ,
TCHAR * Buffer , int BufferSize , const TCHAR * Filename )
{
INI_FILETYPE fp ;
int ok = 0 ;
if ( Buffer = = NULL | | BufferSize < = 0 | | Key = = NULL )
return 0 ;
if ( ini_openread ( Filename , & fp ) ) {
ok = getkeystring ( & fp , Section , Key , - 1 , - 1 , Buffer , BufferSize , NULL ) ;
( void ) ini_close ( & fp ) ;
}
if ( ! ok )
ini_strncpy ( Buffer , ( DefValue ! = NULL ) ? DefValue : __T ( " " ) , BufferSize , QUOTE_NONE ) ;
return ( int ) _tcslen ( Buffer ) ;
}
/** ini_getl()
* \ param Section the name of the section to search for
* \ param Key the name of the entry to find the value of
* \ param DefValue the default value in the event of a failed read
* \ param Filename the name of the . ini file to read from
*
* \ return the value located at Key
*/
long ini_getl ( const TCHAR * Section , const TCHAR * Key , long DefValue , const TCHAR * Filename )
{
TCHAR LocalBuffer [ 64 ] ;
int len = ini_gets ( Section , Key , __T ( " " ) , LocalBuffer , sizearray ( LocalBuffer ) , Filename ) ;
return ( len = = 0 ) ? DefValue
: ( ( len > = 2 & & _totupper ( ( int ) LocalBuffer [ 1 ] ) = = ' X ' ) ? _tcstol ( LocalBuffer , NULL , 16 )
: _tcstol ( LocalBuffer , NULL , 10 ) ) ;
}
# if defined INI_REAL
/** ini_getf()
* \ param Section the name of the section to search for
* \ param Key the name of the entry to find the value of
* \ param DefValue the default value in the event of a failed read
* \ param Filename the name of the . ini file to read from
*
* \ return the value located at Key
*/
INI_REAL ini_getf ( const TCHAR * Section , const TCHAR * Key , INI_REAL DefValue , const TCHAR * Filename )
{
TCHAR LocalBuffer [ 64 ] ;
int len = ini_gets ( Section , Key , __T ( " " ) , LocalBuffer , sizearray ( LocalBuffer ) , Filename ) ;
return ( len = = 0 ) ? DefValue : ini_atof ( LocalBuffer ) ;
}
# endif
/** ini_getbool()
* \ param Section the name of the section to search for
* \ param Key the name of the entry to find the value of
* \ param DefValue default value in the event of a failed read ; it should
* zero ( 0 ) or one ( 1 ) .
* \ param Filename the name and full path of the . ini file to read from
*
* A true boolean is found if one of the following is matched :
* - A string starting with ' y ' or ' Y '
* - A string starting with ' t ' or ' T '
* - A string starting with ' 1 '
*
* A false boolean is found if one of the following is matched :
* - A string starting with ' n ' or ' N '
* - A string starting with ' f ' or ' F '
* - A string starting with ' 0 '
*
* \ return the true / false flag as interpreted at Key
*/
int ini_getbool ( const TCHAR * Section , const TCHAR * Key , int DefValue , const TCHAR * Filename )
{
TCHAR LocalBuffer [ 2 ] = __T ( " " ) ;
int ret ;
ini_gets ( Section , Key , __T ( " " ) , LocalBuffer , sizearray ( LocalBuffer ) , Filename ) ;
LocalBuffer [ 0 ] = ( TCHAR ) _totupper ( ( int ) LocalBuffer [ 0 ] ) ;
if ( LocalBuffer [ 0 ] = = ' Y ' | | LocalBuffer [ 0 ] = = ' 1 ' | | LocalBuffer [ 0 ] = = ' T ' )
ret = 1 ;
else if ( LocalBuffer [ 0 ] = = ' N ' | | LocalBuffer [ 0 ] = = ' 0 ' | | LocalBuffer [ 0 ] = = ' F ' )
ret = 0 ;
else
ret = DefValue ;
return ( ret ) ;
}
/** ini_getsection()
* \ param idx the zero - based sequence number of the section to return
* \ param Buffer a pointer to the buffer to copy into
* \ param BufferSize the maximum number of characters to copy
* \ param Filename the name and full path of the . ini file to read from
*
* \ return the number of characters copied into the supplied buffer
*/
int ini_getsection ( int idx , TCHAR * Buffer , int BufferSize , const TCHAR * Filename )
{
INI_FILETYPE fp ;
int ok = 0 ;
if ( Buffer = = NULL | | BufferSize < = 0 | | idx < 0 )
return 0 ;
if ( ini_openread ( Filename , & fp ) ) {
ok = getkeystring ( & fp , NULL , NULL , idx , - 1 , Buffer , BufferSize , NULL ) ;
( void ) ini_close ( & fp ) ;
}
if ( ! ok )
* Buffer = ' \0 ' ;
return ( int ) _tcslen ( Buffer ) ;
}
/** ini_getkey()
* \ param Section the name of the section to browse through , or NULL to
* browse through the keys outside any section
* \ param idx the zero - based sequence number of the key to return
* \ param Buffer a pointer to the buffer to copy into
* \ param BufferSize the maximum number of characters to copy
* \ param Filename the name and full path of the . ini file to read from
*
* \ return the number of characters copied into the supplied buffer
*/
int ini_getkey ( const TCHAR * Section , int idx , TCHAR * Buffer , int BufferSize , const TCHAR * Filename )
{
INI_FILETYPE fp ;
int ok = 0 ;
if ( Buffer = = NULL | | BufferSize < = 0 | | idx < 0 )
return 0 ;
if ( ini_openread ( Filename , & fp ) ) {
ok = getkeystring ( & fp , Section , NULL , - 1 , idx , Buffer , BufferSize , NULL ) ;
( void ) ini_close ( & fp ) ;
}
if ( ! ok )
* Buffer = ' \0 ' ;
return ( int ) _tcslen ( Buffer ) ;
}
/** ini_hassection()
* \ param Section the name of the section to search for
* \ param Filename the name of the . ini file to read from
*
* \ return 1 if the section is found , 0 if not found
*/
int ini_hassection ( const mTCHAR * Section , const mTCHAR * Filename )
{
TCHAR LocalBuffer [ 32 ] ; /* dummy buffer */
INI_FILETYPE fp ;
int ok = 0 ;
if ( ini_openread ( Filename , & fp ) ) {
ok = getkeystring ( & fp , Section , NULL , - 1 , 0 , LocalBuffer , sizearray ( LocalBuffer ) , NULL ) ;
( void ) ini_close ( & fp ) ;
}
return ok ;
}
/** ini_haskey()
* \ param Section the name of the section to search for
* \ param Key the name of the entry to find the value of
* \ param Filename the name of the . ini file to read from
*
* \ return 1 if the key is found , 0 if not found
*/
int ini_haskey ( const mTCHAR * Section , const mTCHAR * Key , const mTCHAR * Filename )
{
TCHAR LocalBuffer [ 32 ] ; /* dummy buffer */
INI_FILETYPE fp ;
int ok = 0 ;
if ( ini_openread ( Filename , & fp ) ) {
ok = getkeystring ( & fp , Section , Key , - 1 , - 1 , LocalBuffer , sizearray ( LocalBuffer ) , NULL ) ;
( void ) ini_close ( & fp ) ;
}
return ok ;
}
# if !defined INI_NOBROWSE
/** ini_browse()
* \ param Callback a pointer to a function that will be called for every
* setting in the INI file .
* \ param UserData arbitrary data , which the function passes on the
* \ c Callback function
* \ param Filename the name and full path of the . ini file to read from
*
* \ return 1 on success , 0 on failure ( INI file not found )
*
* \ note The \ c Callback function must return 1 to continue
* browsing through the INI file , or 0 to stop . Even when the
* callback stops the browsing , this function will return 1
* ( for success ) .
*/
int ini_browse ( INI_CALLBACK Callback , void * UserData , const TCHAR * Filename )
{
TCHAR LocalBuffer [ INI_BUFFERSIZE ] ;
int lenSec , lenKey ;
enum quote_option quotes ;
INI_FILETYPE fp ;
if ( Callback = = NULL )
return 0 ;
if ( ! ini_openread ( Filename , & fp ) )
return 0 ;
LocalBuffer [ 0 ] = ' \0 ' ; /* copy an empty section in the buffer */
lenSec = ( int ) _tcslen ( LocalBuffer ) + 1 ;
for ( ; ; ) {
TCHAR * sp , * ep ;
if ( ! ini_read ( LocalBuffer + lenSec , INI_BUFFERSIZE - lenSec , & fp ) )
break ;
sp = skipleading ( LocalBuffer + lenSec ) ;
/* ignore empty strings and comments */
if ( * sp = = ' \0 ' | | * sp = = ' ; ' | | * sp = = ' # ' )
continue ;
/* see whether we reached a new section */
ep = _tcsrchr ( sp , ' ] ' ) ;
if ( * sp = = ' [ ' & & ep ! = NULL ) {
sp = skipleading ( sp + 1 ) ;
ep = skiptrailing ( ep , sp ) ;
* ep = ' \0 ' ;
ini_strncpy ( LocalBuffer , sp , INI_BUFFERSIZE , QUOTE_NONE ) ;
lenSec = ( int ) _tcslen ( LocalBuffer ) + 1 ;
continue ;
}
/* not a new section, test for a key/value pair */
ep = _tcschr ( sp , ' = ' ) ; /* test for the equal sign or colon */
if ( ep = = NULL )
ep = _tcschr ( sp , ' : ' ) ;
if ( ep = = NULL )
continue ; /* invalid line, ignore */
* ep + + = ' \0 ' ; /* split the key from the value */
striptrailing ( sp ) ;
ini_strncpy ( LocalBuffer + lenSec , sp , INI_BUFFERSIZE - lenSec , QUOTE_NONE ) ;
lenKey = ( int ) _tcslen ( LocalBuffer + lenSec ) + 1 ;
/* clean up the value */
sp = skipleading ( ep ) ;
sp = cleanstring ( sp , & quotes ) ; /* Remove a trailing comment */
ini_strncpy ( LocalBuffer + lenSec + lenKey , sp , INI_BUFFERSIZE - lenSec - lenKey , quotes ) ;
/* call the callback */
if ( ! Callback ( LocalBuffer , LocalBuffer + lenSec , LocalBuffer + lenSec + lenKey , UserData ) )
break ;
}
( void ) ini_close ( & fp ) ;
return 1 ;
}
# endif /* INI_NOBROWSE */
# if ! defined INI_READONLY
static void ini_tempname ( TCHAR * dest , const TCHAR * source , int maxlength )
{
TCHAR * p ;
ini_strncpy ( dest , source , maxlength , QUOTE_NONE ) ;
p = _tcschr ( dest , ' \0 ' ) ;
assert ( p ! = NULL ) ;
2023-05-23 14:02:19 +02:00
* ( p - 1 ) = ' ~ ' ;
2023-05-23 00:22:04 +02:00
}
static enum quote_option check_enquote ( const TCHAR * Value )
{
const TCHAR * p ;
/* run through the value, if it has trailing spaces, or '"', ';' or '#'
* characters , enquote it
*/
assert ( Value ! = NULL ) ;
for ( p = Value ; * p ! = ' \0 ' & & * p ! = ' " ' & & * p ! = ' ; ' & & * p ! = ' # ' ; p + + )
/* nothing */ ;
return ( * p ! = ' \0 ' | | ( p > Value & & * ( p - 1 ) = = ' ' ) ) ? QUOTE_ENQUOTE : QUOTE_NONE ;
}
static void writesection ( TCHAR * LocalBuffer , const TCHAR * Section , INI_FILETYPE * fp )
{
if ( Section ! = NULL & & _tcslen ( Section ) > 0 ) {
TCHAR * p ;
LocalBuffer [ 0 ] = ' [ ' ;
ini_strncpy ( LocalBuffer + 1 , Section , INI_BUFFERSIZE - 4 , QUOTE_NONE ) ; /* -1 for '[', -1 for ']', -2 for '\r\n' */
p = _tcschr ( LocalBuffer , ' \0 ' ) ;
assert ( p ! = NULL ) ;
* p + + = ' ] ' ;
_tcscpy ( p , INI_LINETERM ) ; /* copy line terminator (typically "\n") */
if ( fp ! = NULL )
( void ) ini_write ( LocalBuffer , fp ) ;
}
}
static void writekey ( TCHAR * LocalBuffer , const TCHAR * Key , const TCHAR * Value , INI_FILETYPE * fp )
{
TCHAR * p ;
enum quote_option option = check_enquote ( Value ) ;
ini_strncpy ( LocalBuffer , Key , INI_BUFFERSIZE - 3 , QUOTE_NONE ) ; /* -1 for '=', -2 for '\r\n' */
p = _tcschr ( LocalBuffer , ' \0 ' ) ;
assert ( p ! = NULL ) ;
* p + + = ' = ' ;
ini_strncpy ( p , Value , INI_BUFFERSIZE - ( p - LocalBuffer ) - 2 , option ) ; /* -2 for '\r\n' */
p = _tcschr ( LocalBuffer , ' \0 ' ) ;
assert ( p ! = NULL ) ;
_tcscpy ( p , INI_LINETERM ) ; /* copy line terminator (typically "\n") */
if ( fp ! = NULL )
( void ) ini_write ( LocalBuffer , fp ) ;
}
static int cache_accum ( const TCHAR * string , int * size , int max )
{
int len = ( int ) _tcslen ( string ) ;
if ( * size + len > = max )
return 0 ;
* size + = len ;
return 1 ;
}
static int cache_flush ( TCHAR * buffer , int * size ,
INI_FILETYPE * rfp , INI_FILETYPE * wfp , INI_FILEPOS * mark )
{
int terminator_len = ( int ) _tcslen ( INI_LINETERM ) ;
int pos = 0 , pos_prev = - 1 ;
( void ) ini_seek ( rfp , mark ) ;
assert ( buffer ! = NULL ) ;
buffer [ 0 ] = ' \0 ' ;
assert ( size ! = NULL ) ;
assert ( * size < = INI_BUFFERSIZE ) ;
while ( pos < * size & & pos ! = pos_prev ) {
pos_prev = pos ; /* to guard against zero bytes in the INI file */
( void ) ini_read ( buffer + pos , INI_BUFFERSIZE - pos , rfp ) ;
while ( pos < * size & & buffer [ pos ] ! = ' \0 ' )
pos + + ; /* cannot use _tcslen() because buffer may not be zero-terminated */
}
if ( buffer [ 0 ] ! = ' \0 ' ) {
assert ( pos > 0 & & pos < = INI_BUFFERSIZE ) ;
if ( pos = = INI_BUFFERSIZE )
pos - - ;
buffer [ pos ] = ' \0 ' ; /* force zero-termination (may be left unterminated in the above while loop) */
( void ) ini_write ( buffer , wfp ) ;
}
ini_tell ( rfp , mark ) ; /* update mark */
* size = 0 ;
/* return whether the buffer ended with a line termination */
return ( pos > terminator_len ) & & ( _tcscmp ( buffer + pos - terminator_len , INI_LINETERM ) = = 0 ) ;
}
static int close_rename ( INI_FILETYPE * rfp , INI_FILETYPE * wfp , const TCHAR * filename , TCHAR * buffer )
{
( void ) ini_close ( rfp ) ;
( void ) ini_close ( wfp ) ;
( void ) ini_tempname ( buffer , filename , INI_BUFFERSIZE ) ;
# if defined ini_remove || defined INI_REMOVE
( void ) ini_remove ( filename ) ;
# endif
( void ) ini_rename ( buffer , filename ) ;
return 1 ;
}
/** ini_puts()
* \ param Section the name of the section to write the string in
* \ param Key the name of the entry to write , or NULL to erase all keys in the section
* \ param Value a pointer to the buffer the string , or NULL to erase the key
* \ param Filename the name and full path of the . ini file to write to
*
* \ return 1 if successful , otherwise 0
*/
int ini_puts ( const TCHAR * Section , const TCHAR * Key , const TCHAR * Value , const TCHAR * Filename )
{
INI_FILETYPE rfp ;
INI_FILETYPE wfp ;
INI_FILEPOS mark ;
INI_FILEPOS head , tail ;
TCHAR * sp , * ep ;
2023-05-23 14:02:19 +02:00
TCHAR LocalBuffer [ INI_BUFFERSIZE ] ;
2023-05-23 00:22:04 +02:00
int len , match , flag , cachelen ;
assert ( Filename ! = NULL ) ;
if ( ! ini_openread ( Filename , & rfp ) ) {
/* If the .ini file doesn't exist, make a new file */
if ( Key ! = NULL & & Value ! = NULL ) {
if ( ! ini_openwrite ( Filename , & wfp ) )
return 0 ;
writesection ( LocalBuffer , Section , & wfp ) ;
writekey ( LocalBuffer , Key , Value , & wfp ) ;
( void ) ini_close ( & wfp ) ;
}
return 1 ;
}
/* If parameters Key and Value are valid (so this is not an "erase" request)
* and the setting already exists , there are two short - cuts to avoid rewriting
* the INI file .
*/
if ( Key ! = NULL & & Value ! = NULL ) {
match = getkeystring ( & rfp , Section , Key , - 1 , - 1 , LocalBuffer , sizearray ( LocalBuffer ) , & head ) ;
if ( match ) {
/* if the current setting is identical to the one to write, there is
* nothing to do .
*/
if ( _tcscmp ( LocalBuffer , Value ) = = 0 ) {
( void ) ini_close ( & rfp ) ;
return 1 ;
}
/* if the new setting has the same length as the current setting, and the
* glue file permits file read / write access , we can modify in place .
*/
# if defined ini_openrewrite || defined INI_OPENREWRITE
/* we already have the start of the (raw) line, get the end too */
ini_tell ( & rfp , & tail ) ;
/* create new buffer (without writing it to file) */
writekey ( LocalBuffer , Key , Value , NULL ) ;
if ( _tcslen ( LocalBuffer ) = = ( size_t ) ( tail - head ) ) {
/* length matches, close the file & re-open for read/write, then
* write at the correct position
*/
( void ) ini_close ( & rfp ) ;
if ( ! ini_openrewrite ( Filename , & wfp ) )
return 0 ;
( void ) ini_seek ( & wfp , & head ) ;
( void ) ini_write ( LocalBuffer , & wfp ) ;
( void ) ini_close ( & wfp ) ;
return 1 ;
}
# endif
}
/* key not found, or different value & length -> proceed */
} else if ( Key ! = NULL & & Value = = NULL ) {
/* Conversely, for a request to delete a setting; if that setting isn't
present , just return */
match = getkeystring ( & rfp , Section , Key , - 1 , - 1 , LocalBuffer , sizearray ( LocalBuffer ) , NULL ) ;
if ( ! match ) {
( void ) ini_close ( & rfp ) ;
return 1 ;
}
/* key found -> proceed to delete it */
}
/* Get a temporary file name to copy to. Use the existing name, but with
* the last character set to a ' ~ ' .
*/
( void ) ini_close ( & rfp ) ;
ini_tempname ( LocalBuffer , Filename , INI_BUFFERSIZE ) ;
if ( ! ini_openwrite ( LocalBuffer , & wfp ) )
return 0 ;
/* In the case of (advisory) file locks, ini_openwrite() may have been blocked
* on the open , and after the block is lifted , the original file may have been
* renamed , which is why the original file was closed and is now reopened */
if ( ! ini_openread ( Filename , & rfp ) ) {
/* If the .ini file doesn't exist any more, make a new file */
assert ( Key ! = NULL & & Value ! = NULL ) ;
writesection ( LocalBuffer , Section , & wfp ) ;
writekey ( LocalBuffer , Key , Value , & wfp ) ;
( void ) ini_close ( & wfp ) ;
return 1 ;
}
( void ) ini_tell ( & rfp , & mark ) ;
cachelen = 0 ;
/* Move through the file one line at a time until a section is
* matched or until EOF . Copy to temp file as it is read .
*/
len = ( Section ! = NULL ) ? ( int ) _tcslen ( Section ) : 0 ;
if ( len > 0 ) {
do {
if ( ! ini_read ( LocalBuffer , INI_BUFFERSIZE , & rfp ) ) {
/* Failed to find section, so add one to the end */
flag = cache_flush ( LocalBuffer , & cachelen , & rfp , & wfp , & mark ) ;
if ( Key ! = NULL & & Value ! = NULL ) {
if ( ! flag )
( void ) ini_write ( INI_LINETERM , & wfp ) ; /* force a new line behind the last line of the INI file */
writesection ( LocalBuffer , Section , & wfp ) ;
writekey ( LocalBuffer , Key , Value , & wfp ) ;
}
return close_rename ( & rfp , & wfp , Filename , LocalBuffer ) ; /* clean up and rename */
}
/* Check whether this line is a section */
sp = skipleading ( LocalBuffer ) ;
ep = _tcsrchr ( sp , ' ] ' ) ;
match = ( * sp = = ' [ ' & & ep ! = NULL ) ;
if ( match ) {
/* A section was found, skip leading and trailing whitespace */
assert ( sp ! = NULL & & * sp = = ' [ ' ) ;
sp = skipleading ( sp + 1 ) ;
assert ( ep ! = NULL & & * ep = = ' ] ' ) ;
ep = skiptrailing ( ep , sp ) ;
match = ( ( int ) ( ep - sp ) = = len & & _tcsnicmp ( sp , Section , len ) = = 0 ) ;
}
/* Copy the line from source to dest, but not if this is the section that
* we are looking for and this section must be removed
*/
if ( ! match | | Key ! = NULL ) {
if ( ! cache_accum ( LocalBuffer , & cachelen , INI_BUFFERSIZE ) ) {
cache_flush ( LocalBuffer , & cachelen , & rfp , & wfp , & mark ) ;
( void ) ini_read ( LocalBuffer , INI_BUFFERSIZE , & rfp ) ;
cache_accum ( LocalBuffer , & cachelen , INI_BUFFERSIZE ) ;
}
}
} while ( ! match ) ;
}
cache_flush ( LocalBuffer , & cachelen , & rfp , & wfp , & mark ) ;
/* when deleting a section, the section head that was just found has not been
* copied to the output file , but because this line was not " accumulated " in
* the cache , the position in the input file was reset to the point just
* before the section ; this must now be skipped ( again )
*/
if ( Key = = NULL ) {
( void ) ini_read ( LocalBuffer , INI_BUFFERSIZE , & rfp ) ;
( void ) ini_tell ( & rfp , & mark ) ;
}
/* Now that the section has been found, find the entry. Stop searching
* upon leaving the section ' s area . Copy the file as it is read
* and create an entry if one is not found .
*/
len = ( Key ! = NULL ) ? ( int ) _tcslen ( Key ) : 0 ;
for ( ; ; ) {
if ( ! ini_read ( LocalBuffer , INI_BUFFERSIZE , & rfp ) ) {
/* EOF without an entry so make one */
flag = cache_flush ( LocalBuffer , & cachelen , & rfp , & wfp , & mark ) ;
if ( Key ! = NULL & & Value ! = NULL ) {
if ( ! flag )
( void ) ini_write ( INI_LINETERM , & wfp ) ; /* force a new line behind the last line of the INI file */
writekey ( LocalBuffer , Key , Value , & wfp ) ;
}
return close_rename ( & rfp , & wfp , Filename , LocalBuffer ) ; /* clean up and rename */
}
sp = skipleading ( LocalBuffer ) ;
ep = _tcschr ( sp , ' = ' ) ; /* Parse out the equal sign */
if ( ep = = NULL )
ep = _tcschr ( sp , ' : ' ) ;
match = ( ep ! = NULL & & len > 0 & & ( int ) ( skiptrailing ( ep , sp ) - sp ) = = len & & _tcsnicmp ( sp , Key , len ) = = 0 ) ;
if ( ( Key ! = NULL & & match ) | | * sp = = ' [ ' )
break ; /* found the key, or found a new section */
/* copy other keys in the section */
if ( Key = = NULL ) {
( void ) ini_tell ( & rfp , & mark ) ; /* we are deleting the entire section, so update the read position */
} else {
if ( ! cache_accum ( LocalBuffer , & cachelen , INI_BUFFERSIZE ) ) {
cache_flush ( LocalBuffer , & cachelen , & rfp , & wfp , & mark ) ;
( void ) ini_read ( LocalBuffer , INI_BUFFERSIZE , & rfp ) ;
cache_accum ( LocalBuffer , & cachelen , INI_BUFFERSIZE ) ;
}
}
}
/* the key was found, or we just dropped on the next section (meaning that it
* wasn ' t found ) ; in both cases we need to write the key , but in the latter
* case , we also need to write the line starting the new section after writing
* the key
*/
flag = ( * sp = = ' [ ' ) ;
cache_flush ( LocalBuffer , & cachelen , & rfp , & wfp , & mark ) ;
if ( Key ! = NULL & & Value ! = NULL )
writekey ( LocalBuffer , Key , Value , & wfp ) ;
/* cache_flush() reset the "read pointer" to the start of the line with the
* previous key or the new section ; read it again ( because writekey ( ) destroyed
* the buffer )
*/
( void ) ini_read ( LocalBuffer , INI_BUFFERSIZE , & rfp ) ;
if ( flag ) {
/* the new section heading needs to be copied to the output file */
cache_accum ( LocalBuffer , & cachelen , INI_BUFFERSIZE ) ;
} else {
/* forget the old key line */
( void ) ini_tell ( & rfp , & mark ) ;
}
/* Copy the rest of the INI file */
while ( ini_read ( LocalBuffer , INI_BUFFERSIZE , & rfp ) ) {
if ( ! cache_accum ( LocalBuffer , & cachelen , INI_BUFFERSIZE ) ) {
cache_flush ( LocalBuffer , & cachelen , & rfp , & wfp , & mark ) ;
( void ) ini_read ( LocalBuffer , INI_BUFFERSIZE , & rfp ) ;
cache_accum ( LocalBuffer , & cachelen , INI_BUFFERSIZE ) ;
}
}
cache_flush ( LocalBuffer , & cachelen , & rfp , & wfp , & mark ) ;
return close_rename ( & rfp , & wfp , Filename , LocalBuffer ) ; /* clean up and rename */
}
/* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C" book. */
# define ABS(v) ((v) < 0 ? -(v) : (v))
static void strreverse ( TCHAR * str )
{
int i , j ;
for ( i = 0 , j = ( int ) _tcslen ( str ) - 1 ; i < j ; i + + , j - - ) {
TCHAR t = str [ i ] ;
str [ i ] = str [ j ] ;
str [ j ] = t ;
}
}
static void long2str ( long value , TCHAR * str )
{
int i = 0 ;
long sign = value ;
/* generate digits in reverse order */
do {
int n = ( int ) ( value % 10 ) ; /* get next lowest digit */
str [ i + + ] = ( TCHAR ) ( ABS ( n ) + ' 0 ' ) ; /* handle case of negative digit */
} while ( value / = 10 ) ; /* delete the lowest digit */
if ( sign < 0 )
str [ i + + ] = ' - ' ;
str [ i ] = ' \0 ' ;
strreverse ( str ) ;
}
/** ini_putl()
* \ param Section the name of the section to write the value in
* \ param Key the name of the entry to write
* \ param Value the value to write
* \ param Filename the name and full path of the . ini file to write to
*
* \ return 1 if successful , otherwise 0
*/
int ini_putl ( const TCHAR * Section , const TCHAR * Key , long Value , const TCHAR * Filename )
{
TCHAR LocalBuffer [ 32 ] ;
long2str ( Value , LocalBuffer ) ;
return ini_puts ( Section , Key , LocalBuffer , Filename ) ;
}
# if defined INI_REAL
/** ini_putf()
* \ param Section the name of the section to write the value in
* \ param Key the name of the entry to write
* \ param Value the value to write
* \ param Filename the name and full path of the . ini file to write to
*
* \ return 1 if successful , otherwise 0
*/
int ini_putf ( const TCHAR * Section , const TCHAR * Key , INI_REAL Value , const TCHAR * Filename )
{
TCHAR LocalBuffer [ 64 ] ;
ini_ftoa ( LocalBuffer , Value ) ;
return ini_puts ( Section , Key , LocalBuffer , Filename ) ;
}
# endif /* INI_REAL */
# endif /* !INI_READONLY */