Subversion Repositories Games.Chess Giants

Rev

Rev 161 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. // inifile.c
  2.  
  3. #include "common.h"
  4.  
  5.  
  6. // WARNING: INI SECTION NAMES ARE CASE SENSITIVE, BUT KEY NAMES ARE NOT!
  7.  
  8.  
  9. // internal definitions
  10. #define MAX_KEY_SIZE 124
  11. #define MAX_VALUE_SIZE 3968
  12. #define DICTIONARY_PAGE_SIZE 128
  13. #define INI_SECTION_SEPARATOR ']'
  14. #define INI_INVALID_KEY (wchar_t *) 0x7fffffff
  15. #define WSIZEOF(a) (sizeof (a) / sizeof (wchar_t))
  16.  
  17.  
  18. // dictionary structure definitions
  19. typedef struct dictionary_entry_s
  20. {
  21.    wchar_t key[MAX_KEY_SIZE]; // key string
  22.    unsigned long hash; // key hash value
  23.    wchar_t value[MAX_VALUE_SIZE]; // value string
  24. } dictionary_entry_t;
  25.  
  26.  
  27. typedef struct dictionary_s
  28. {
  29.    int size; // storage size
  30.    dictionary_entry_t *entries; // array of entries (mallocated)
  31.    int entry_count; // number of entries in dictionary
  32. } dictionary_t;
  33.  
  34.  
  35. // dictionary function prototypes
  36. static dictionary_t *Dictionary_CreateDictionary (int size);
  37. static void Dictionary_DestroyDictionary (dictionary_t *dictionary);
  38. static dictionary_entry_t *Dictionary_GetKey (dictionary_t *dictionary, wchar_t *key);
  39. static wchar_t *Dictionary_ReadKey (dictionary_t *dictionary, wchar_t *key, wchar_t *default_value);
  40. static void Dictionary_WriteKey (dictionary_t *dictionary, wchar_t *key, wchar_t *value);
  41. static void Dictionary_DeleteKey (dictionary_t *dictionary, wchar_t *key);
  42. static unsigned long Dictionary_ComputeHashValueForKey (wchar_t *key);
  43.  
  44.  
  45. // ini file function prototypes
  46. void *INIFile_NewINIFile (void);
  47. int INIFile_GetNumberOfSections (void *ini_data);
  48. wchar_t *INIFile_GetSectionName (void *ini_data, int section_index);
  49. int INIFile_GetNumberOfEntries (void *ini_data, wchar_t *section);
  50. wchar_t *INIFile_GetEntryName (void *ini_data, wchar_t *section, int entry_index);
  51. unsigned char INIFile_ReadEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char default_value);
  52. long INIFile_ReadEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long default_value);
  53. unsigned long INIFile_ReadEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long default_value);
  54. double INIFile_ReadEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double default_value);
  55. wchar_t *INIFile_ReadEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *default_value);
  56. void INIFile_WriteEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char value);
  57. void INIFile_WriteEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long value);
  58. void INIFile_WriteEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long value);
  59. void INIFile_WriteEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double value);
  60. void INIFile_WriteEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *value);
  61. void INIFile_DeleteEntry (void *ini_data, wchar_t *section, wchar_t *entry);
  62. void INIFile_DeleteSection (void *ini_data, wchar_t *section);
  63. void *INIFile_LoadINIFile (const wchar_t *filename);
  64. unsigned char INIFile_SaveINIFile (const wchar_t *filename, void *ini_data);
  65. void INIFile_FreeINIFile (void *ini_data);
  66.  
  67.  
  68. // internal variables
  69. static wchar_t *INIFile_default_section = L"__default";
  70. static wchar_t line_buffer[MAX_KEY_SIZE + MAX_VALUE_SIZE];
  71. static wchar_t temp_key[MAX_KEY_SIZE];
  72. static wchar_t number_as_string[256];
  73. static wchar_t temp_section[MAX_KEY_SIZE];
  74. static wchar_t temp_entry[MAX_KEY_SIZE];
  75. static wchar_t temp_value[MAX_VALUE_SIZE];
  76.  
  77.  
  78. static dictionary_t *Dictionary_CreateDictionary (int size)
  79. {
  80.    // this function allocates a new dictionary object of given size and returns it. If you do
  81.    // not know in advance (roughly) the number of entries in the dictionary, give size = 0.
  82.  
  83.    dictionary_t *dictionary;
  84.  
  85.    // if no size was specified, allocate space for one page
  86.    if (size < DICTIONARY_PAGE_SIZE)
  87.       size = DICTIONARY_PAGE_SIZE;
  88.  
  89.    // allocate one instance of the dictionary and blank it out
  90.    dictionary = (dictionary_t *) SAFE_malloc (1, sizeof (dictionary_t), true);
  91.    dictionary->size = size; // dictionary can currently hold size entries
  92.  
  93.    // allocate space for the dictionary entries and zero all the crap out
  94.    dictionary->entries = (dictionary_entry_t *) SAFE_malloc (size, sizeof (dictionary_entry_t), true);
  95.  
  96.    return (dictionary); // finished, return a pointer to the dictionary object
  97. }
  98.  
  99.  
  100. static void Dictionary_DestroyDictionary (dictionary_t *dictionary)
  101. {
  102.    // frees a dictionary object and all memory associated to it.
  103.  
  104.    if (dictionary == NULL)
  105.       return; // consistency check
  106.  
  107.    SAFE_free ((void **) &dictionary->entries); // free the entries array
  108.    SAFE_free ((void **) &dictionary); // and finally, free the dictionary itself
  109.  
  110.    return; // finished
  111. }
  112.  
  113.  
  114. static dictionary_entry_t *Dictionary_GetKey (dictionary_t *dictionary, wchar_t *key)
  115. {
  116.    // this function locates a key in a dictionary and returns a pointer to it, or a NULL
  117.    // pointer if no such key can be found in dictionary. The returned pointer points to data
  118.    // internal to the dictionary object, do not try to free or modify it.
  119.  
  120.    unsigned long hash;
  121.    register int entry_index;
  122.  
  123.    hash = Dictionary_ComputeHashValueForKey (key); // get the hash value for key
  124.  
  125.    // cycle through all entries in the dictionary
  126.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  127.    {
  128.       if (dictionary->entries[entry_index].key[0] == 0)
  129.          continue; // skip empty slots
  130.  
  131.       // compare hash AND string, to avoid hash collisions
  132.       if ((hash == dictionary->entries[entry_index].hash) && (wcscmp (key, dictionary->entries[entry_index].key) == 0))
  133.          return (&dictionary->entries[entry_index]); // return a pointer to the key if we find it
  134.    }
  135.  
  136.    return (NULL); // else return a NULL pointer
  137. }
  138.  
  139.  
  140. static wchar_t *Dictionary_ReadKey (dictionary_t *dictionary, wchar_t *key, wchar_t *default_value)
  141. {
  142.    // this function locates a key in a dictionary and returns a pointer to its value, or the
  143.    // passed 'def' pointer if no such key can be found in dictionary. The returned char pointer
  144.    // points to data internal to the dictionary object, do not try to free or modify it.
  145.  
  146.    dictionary_entry_t *element;
  147.  
  148.    element = Dictionary_GetKey (dictionary, key); // query the dictionary for the key
  149.    if (element != NULL)
  150.       return (element->value); // if we found a valid key, return the value associed to it
  151.  
  152.    return (default_value); // else return the default value
  153. }
  154.  
  155.  
  156. static void Dictionary_WriteKey (dictionary_t *dictionary, wchar_t *key, wchar_t *value)
  157. {
  158.    // sets a value in a dictionary. If the given key is found in the dictionary, the associated
  159.    // value is replaced by the provided one. If the key cannot be found in the dictionary, it
  160.    // is added to it.
  161.  
  162.    unsigned long hash;
  163.    register int entry_index;
  164.  
  165.    hash = Dictionary_ComputeHashValueForKey (key); // compute hash for this key
  166.  
  167.    // for each entry in the dictionary...
  168.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  169.    {
  170.       if (dictionary->entries[entry_index].key[0] == 0)
  171.           continue; // skip empty slots
  172.  
  173.       // does that key have the same hash value AND the same name ?
  174.       if ((hash == dictionary->entries[entry_index].hash) && (wcscmp (key, dictionary->entries[entry_index].key) == 0))
  175.       {
  176.          // we've found the right key: modify its value and return
  177.  
  178.          // have we provided a valid value ?
  179.          if (value != NULL)
  180.             wcscpy_s (dictionary->entries[entry_index].value, WSIZEOF (dictionary->entries[entry_index].value), value); // copy the value string
  181.          else
  182.             dictionary->entries[entry_index].value[0] = 0; // reset the value string
  183.  
  184.          return; // value has been modified: return
  185.       }
  186.    }
  187.  
  188.    // here either the dictionary was empty, or we couldn't find a similar value: add a new one
  189.  
  190.    // cycle through all entries in the dictionary...
  191.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  192.       if (dictionary->entries[entry_index].key[0] == 0)
  193.          break; // and stop at the first empty one we find
  194.  
  195.    // no empty entry found, see if dictionary needs to grow or if there's still room left
  196.    if (entry_index == dictionary->size)
  197.    {
  198.       // mallocate some more space for the dictionary and zero all that crap out
  199.       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
  200.       dictionary->size += DICTIONARY_PAGE_SIZE; // increase dictionary size
  201.    }
  202.  
  203.    // copy the new key
  204.  
  205.    wcscpy_s (dictionary->entries[entry_index].key, WSIZEOF (dictionary->entries[entry_index].key), key); // copy the key string...
  206.    dictionary->entries[entry_index].hash = hash; // ...and store the hash value
  207.  
  208.    // have we provided a valid value ?
  209.    if (value != NULL)
  210.       wcscpy_s (dictionary->entries[entry_index].value, WSIZEOF (dictionary->entries[entry_index].value), value); // copy the value string
  211.    else
  212.       dictionary->entries[entry_index].value[0] = 0; // reset the value string
  213.  
  214.    dictionary->entry_count++; // there is one more entry in the dictionary now
  215.    return;
  216. }
  217.  
  218.  
  219. static void Dictionary_DeleteKey (dictionary_t *dictionary, wchar_t *key)
  220. {
  221.    // this function deletes a key in a dictionary. Nothing is done if the key cannot be found.
  222.  
  223.    unsigned long hash;
  224.    register int entry_index;
  225.  
  226.    hash = Dictionary_ComputeHashValueForKey (key); // get the hash value for key
  227.  
  228.    // cycle through all entries in the dictionary...
  229.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  230.    {
  231.       if (dictionary->entries[entry_index].key[0] == 0)
  232.          continue; // skip empty slots
  233.  
  234.       // compare hash AND string, to avoid hash collisions
  235.       if ((hash == dictionary->entries[entry_index].hash) && (wcscmp (key, dictionary->entries[entry_index].key) == 0))
  236.          break; // break as soon as we've found the key we want
  237.    }
  238.  
  239.    if (entry_index == dictionary->entry_count)
  240.       return; // if key was not found, just return
  241.  
  242.    // clear the key, the hash and its value data
  243.    memset (&dictionary->entries[entry_index], 0, sizeof (dictionary_entry_t));
  244.  
  245.    dictionary->entry_count--; // there is one entry less in the dictionary now
  246.    return;
  247. }
  248.  
  249.  
  250. static unsigned long Dictionary_ComputeHashValueForKey (wchar_t *key)
  251. {
  252.    // Compute the hash key for a string. This hash function has been taken from an article in
  253.    // Dr Dobbs Journal. This is normally a collision-free function, distributing keys evenly.
  254.    // The key is stored anyway in the struct so that collision can be avoided by comparing the
  255.    // key itself in last resort. THIS FUNCTION IS TO BE USED INTERNALLY ONLY!
  256.  
  257.    register unsigned long hash;
  258.    int length;
  259.    int char_index;
  260.  
  261.    hash = 0;
  262.    length = (int) wcslen (key);
  263.  
  264.    // for each character of the string...
  265.    for (char_index = 0; char_index < length; char_index++)
  266.    {
  267.       hash += (unsigned long) key[char_index]; // take it in account and compute the hash value
  268.       hash += (hash << 10);
  269.       hash ^= (hash >> 6);
  270.    }
  271.  
  272.    hash += (hash << 3); // finalize hashing (what the hell does it do, I have no clue)
  273.    hash ^= (hash >> 11);
  274.    hash += (hash << 15);
  275.  
  276.    return (hash); // and return the hash value
  277. }
  278.  
  279.  
  280. void *INIFile_NewINIFile (void)
  281. {
  282.    // allocates a dictionary for a new INI file. Mostly a helper function.
  283.  
  284.    return ((void *) Dictionary_CreateDictionary (0));
  285. }
  286.  
  287.  
  288. int INIFile_GetNumberOfSections (void  *ini_data)
  289. {
  290.    // this function returns the number of sections found in a dictionary. The test to recognize
  291.    // sections is done on the string stored in the dictionary: a section name is given as
  292.    // "section" whereas a key is stored as "section]key", thus the test looks for entries that
  293.    // do NOT contain a ']'. This clearly fails in the case a section name contains a ']',
  294.    // but this should simply be avoided.
  295.  
  296.    int section_count;
  297.    int entry_index;
  298.    dictionary_t *dictionary;
  299.  
  300.    if (ini_data == NULL)
  301.       return (0); // consistency check
  302.  
  303.    dictionary = (dictionary_t *) ini_data;
  304.  
  305.    section_count = 0; // no sections found yet
  306.  
  307.    // for each entry in the dictionary...
  308.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  309.    {
  310.       if (dictionary->entries[entry_index].key[0] == 0)
  311.          continue; // skip empty slots
  312.  
  313.       if (wcschr (dictionary->entries[entry_index].key, INI_SECTION_SEPARATOR) == NULL)
  314.          section_count++; // if this entry has NO section separator, then it's a section name
  315.    }
  316.  
  317.    return (section_count); // return the section count we found
  318. }
  319.  
  320.  
  321. wchar_t *INIFile_GetSectionName (void *ini_data, int section_index)
  322. {
  323.    // this function locates the n-th section in a dictionary and returns its name as a pointer
  324.    // to a string allocated inside the dictionary. Do not free or modify the returned string!!
  325.    // This function returns NULL in case of error.
  326.  
  327.    int sections_found;
  328.    int entry_index;
  329.    dictionary_t *dictionary;
  330.  
  331.    if ((ini_data == NULL) || (section_index < 0))
  332.       return (NULL); // consistency check
  333.  
  334.    dictionary = (dictionary_t *) ini_data;
  335.  
  336.    sections_found = 0; // no sections found yet
  337.  
  338.    // for each entry in the dictionary...
  339.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  340.    {
  341.       if (dictionary->entries[entry_index].key[0] == 0)
  342.          continue; // skip empty slots
  343.  
  344.       // is this entry a section name ?
  345.       if (wcschr (dictionary->entries[entry_index].key, INI_SECTION_SEPARATOR) == NULL)
  346.       {
  347.          // is it the section we want ?
  348.          if (sections_found == section_index)
  349.             return (dictionary->entries[entry_index].key); // then return its name
  350.  
  351.          sections_found++; // we found one section more
  352.       }
  353.    }
  354.  
  355.    return (INIFile_default_section); // section was not found, return the default section name
  356. }
  357.  
  358.  
  359. int INIFile_GetNumberOfEntries (void  *ini_data, wchar_t *section)
  360. {
  361.    // this function returns the number of entries found in a dictionary within a given section.
  362.    // The test to recognize section entries is done on the string stored in the dictionary: a
  363.    // section name is given as "section" whereas a key is stored as "section]key", thus the test
  364.    // looks for all entries that begin with "section]".
  365.  
  366.    int entry_count;
  367.    int entry_index;
  368.    int sectionname_length;
  369.    dictionary_t *dictionary;
  370.  
  371.    if (ini_data == NULL)
  372.       return (0); // consistency check
  373.  
  374.    dictionary = (dictionary_t *) ini_data;
  375.    sectionname_length = wcslen (section);
  376.  
  377.    entry_count = 0; // no entries found yet
  378.  
  379.    // for each entry in the dictionary...
  380.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  381.    {
  382.       if (dictionary->entries[entry_index].key[0] == 0)
  383.          continue; // skip empty slots
  384.  
  385.       if ((wcsncmp (dictionary->entries[entry_index].key, section, sectionname_length) == 0)
  386.           && (dictionary->entries[entry_index].key[sectionname_length] == INI_SECTION_SEPARATOR))
  387.          entry_count++; // if this entry has section name + separator, then it's one of those we want
  388.    }
  389.  
  390.    return (entry_count); // return the number of entries we found in the given section
  391. }
  392.  
  393.  
  394. wchar_t *INIFile_GetEntryName (void *ini_data, wchar_t *section, int entry_index)
  395. {
  396.    // this function queries a dictionary for a key. A key as read from an ini file is given as
  397.    // "section]key". If the key cannot be found, NULL is returned. The returned char pointer
  398.    // points to a string allocated in the dictionary, do not free or modify it.
  399.  
  400.    int i;
  401.    int length;
  402.    int found_keys;
  403.    dictionary_t *dictionary;
  404.    dictionary_entry_t *entry;
  405.  
  406.    if (ini_data == NULL)
  407.       return (NULL); // consistency check
  408.  
  409.    if (section == NULL)
  410.       section = INIFile_default_section; // if no section was provided, use empty section name
  411.  
  412.    dictionary = (dictionary_t *) ini_data; // quick access to dictionary
  413.  
  414.    // build the key entry prefix
  415.    swprintf_s (temp_key, WSIZEOF (temp_key), L"%s%c", section, INI_SECTION_SEPARATOR); // compose the key
  416.    length = wcslen (temp_key);
  417.  
  418.    // cycle through all remaining entries
  419.    found_keys = 0;
  420.    for (i = 0; i < dictionary->entry_count; i++)
  421.    {
  422.       entry = &dictionary->entries[i]; // quick access to each entry
  423.  
  424.       if (wcsncmp (entry->key, temp_key, length) != 0)
  425.          continue; // if this key doesn't belong to the wanted section, skip it
  426.  
  427.       // this key belongs to the wanted section. Is it the key we want ?
  428.       if (found_keys == entry_index)
  429.          return (&entry->key[length]); // if so, return its name
  430.  
  431.       found_keys++; // it wasn't the key we want, so increase counter and proceed to the next one
  432.    }
  433.  
  434.    return (NULL); // if not found, return NULL
  435. }
  436.  
  437.  
  438. unsigned char INIFile_ReadEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char default_value)
  439. {
  440.    // this function queries a dictionary for a key. A key as read from an ini file is given as
  441.    // "section]key". If the key cannot be found, the notfound value is returned. A true boolean
  442.    // is found if a string starting with either 'y', 'Y', 't', 'T' or '1' is matched. A false
  443.    // boolean is found if a string starting with 'n', 'N', 'f', 'F' or '0' is matched.
  444.  
  445.    wchar_t *value;
  446.  
  447.    if (ini_data == NULL)
  448.       return (default_value); // consistency check
  449.  
  450.    // get the value as a string first
  451.    value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
  452.  
  453.    if (value == INI_INVALID_KEY)
  454.       return (default_value); // if the entry could not be read, return the default value
  455.  
  456.    // decide of the boolean's value by looking at the first character
  457.    if ((toupper (value[0]) == 'T') || (toupper (value[0]) == 'Y') || (value[0] == '1'))
  458.      return (1); // boolean value is TRUE
  459.    else if ((toupper (value[0]) == 'F') || (toupper (value[0]) == 'N') || (value[0] == '0'))
  460.      return (0); // boolean value is FALSE
  461.  
  462.    return (default_value); // boolean value is undefined, return default value
  463. }
  464.  
  465.  
  466. long INIFile_ReadEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long default_value)
  467. {
  468.    // this function queries a dictionary for a key. A key as read from an ini file is given as
  469.    // "section]key". If the key cannot be found, the notfound value is returned.
  470.  
  471.    wchar_t *value;
  472.  
  473.    if (ini_data == NULL)
  474.       return (default_value); // consistency check
  475.  
  476.    // get the value as a string first
  477.    value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
  478.  
  479.    if (value == INI_INVALID_KEY)
  480.       return (default_value); // if the entry could not be read, return the default value
  481.  
  482.    return (_wtol (value)); // else convert the value to a long integer and return it
  483. }
  484.  
  485.  
  486. unsigned long INIFile_ReadEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long default_value)
  487. {
  488.    // this function queries a dictionary for a key. A key as read from an ini file is given as
  489.    // "section]key". If the key cannot be found, the notfound value is returned.
  490.  
  491.    wchar_t *value;
  492.  
  493.    if (ini_data == NULL)
  494.       return (default_value); // consistency check
  495.  
  496.    // get the value as a string first
  497.    value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
  498.  
  499.    if (value == INI_INVALID_KEY)
  500.       return (default_value); // if the entry could not be read, return the default value
  501.  
  502.    return (wcstoul (value, NULL, 10)); // else convert the value to a long integer and return it
  503. }
  504.  
  505.  
  506. double INIFile_ReadEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double default_value)
  507. {
  508.    // this function queries a dictionary for a key. A key as read from an ini file is given as
  509.    // "section]key". If the key cannot be found, the notfound value is returned.
  510.  
  511.    wchar_t *value;
  512.  
  513.    if (ini_data == NULL)
  514.       return (default_value); // consistency check
  515.  
  516.    // get the value as a string first
  517.    value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
  518.  
  519.    if (value == INI_INVALID_KEY)
  520.       return (default_value); // if the entry could not be read, return the default value
  521.  
  522.    return (_wtof (value)); // else convert the value to a double precision number and return it
  523. }
  524.  
  525.  
  526. wchar_t *INIFile_ReadEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *default_value)
  527. {
  528.    // this function queries a dictionary for a key. A key as read from an ini file is given as
  529.    // "section]key". If the key cannot be found, the pointer passed as 'def' is returned. The
  530.    // returned char pointer points to a string allocated in the dictionary, do not free or
  531.    // modify it.
  532.  
  533.    int length;
  534.    int i;
  535.    wchar_t *value;
  536.  
  537.    if (ini_data == NULL)
  538.       return (default_value); // consistency check
  539.  
  540.    if (section == NULL)
  541.       section = INIFile_default_section; // if no section was provided, use empty section name
  542.  
  543.    // if we were given an entry, build a key as section]entry
  544.    if (entry != NULL)
  545.    {
  546.       swprintf_s (temp_key, WSIZEOF (temp_key), L"%s%c%s", section, INI_SECTION_SEPARATOR, entry); // compose the key
  547.  
  548.       i = (int) wcslen (temp_key); // get the key string length
  549.       if (i < WSIZEOF (temp_key))
  550.          length = i;
  551.       else
  552.          length = WSIZEOF (temp_key) - 1; // clamp it to a max value
  553.  
  554.       // for each character in the string after the section separator...
  555.       for (i = (int) wcslen (section) + 1; i < length; i++)
  556.          temp_key[i] = towlower (temp_key[i]); // convert it to lowercase
  557.       temp_key[i] = 0; // terminate the string
  558.    }
  559.  
  560.    // else it must be a section name
  561.    else
  562.       wcscpy_s (temp_key, WSIZEOF (temp_key), section); // copy the name into the key
  563.  
  564.    value = Dictionary_ReadKey ((dictionary_t *) ini_data, temp_key, default_value); // query the dictionary...
  565.    return (value); // ...and return the value
  566. }
  567.  
  568.  
  569. void INIFile_WriteEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char value)
  570. {
  571.    // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
  572.    // modified to contain the provided value. If it cannot be found, it is created.
  573.  
  574.    // according the boolean's value, write the equivalent string
  575.    if (value)
  576.       INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, L"true"); // write the new entry
  577.    else
  578.       INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, L"false"); // write the new entry
  579.  
  580.    return; // finished
  581. }
  582.  
  583.  
  584. void INIFile_WriteEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long value)
  585. {
  586.    // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
  587.    // modified to contain the provided value. If it cannot be found, it is created.
  588.  
  589.    // build the long integer value equivalent string (use printf facility)
  590.    swprintf_s (number_as_string, WSIZEOF (number_as_string), L"%ld", value);
  591.  
  592.    INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, number_as_string); // write the new entry
  593.    return; // finished
  594. }
  595.  
  596.  
  597. void INIFile_WriteEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long value)
  598. {
  599.    // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
  600.    // modified to contain the provided value. If it cannot be found, it is created.
  601.  
  602.    // build the long integer value equivalent string (use printf facility)
  603.    swprintf_s (number_as_string, WSIZEOF (number_as_string), L"%lu", value);
  604.  
  605.    INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, number_as_string); // write the new entry
  606.    return; // finished
  607. }
  608.  
  609.  
  610. void INIFile_WriteEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double value)
  611. {
  612.    // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
  613.    // modified to contain the provided value. If it cannot be found, it is created.
  614.  
  615.    // build the long integer value equivalent string (use printf facility)
  616.    swprintf_s (number_as_string, WSIZEOF (number_as_string), L"%g", value);
  617.  
  618.    INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, number_as_string); // write the new entry
  619.    return; // finished
  620. }
  621.  
  622.  
  623. void INIFile_WriteEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *value)
  624. {
  625.    // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
  626.    // modified to contain the provided value. If it cannot be found, it is created.
  627.  
  628.    int length;
  629.    int i;
  630.  
  631.    if (ini_data == NULL)
  632.       return; // consistency check
  633.  
  634.    if (section == NULL)
  635.       section = INIFile_default_section; // if no section was provided, use empty section name
  636.  
  637.    // if we were given an entry, build a key as section#entry
  638.    if (entry != NULL)
  639.    {
  640.       Dictionary_WriteKey ((dictionary_t *) ini_data, section, NULL); // create the section if it doesn't exist
  641.  
  642.       swprintf_s (temp_key, WSIZEOF (temp_key), L"%s%c%s", section, INI_SECTION_SEPARATOR, entry); // compose the key
  643.  
  644.       i = (int) wcslen (temp_key); // get the key string length
  645.       if (i < WSIZEOF (temp_key))
  646.          length = i;
  647.       else
  648.          length = WSIZEOF (temp_key) - 1; // clamp it to a max value
  649.  
  650.       // for each character in the string after the section separator...
  651.       for (i = (int) wcslen (section) + 1; i < length; i++)
  652.          temp_key[i] = towlower (temp_key[i]); // convert it to lowercase
  653.       temp_key[i] = 0; // terminate the string
  654.    }
  655.  
  656.    // else it must be a section name
  657.    else
  658.       wcscpy_s (temp_key, WSIZEOF (temp_key), section); // copy the name into the key
  659.  
  660.    Dictionary_WriteKey ((dictionary_t *) ini_data, temp_key, value); // write the new key in the dictionary
  661.    return; // finished
  662. }
  663.  
  664.  
  665. void INIFile_DeleteEntry (void *ini_data, wchar_t *section, wchar_t *entry)
  666. {
  667.    // deletes an entry in the dictionary
  668.  
  669.    int length;
  670.    int i;
  671.  
  672.    if (ini_data == NULL)
  673.       return; // consistency check
  674.  
  675.    if (section == NULL)
  676.       section = INIFile_default_section; // if no section was provided, use empty section name
  677.  
  678.    // if we were given an entry, build a key as section#entry
  679.    if (entry != NULL)
  680.    {
  681.       swprintf_s (temp_key, WSIZEOF (temp_key), L"%s%c%s", section, INI_SECTION_SEPARATOR, entry); // compose the key
  682.  
  683.       i = (int) wcslen (temp_key); // get the key string length
  684.       if (i < WSIZEOF (temp_key))
  685.          length = i;
  686.       else
  687.          length = WSIZEOF (temp_key) - 1; // clamp it to a max value
  688.  
  689.       // for each character in the string after the section separator...
  690.       for (i = (int) wcslen (section) + 1; i < length; i++)
  691.          temp_key[i] = towlower (temp_key[i]); // convert it to lowercase
  692.       temp_key[i] = 0; // terminate the string
  693.    }
  694.  
  695.    // else it must be a section name
  696.    else
  697.       wcscpy_s (temp_key, WSIZEOF (temp_key), section); // copy the name into the key
  698.  
  699.    Dictionary_DeleteKey ((dictionary_t *) ini_data, temp_key);
  700.    return;
  701. }
  702.  
  703.  
  704. void INIFile_DeleteSection (void *ini_data, wchar_t *section)
  705. {
  706.    // deletes a whole INI section in the dictionary
  707.  
  708.    int length;
  709.    int i;
  710.    dictionary_t *dictionary;
  711.  
  712.    if (ini_data == NULL)
  713.       return; // consistency check
  714.  
  715.    if (section == NULL)
  716.       section = INIFile_default_section; // if no section was provided, use empty section name
  717.  
  718.    dictionary = (dictionary_t *) ini_data;
  719.  
  720.    swprintf_s (temp_key, WSIZEOF (temp_key), L"%s%c", section, INI_SECTION_SEPARATOR); // compose the key
  721.    length = (int) wcslen (temp_key); // get the key string length
  722.  
  723.    // for each entry in the dictionary...
  724.    for (i = 0; i < dictionary->entry_count; i++)
  725.    {
  726.       if (dictionary->entries[i].key[0] == 0)
  727.          continue; // skip empty slots
  728.  
  729.       // does this entry belong to the section we want ?
  730.       if (wcsncmp (dictionary->entries[i].key, temp_key, length) == 0)
  731.          Dictionary_DeleteKey (dictionary, dictionary->entries[i].key); // yes, delete it
  732.    }
  733.  
  734.    Dictionary_DeleteKey (dictionary, section); // and finally delete the section name itself
  735.    return;
  736. }
  737.  
  738.  
  739. void *INIFile_LoadINIFile (const wchar_t *filename)
  740. {
  741.    // this is the parser for ini files. This function is called, providing the name of the file
  742.    // to be read. It returns a dictionary object that should not be accessed directly, but
  743.    // through accessor functions instead.
  744.  
  745.    FILE *fp;
  746.    int fieldstart;
  747.    int fieldstop;
  748.    int length;
  749.    int i;
  750.    dictionary_t *dictionary;
  751.  
  752.    // try to open the INI file in ASCII read-only mode
  753.    fp = _wfsopen (filename, L"r, ccs=UNICODE", _SH_DENYNO);
  754.    if (fp == NULL)
  755.       return (NULL); // cancel if file not found
  756.  
  757.    dictionary = Dictionary_CreateDictionary (0);
  758.  
  759.    // set the default section for orphaned entries and add it to the dictionary
  760.    wcscpy_s (temp_section, WSIZEOF (temp_section), INIFile_default_section);
  761.    INIFile_WriteEntryAsString (dictionary, temp_section, NULL, NULL);
  762.  
  763.    // read line per line...
  764.    while (fgetws (line_buffer, WSIZEOF (line_buffer), fp) != NULL)
  765.    {
  766.       length = (int) wcslen (line_buffer); // get line length
  767.  
  768.       while ((length > 0) && ((line_buffer[length - 1] == '\n') || (line_buffer[length - 1] == '\r')))
  769.          length--; // discard trailing newlines
  770.  
  771.       fieldstart = 0; // let's now strip leading blanks
  772.       while ((fieldstart < length) && iswspace (line_buffer[fieldstart]))
  773.          fieldstart++; // ignore any tabs or spaces, going forward from the start
  774.  
  775.       fieldstop = length - 1; // let's now strip trailing blanks
  776.       while ((fieldstop >= 0) && iswspace (line_buffer[fieldstop]))
  777.          fieldstop--; // ignore any tabs or spaces, going backwards from the end
  778.  
  779.       for (i = fieldstart; i <= fieldstop; i++)
  780.          line_buffer[i - fieldstart] = line_buffer[i]; // recopy line buffer without the spaces
  781.       line_buffer[i - fieldstart] = 0; // and terminate the string
  782.  
  783.       if ((line_buffer[0] == ';') || (line_buffer[0] == '#') || (line_buffer[0] == 0))
  784.          continue; // skip comment lines
  785.  
  786.       // is it a valid section name ?
  787.       if (swscanf_s (line_buffer, L"[%[^]]", temp_section, WSIZEOF (temp_section)) == 1)
  788.       {
  789.          length = (int) wcslen (temp_section); // get the section string length
  790.  
  791.          fieldstart = 0; // let's now strip leading blanks
  792.          while ((fieldstart < length) && iswspace (temp_section[fieldstart]))
  793.             fieldstart++; // ignore any tabs or spaces, going forward from the start
  794.  
  795.          fieldstop = length - 1; // let's now strip trailing blanks
  796.          while ((fieldstop >= 0) && iswspace (temp_section[fieldstop]))
  797.             fieldstop--; // ignore any tabs or spaces, going backwards from the end
  798.  
  799.          for (i = fieldstart; i <= fieldstop; i++)
  800.             temp_section[i - fieldstart] = temp_section[i]; // recopy section name w/out spaces
  801.          temp_section[i - fieldstart] = 0; // and terminate the string
  802.  
  803.          INIFile_WriteEntryAsString (dictionary, temp_section, NULL, NULL); // add to dictionary
  804.       }
  805.  
  806.       // else is it a valid entry/value pair that is enclosed between quotes?
  807.       else if (swscanf_s (line_buffer, L"%[^=] = \"%[^\"]\"", temp_entry, WSIZEOF (temp_entry), temp_value, WSIZEOF (temp_value)) == 2)
  808.       {
  809.          length = (int) wcslen (temp_entry); // get the entry string length
  810.  
  811.          fieldstart = 0; // let's now strip leading blanks
  812.          while ((fieldstart < length) && iswspace (temp_entry[fieldstart]))
  813.             fieldstart++; // ignore any tabs or spaces, going forward from the start
  814.  
  815.          fieldstop = length - 1; // let's now strip trailing blanks
  816.          while ((fieldstop >= 0) && iswspace (temp_entry[fieldstop]))
  817.             fieldstop--; // ignore any tabs or spaces, going backwards from the end
  818.  
  819.          for (i = fieldstart; i <= fieldstop; i++)
  820.             temp_entry[i - fieldstart] = towlower (temp_entry[i]); // recopy entry name w/out spaces
  821.          temp_entry[i - fieldstart] = 0; // and terminate the string
  822.  
  823.          // when value is enclosed between quotes, DO NOT strip the blanks
  824.  
  825.          // sscanf cannot handle "" or '' as empty value, this is done here
  826.          if ((wcscmp (temp_value, L"\"\"") == 0) || (wcscmp (temp_value, L"''") == 0))
  827.             temp_value[0] = 0; // empty string
  828.  
  829.          INIFile_WriteEntryAsString (dictionary, temp_section, temp_entry, temp_value); // add to dictionary
  830.       }
  831.  
  832.       // else is it a valid entry/value pair without quotes ?
  833.       else if ((swscanf_s (line_buffer, L"%[^=] = '%[^\']'", temp_entry, WSIZEOF (temp_entry), temp_value, WSIZEOF (temp_value)) == 2)
  834.                || (swscanf_s (line_buffer, L"%[^=] = %[^;#]", temp_entry, WSIZEOF (temp_entry), temp_value, WSIZEOF (temp_value)) == 2))
  835.       {
  836.          length = (int) wcslen (temp_entry); // get the entry string length
  837.  
  838.          fieldstart = 0; // let's now strip leading blanks
  839.          while ((fieldstart < length) && iswspace (temp_entry[fieldstart]))
  840.             fieldstart++; // ignore any tabs or spaces, going forward from the start
  841.  
  842.          fieldstop = length - 1; // let's now strip trailing blanks
  843.          while ((fieldstop >= 0) && iswspace (temp_entry[fieldstop]))
  844.             fieldstop--; // ignore any tabs or spaces, going backwards from the end
  845.  
  846.          for (i = fieldstart; i <= fieldstop; i++)
  847.             temp_entry[i - fieldstart] = towlower (temp_entry[i]); // recopy entry name w/out spaces
  848.          temp_entry[i - fieldstart] = 0; // and terminate the string
  849.  
  850.          length = (int) wcslen (temp_value); // get the value string length
  851.  
  852.          fieldstart = 0; // let's now strip leading blanks
  853.          while ((fieldstart < length) && iswspace (temp_value[fieldstart]))
  854.             fieldstart++; // ignore any tabs or spaces, going forward from the start
  855.  
  856.          fieldstop = length - 1; // let's now strip trailing blanks
  857.          while ((fieldstop >= 0) && iswspace (temp_value[fieldstop]))
  858.             fieldstop--; // ignore any tabs or spaces, going backwards from the end
  859.  
  860.          for (i = fieldstart; i <= fieldstop; i++)
  861.             temp_value[i - fieldstart] = temp_value[i]; // recopy entry name w/out spaces
  862.          temp_value[i - fieldstart] = 0; // and terminate the string
  863.  
  864.          // sscanf cannot handle "" or '' as empty value, this is done here
  865.          if ((wcscmp (temp_value, L"\"\"") == 0) || (wcscmp (temp_value, L"''") == 0))
  866.             temp_value[0] = 0; // empty string
  867.  
  868.          INIFile_WriteEntryAsString (dictionary, temp_section, temp_entry, temp_value); // add to dictionary
  869.       }
  870.    }
  871.  
  872.    fclose (fp); // finished, close the file
  873.  
  874.    return ((void *) dictionary); // and return
  875. }
  876.  
  877.  
  878. unsigned char INIFile_SaveINIFile (const wchar_t *filename, void *ini_data)
  879. {
  880.    // this function dumps a given dictionary into a loadable ini file.
  881.  
  882.    FILE *fp;
  883.    int section_index;
  884.    wchar_t *section_name;
  885.    int section_count;
  886.    int entry_index;
  887.    int length;
  888.    dictionary_t *dictionary;
  889.    dictionary_entry_t *entry;
  890.  
  891.    // try to open the INI file in ASCII write mode
  892.    fp = _wfsopen (filename, L"w, ccs=UNICODE", _SH_DENYNO);
  893.    if (fp == NULL)
  894.       return (0); // cancel if unable to open file
  895.  
  896.    // get a hand on the INI data dictionary
  897.    dictionary = (dictionary_t *) ini_data;
  898.  
  899.    // keep only the file name for the comment
  900.    if (wcsrchr (filename, '/') != NULL)
  901.       filename = wcsrchr (filename, '/') + 1;
  902.    else if (wcsrchr (filename, '\\') != NULL)
  903.       filename = wcsrchr (filename, '\\') + 1;
  904.  
  905.    // print the INI file name as a comment
  906.    fwprintf (fp, L"# %s\n", filename);
  907.  
  908.    // get the number of sections there are in this INI dictionary
  909.    section_count = INIFile_GetNumberOfSections (dictionary);
  910.  
  911.    // for each section...
  912.    for (section_index = 0; section_index < section_count; section_index++)
  913.    {
  914.       section_name = INIFile_GetSectionName (dictionary, section_index); // read section name
  915.  
  916.       // is it the default section ?
  917.       if (wcscmp (section_name, INIFile_default_section) == 0)
  918.          fwprintf (fp, L"\n"); // don't put the default section's name in the INI file
  919.       else
  920.          fwprintf (fp, L"\n[%s]\n", section_name); // dump all other sections into the INI file
  921.  
  922.       // build the section identifier to be used when looking up the dictionary
  923.       swprintf_s (temp_key, WSIZEOF (temp_key), L"%s%c", section_name, INI_SECTION_SEPARATOR);
  924.       length = (int) wcslen (temp_key);
  925.  
  926.       // then for each entry in the dictionary...
  927.       for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  928.       {
  929.          entry = &dictionary->entries[entry_index]; // quick access to entry
  930.  
  931.          if (entry->key[0] == 0)
  932.             continue; // skip empty slots
  933.  
  934.          // does this key belong to the section we want ? if so, dump it
  935.          if (wcsncmp (entry->key, temp_key, length) == 0)
  936.          {
  937.             // if key starts or ends with a space, enclose it between quotes, else don't
  938.             if ((entry->value[0] != 0)
  939.                 && (iswspace (entry->value[0]) || iswspace (entry->value[wcslen (entry->value) - 1])))
  940.                fwprintf (fp, L"%s = \"%s\"\n", entry->key + length, entry->value);
  941.             else
  942.                fwprintf (fp, L"%s = %s\n", entry->key + length, entry->value);
  943.          }
  944.       }
  945.    }
  946.  
  947.    fclose (fp); // finished, close the file
  948.  
  949.    return (1); // and return TRUE as the save occured successfully
  950. }
  951.  
  952.  
  953. void INIFile_FreeINIFile (void *ini_data)
  954. {
  955.    // this function frees the dictionary object used to store an INI file data
  956.  
  957.    if (ini_data == NULL)
  958.       return; // consistency check
  959.  
  960.    Dictionary_DestroyDictionary ((dictionary_t *) ini_data); // just destroy the dictionary
  961.    return; // finished
  962. }
  963.