mirror of https://github.com/r4sas/ExtraMirror
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
831 lines
28 KiB
831 lines
28 KiB
/* 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-2012 |
|
* |
|
* 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 45 2012-05-14 11:53:09Z thiadmer.riemersma $ |
|
*/ |
|
|
|
#if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined MININI_ANSI |
|
# 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 |
|
#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__ |
|
#define strnicmp strncasecmp |
|
#endif |
|
#endif |
|
|
|
#if !defined INI_LINETERM |
|
#define INI_LINETERM __T("\n") |
|
#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) |
|
{ |
|
register int c1, c2; |
|
|
|
while (n-- != 0 && (*s1 || *s2)) { |
|
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; |
|
} /* while */ |
|
return 0; |
|
} |
|
#endif /* PORTABLE_STRNICMP */ |
|
|
|
static TCHAR *skipleading(const TCHAR *str) |
|
{ |
|
assert(str != NULL); |
|
while (*str != '\0' && *str <= ' ') |
|
str++; |
|
return (TCHAR *)str; |
|
} |
|
|
|
static TCHAR *skiptrailing(const TCHAR *str, const TCHAR *base) |
|
{ |
|
assert(str != NULL); |
|
assert(base != NULL); |
|
while (str > base && *(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 *save_strncpy(TCHAR *dest, const TCHAR *source, size_t maxlen, enum quote_option option) |
|
{ |
|
size_t d, s; |
|
|
|
assert(maxlen>0); |
|
assert(dest <= source || dest >= source + maxlen); |
|
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++] = '\\'; |
|
} /* if */ |
|
dest[d] = source[s]; |
|
} /* for */ |
|
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]; |
|
} /* for */ |
|
dest[d] = '\0'; |
|
break; |
|
default: |
|
assert(0); |
|
} /* switch */ |
|
|
|
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 */ |
|
} /* if */ |
|
} /* for */ |
|
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 */ |
|
} /* if */ |
|
return string; |
|
} |
|
|
|
static int getkeystring(INI_FILETYPE *fp, const TCHAR *Section, const TCHAR *Key, |
|
int idxSection, int idxKey, TCHAR *Buffer, int BufferSize) |
|
{ |
|
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 postive, copy the relevant section name. |
|
*/ |
|
len = (Section != NULL) ? _tcslen(Section) : 0; |
|
if (len > 0 || idxSection >= 0) { |
|
idx = -1; |
|
do { |
|
if (!ini_read(LocalBuffer, INI_BUFFERSIZE, fp)) |
|
return 0; |
|
sp = skipleading(LocalBuffer); |
|
ep = _tcschr(sp, ']'); |
|
} while (*sp != '[' || ep == NULL || (((int)(ep-sp-1) != len || _tcsnicmp(sp+1,Section,len) != 0) && ++idx != idxSection)); |
|
if (idxSection >= 0) { |
|
if (idx == idxSection) { |
|
assert(ep != NULL); |
|
assert(*ep == ']'); |
|
*ep = '\0'; |
|
save_strncpy(Buffer, sp + 1, BufferSize, QUOTE_NONE); |
|
return 1; |
|
} /* if */ |
|
return 0; /* no more section found */ |
|
} /* if */ |
|
} /* if */ |
|
|
|
/* 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 (!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 || (((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); |
|
save_strncpy(Buffer, sp, BufferSize, QUOTE_NONE); |
|
return 1; |
|
} /* if */ |
|
return 0; /* no more key found (in this section) */ |
|
} /* if */ |
|
|
|
/* Copy up to BufferSize chars to buffer */ |
|
assert(ep != NULL); |
|
assert(*ep == '=' || *ep == ':'); |
|
sp = skipleading(ep + 1); |
|
sp = cleanstring(sp, "es); /* Remove a trailing comment */ |
|
save_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); |
|
(void)ini_close(&fp); |
|
} /* if */ |
|
if (!ok) |
|
save_strncpy(Buffer, DefValue, BufferSize, QUOTE_NONE); |
|
return _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(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 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 |
|
* |
|
* 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]; |
|
int ret; |
|
|
|
ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); |
|
LocalBuffer[0] = (TCHAR)toupper(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); |
|
(void)ini_close(&fp); |
|
} /* if */ |
|
if (!ok) |
|
*Buffer = '\0'; |
|
return _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); |
|
(void)ini_close(&fp); |
|
} /* if */ |
|
if (!ok) |
|
*Buffer = '\0'; |
|
return _tcslen(Buffer); |
|
} |
|
|
|
|
|
#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 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, const void *UserData, const TCHAR *Filename) |
|
{ |
|
TCHAR LocalBuffer[INI_BUFFERSIZE]; |
|
TCHAR *sp, *ep; |
|
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 = _tcslen(LocalBuffer) + 1; |
|
for ( ;; ) { |
|
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 = _tcschr(sp, ']'); |
|
if (*sp == '[' && ep != NULL) { |
|
*ep = '\0'; |
|
save_strncpy(LocalBuffer, sp + 1, INI_BUFFERSIZE, QUOTE_NONE); |
|
lenSec = _tcslen(LocalBuffer) + 1; |
|
continue; |
|
} /* if */ |
|
/* 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); |
|
save_strncpy(LocalBuffer + lenSec, sp, INI_BUFFERSIZE - lenSec, QUOTE_NONE); |
|
lenKey = _tcslen(LocalBuffer + lenSec) + 1; |
|
/* clean up the value */ |
|
sp = skipleading(ep); |
|
sp = cleanstring(sp, "es); /* Remove a trailing comment */ |
|
save_strncpy(LocalBuffer + lenSec + lenKey, sp, INI_BUFFERSIZE - lenSec - lenKey, quotes); |
|
/* call the callback */ |
|
if (!Callback(LocalBuffer, LocalBuffer + lenSec, LocalBuffer + lenSec + lenKey, UserData)) |
|
break; |
|
} /* for */ |
|
|
|
(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; |
|
|
|
save_strncpy(dest, source, maxlength, QUOTE_NONE); |
|
p = _tcsrchr(dest, '\0'); |
|
assert(p != NULL); |
|
*(p - 1) = '~'; |
|
} |
|
|
|
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) |
|
{ |
|
TCHAR *p; |
|
|
|
if (Section != NULL && _tcslen(Section) > 0) { |
|
LocalBuffer[0] = '['; |
|
save_strncpy(LocalBuffer + 1, Section, INI_BUFFERSIZE - 4, QUOTE_NONE); /* -1 for '[', -1 for ']', -2 for '\r\n' */ |
|
p = _tcsrchr(LocalBuffer, '\0'); |
|
assert(p != NULL); |
|
*p++ = ']'; |
|
_tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */ |
|
(void)ini_write(LocalBuffer, fp); |
|
} /* if */ |
|
} |
|
|
|
static void writekey(TCHAR *LocalBuffer, const TCHAR *Key, const TCHAR *Value, INI_FILETYPE *fp) |
|
{ |
|
TCHAR *p; |
|
enum quote_option option = check_enquote(Value); |
|
save_strncpy(LocalBuffer, Key, INI_BUFFERSIZE - 3, QUOTE_NONE); /* -1 for '=', -2 for '\r\n' */ |
|
p = _tcsrchr(LocalBuffer, '\0'); |
|
assert(p != NULL); |
|
*p++ = '='; |
|
save_strncpy(p, Value, INI_BUFFERSIZE - (p - LocalBuffer) - 2, option); /* -2 for '\r\n' */ |
|
p = _tcsrchr(LocalBuffer, '\0'); |
|
assert(p != NULL); |
|
_tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */ |
|
(void)ini_write(LocalBuffer, fp); |
|
} |
|
|
|
static int cache_accum(const TCHAR *string, int *size, int max) |
|
{ |
|
int len = _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 pos = 0; |
|
|
|
(void)ini_seek(rfp, mark); |
|
assert(buffer != NULL); |
|
buffer[0] = '\0'; |
|
assert(size != NULL); |
|
while (pos < *size) { |
|
(void)ini_read(buffer + pos, INI_BUFFERSIZE - pos, rfp); |
|
pos += _tcslen(buffer + pos); |
|
assert(pos <= *size); |
|
} /* while */ |
|
if (buffer[0] != '\0') |
|
(void)ini_write(buffer, wfp); |
|
(void)ini_tell(rfp, mark); /* update mark */ |
|
*size = 0; |
|
/* return whether the buffer ended with a line termination */ |
|
return (_tcscmp(buffer + pos - _tcslen(INI_LINETERM), 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_remove(filename); |
|
(void)ini_tempname(buffer, filename, INI_BUFFERSIZE); |
|
(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; |
|
TCHAR *sp, *ep; |
|
TCHAR LocalBuffer[INI_BUFFERSIZE]; |
|
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); |
|
} /* if */ |
|
return 1; |
|
} /* if */ |
|
|
|
/* If parameters Key and Value are valid (so this is not an "erase" request) |
|
* and the setting already exists and it already has the correct value, do |
|
* nothing. This early bail-out avoids rewriting the INI file for no reason. |
|
*/ |
|
if (Key != NULL && Value != NULL) { |
|
(void)ini_tell(&rfp, &mark); |
|
match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer)); |
|
if (match && _tcscmp(LocalBuffer,Value) == 0) { |
|
(void)ini_close(&rfp); |
|
return 1; |
|
} /* if */ |
|
/* key not found, or different value -> proceed (but rewind the input file first) */ |
|
(void)ini_seek(&rfp, &mark); |
|
} /* if */ |
|
|
|
/* Get a temporary file name to copy to. Use the existing name, but with |
|
* the last character set to a '~'. |
|
*/ |
|
ini_tempname(LocalBuffer, Filename, INI_BUFFERSIZE); |
|
if (!ini_openwrite(LocalBuffer, &wfp)) { |
|
(void)ini_close(&rfp); |
|
return 0; |
|
} /* if */ |
|
(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) ? _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); |
|
} /* if */ |
|
return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ |
|
} /* if */ |
|
/* 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 |
|
*/ |
|
sp = skipleading(LocalBuffer); |
|
ep = _tcschr(sp, ']'); |
|
match = (*sp == '[' && ep != NULL && (int)(ep-sp-1) == len && _tcsnicmp(sp + 1,Section,len) == 0); |
|
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); |
|
} /* if */ |
|
} /* if */ |
|
} while (!match); |
|
} /* if */ |
|
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); |
|
} /* if */ |
|
|
|
/* 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) ? _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); |
|
} /* if */ |
|
return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ |
|
} /* if */ |
|
sp = skipleading(LocalBuffer); |
|
ep = _tcschr(sp, '='); /* Parse out the equal sign */ |
|
if (ep == NULL) |
|
ep = _tcschr(sp, ':'); |
|
match = (ep != NULL && (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); |
|
} /* if */ |
|
} /* if */ |
|
} /* for */ |
|
/* 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); |
|
} /* if */ |
|
/* 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); |
|
} /* if */ |
|
} /* while */ |
|
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) |
|
{ |
|
TCHAR t; |
|
int i, j; |
|
|
|
for (i = 0, j = _tcslen(str) - 1; i < j; i++, j--) { |
|
t = str[i]; |
|
str[i] = str[j]; |
|
str[j] = t; |
|
} /* for */ |
|
} |
|
|
|
static void long2str(long value, TCHAR *str) |
|
{ |
|
int i = 0; |
|
long sign = value; |
|
int n; |
|
|
|
/* generate digits in reverse order */ |
|
do { |
|
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 */
|
|
|