2024-04-22 00:09:51 +08:00

1401 lines
36 KiB
C

/*********************************************************************************************************
* ------------------------------------------------------------------------------------------------------
* file description
* ------------------------------------------------------------------------------------------------------
* \file ini.h
* \unit ini
* \brief This is a C language version of ini parser
* \author Lamdonn
* \version v1.0.0
* \license GPL-2.0
* \copyright Copyright (C) 2023 Lamdonn.
********************************************************************************************************/
#include "ini.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* dump buffer define */
typedef struct
{
char* address; /**< buffer base address */
unsigned int size; /**< size of buffer */
unsigned int end; /**< end of buffer used */
} BUFFER;
/* iterator define */
typedef struct
{
void *p; /**< iteration pointer */
unsigned int i; /**< iteration index */
} ITERATOR;
/* key-value define */
typedef struct PAIR
{
struct PAIR *next; /**< next pair */
char* key; /**< key */
char* value; /**< value */
} PAIR;
/* section define */
typedef struct SECTION
{
struct SECTION *next; /**< next section */
char* name; /**< section name */
PAIR* pairs; /**< pairs base */
ITERATOR iterator; /**< pair iterator */
int count; /**< pair count */
} SECTION;
/* ini structure define */
typedef struct INI
{
SECTION* sections; /**< sections base */
ITERATOR iterator; /**< section iterator */
int count; /**< section count */
} INI;
static int etype = 0; /**< error type */
static int eline = 0; /**< error line */
#define E(type) etype=(type)
#define iscomment(c) ((c) == '#' || (c) == ';') /* ini supports `#` and `;` style annotations */
/**
* \brief Calculate the smallest power of 2 that is greater than or equal to a given number.
*
* \param[in] x The given number.
* \return The smallest power of 2 greater than or equal to x.
*/
static unsigned int pow2gt(unsigned int x)
{
int b = sizeof(int) * 8;
int i = 1;
--x;
while (i < b)
{
x |= (x >> i);
i <<= 1;
}
return x + 1;
}
/**
* \brief Duplicate a given string.
*
* \param[in] str String to be duplicated.
* \param[in] len Length of the string.
* \return Pointer to the duplicated string if successful, NULL otherwise.
*/
static char* ini_strdup(const char* str, int len)
{
char* s;
/* Allocate memory for the new string */
s = (char*)malloc(len + 1);
if (!s) return NULL;
/* Copy the given string into the allocated memory */
memcpy(s, str, len);
s[len] = '\0';
return s;
}
/**
* \brief Compare the first n1 characters of two strings.
*
* \param[in] s1 The first string to compare
* \param[in] n1 The number of characters to compare in s1
* \param[in] s2 The second string to compare
* \param[in] n2 The number of characters to compare in s2
*
* \return Returns an integer less than, equal to, or greater than zero if the first n1 characters of s1 is found,
* respectively, to be less than, to match, or be greater than the first n2 characters of s2.
*/
static int ini_strnncmp(const char* s1, int n1, const char* s2, int n2)
{
int i = 0;
if (s1 == s2) return 0;
if (!s1) return -1;
if (!s2) return 1;
if (n1 != n2) return n1 - n2;
for (i = 0; i < n1; i++)
{
if (s1[i] != s2[i]) return (s1[i] - s2[i]);
}
return 0;
}
/**
* \brief Compare the first n1 characters of two strings case-insensitively.
*
* \param[in] s1 The first string to compare
* \param[in] n1 The number of characters to compare in s1
* \param[in] s2 The second string to compare
* \param[in] n2 The number of characters to compare in s2
*
* \return Returns an integer less than, equal to, or greater than zero if the first n1 characters of s1 is found,
* respectively, to be less than, to match, or be greater than the first n2 characters of s2.
*/
static int ini_strcsnncmp(const char* s1, int n1, const char* s2, int n2)
{
int i = 0;
if (s1 == s2) return 0;
if (!s1) return -1;
if (!s2) return 1;
if (n1 != n2) return n1 - n2;
for (i = 0; i < n1; i++)
{
if (tolower(s1[i]) != tolower(s2[i])) return (tolower(s1[i]) - tolower(s2[i]));
}
return 0;
}
/**
* \brief confirm whether buf still has the required capacity, otherwise add capacity.
* \param[in] buf: buf handle
* \param[in] needed: required capacity
* \return 1 success or 0 fail
*/
static int expansion(BUFFER* buf, unsigned int needed)
{
char* address;
unsigned int size;
if (!buf || !buf->address) return 0;
needed += buf->end;
if (needed <= buf->size) return 1; /* there is still enough space in the current buf */
size = pow2gt(needed);
address = (char*)realloc(buf->address, size);
if (!address) return 0;
buf->size = size;
buf->address = address;
return 1;
}
#define buf_append(n) expansion(buf, (n)) /* append n size space for buf */
#define buf_putc(c) (buf->address[buf->end++]=(c)) /* put a non zero character into buf */
#define buf_end() (buf->address[buf->end]) /* obtain the tail of buf */
/**
* \brief Get the section at the specified index in an INI file.
*
* \param[in] ini The INI file object
* \param[in] index The index of the section to retrieve
*
* \return Returns a pointer to the section at the specified index, or NULL if the index is out of range
*/
static SECTION* ini_section(ini_t ini, int index)
{
if (index >= ini->count) return NULL;
if (index < ini->iterator.i || !ini->iterator.p || index == 0)
{
ini->iterator.i = 0;
ini->iterator.p = ini->sections;
}
while (ini->iterator.p && ini->iterator.i < index)
{
ini->iterator.p = ((SECTION *)(ini->iterator.p))->next;
ini->iterator.i++;
}
return ini->iterator.p;
}
/**
* \brief Get the pair at the specified index in a section of an INI file.
*
* \param[in] sect The section object
* \param[in] index The index of the pair to retrieve
*
* \return Returns a pointer to the pair at the specified index, or NULL if the index is out of range
*/
static PAIR* section_pair(SECTION *sect, int index)
{
if (index >= sect->count) return NULL;
if (index < sect->iterator.i || !sect->iterator.p || index == 0)
{
sect->iterator.i = 0;
sect->iterator.p = sect->pairs;
}
while (sect->iterator.p && sect->iterator.i < index)
{
sect->iterator.p = ((SECTION *)(sect->iterator.p))->next;
sect->iterator.i++;
}
return sect->iterator.p;
}
/**
* \brief create an ini object.
* \param[in] none
* \return ini object
*/
ini_t ini_create(void)
{
ini_t ini = NULL;
/* Allocate ini structure space */
ini = (ini_t)malloc(sizeof(INI));
if (!ini) return NULL;
/* Initialize ini structure member variables */
ini->sections = NULL;
ini->count = 0;
ini->iterator.p = NULL;
ini->iterator.i = 0;
return ini;
}
/**
* \brief Free the memory allocated for a section in an INI file.
*
* \param[in] sect The section object to be freed
*/
static void section_free(SECTION *sect)
{
PAIR *pair = NULL, *next = NULL;
if (!sect) return;
/* Traverse and free each pair */
pair = sect->pairs;
while (pair)
{
next = pair->next;
if (pair->key) free(pair->key);
if (pair->value) free(pair->value);
free(pair);
pair = next;
}
if (sect->name) free(sect->name);
free(sect);
}
/**
* \brief delete an ini object.
* \param[in] ini object
* \return none
*/
void ini_delete(ini_t ini)
{
SECTION *sect = NULL, *next = NULL;
if (!ini) return;
/* Traverse and free each section */
sect = ini->sections;
while (sect)
{
next = sect->next;
section_free(sect);
sect = next;
}
free(ini);
}
/**
* \brief Find the index of a section in an INI file with a specified name.
*
* \param[in] ini The INI file object
* \param[in] section The name of the section to find
* \param[in] len The length of the section name
*
* \return Returns the index of the section if found, or -1 if the section is not found
*/
static int find_section(ini_t ini, const char* section, int len)
{
int i;
SECTION* sect = NULL;
if (!ini) return -1;
/* Find if a section with the same name already exists */
for (i = 0; i < ini->count; i++)
{
sect = ini_section(ini, i);
if (ini_strnncmp(sect->name, strlen(sect->name), section, len) == 0)
{
return i;
}
}
return -1;
}
/**
* \brief find the index where the section is located.
* \param[in] ini: ini handle
* \param[in] *section: section name
* \return index or negative number not found
*/
int ini_section_index(ini_t ini, const char* section)
{
int i;
SECTION* sect = NULL;
if (!ini) return -1;
if (!section) return -1;
if (ini->iterator.p)
{
/* at the current iteration position */
if (strcmp(((SECTION*)(ini->iterator.p))->name, section) == 0)
{
return ini->iterator.i;
}
}
/* Find if a section with the same name already exists */
for (i = 0; i < ini->count; i++)
{
sect = ini_section(ini, i);
if (strcmp(sect->name, section) == 0)
{
return i;
}
}
return -1;
}
/**
* \brief get the section name of the specified index.
* \param[in] ini: ini handle
* \param[in] index: index
* \return section name or NULL fail
*/
const char* ini_section_name(ini_t ini, int index)
{
SECTION* sect = NULL;
if (!ini) return NULL;
if (index < 0 || index >= ini->count) return NULL;
sect = ini_section(ini, index);
if (!sect) return NULL;
return sect->name;
}
/**
* \brief Add a new section to an INI file with the specified name.
*
* \param[in] ini The INI file object
* \param[in] section The name of the section to add
* \param[in] len The length of the section name
*
* \return Returns a pointer to the newly added section, or NULL if the section cannot be added
*/
static SECTION* add_section(ini_t ini, const char* section, int len)
{
SECTION* sect = NULL;
/* Create a new section and add it to the INI file structure */
sect = (SECTION*)malloc(sizeof(SECTION));
if (!sect) return NULL;
/* Allocate space for section name */
sect->name = ini_strdup(section, len);
if (!sect->name)
{
free(sect);
return NULL;
}
/* Initialize section structure variables */
sect->pairs = NULL;
sect->count = 0;
sect->iterator.p = NULL;
sect->iterator.i = 0;
sect->next = NULL;
/* Link the new section to ini */
if (ini->count == 0) ini->sections = sect;
else ini_section(ini, ini->count - 1)->next = sect;
ini->count++;
return sect;
}
/**
* \brief Find the index of a pair in a section of an INI file with a specified key.
*
* \param[in] sect The section object
* \param[in] key The key of the pair to find
* \param[in] len The length of the key
*
* \return Returns the index of the pair if found, or -1 if the pair is not found
*/
static int find_pair(SECTION *sect, const char *key, int len)
{
int i = 0;
PAIR *pair = NULL;
if (sect->iterator.p)
{
/* at the current iteration position */
if (ini_strcsnncmp(((PAIR*)(sect->iterator.p))->key, strlen(((PAIR*)(sect->iterator.p))->key), key, len) == 0)
{
return sect->iterator.i;
}
}
/* Traverse the pairs in the section and match the same key */
for (i = 0; i < sect->count; i++)
{
pair = section_pair(sect, i);
if (ini_strcsnncmp(pair->key, strlen(pair->key), key, len) == 0)
{
return i;
}
}
return -1; /* No match found */
}
/**
* \brief Add a new key-value pair to a section in an INI file.
*
* \param[in] sect The section object
* \param[in] key The key to add
* \param[in] key_len The length of the key
* \param[in] value The value to add
* \param[in] value_len The length of the value
*
* \return Returns a pointer to the newly added pair, or NULL if the pair cannot be added
*/
static PAIR* add_pair(SECTION* sect, const char* key, int key_len, const char* value, int value_len)
{
PAIR* pair;
if (!sect) return NULL;
/* Allocate pair space and initialize */
pair = (PAIR *)malloc(sizeof(PAIR));
if (!pair) return NULL;
pair->next = NULL;
pair->key = NULL;
pair->value = NULL;
/* duplicate the key */
if (key)
{
pair->key = ini_strdup(key, key_len);
if (!pair->key) goto FAIL;
}
/* Duplicate the value */
if (value)
{
pair->value = ini_strdup(value, value_len);
if (!pair->value) goto FAIL;
}
/* Link the new pair to section */
if (sect->count > 0) section_pair(sect, sect->count - 1)->next = pair;
else sect->pairs = pair;
sect->count++;
return pair;
FAIL:
/* Free the allocated space before exiting the function */
if (pair)
{
if (pair->key) free(pair->key);
if (pair->value) free(pair->value);
free(pair);
}
return NULL;
}
/**
* \brief add section to ini.
* \param[in] ini: ini handle
* \param[in] *section: section name
* \return 1 success or 0 fail
*/
int ini_add_section(ini_t ini, const char* section)
{
int i;
if (!ini) return 0;
if (!section) return 0;
/* Find if a section with the same name already exists */
if (ini_section_index(ini, section) >= 0) return 0;
/* Calculate the length of section name */
for (i = 0;;i++)
{
if (section[i] == '\0') break;
// if (!(section[i] >= ' ' && section[i] <= '~')) return 0; /* Not a printable character */
}
return add_section(ini, section, i) == NULL ? 0 : 1;
}
/**
* \brief remove section from ini.
* \param[in] ini: ini handle
* \param[in] *section: section name
* \return 1 success or 0 fail
*/
int ini_remove_section(ini_t ini, const char* section)
{
int i;
SECTION* sect = NULL;
SECTION* prev = NULL;
if (!ini) return 0;
if (!section) return 0;
/* Find if a section with the same name already exists */
for (i = 0; i < ini->count; i++)
{
sect = ini_section(ini, i);
if (strcmp(sect->name, section) == 0) break;
prev = sect;
}
if (i == ini->count || sect == NULL) return 0;
/* Adjusting linked list */
if (prev) prev->next = sect->next;
else ini->sections = sect->next;
section_free(sect);
ini->count--;
/* reset iterator */
ini->iterator.p = NULL;
return 1;
}
/**
* \brief set the specified value, it will be created if not exist.
* \param[in] ini: ini handle
* \param[in] *section: section name
* \param[in] *key: key
* \param[in] *value: value
* \return 1 success or 0 fail
*/
int ini_set_value(ini_t ini, const char* section, const char* key, const char* value)
{
int i;
char *tvalue = NULL;
SECTION *sect = NULL;
PAIR *pair = NULL;
if (!ini) return 0;
if (!section) return 0;
if (!key) return 0;
if (!value) return 0;
i = ini_section_index(ini, section);
if (i < 0) /* Add this section without it */
{
/* Calculate the length of section name */
for (i = 0;;i++)
{
if (section[i] == '\0') break;
// if (!(section[i] >= ' ' && section[i] <= '~')) return 0; /* Not a printable character */
}
if (add_section(ini, section, i) == NULL) return 0;
sect = ini_section(ini, ini->count - 1);
}
else
{
sect = ini_section(ini, i);
}
i = find_pair(sect, key, strlen(key));
if (i < 0) /* Add this pair without it */
{
/* Calculate the length of section name */
for (i = 0;;i++)
{
if (key[i] == '\0') break;
// if (!(key[i] >= ' ' && key[i] <= '~')) return 0; /* Not a printable character */
}
if (add_pair(sect, key, i, value, strlen(value)) == NULL) return 0;
}
else
{
pair = section_pair(sect, i);
/* Duplicate the value to be set first */
tvalue = ini_strdup(value, strlen(value));
if (!tvalue) return 0;
/* The original value needs to be free */
if (pair->value) free(pair->value);
/* Update value */
pair->value = tvalue;
}
return 1;
}
/**
* \brief get the specified value.
* \param[in] ini: ini handle
* \param[in] *section: section name
* \param[in] *key: key
* \return address of value or NULL fail
*/
const char* ini_get_value(ini_t ini, const char* section, const char* key)
{
int i = 0;
SECTION *sect = NULL;
PAIR *pair = NULL;
if (!ini) return NULL;
if (!section) return NULL;
if (!key) return NULL;
/* Obtain the corresponding index based on the section name */
i = ini_section_index(ini, section);
if (i < 0) return NULL;
/* Obtain the section handle based on the index */
sect = ini_section(ini, i);
if (!sect) return NULL;
/* Obtain the corresponding index based on the key name */
i = find_pair(sect, key, strlen(key));
if (i < 0) return NULL;
/* Obtain the pair handle based on the index */
pair = section_pair(sect, i);
if (!pair) return NULL;
return pair->value;
}
/**
* \brief get the index of the specified key under the section.
* \param[in] ini: ini handle
* \param[in] *section: section name
* \param[in] *key: key
* \return index or negative number not found
*/
int ini_key_index(ini_t ini, const char* section, const char* key)
{
int i = 0;
SECTION *sect = NULL;
PAIR *pair = NULL;
if (!ini) return -1;
if (!section) return -1;
if (!key) return -1;
/* Obtain the corresponding index based on the section name */
i = ini_section_index(ini, section);
if (i < 0) return -1;
/* Obtain the section handle based on the index */
sect = ini_section(ini, i);
if (!sect) return -1;
/* Matching key */
i = find_pair(sect, key, strlen(key));
if (i < 0) return -1;
return i;
}
/**
* \brief get the key name of the specified index under the section.
* \param[in] ini: ini handle
* \param[in] *section: section name
* \param[in] index: index
* \return key name or NULL fail
*/
const char* ini_key_name(ini_t ini, const char* section, int index)
{
int i = 0;
SECTION *sect = NULL;
PAIR *pair = NULL;
if (!ini) return NULL;
if (!section) return NULL;
if (index < 0) return NULL;
/* Obtain the corresponding index based on the section name */
i = ini_section_index(ini, section);
if (i < 0) return NULL;
/* Obtain the section handle based on the index */
sect = ini_section(ini, i);
if (!sect) return NULL;
/* Matching index */
pair = section_pair(sect, index);
if (!pair) return NULL;
return pair->key;
}
/**
* \brief get the section count of ini.
* \param[in] ini: ini handle
* \return section count of ini
*/
int ini_section_count(ini_t ini)
{
if (!ini) return 0;
return ini->count;
}
/**
* \brief get the pair count of ini section.
* \param[in] ini: ini handle
* \param[in] *section: section name
* \return pair count of ini section
*/
int ini_pair_count(ini_t ini, const char* section)
{
SECTION *sect = NULL;
int index = 0;
if (!ini) return 0;
/* Obtain the corresponding index based on the section name */
index = ini_section_index(ini, section);
if (index < 0) return 0;
/* Obtain the section handle based on the index */
sect = ini_section(ini, index);
if (!sect) return 0;
return sect->count;
}
/**
* \brief remove key from ini section.
* \param[in] ini: ini handle
* \param[in] *section: section name
* \param[in] *key: key
* \return 1 success or 0 fail
*/
int ini_remove_key(ini_t ini, const char* section, const char* key)
{
int i = 0;
SECTION *sect = NULL;
PAIR *pair = NULL, *prev = NULL;
if (!ini) return 0;
if (!section) return 0;
if (!key) return 0;
/* Obtain the corresponding index based on the section name */
i = ini_section_index(ini, section);
if (i < 0) return 0;
/* Obtain the section handle based on the index */
sect = ini_section(ini, i);
if (!sect) return 0;
/* Traverse section matching key */
for (i = 0; i < sect->count; i++)
{
pair = section_pair(sect, i);
if (ini_strcsnncmp(pair->key, strlen(pair->key), key, strlen(key)) == 0) break;
prev = pair;
}
if (i == sect->count) return 0; /* Not matched */
/* Adjusting linked list */
if (prev) prev->next = pair->next;
else sect->pairs = pair->next;
/* Free pair space */
if (pair->key) free(pair->key);
if (pair->value) free(pair->value);
free(pair);
sect->count--;
/* Reset iterator */
sect->iterator.p = NULL;
return 1;
}
/**
* \brief Print the contents of an INI file to a buffer.
*
* \param[in] ini The INI file object
* \param[in] buf The buffer to print the INI contents to
*
* \return Returns 1 if the printing is successful, 0 otherwise
*/
static int print_ini(ini_t ini, BUFFER* buf)
{
int i, j, k;
int olen = 0;
SECTION* sect = NULL;
PAIR* pair = NULL;
/* Traverse each section */
for (i = 0; i < ini->count; i++)
{
/* Get section handle */
sect = ini_section(ini, i);
if (!sect) return 0;
/* Print section name */
olen = strlen(sect->name);
if (!buf_append(olen + 3)) return 0;
buf_putc('[');
for (k = 0; k < olen; k++)
{
buf_putc(sect->name[k]);
}
buf_putc(']');
buf_putc('\n');
/* Traverse each pair */
for (j = 0; j < sect->count; j++)
{
/* Get pair handle */
pair = section_pair(sect, j);
/* Print key */
olen = strlen(pair->key);
if (!buf_append(olen + 3)) return 0;
for (k = 0; k < olen; k++)
{
buf_putc(tolower(pair->key[k]));
}
buf_putc(' ');
buf_putc('=');
buf_putc(' ');
/* Get length of value */
olen = 0;
k = 0;
while (1)
{
if (!pair->value[olen]) break;
if (pair->value[olen] == '\n') k++; /* Print `\t` before line breaks */
olen++;
}
if (!buf_append(olen + k + 1)) return 0;
/* Print value */
for (k = 0; k < olen; k++)
{
buf_putc(pair->value[k]);
if (pair->value[k] == '\n') buf_putc('\t');
}
buf_putc('\n');
}
/* Add a blank line to distinguish each section */
if (!buf_append(1)) return 0;
buf_putc('\n');
}
/* End of text */
if (!buf_append(1)) return 0;
buf_end() = '\0';
return 1;
}
/**
* \brief dump the ini object into a string.
* \param[in] ini: ini handle
* \param[in] preset: preset string length
* \param[out] *len: the length of the string actually dumped
* \return dumped string, please release this string space after use
*/
char* ini_dumps(ini_t ini, int preset, int *len)
{
BUFFER p;
if (!ini) return NULL;
/* initialize buffer */
if (preset < 1) preset = 1;
p.address = (char*)malloc(preset);
if (!p.address) return NULL;
p.size = preset;
p.end = 0;
/* Print the ini data to the BUFFER structure */
if (!print_ini(ini, &p))
{
free(p.address);
return NULL;
}
/* Store the length of the generated ini string if the len parameter is provided */
if (len) *len = p.end;
return p.address;
}
/**
* \brief dump the ini object into a file.
* \param[in] ini: ini handle
* \param[in] *filename: file name
* \return length of the dump or negative fail
*/
int ini_file_dump(ini_t ini, char* filename)
{
FILE* f;
char* out;
int len;
if (!ini) return -1;
out = (char *)ini_dumps(ini, 0, &len);
if (!out) return -1;
f = fopen(filename, "w");
if (!f)
{
free(out);
return -1;
}
fwrite(out, 1, len - 1, f);
fclose(f);
free(out);
return len;
}
/**
* \brief Skip leading spaces, tabs, and carriage returns in a string.
*
* \param[in] text The input string to be skipped
*
* \return Returns a pointer to the first non-space, non-tab, non-carriage return character in the string
*/
static const char* skip(const char* text)
{
while (*text && (*text == ' ' || *text == '\r' || *text == '\t'))
{
text++; // Skip spaces, tabs, and carriage returns
}
return text;
}
/**
* \brief Skip trailing spaces, tabs, and carriage returns in a string, starting from the end and moving backwards.
*
* \param[in] text The input string to be skipped
* \param[in] base The base of the string to limit the backward movement
*
* \return Returns a pointer to the last non-space, non-tab, non-carriage return character in the string
*/
static const char* rskip(const char* text, const char* base)
{
while ((text >= base) && (*text == ' ' || *text == '\r' || *text == '\t'))
{
text--;
}
return text;
}
/**
* \brief Find the end of the current line in a string.
*
* \param[in] text The input string
*
* \return Returns a pointer to the end of the current line in the string
*/
static const char* lend(const char* text)
{
while (*text && *text != '\n')
{
text++; // Move to the next character until the end of the line or the end of the string is reached
}
return text; // Return the pointer to the end of the line
}
/**
* \brief Find the scope of a value in a string, considering the given depth.
*
* \param[in] text The input string
* \param[in] depth The depth of the scope to consider
*
* \return Returns a pointer to the end of the scope of the value in the string
*/
static const char* value_scope(const char* text, int depth)
{
const char *s = text;
const char *sentinel = NULL;
s = lend(s);
while (*s)
{
/* Sentinel locate non empty characters on a new line */
sentinel = skip(s + 1);
/* skip comments */
if (iscomment(*sentinel))
{
s = lend(sentinel);
continue;
}
/* The depth of the current row is greater than the depth of the section, indicating that it still belongs to that section */
if (sentinel - s - 1 > depth)
{
s = sentinel;
s = lend(s);
}
/* Exceeding the depth, determine whether to stop detection based on the current sentinel character */
else
{
/* If it is not at the end of the text or line, it indicates that this is a valid character.
* If it exceeds the scope of the section, it will not belong to the scope of this value and can exit the detection.
*/
if (*sentinel != 0 && *sentinel != '\n') break;
/* As an empty line, it is still within the scope of the value */
else s = sentinel;
}
}
/* Starting from the position of the sentry, probe back and remove empty lines (as there are no actual characters) */
while (*s == 0 || *s == '\n')
{
/* Starting from the line break position and detecting backwards */
sentinel = rskip(s - 1, text);
if (*sentinel != 0 && *sentinel != '\n') break;
/* Fallback by an empty line */
s = sentinel;
}
return s;
}
/**
* \brief load ini from string.
* \param[in] *text: string text
* \return ini handle or NULL fail
*/
ini_t ini_loads(const char* text)
{
ini_t ini;
const char *s = NULL, *tail = NULL;
const char *scope = NULL;
char *value = NULL;
int len = 0;
int depth = 0; /* current line depth */
SECTION *sect = NULL;
PAIR *pair = NULL;
if (!text) return NULL;
ini = ini_create();
if (!ini) return NULL;
eline = 1;
etype = INI_E_OK;
while (*text)
{
/* skip useless characters */
s = skip(text);
depth = s - text;
text = s;
/* end of text, exit parsing */
if (*text == 0) break;
/* skip comments */
if (iscomment(*text))
{
text = lend(text);
if (*text == '\n')
{
text++;
eline++;
continue;
}
}
/* Exceeded the range of the previous value and does not belong to the previous value. Starting a new parsing */
if (text >= scope)
{
/* Blank line, skip */
if (*text == '\n')
{
text++;
eline++;
continue;
}
/* Parsing section */
if (*text == '[')
{
/* Locate the end of the line to determine the range of section names */
s = lend(text);
tail = rskip(s - 1, text);
/* The square brackets `[]` do not appear in pairs, and the section name does not hold */
if (*tail != ']')
{
E(INI_E_BRACKETS);
goto FAIL;
}
/* Only one pair of empty square brackets `[]` without a section name */
if (tail - text - 1 <= 0)
{
E(INI_E_SECTION);
goto FAIL;
}
/* Check for duplicates and determine if the section already exists */
if (find_section(ini, text + 1, tail - text - 1) >= 0)
{
E(INI_E_RESECTION);
goto FAIL;
}
/* Add section to ini */
sect = add_section(ini, text + 1, tail - text - 1);
if (!sect)
{
E(INI_E_MEMORY);
goto FAIL;
}
/* Update parsing location */
text = s;
pair = NULL;
}
/* Parsing key-value pair */
else
{
s = text;
tail = NULL;
/* Parse the key and determine its scope */
while (*s)
{
/* The key value pair is incomplete as it breaks a line before parsing the delimiter */
if (*s == '\n')
{
E(INI_E_DELIM);
goto FAIL;
}
/* Parsed key value delimiter, the scope of the key has been determined */
if (*s == '=' || *s == ':')
{
tail = s - 1;
tail = rskip(tail, text);
break;
}
s++;
}
/* Confirmed the scope of the key and determined whether the key is empty */
if (!tail || tail < text)
{
E(INI_E_KEY);
goto FAIL;
}
/* When there is no record of the current section, even if the key is parsed, it is invalid */
if (!sect)
{
E(INI_E_SECTION);
goto FAIL;
}
/* Check for duplicates and determine if the key already exists */
if (find_pair(sect, text, tail - text + 1) >= 0)
{
E(INI_E_REKEY);
goto FAIL;
}
/* Add pair to section */
pair = add_pair(sect, text, tail - text + 1, "", 0);
if (!pair)
{
E(INI_E_MEMORY);
goto FAIL;
}
/* Update parsing location */
len = 0;
text = s + 1; /* skip delimiter '=' and ':' */
/* After determining the key, we need to explore the scope of the value.
*
* The indentation depth of the incoming section is used to determine
* whether the depth of the value is within the depth range of the section.
*/
scope = value_scope(text, depth);
}
}
/* This line belongs to the previous line, still within the range of the previous value */
else
{
/* There is no record of pair, which means there is no key, and the key value pair is not valid */
if (!pair)
{
E(INI_E_MEMORY);
goto FAIL;
}
/* Value can contain multiple lines.
* If the first line value after the key is empty, record the `\n` first
*/
if (*text == '\n')
{
value = realloc(pair->value, len + 1);
if (!value)
{
E(INI_E_MEMORY);
goto FAIL;
}
pair->value = value;
pair->value[len++] = '\n';
pair->value[len] = 0;
}
text = skip(text);
/* Empty line, skip first */
if (*text == '\n')
{
text++;
eline++;
continue;
}
/* Go to the end of the current line and temporarily do not record blank line endings.
*
* If the next line is still within the scope of value, space will be reallocated next time,
* and these blank characters will be added.
*/
tail = lend(text);
s = rskip(tail - 1, text);
/* Reassign space and append the content of the current line */
value = realloc(pair->value, len + (s - text + 1));
if (!value)
{
E(INI_E_MEMORY);
goto FAIL;
}
pair->value = value;
/* Assign and update length */
memcpy(&pair->value[len], text, (s - text + 1));
len += (s - text + 1);
pair->value[len] = 0;
/* Update parsing location */
text = tail;
}
}
return ini;
FAIL:
ini_delete(ini);
return NULL;
}
/**
* \brief load ini from file.
* \param[in] *filename: file name
* \return ini handle or NULL fail
*/
ini_t ini_file_load(const char* filename)
{
FILE* f = NULL;
ini_t ini = NULL;
long len;
char* text = NULL;
if (!filename) return NULL;
eline = 0;
/* open file and get the length of file */
f = fopen(filename, "rb");
if (!f)
{
E(INI_E_OPEN);
goto FAIL;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
/* read file */
text = (char*)malloc(len + 1);
if (text)
{
fread(text, 1, len, f);
fclose(f);
f = NULL;
}
else
{
E(INI_E_MEMORY);
goto FAIL;
}
text[len] = 0;
/* load text */
ini = ini_loads(text);
if (!ini)
{
goto FAIL;
}
free(text);
return ini;
FAIL:
// printf("ini parse error! line %d, error %d.\r\n", eline, etype);
if (f) fclose(f);
if (text) free(text);
return NULL;
}
/**
* \brief obtain parsing error information.
* \param[out] *line: line number where the error occurred
* \param[out] *type: error type, ref INI_E_XXX
* \return 1 has an error or 0 does not exist
*/
int ini_error_info(int* line, int* type)
{
if (etype == INI_E_OK) return 0;
if (line) *line = eline;
if (type) *type = etype;
return 1;
}