INI files
From Bots-United Wiki
INI files are a convenient way to store configuration data for your programs. Contrarily to other fashionable file formats, INI files are extremely simple and immediately understandable by a human reader. Furthermore, they do not depend on operating system-specific configuration facilities (such as Microsoft Windows' registry), thus making the INI file format the ideal simple configuration file format for portable applications.
We will introduce here a very simple C library offering INI file parsing services. The library is pretty small, and does not depend on any other external library to compile. It is written in ANSI C and should compile anywhere without difficulty. And additionally, it has been written in such a way that it would be straightforward to port it to C++.
This library was inspired by Nicolas Devillard's INI parser.
What is an INI file ?
An INI file is an ASCII file describing simple parameters (character strings, integers, floating-point values or booleans) in an explicit format, easy to use and modify for users.
An INI file is segmented into sections, declared by the following syntax:
[Section Name]
i.e. the section name is enclosed in square brackets, alone on a line. Sections names are allowed to contain any character but square brackets or linefeeds. Slashes (/) are also reserved for hierarchical sections.
In any section are zero or more variables, declared with the following syntax:
Key = value ; comment
The key is any string (possibly containing blanks). The value is any character on the right side of the equal sign. Values can be given enclosed with quotes. If no quotes are present, the value is understood as containing all characters between the first and the last non-blank characters. Thus, the following declarations are identical:
Hello = "this is a long string value" ; comment Hello = this is a long string value ; comment
The semicolon and comment at the end of the line are optional. If there is a comment, it starts from the first character after the semicolon up to the end of the line.
Comments in an INI file are:
- Lines starting with a hash sign
- Blank lines (only blanks or tabs)
- Comments given on value lines after the semicolon (if present)
Some entries can also appear in an INI file without belonging to a particular section, for example when key/value pairs are inserted at the top of the INI file, before the first section. In this case, it is assumed that these entries belong to the default (empty) section.
NOTE: the last thing you should know, is that even if the INI format is well-known by everybody, there is no official paper anywhere to describe it. Even though it was initiated by Microsoft and has been adopted by a great number of software companies, there is no such thing as the INI standard.
Example of an INI file
This INI file is taken from your C:\WINDOWS directory. It has two sections, each of which hold several keys. It does not contain any in-line comment except for the first 5 lines (those beginning with a hash sign).
# CDPlayer.ini (Microsoft Windows' CD player configuration file). # # Each time you insert a new disk, Windows queries the Internet for the track titles. # After what, the data is stored in this file. # The section names are the CDDB signatures of the compact disks. [1475BDC] artist = Green Day title = American Idiot genre = Punk Rock comment = year = 2004 EntryType = 1 numtracks = 13 numplay = 13 0 = American Idiot TrackArtist0 = Green Day 1 = Jesus Of Suburbia TrackArtist1 = Green Day 2 = Holiday TrackArtist2 = Green Day 3 = Boulevard Of Broken Dreams TrackArtist3 = Green Day 4 = Are We The Waiting TrackArtist4 = Green Day [1053166] artist = Linkin Park title = Live In Texas genre = Rock comment = year = 2003 EntryType = 1 numtracks = 12 numplay = 12 0 = Somewhere I Belong TrackArtist0 = Linkin Park 1 = Lying From You TrackArtist1 = Linkin Park 2 = Crawling TrackArtist2 = Linkin Park
INI parser implementation
Dictionary storage
The biggest problem when dealing with INI files is the subdivision of the data with sections. Hence the key to simplify a lot the parsing and the handling of INI data is to consider each section name as part of the key name. I.e, convert
[Section] Keyword = value ; comment
to
{"section#keyword", "value"}
The merge of the section and entry names thanks to a separator into a single key name allows the whole INI data to be stored in something as simple as a dictionary.
A dictionary is an array in which two strings are associated together. This INI parser relies on a simple dictionary implementation written in ANSI C that uses paged allocation and hash values for quick access to the data. This implementation is described in another section of this wiki.
Case mangling
Section names and key names are converted to lowercase before storage because the INI file format makes no difference between uppercase and lowercase sections and entries. Only the value associated to each key is left untouched and can be case-sensitive.
The following sections are equivalent:
[SECTION] keYwOrD = value
[sECtIOn] KEYWORD = value
[Section] Keyword = value
Values as character strings can be stored enclosed with simple (') or double quotes ("). The parser strips them when it encounters some.
Orphaned entries
In case an INI entry does not belong to a particular section (i.e, when it is declared at the top of the file before the first section tag), it will be represented the following way in the dictionary:
{"__default#keyword", "value"}
to indicate that this entry does not belong to any particular section. It is thus stored in the "__default" section, which serves as a storage space for all orphaned entries. Then, when the INI file is written back to disk, this section is written first, without the section name.
INI parser source code
Files
The INI parser source code is available from the Bots United Filebase.
Features
This parser provides the following facilities:
// iniparser.c function prototypes int INIFile_GetNumberOfSections (dictionary_t *dictionary); char *INIFile_GetSectionName (dictionary_t *dictionary, int section_index); bool INIFile_ReadEntryAsBool (dictionary_t *dictionary, char *section, char *entry, bool default_value); long INIFile_ReadEntryAsLong (dictionary_t *dictionary, char *section, char *entry, long default_value); double INIFile_ReadEntryAsDouble (dictionary_t *dictionary, char *section, char *entry, double default_value); char *INIFile_ReadEntryAsString (dictionary_t *dictionary, char *section, char *entry, char *default_value); void INIFile_WriteEntryAsBool (dictionary_t *dictionary, char *section, char *entry, bool value); void INIFile_WriteEntryAsLong (dictionary_t *dictionary, char *section, char *entry, long value); void INIFile_WriteEntryAsDouble (dictionary_t *dictionary, char *section, char *entry, double value); void INIFile_WriteEntryAsString (dictionary_t *dictionary, char *section, char *entry, char *value); void INIFile_DeleteEntry (dictionary_t *dictionary, char *section, char *entry); void INIFile_DeleteSection (dictionary_t *dictionary, char *section); void INIFile_LoadINIFile (char *filename, dictionary_t *dictionary); void INIFile_SaveINIFile (char *filename, dictionary_t *dictionary);
INI parser usage
In order to use the parser, you must first allocate space for a dictionary structure, and then use that structure for all your INI data operations. Once you're done with the INI file, just destroy the dictionary. The following examples will show you what can be done with the parser.
How to load an INI file
Initialize a new dictionary and fill it with the INI data by calling INIFile_LoadINIFile().
dictionary_t *d = Dictionary_CreateDictionary (0); // initialize a new dictionary INIFile_LoadINIFile ("some_file.ini", d); // load an INI file into that dictionary
How to find the different sections of an INI file
Start by getting the number of sections, and store this number.
// get the number of sections in an INI file int count = INIFile_GetNumberOfSections (d); printf ("there are %d sections in this INI file\n", count);
Now use this number to know the index range for each section, and query each one by its index.
// print the name of each section for (int i = 0; i < count; i++) printf ("section %d is named: %s\n", i, INIFile_GetSectionName (d, i));
How to write keys, modify values and create new sections in the INI file
To write data in the INI dictionary structure, use one of the INIFile_WriteEntryAs...() functions. Provide the section name, the entry name and the value you want to set.
// write key "sound1" in the default section INIFile_WriteEntryAsString (d, NULL, "sound1", "w00tw00t.wav");
If the section name you provide doesn't exist yet, the section will be created.
// write key "double" in a section called "test" (create key and section if needed) INIFile_WriteEntryAsDouble (d, "test", "double", 22.0/7.0);
If the entry name doesn't exist either, another entry will be created under the section you specified (of course, if the entry already exists, its value will be updated).
// write key "bool" in a section called "bool" (create key and section if needed) INIFile_WriteEntryAsBool (d, "bool", "bool", true);
If you provide no section name, the entry will be stored in the "__default" section, with all the orphaned entries.
// write key "big_number" in the default section INIFile_WriteEntryAsLong (d, NULL, "big_number", 999999999);
How to read keys and sections from the INI file
The pending functions of the INIFile_WriteEntryAs...() series are the INIFile_ReadEntryAs...() one. They work exactly the same way. Provide the section name you want to read from, the entry name, and optionally a default value to be returned to you in case the section or the entry cannot be read (doesn't exist) in the INI file.
// read key "double" in a section called "test" printf ("key double in section test: %f\n", INIFile_ReadEntryAsDouble (d, "test", "double", -1.0));
Identically, if you don't provide the section name, the parser assumes you're reading from the "__default" section (orphaned keys).
// read key "sound1" in the default section printf ("key sound1 in the default section: %s\n", INIFile_ReadEntryAsString (d, NULL, "sound1", "key not found!"));
How to delete entries and sections in the INI file
You can delete single entries in an INI file under a particular section (or the default section if you don't provide the section name)...
// delete entry "big_number" in the default section INIFile_DeleteEntry (d, NULL, "big_number");
...or whole sections at once.
// delete section "test" and all its contents INIFile_DeleteSection (d, "test");
Note that here you cannot destroy the whole INI data at once if you don't specify the section. To flush the INI dictionary, destroy it and create it anew.
How to save the INI data back to disk
To save the INI file back to disk, use INIFile_SaveINIFile(). This function will re-create the INI file taking in account any changes you made to the data. Once this is done, you can safely destroy the dictionary that served as a support for the INI data.
INIFile_SaveINIFile ("new_file.ini", d); // save the INI data back to a file Dictionary_DestroyDictionary (d); // don't forget to destroy the dictionary
Other resources
If you are interested in a more complicated, bigger and more powerful parser, see KoraX's utilities here. KoraX is a long-time member of Bots United and has done quite a couple quality things for the community, that he released under the terms of the GNU General Public License (GPL).