diff options
| author | Clecio Jung | 2023-03-18 18:02:41 -0300 |
|---|---|---|
| committer | Clecio Jung | 2023-03-18 18:02:41 -0300 |
| commit | a3942b3241189a003414874a96732e373e63ccae (patch) | |
| tree | 32df11e16faaecb62b1b407ab49490dffe546cec /ini_file.c | |
| download | libini-a3942b3241189a003414874a96732e373e63ccae.tar.xz libini-a3942b3241189a003414874a96732e373e63ccae.tar.zst | |
Initial commit
Diffstat (limited to 'ini_file.c')
| -rw-r--r-- | ini_file.c | 478 |
1 files changed, 478 insertions, 0 deletions
diff --git a/ini_file.c b/ini_file.c new file mode 100644 index 0000000..f844ec6 --- /dev/null +++ b/ini_file.c @@ -0,0 +1,478 @@ + +/*------------------------------------------------------------------------------ + * SOURCE + *------------------------------------------------------------------------------ + */ + +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ini_file.h" + +/* Most systems do not allow for a line greather than 4 kbytes */ +#define MAX_LINE_SIZE 4096 + +#define INITIAL_NUMBER_OF_SECTIONS 10 +#define INITIAL_NUMBER_OF_PROPERTIES 10 + +size_t get_file_size(FILE *const file) { + long file_size; + fseek(file, 0, SEEK_END); + file_size = ftell(file); + fseek(file, 0, SEEK_SET); + return (size_t)file_size; +} + +/* Remember to free the memory allocated for the returned string */ +char *get_content_from_file(const char *const file_name) { + char *buffer = NULL; + FILE *const file = fopen(file_name, "rb"); + if (file != NULL) { + const size_t file_size = get_file_size(file); + /* Allocate memory to store the entire file */ + buffer = (char *)malloc((file_size + 1)*sizeof(char)); + if (buffer != NULL) { + /* Copy the contents of the file to the buffer */ + const size_t result = fread(buffer, sizeof(char), file_size, file); + buffer[file_size] = '\0'; + if (result != file_size) { + /* Reading file error, free dinamically allocated memory */ + free((void *)buffer); + buffer = NULL; + } + } + fclose(file); + } + return buffer; +} + +struct Ini_File *ini_file_new(void) { + struct Ini_File *ini_file = malloc(sizeof(struct Ini_File)); + if (ini_file != NULL) { + ini_file->sections_size = 0; + ini_file->sections_capacity = INITIAL_NUMBER_OF_SECTIONS; + ini_file->sections = malloc(ini_file->sections_capacity * sizeof(struct Ini_Section)); + if (ini_file->sections == NULL) { + free(ini_file); + return NULL; + } + /* The first section is global to the file */ + if (ini_file_add_section(ini_file, "global") != 0) { + ini_file_free(ini_file); + return NULL; + } + } + return ini_file; +} + +void ini_file_free(struct Ini_File *const ini_file) { + size_t i, j; + if (ini_file == NULL) { + return; + } + for (i = 0; i < ini_file->sections_size; i++) { + free((void *)ini_file->sections[i].name); + for (j = 0; j < ini_file->sections[i].properties_size; j++) { + free((void *)ini_file->sections[i].properties[j].key); + free((void *)ini_file->sections[i].properties[j].value); + } + free(ini_file->sections[i].properties); + ini_file->sections[i].properties_capacity = 0; + ini_file->sections[i].properties_size = 0; + } + free(ini_file->sections); + ini_file->sections_capacity = 0; + ini_file->sections_size = 0; + free((void *)ini_file); +} + +void ini_section_print_to(const struct Ini_Section *const ini_section, FILE *const sink) { + size_t property_index; + if (ini_section == NULL) { + return; + } + fprintf(sink, "[%s]\n", ini_section->name); + for (property_index = 0; property_index < ini_section->properties_size; property_index++) { + fprintf(sink, "%s = %s\n", ini_section->properties[property_index].key, ini_section->properties[property_index].value); + } +} + +void ini_file_print_to(const struct Ini_File *const ini_file, FILE *const sink) { + size_t section_index; + if (ini_file == NULL) { + return; + } + for (section_index = 0; section_index < ini_file->sections_size; section_index++) { + ini_section_print_to(&ini_file->sections[section_index], sink); + putchar('\n'); + } +} + +char *ini_file_error_to_string(const enum Ini_File_Errors error) { + static char *const error_messages[] = { + "No error has occured", + "Couldn't allocate more memory", + "Invalid parameters passed to the function", + "Couldn't open file", + "Expected closing square bracket ']'", + "Expected equals sign '='", + "Expected a value, but found a comment", + "Didn't found the requested section", + "Didn't found the requested property", + "The requested property is not a valid integer number", + "The requested property is not a valid floating point number", + }; +#ifdef _Static_assert + _Static_assert((NUMBER_OF_INI_FILE_ERRORS == (sizeof(error_messages)/sizeof(error_messages[0]))), + "Don't forget to add the messages to this table"); +#endif + return error_messages[error]; +} + +static void advance_white_spaces(char **const str) { + while (isspace((unsigned char) **str)) { + (*str)++; + } +} + +static void advance_string_until(char **const str, const char *const chars) { + while (strchr(chars, **str) == NULL) { + (*str)++; + } +} + +/* TODO: We can have a large buffer of memory and store each new string in there */ +static char *copy_sized_string(const char *const sized_str, const size_t len) { + char *str = malloc(len + 1); + if (str != NULL) { + strncpy(str, sized_str, len); + str[len] = '\0'; + } + return str; +} + +static int ini_file_parse_handle_error(Ini_File_Error_Callback callback, const char *const filename, const size_t line_number, const char *const line, const enum Ini_File_Errors error) { + /* This function is called when we found an error in the parsing. + * So, we report it to the user using the callback provided. + * If the callback returns an integer different from zero, + * we end the parsing and return NULL. */ + if (callback != NULL) { + return callback(filename, line_number, line, error); + } + return 0; +} + +/* TODO: Check for repeated section and key names? */ +/* TODO: Check for spaces inside of section and key names? */ +/* TODO: Sort the sections and keys? This would allow us to use binary search */ +/* TODO: we could allow keys and values to be strings delimited by "" or '', + * which would allow us to use the characters "=,#,;" inside keys and values */ + +/* Remember to free the memory allocated for the returned ini file structure */ +struct Ini_File *ini_file_parse(const char *const filename, Ini_File_Error_Callback callback) { + enum Ini_File_Errors error; + char line[MAX_LINE_SIZE]; + size_t line_number; + FILE *file; + struct Ini_File *ini_file = ini_file_new(); + if (ini_file == NULL) { + ini_file_parse_handle_error(callback, filename, 0, NULL, ini_allocation); + return NULL; + } + file = fopen(filename, "rb"); + if (file == NULL) { + ini_file_parse_handle_error(callback, filename, 0, NULL, ini_couldnt_open_file); + ini_file_free(ini_file); + return NULL; + } + for (line_number = 1; fgets(line, sizeof(line), file) != NULL; line_number++) { + char *cursor = line; + char *key, *value; + size_t key_len, value_len; + advance_white_spaces(&cursor); + /* Discards commments */ + if (strchr("#;", *cursor) != NULL) { + continue; + } + /* Check if is a new section */ + if (*cursor == '[') { + size_t name_len; + char *name; + cursor++; + advance_white_spaces(&cursor); + name = cursor; + advance_string_until(&cursor, "]#;\n"); + if (*cursor != ']') { + if (ini_file_parse_handle_error(callback, filename, line_number, line, ini_expected_clocing_bracket) != 0) { + goto ini_file_parse_error; + } + continue; + } + /* Compute length of the key string and remove trailing whitespaces */ + name_len = (size_t)(cursor - name); + while ((name_len > 0) && (isspace((unsigned char)name[name_len - 1]))) { + name_len--; + } + error = ini_file_add_section_sized(ini_file, name, name_len); + if (error != ini_no_error) { + if (ini_file_parse_handle_error(callback, filename, line_number, line, error) != 0) { + goto ini_file_parse_error; + } + } + /* We just ignore the possible characters after the end of the declaration of the section */ + continue; + } + key = cursor; + advance_string_until(&cursor, "=#;\n"); + if (*cursor != '=') { + /* Found an error, so we report it to the user using the callback provided. + * In case of an error, if the callback returns an integer different from zero, + * we end the parsing and return NULL. */ + if (ini_file_parse_handle_error(callback, filename, line_number, line, ini_expected_equals) != 0) { + goto ini_file_parse_error; + } + continue; + } + /* Compute length of the key string and remove trailing whitespaces */ + key_len = (size_t)(cursor - key); + while ((key_len > 0) && (isspace((unsigned char)key[key_len - 1]))) { + key_len--; + } + cursor++; + advance_white_spaces(&cursor); + /* If it is a commment, we found an error */ + if (strchr("#;", *cursor) != NULL) { + /* Found an error, so we report it to the user using the callback provided. + * In case of an error, if the callback returns an integer different from zero, + * we end the parsing and return NULL. */ + if (ini_file_parse_handle_error(callback, filename, line_number, line, ini_expected_value_got_comment) != 0) { + goto ini_file_parse_error; + } + continue; + } + value = cursor; + advance_string_until(&cursor, "#;\n"); + /* Compute length of the value string and remove trailing whitespaces */ + value_len = (size_t)(cursor - value); + while ((value_len > 0) && (isspace((unsigned char)value[value_len - 1]))) { + value_len--; + } + error = ini_file_add_property_sized(ini_file, key, key_len, value, value_len); + if (error != ini_no_error) { + if (ini_file_parse_handle_error(callback, filename, line_number, line, error) != 0) { + goto ini_file_parse_error; + } + } + } + fclose(file); + return ini_file; +ini_file_parse_error: + ini_file_free(ini_file); + fclose(file); + return NULL; +} + +enum Ini_File_Errors ini_file_add_section_sized(struct Ini_File *const ini_file, const char *const name, const size_t name_len) { + struct Ini_Section *section; + if ((ini_file == NULL) || (name == NULL)) { + return ini_invalid_parameters; + } + if (name_len == 0) { + return ini_invalid_parameters; + } + if ((ini_file->sections_size + 1) >= ini_file->sections_capacity) { + const size_t new_cap = 2 * ini_file->sections_capacity; + struct Ini_Section *const new_sections = realloc(ini_file->sections, new_cap * sizeof(struct Ini_Section)); + if (new_sections == NULL) { + return ini_allocation; + } + ini_file->sections = new_sections; + ini_file->sections_capacity = new_cap; + } + section = &ini_file->sections[ini_file->sections_size]; + section->name = copy_sized_string(name, name_len); + if (section->name == NULL) { + return ini_allocation; + } + section->properties_size = 0; + section->properties_capacity = INITIAL_NUMBER_OF_PROPERTIES; + section->properties = malloc(section->properties_capacity * sizeof(struct Key_Value_Pair)); + if (section->properties == NULL) { + free(section->name); + return ini_allocation; + } + ini_file->sections_size++; + return ini_no_error; +} + +enum Ini_File_Errors ini_file_add_section(struct Ini_File *const ini_file, const char *const name) { + if (name == NULL) { + return ini_invalid_parameters; + } + return ini_file_add_section_sized(ini_file, name, strlen(name)); +} + +enum Ini_File_Errors ini_file_add_property_sized(struct Ini_File *const ini_file, const char *const key, const size_t key_len, const char *const value, const size_t value_len) { + struct Ini_Section *section; + struct Key_Value_Pair *property; + if ((ini_file == NULL) || (key == NULL) || (value == NULL)) { + return ini_invalid_parameters; + } + if ((key_len == 0) || (value_len == 0)) { + return ini_invalid_parameters; + } + if (ini_file->sections_size == 0) { + return ini_allocation; + } + /* Insert the new property at the last section */ + section = &ini_file->sections[ini_file->sections_size - 1]; + if ((section->properties_size + 1) >= section->properties_capacity) { + const size_t new_cap = 2 * section->properties_capacity; + struct Key_Value_Pair *const new_properties = realloc(section->properties, new_cap * sizeof(struct Key_Value_Pair)); + if (new_properties == NULL) { + return ini_allocation; + } + section->properties = new_properties; + section->properties_capacity = new_cap; + } + property = §ion->properties[section->properties_size]; + property->key = copy_sized_string(key, key_len); + if (property->key == NULL) { + return ini_allocation; + } + property->value = copy_sized_string(value, value_len); + if (property->value == NULL) { + free(property->key); + return ini_allocation; + } + section->properties_size++; + return ini_no_error; +} + +enum Ini_File_Errors ini_file_add_property(struct Ini_File *const ini_file, const char *const key, const char *const value) { + if ((key == NULL) || (value == NULL)) { + return ini_invalid_parameters; + } + return ini_file_add_property_sized(ini_file, key, strlen(key), value, strlen(value)); +} + +enum Ini_File_Errors ini_file_save(const struct Ini_File *const ini_file, const char *const filename) { + FILE *file; + if (ini_file == NULL) { + return ini_invalid_parameters; + } + file = fopen(filename, "wb"); + if (file == NULL) { + return ini_couldnt_open_file; + } + ini_file_print_to(ini_file, file); + fclose(file); + return ini_no_error; +} + +enum Ini_File_Errors ini_file_find_section(struct Ini_File *const ini_file, const char *const section, struct Ini_Section **ini_section) { + size_t section_index; + if ((ini_file == NULL) || (section == NULL) || (ini_section == NULL)) { + return ini_invalid_parameters; + } + if (strlen(section) == 0) { + return ini_invalid_parameters; + } + for (section_index = 0; section_index < ini_file->sections_size; section_index++) { + if (strcmp(ini_file->sections[section_index].name, section) == 0) { + *ini_section = &ini_file->sections[section_index]; + return ini_no_error; + } + } + /* Didn't found the requested section */ + return ini_no_such_section; +} + +enum Ini_File_Errors ini_file_find_property(struct Ini_File *const ini_file, const char *const section, const char *const key, char **value) { + struct Ini_Section *ini_section; + size_t property_index; + enum Ini_File_Errors error; + if ((ini_file == NULL) || (section == NULL) || (key == NULL) || (value == NULL)) { + return ini_invalid_parameters; + } + if (strlen(key) == 0) { + return ini_invalid_parameters; + } + error = ini_file_find_section(ini_file, section, &ini_section); + if (error != ini_no_error) { + return error; + } + for (property_index = 0; property_index < ini_section->properties_size; property_index++) { + if (strcmp(ini_section->properties[property_index].key, key) == 0) { + *value = ini_section->properties[property_index].value; + return ini_no_error; + } + } + /* Didn't found the requested property */ + return ini_no_such_property; +} + +enum Ini_File_Errors ini_file_find_integer(struct Ini_File *const ini_file, const char *const section, const char *const key, long *integer) { + char *value, *end; + enum Ini_File_Errors error; + if (integer == NULL) { + return ini_invalid_parameters; + } + error = ini_file_find_property(ini_file, section, key, &value); + if (error != ini_no_error) { + return error; + } + *integer = strtol(value, &end, 10); + if (*end != '\0') { + return ini_not_integer; + } + return ini_no_error; +} + +enum Ini_File_Errors ini_file_find_float(struct Ini_File *const ini_file, const char *const section, const char *const key, double *real) { + char *value, *end; + enum Ini_File_Errors error; + if (real == NULL) { + return ini_invalid_parameters; + } + error = ini_file_find_property(ini_file, section, key, &value); + if (error != ini_no_error) { + return error; + } + *real = strtod(value, &end); + if (*end != '\0') { + return ini_not_float; + } + return ini_no_error; +} + +/*------------------------------------------------------------------------------ + * END + *------------------------------------------------------------------------------ + */ + +/* MIT License + * + * Copyright (c) 2023 CLECIO JUNG <clecio.jung@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ |
