// inifile.c
#include "common.h"
// WARNING: INI SECTION NAMES ARE CASE SENSITIVE, BUT KEY NAMES ARE NOT!
// internal definitions
#define DICTIONARY_PAGE_SIZE 128
#define STRING_MAXSIZE 65536
#define INI_SECTION_SEPARATOR ']'
#define INI_INVALID_KEY (wchar_t *) 0x7fffffff
#define WSIZEOF(a) (sizeof (a) / sizeof (wchar_t))
// dictionary structure definitions
typedef struct dictionary_entry_s
{
wchar_t *key; // key string (mallocated)
unsigned long hash; // key hash value
wchar_t *value; // value string (mallocated)
} dictionary_entry_t;
typedef struct dictionary_s
{
int size; // storage size
dictionary_entry_t *entries; // array of entries (mallocated)
int entry_count; // number of entries in dictionary
} dictionary_t;
// dictionary function prototypes
static dictionary_t *Dictionary_CreateDictionary (int size);
static void Dictionary_DestroyDictionary (dictionary_t *dictionary);
static dictionary_entry_t *Dictionary_GetKey (dictionary_t *dictionary, wchar_t *key);
static wchar_t *Dictionary_ReadKey (dictionary_t *dictionary, wchar_t *key, wchar_t *default_value);
static void Dictionary_WriteKey (dictionary_t *dictionary, wchar_t *key, wchar_t *value);
static void Dictionary_DeleteKey (dictionary_t *dictionary, wchar_t *key);
static unsigned long Dictionary_ComputeHashValueForKey (wchar_t *key);
// ini file function prototypes
void *INIFile_NewINIFile (void);
int INIFile_GetNumberOfSections (void *ini_data);
wchar_t *INIFile_GetSectionName (void *ini_data, int section_index);
int INIFile_GetNumberOfEntries (void *ini_data, wchar_t *section);
wchar_t *INIFile_GetEntryName (void *ini_data, wchar_t *section, int entry_index);
unsigned char INIFile_ReadEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char default_value);
long INIFile_ReadEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long default_value);
unsigned long INIFile_ReadEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long default_value);
double INIFile_ReadEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double default_value);
wchar_t *INIFile_ReadEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *default_value);
void INIFile_WriteEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char value);
void INIFile_WriteEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long value);
void INIFile_WriteEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long value);
void INIFile_WriteEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double value);
void INIFile_WriteEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *value);
void INIFile_DeleteEntry (void *ini_data, wchar_t *section, wchar_t *entry);
void INIFile_DeleteSection (void *ini_data, wchar_t *section);
void *INIFile_LoadINIFile (const wchar_t *filename);
unsigned char INIFile_SaveINIFile (const wchar_t *filename, void *ini_data);
void INIFile_FreeINIFile (void *ini_data);
// internal variables
static wchar_t *INIFile_default_section = L"__default"; // MUST BE A READ-WRITE VARIABLE AND NOT A DEFINE
static dictionary_t *Dictionary_CreateDictionary (int size)
{
// this function allocates a new dictionary object of given size and returns it. If you do
// not know in advance (roughly) the number of entries in the dictionary, give size = 0.
dictionary_t *dictionary;
// if no size was specified, allocate space for one page
if (size < DICTIONARY_PAGE_SIZE)
size = DICTIONARY_PAGE_SIZE;
// allocate one instance of the dictionary and blank it out
dictionary = (dictionary_t *) SAFE_malloc (1, sizeof (dictionary_t), true);
dictionary->size = size; // dictionary can currently hold size entries
// allocate space for the dictionary entries and zero all the crap out
dictionary->entries = (dictionary_entry_t *) SAFE_malloc (size, sizeof (dictionary_entry_t), true);
return (dictionary); // finished, return a pointer to the dictionary object
}
static void Dictionary_DestroyDictionary (dictionary_t *dictionary)
{
// frees a dictionary object and all memory associated to it.
int entry_index;
if (dictionary == NULL)
return; // consistency check
for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
{
SAFE_free ((void **) &dictionary->entries[entry_index].key); // free all dictionary keys
SAFE_free ((void **) &dictionary->entries[entry_index].value); // free all dictionary values
}
SAFE_free ((void **) &dictionary->entries); // free the entries array
SAFE_free ((void **) &dictionary); // and finally, free the dictionary itself
return; // finished
}
static dictionary_entry_t *Dictionary_GetKey (dictionary_t *dictionary, wchar_t *key)
{
// this function locates a key in a dictionary and returns a pointer to it, or a NULL
// pointer if no such key can be found in dictionary. The returned pointer points to data
// internal to the dictionary object, do not try to free or modify it.
unsigned long hash;
register int entry_index;
hash = Dictionary_ComputeHashValueForKey (key); // get the hash value for key
// cycle through all entries in the dictionary
for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
{
if (dictionary->entries[entry_index].key[0] == 0)
continue; // skip empty slots
// compare hash AND string, to avoid hash collisions
if ((hash == dictionary->entries[entry_index].hash) && (wcscmp (key, dictionary->entries[entry_index].key) == 0))
return (&dictionary->entries[entry_index]); // return a pointer to the key if we find it
}
return (NULL); // else return a NULL pointer
}
static wchar_t *Dictionary_ReadKey (dictionary_t *dictionary, wchar_t *key, wchar_t *default_value)
{
// this function locates a key in a dictionary and returns a pointer to its value, or the
// passed 'def' pointer if no such key can be found in dictionary. The returned char pointer
// points to data internal to the dictionary object, do not try to free or modify it.
dictionary_entry_t *element;
element = Dictionary_GetKey (dictionary, key); // query the dictionary for the key
if (element != NULL)
return (element->value); // if we found a valid key, return the value associed to it
return (default_value); // else return the default value
}
static void Dictionary_WriteKey (dictionary_t *dictionary, wchar_t *key, wchar_t *value)
{
// sets a value in a dictionary. If the given key is found in the dictionary, the associated
// value is replaced by the provided one. If the key cannot be found in the dictionary, it
// is added to it.
unsigned long hash;
register int entry_index;
size_t bufsize;
hash = Dictionary_ComputeHashValueForKey (key); // compute hash for this key
// for each entry in the dictionary...
for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
{
if (dictionary->entries[entry_index].key[0] == 0)
continue; // skip empty slots
// does that key have the same hash value AND the same name ?
if ((hash == dictionary->entries[entry_index].hash) && (wcscmp (key, dictionary->entries[entry_index].key) == 0))
{
// we've found the right key: modify its value and return
if (value == NULL)
value = L""; // if a null value was specified, use an empty string
bufsize = wcslen (value) + 1;
dictionary->entries[entry_index].value = (wchar_t *) SAFE_realloc (dictionary->entries[entry_index].value, 0, bufsize, sizeof (wchar_t), false);
wcscpy_s (dictionary->entries[entry_index].value, bufsize, value); // copy the value string
return; // value has been modified: return
}
}
// here either the dictionary was empty, or we couldn't find a similar value: add a new one
// cycle through all entries in the dictionary...
for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
if (dictionary->entries[entry_index].key[0] == 0)
break; // and stop at the first empty one we find
// no empty entry found, see if dictionary needs to grow or if there's still room left
if (entry_index == dictionary->size)
{
// mallocate some more space for the dictionary and zero all that crap out
dictionary->entries = (dictionary_entry_t *) SAFE_realloc (dictionary->entries, dictionary->size, dictionary->size + DICTIONARY_PAGE_SIZE, sizeof (dictionary_entry_t), false); // alloc 1 more page
dictionary->size += DICTIONARY_PAGE_SIZE; // increase dictionary size
}
// copy the new key
bufsize = wcslen (key) + 1;
dictionary->entries[entry_index].key = (wchar_t *) SAFE_malloc (bufsize, sizeof (wchar_t), false);
wcscpy_s (dictionary->entries[entry_index].key, bufsize, key); // copy the key string...
dictionary->entries[entry_index].hash = hash; // ...and store the hash value
// copy the new value
if (value == NULL)
value = L""; // if a null value was specified, use an empty string
bufsize = wcslen (value) + 1;
dictionary->entries[entry_index].value = (wchar_t *) SAFE_malloc (bufsize, sizeof (wchar_t), false);
wcscpy_s (dictionary->entries[entry_index].value, bufsize, value); // copy the value string
dictionary->entry_count++; // there is one more entry in the dictionary now
return;
}
static void Dictionary_DeleteKey (dictionary_t *dictionary, wchar_t *key)
{
// this function deletes a key in a dictionary. Nothing is done if the key cannot be found.
unsigned long hash;
register int entry_index;
hash = Dictionary_ComputeHashValueForKey (key); // get the hash value for key
// cycle through all entries in the dictionary...
for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
{
if (dictionary->entries[entry_index].key[0] == 0)
continue; // skip empty slots
// compare hash AND string, to avoid hash collisions
if ((hash == dictionary->entries[entry_index].hash) && (wcscmp (key, dictionary->entries[entry_index].key) == 0))
break; // break as soon as we've found the key we want
}
if (entry_index == dictionary->entry_count)
return; // if key was not found, just return
// free its buffers, then clear the key, the hash and its value data
SAFE_free ((void **) &dictionary->entries[entry_index].key);
SAFE_free ((void **) &dictionary->entries[entry_index].value);
memset (&dictionary->entries[entry_index], 0, sizeof (dictionary_entry_t));
dictionary->entry_count--; // there is one entry less in the dictionary now
return;
}
static unsigned long Dictionary_ComputeHashValueForKey (wchar_t *key)
{
// Compute the hash key for a string. This hash function has been taken from an article in
// Dr Dobbs Journal. This is normally a collision-free function, distributing keys evenly.
// The key is stored anyway in the struct so that collision can be avoided by comparing the
// key itself in last resort. THIS FUNCTION IS TO BE USED INTERNALLY ONLY!
register unsigned long hash;
int length;
int char_index;
hash = 0;
length = (int) wcslen (key);
// for each character of the string...
for (char_index = 0; char_index < length; char_index++)
{
hash += (unsigned long) key[char_index]; // take it in account and compute the hash value
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3); // finalize hashing (what the hell does it do, I have no clue)
hash ^= (hash >> 11);
hash += (hash << 15);
return (hash); // and return the hash value
}
void *INIFile_NewINIFile (void)
{
// allocates a dictionary for a new INI file. Mostly a helper function.
return ((void *) Dictionary_CreateDictionary (0));
}
int INIFile_GetNumberOfSections (void *ini_data)
{
// this function returns the number of sections found in a dictionary. The test to recognize
// sections is done on the string stored in the dictionary: a section name is given as
// "section" whereas a key is stored as "section]key", thus the test looks for entries that
// do NOT contain a ']'. This clearly fails in the case a section name contains a ']',
// but this should simply be avoided.
int section_count;
int entry_index;
dictionary_t *dictionary;
if (ini_data == NULL)
return (0); // consistency check
dictionary = (dictionary_t *) ini_data;
section_count = 0; // no sections found yet
// for each entry in the dictionary...
for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
{
if (dictionary->entries[entry_index].key[0] == 0)
continue; // skip empty slots
if (wcschr (dictionary->entries[entry_index].key, INI_SECTION_SEPARATOR) == NULL)
section_count++; // if this entry has NO section separator, then it's a section name
}
return (section_count); // return the section count we found
}
wchar_t *INIFile_GetSectionName (void *ini_data, int section_index)
{
// this function locates the n-th section in a dictionary and returns its name as a pointer
// to a string allocated inside the dictionary. Do not free or modify the returned string!!
// This function returns NULL in case of error.
int sections_found;
int entry_index;
dictionary_t *dictionary;
if ((ini_data == NULL) || (section_index < 0))
return (NULL); // consistency check
dictionary = (dictionary_t *) ini_data;
sections_found = 0; // no sections found yet
// for each entry in the dictionary...
for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
{
if (dictionary->entries[entry_index].key[0] == 0)
continue; // skip empty slots
// is this entry a section name ?
if (wcschr (dictionary->entries[entry_index].key, INI_SECTION_SEPARATOR) == NULL)
{
// is it the section we want ?
if (sections_found == section_index)
return (dictionary->entries[entry_index].key); // then return its name
sections_found++; // we found one section more
}
}
return (INIFile_default_section); // section was not found, return the default section name
}
int INIFile_GetNumberOfEntries (void *ini_data, wchar_t *section)
{
// this function returns the number of entries found in a dictionary within a given section.
// The test to recognize section entries is done on the string stored in the dictionary: a
// section name is given as "section" whereas a key is stored as "section]key", thus the test
// looks for all entries that begin with "section]".
int entry_count;
int entry_index;
int sectionname_length;
dictionary_t *dictionary;
if (ini_data == NULL)
return (0); // consistency check
dictionary = (dictionary_t *) ini_data;
sectionname_length = wcslen (section);
entry_count = 0; // no entries found yet
// for each entry in the dictionary...
for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
{
if (dictionary->entries[entry_index].key[0] == 0)
continue; // skip empty slots
if ((wcsncmp (dictionary->entries[entry_index].key, section, sectionname_length) == 0)
&& (dictionary->entries[entry_index].key[sectionname_length] == INI_SECTION_SEPARATOR))
entry_count++; // if this entry has section name + separator, then it's one of those we want
}
return (entry_count); // return the number of entries we found in the given section
}
wchar_t *INIFile_GetEntryName (void *ini_data, wchar_t *section, int entry_index)
{
// this function queries a dictionary for a key. A key as read from an ini file is given as
// "section]key". If the key cannot be found, NULL is returned. The returned char pointer
// points to a string allocated in the dictionary, do not free or modify it.
int i;
int length;
int found_keys;
dictionary_t *dictionary;
dictionary_entry_t *entry;
if (ini_data == NULL)
return (NULL); // consistency check
if (section == NULL)
section = INIFile_default_section; // if no section was provided, use empty section name
dictionary = (dictionary_t *) ini_data; // quick access to dictionary
length = wcslen (section);
// cycle through all remaining entries
found_keys = 0;
for (i = 0; i < dictionary->entry_count; i++)
{
entry = &dictionary->entries[i]; // quick access to each entry
if ((wcsncmp (entry->key, section, length) != 0) || (entry->key[length] != INI_SECTION_SEPARATOR))
continue; // if this key doesn't belong to the wanted section, skip it
// this key belongs to the wanted section. Is it the key we want ?
if (found_keys == entry_index)
return (&entry->key[length + 1]); // if so, return its name
found_keys++; // it wasn't the key we want, so increase counter and proceed to the next one
}
return (NULL); // if not found, return NULL
}
unsigned char INIFile_ReadEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char default_value)
{
// this function queries a dictionary for a key. A key as read from an ini file is given as
// "section]key". If the key cannot be found, the notfound value is returned. A true boolean
// is found if a string starting with either 'y', 'Y', 't', 'T' or '1' is matched. A false
// boolean is found if a string starting with 'n', 'N', 'f', 'F' or '0' is matched.
wchar_t *value;
if (ini_data == NULL)
return (default_value); // consistency check
// get the value as a string first
value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
if (value == INI_INVALID_KEY)
return (default_value); // if the entry could not be read, return the default value
// decide of the boolean's value by looking at the first character
if ((toupper (value[0]) == 'T') || (toupper (value[0]) == 'Y') || (value[0] == '1'))
return (1); // boolean value is TRUE
else if ((toupper (value[0]) == 'F') || (toupper (value[0]) == 'N') || (value[0] == '0'))
return (0); // boolean value is FALSE
return (default_value); // boolean value is undefined, return default value
}
long INIFile_ReadEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long default_value)
{
// this function queries a dictionary for a key. A key as read from an ini file is given as
// "section]key". If the key cannot be found, the notfound value is returned.
wchar_t *value;
if (ini_data == NULL)
return (default_value); // consistency check
// get the value as a string first
value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
if (value == INI_INVALID_KEY)
return (default_value); // if the entry could not be read, return the default value
return (_wtol (value)); // else convert the value to a long integer and return it
}
unsigned long INIFile_ReadEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long default_value)
{
// this function queries a dictionary for a key. A key as read from an ini file is given as
// "section]key". If the key cannot be found, the notfound value is returned.
wchar_t *value;
if (ini_data == NULL)
return (default_value); // consistency check
// get the value as a string first
value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
if (value == INI_INVALID_KEY)
return (default_value); // if the entry could not be read, return the default value
return (wcstoul (value, NULL, 10)); // else convert the value to a long integer and return it
}
double INIFile_ReadEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double default_value)
{
// this function queries a dictionary for a key. A key as read from an ini file is given as
// "section]key". If the key cannot be found, the notfound value is returned.
wchar_t *value;
if (ini_data == NULL)
return (default_value); // consistency check
// get the value as a string first
value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
if (value == INI_INVALID_KEY)
return (default_value); // if the entry could not be read, return the default value
return (_wtof (value)); // else convert the value to a double precision number and return it
}
wchar_t *INIFile_ReadEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *default_value)
{
// this function queries a dictionary for a key. A key as read from an ini file is given as
// "section]key". If the key cannot be found, the pointer passed as 'def' is returned. The
// returned char pointer points to a string allocated in the dictionary, do not free or
// modify it.
size_t length;
size_t i;
size_t keysize;
wchar_t *key;
wchar_t *value;
if (ini_data == NULL)
return (default_value); // consistency check
if (section == NULL)
section = INIFile_default_section; // if no section was provided, use empty section name
// allocate some space for the dictionary key name
keysize = wcslen (section) + 1 + wcslen (entry) + 1;
key = (wchar_t *) SAFE_malloc (keysize, sizeof (wchar_t), false);
// if we were given an entry, build a key as section]entry
if (entry != NULL)
{
swprintf_s (key, keysize, L"%s%c%s", section, INI_SECTION_SEPARATOR, entry); // compose the key
i = wcslen (key); // get the key string length
if (i < keysize)
length = i;
else
length = keysize - 1; // clamp it to a max value
// for each character in the string after the section separator...
for (i = wcslen (section) + 1; i < length; i++)
key[i] = towlower (key[i]); // convert it to lowercase
key[i] = 0; // terminate the string
}
// else it must be a section name
else
wcscpy_s (key, keysize, section); // copy the name into the key
value = Dictionary_ReadKey ((dictionary_t *) ini_data, key, default_value); // query the dictionary...
SAFE_free ((void **) &key); // free the key name, we no longer need it
return (value); // ...and return the value
}
void INIFile_WriteEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char value)
{
// sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
// modified to contain the provided value. If it cannot be found, it is created.
// according the boolean's value, write the equivalent string
if (value)
INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, L"true"); // write the new entry
else
INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, L"false"); // write the new entry
return; // finished
}
void INIFile_WriteEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long value)
{
// sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
// modified to contain the provided value. If it cannot be found, it is created.
wchar_t *number_as_string;
// allocate some space for the number's string representation
number_as_string = (wchar_t *) SAFE_malloc (32, sizeof (wchar_t), false);
// build the long integer value equivalent string (use printf facility)
swprintf_s (number_as_string, 32, L"%ld", value);
INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, number_as_string); // write the new entry
SAFE_free ((void **) &number_as_string); // free the number's string representation, we no longer need it
return; // finished
}
void INIFile_WriteEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long value)
{
// sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
// modified to contain the provided value. If it cannot be found, it is created.
wchar_t *number_as_string;
// allocate some space for the number's string representation
number_as_string = (wchar_t *) SAFE_malloc (32, sizeof (wchar_t), false);
// build the long integer value equivalent string (use printf facility)
swprintf_s (number_as_string, 32, L"%lu", value);
INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, number_as_string); // write the new entry
SAFE_free ((void **) &number_as_string); // free the number's string representation, we no longer need it
return; // finished
}
void INIFile_WriteEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double value)
{
// sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
// modified to contain the provided value. If it cannot be found, it is created.
wchar_t *number_as_string;
// allocate some space for the number's string representation
number_as_string = (wchar_t *) SAFE_malloc (64, sizeof (wchar_t), false);
// build the long integer value equivalent string (use printf facility)
swprintf_s (number_as_string, 64, L"%g", value);
INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, number_as_string); // write the new entry
SAFE_free ((void **) &number_as_string); // free the number's string representation, we no longer need it
return; // finished
}
void INIFile_WriteEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *value)
{
// sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
// modified to contain the provided value. If it cannot be found, it is created.
size_t length;
size_t i;
size_t keysize;
wchar_t *key;
if (ini_data == NULL)
return; // consistency check
if (section == NULL)
section = INIFile_default_section; // if no section was provided, use empty section name
// allocate some space for the dictionary key name
keysize = wcslen (section) + 1 + (entry != NULL ? wcslen (entry) + 1 : 0);
key = (wchar_t *) SAFE_malloc (keysize, sizeof (wchar_t), false);
// if we were given an entry, build a key as section#entry
if (entry != NULL)
{
Dictionary_WriteKey ((dictionary_t *) ini_data, section, NULL); // create the section if it doesn't exist
swprintf_s (key, keysize, L"%s%c%s", section, INI_SECTION_SEPARATOR, entry); // compose the key
i = wcslen (key); // get the key string length
if (i < keysize)
length = i;
else
length = keysize - 1; // clamp it to a max value
// for each character in the string after the section separator...
for (i = wcslen (section) + 1; i < length; i++)
key[i] = towlower (key[i]); // convert it to lowercase
key[i] = 0; // terminate the string
}
// else it must be a section name
else
wcscpy_s (key, keysize, section); // copy the name into the key
Dictionary_WriteKey ((dictionary_t *) ini_data, key, value); // write the new key in the dictionary
SAFE_free ((void **) &key); // free the key name, we no longer need it
return; // finished
}
void INIFile_DeleteEntry (void *ini_data, wchar_t *section, wchar_t *entry)
{
// deletes an entry in the dictionary
size_t length;
size_t i;
size_t keysize;
wchar_t *key;
if (ini_data == NULL)
return; // consistency check
if (section == NULL)
section = INIFile_default_section; // if no section was provided, use empty section name
// allocate some space for the dictionary key name
keysize = wcslen (section) + 1 + wcslen (entry) + 1;
key = (wchar_t *) SAFE_malloc (keysize, sizeof (wchar_t), false);
// if we were given an entry, build a key as section#entry
if (entry != NULL)
{
swprintf_s (key, keysize, L"%s%c%s", section, INI_SECTION_SEPARATOR, entry); // compose the key
i = wcslen (key); // get the key string length
if (i < keysize)
length = i;
else
length = keysize - 1; // clamp it to a max value
// for each character in the string after the section separator...
for (i = wcslen (section) + 1; i < length; i++)
key[i] = towlower (key[i]); // convert it to lowercase
key[i] = 0; // terminate the string
}
// else it must be a section name
else
wcscpy_s (key, keysize, section); // copy the name into the key
Dictionary_DeleteKey ((dictionary_t *) ini_data, key);
SAFE_free ((void **) &key); // free the key name, we no longer need it
return;
}
void INIFile_DeleteSection (void *ini_data, wchar_t *section)
{
// deletes a whole INI section in the dictionary
int length;
int i;
dictionary_t *dictionary;
dictionary_entry_t *entry;
if (ini_data == NULL)
return; // consistency check
if (section == NULL)
section = INIFile_default_section; // if no section was provided, use empty section name
dictionary = (dictionary_t *) ini_data;
length = (int) wcslen (section); // get the section string length
// for each entry in the dictionary...
for (i = 0; i < dictionary->entry_count; i++)
{
entry = &dictionary->entries[i]; // quick access to each entry
if (entry->key[0] == 0)
continue; // skip empty slots
// does this entry belong to the section we want ?
if ((wcsncmp (entry->key, section, length) == 0) && (entry->key[length] == INI_SECTION_SEPARATOR))
Dictionary_DeleteKey (dictionary, entry->key); // yes, delete it
}
Dictionary_DeleteKey (dictionary, section); // and finally delete the section name itself
return;
}
void *INIFile_LoadINIFile (const wchar_t *filename)
{
// this is the parser for ini files. This function is called, providing the name of the file
// to be read. It returns a dictionary object that should not be accessed directly, but
// through accessor functions instead.
wchar_t *line_buffer;
FILE *fp;
int fieldstart;
int fieldstop;
int length;
int i;
dictionary_t *dictionary;
wchar_t *current_section;
wchar_t *current_entry;
wchar_t *current_value;
// try to open the INI file in ASCII read-only mode
fp = _wfsopen (filename, L"r, ccs=UNICODE", _SH_DENYNO);
if (fp == NULL)
return (NULL); // cancel if file not found
dictionary = Dictionary_CreateDictionary (0);
// allocate some space for the current section, entry and values
current_section = (wchar_t *) SAFE_malloc (STRING_MAXSIZE, sizeof (wchar_t), false);
current_entry = (wchar_t *) SAFE_malloc (STRING_MAXSIZE, sizeof (wchar_t), false);
current_value = (wchar_t *) SAFE_malloc (STRING_MAXSIZE, sizeof (wchar_t), false);
// set the default section for orphaned entries and add it to the dictionary
wcscpy_s (current_section, STRING_MAXSIZE, INIFile_default_section);
INIFile_WriteEntryAsString (dictionary, current_section, NULL, NULL);
// allocate some space for a line buffer
line_buffer = (wchar_t *) SAFE_malloc (STRING_MAXSIZE + STRING_MAXSIZE, sizeof (wchar_t), false);
// read line per line...
while (fgetws (line_buffer, STRING_MAXSIZE + STRING_MAXSIZE, fp) != NULL)
{
length = (int) wcslen (line_buffer); // get line length
while ((length > 0) && ((line_buffer[length - 1] == '\n') || (line_buffer[length - 1] == '\r')))
length--; // discard trailing newlines
fieldstart = 0; // let's now strip leading blanks
while ((fieldstart < length) && iswspace (line_buffer[fieldstart]))
fieldstart++; // ignore any tabs or spaces, going forward from the start
fieldstop = length - 1; // let's now strip trailing blanks
while ((fieldstop >= 0) && iswspace (line_buffer[fieldstop]))
fieldstop--; // ignore any tabs or spaces, going backwards from the end
for (i = fieldstart; i <= fieldstop; i++)
line_buffer[i - fieldstart] = line_buffer[i]; // recopy line buffer without the spaces
line_buffer[i - fieldstart] = 0; // and terminate the string
if ((line_buffer[0] == ';') || (line_buffer[0] == '#') || (line_buffer[0] == 0))
continue; // skip comment lines
// is it a valid section name ?
if (swscanf_s (line_buffer, L"[%[^]]", current_section, STRING_MAXSIZE) == 1)
{
length = (int) wcslen (current_section); // get the section string length
fieldstart = 0; // let's now strip leading blanks
while ((fieldstart < length) && iswspace (current_section[fieldstart]))
fieldstart++; // ignore any tabs or spaces, going forward from the start
fieldstop = length - 1; // let's now strip trailing blanks
while ((fieldstop >= 0) && iswspace (current_section[fieldstop]))
fieldstop--; // ignore any tabs or spaces, going backwards from the end
for (i = fieldstart; i <= fieldstop; i++)
current_section[i - fieldstart] = current_section[i]; // recopy section name w/out spaces
current_section[i - fieldstart] = 0; // and terminate the string
INIFile_WriteEntryAsString (dictionary, current_section, NULL, NULL); // add to dictionary
}
// else is it a valid entry/value pair whose entry is enclosed between quotes?
else if ((swscanf_s (line_buffer, L"\"%[^\"]\" = %[^;#]", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2)
|| (swscanf_s (line_buffer, L"'%[^']' = %[^;#]", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2))
{
// when entry is enclosed between quotes, DO NOT strip the blanks
length = (int) wcslen (current_value); // get the value string length
fieldstart = 0; // let's now strip leading blanks
while ((fieldstart < length) && iswspace (current_value[fieldstart]))
fieldstart++; // ignore any tabs or spaces, going forward from the start
fieldstop = length - 1; // let's now strip trailing blanks
while ((fieldstop >= 0) && iswspace (current_value[fieldstop]))
fieldstop--; // ignore any tabs or spaces, going backwards from the end
for (i = fieldstart; i <= fieldstop; i++)
current_value[i - fieldstart] = current_value[i]; // recopy entry name w/out spaces
current_value[i - fieldstart] = 0; // and terminate the string
// sscanf cannot handle "" or '' as empty value, this is done here
if ((wcscmp (current_value, L"\"\"") == 0) || (wcscmp (current_value, L"''") == 0))
current_value[0] = 0; // empty string
INIFile_WriteEntryAsString (dictionary, current_section, current_entry, current_value); // add to dictionary
}
// else is it a valid entry/value pair whose entry AND value are enclosed between quotes?
else if ((swscanf_s (line_buffer, L"\"%[^\"]\" = \"%[^\"]\"", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2)
|| (swscanf_s (line_buffer, L"'%[^']' = \"%[^\"]\"", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2)
|| (swscanf_s (line_buffer, L"\"%[^\"]\" = '%[^']'", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2)
|| (swscanf_s (line_buffer, L"'%[^']' = '%[^']'", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2))
{
// when entry is enclosed between quotes, DO NOT strip the blanks
// when value is enclosed between quotes, DO NOT strip the blanks
// sscanf cannot handle "" or '' as empty value, this is done here
if ((wcscmp (current_value, L"\"\"") == 0) || (wcscmp (current_value, L"''") == 0))
current_value[0] = 0; // empty string
INIFile_WriteEntryAsString (dictionary, current_section, current_entry, current_value); // add to dictionary
}
// else is it a valid entry/value pair whose value is enclosed between quotes?
else if ((swscanf_s (line_buffer, L"%[^=] = \"%[^\"]\"", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2)
|| (swscanf_s (line_buffer, L"%[^=] = '%[^']'", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2))
{
length = (int) wcslen (current_entry); // get the entry string length
fieldstart = 0; // let's now strip leading blanks
while ((fieldstart < length) && iswspace (current_entry[fieldstart]))
fieldstart++; // ignore any tabs or spaces, going forward from the start
fieldstop = length - 1; // let's now strip trailing blanks
while ((fieldstop >= 0) && iswspace (current_entry[fieldstop]))
fieldstop--; // ignore any tabs or spaces, going backwards from the end
for (i = fieldstart; i <= fieldstop; i++)
current_entry[i - fieldstart] = towlower (current_entry[i]); // recopy entry name w/out spaces
current_entry[i - fieldstart] = 0; // and terminate the string
// when value is enclosed between quotes, DO NOT strip the blanks
// sscanf cannot handle "" or '' as empty value, this is done here
if ((wcscmp (current_value, L"\"\"") == 0) || (wcscmp (current_value, L"''") == 0))
current_value[0] = 0; // empty string
INIFile_WriteEntryAsString (dictionary, current_section, current_entry, current_value); // add to dictionary
}
// else is it a valid entry/value pair without quotes ?
else if (swscanf_s (line_buffer, L"%[^=] = %[^;#]", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2)
{
length = (int) wcslen (current_entry); // get the entry string length
fieldstart = 0; // let's now strip leading blanks
while ((fieldstart < length) && iswspace (current_entry[fieldstart]))
fieldstart++; // ignore any tabs or spaces, going forward from the start
fieldstop = length - 1; // let's now strip trailing blanks
while ((fieldstop >= 0) && iswspace (current_entry[fieldstop]))
fieldstop--; // ignore any tabs or spaces, going backwards from the end
for (i = fieldstart; i <= fieldstop; i++)
current_entry[i - fieldstart] = towlower (current_entry[i]); // recopy entry name w/out spaces
current_entry[i - fieldstart] = 0; // and terminate the string
length = (int) wcslen (current_value); // get the value string length
fieldstart = 0; // let's now strip leading blanks
while ((fieldstart < length) && iswspace (current_value[fieldstart]))
fieldstart++; // ignore any tabs or spaces, going forward from the start
fieldstop = length - 1; // let's now strip trailing blanks
while ((fieldstop >= 0) && iswspace (current_value[fieldstop]))
fieldstop--; // ignore any tabs or spaces, going backwards from the end
for (i = fieldstart; i <= fieldstop; i++)
current_value[i - fieldstart] = current_value[i]; // recopy entry name w/out spaces
current_value[i - fieldstart] = 0; // and terminate the string
// sscanf cannot handle "" or '' as empty value, this is done here
if ((wcscmp (current_value, L"\"\"") == 0) || (wcscmp (current_value, L"''") == 0))
current_value[0] = 0; // empty string
INIFile_WriteEntryAsString (dictionary, current_section, current_entry, current_value); // add to dictionary
}
}
fclose (fp); // finished, close the file
SAFE_free ((void **) &line_buffer); // free the line buffer we allocated at the beginning of the function
SAFE_free ((void **) ¤t_value); // free the current value buffer we allocated at the beginning of the function
SAFE_free ((void **) ¤t_entry); // free the current entry buffer we allocated at the beginning of the function
SAFE_free ((void **) ¤t_section); // free the current section buffer we allocated at the beginning of the function
return ((void *) dictionary); // and return a pointer to it
}
unsigned char INIFile_SaveINIFile (const wchar_t *filename, void *ini_data)
{
// this function dumps a given dictionary into a loadable ini file.
FILE *fp;
int section_index;
wchar_t *section_name;
int section_count;
int entry_index;
int length;
dictionary_t *dictionary;
dictionary_entry_t *entry;
// try to open the INI file in ASCII write mode
fp = _wfsopen (filename, L"w, ccs=UNICODE", _SH_DENYNO);
if (fp == NULL)
return (0); // cancel if unable to open file
// get a hand on the INI data dictionary
dictionary = (dictionary_t *) ini_data;
// keep only the file name for the comment
if (wcsrchr (filename, '/') != NULL)
filename = wcsrchr (filename, '/') + 1;
else if (wcsrchr (filename, '\\') != NULL)
filename = wcsrchr (filename, '\\') + 1;
// print the INI file name as a comment
fwprintf (fp, L"# %s\n", filename);
// get the number of sections there are in this INI dictionary
section_count = INIFile_GetNumberOfSections (dictionary);
// for each section...
for (section_index = 0; section_index < section_count; section_index++)
{
section_name = INIFile_GetSectionName (dictionary, section_index); // read section name
// is it the default section ?
if (wcscmp (section_name, INIFile_default_section) == 0)
fwprintf (fp, L"\n"); // don't put the default section's name in the INI file
else
fwprintf (fp, L"\n[%s]\n", section_name); // dump all other sections into the INI file
length = (int) wcslen (section_name);
// then for each entry in the dictionary...
for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
{
entry = &dictionary->entries[entry_index]; // quick access to entry
if (entry->key[0] == 0)
continue; // skip empty slots
// does this key belong to the section we want ? if so, dump it
if ((wcsncmp (entry->key, section_name, length) == 0) && (entry->key[length] == INI_SECTION_SEPARATOR))
{
// if key starts or ends with a space, enclose it between quotes, else don't
if ((entry->value[0] != 0)
&& (iswspace (entry->value[0]) || iswspace (entry->value[wcslen (entry->value) - 1])))
fwprintf (fp, L"%s = \"%s\"\n", &entry->key[length + 1], entry->value);
else
fwprintf (fp, L"%s = %s\n", &entry->key[length + 1], entry->value);
}
}
}
fclose (fp); // finished, close the file
return (1); // and return TRUE as the save occured successfully
}
void INIFile_FreeINIFile (void *ini_data)
{
// this function frees the dictionary object used to store an INI file data
if (ini_data == NULL)
return; // consistency check
Dictionary_DestroyDictionary ((dictionary_t *) ini_data); // just destroy the dictionary
return; // finished
}