I've written a function to parse configuration files. It doesn't handle file opening, but instead takes an array with the file contents and tries to find fields based on an array containing the fields' names. If found, it puts the content into its respective place.
The only rules it has so far are:
- each field is delimited by a newline
'\n'
- case doesn't matter for the field's name
- after a field's name has been found, leading spaces and tabs are irrelevant and discarded
- lines containing unrecognized fields are ignored
- the character separating the field's name and its respective content must be specified in the field's name
Example file:
FIELD 1: CONTENT 1 FIELD 2: CONTENT 2 FIELD 3: CONTENT 3 FIELD 4: CONTENT 4 FIELD 5: CONTENT 5
Here's an example call:
char *configuration = load_file(file_name);
char *field1;
char *field2;
char *field3;
char *field4;
char *field5;
static const char *fields[] = {
"FIELD 1:",
"FIELD 2:",
"FIELD 3:",
"FIELD 4:",
"FIELD 5:",
0
};
char **destinations[] = {
&field1,
&field2,
&field3,
&field4,
&field5,
0
};
//Parse and load fields
parse(destinations, fields, configuration);
And the function:
//strings allocated must be freed by the caller
static void parse(char ***destinations, const char **fields, const char *source)
{
char buffer[BUFFER_SIZE];
int i, j, buffer_position;
while(*source != '\0')
{
for(i = 0; fields[i] != 0; ++i)
{
//check if first character match, case insensitive
if(*fields[i] == *source || inverse_case(*fields[i]) == *source)
{
//check if subsequent characters match
for(j = 0; fields[i][j] != '\0'; ++j)
{
if(fields[i][j] != source[j] && inverse_case(fields[i][j]) != source[j]) break;
}
//it's not the right field, look for another
if(fields[i][j] != '\0') continue;
//found, update pointer position
source += j;
//skip leading spaces and tabs
while(*source == ' ' || *source == '\t') ++source;
//collect content
for(buffer_position = 0; *source != '\n' && buffer_position < BUFFER_SIZE - 1; ++buffer_position, ++source)
{
buffer[buffer_position] = *source;
}
buffer[buffer_position] = '\0';
//if it's an empty line, nothing to copy
if(buffer_position > 0)
{
//allocate memory for destination and copy
if((*destinations[i] = malloc(buffer_position + 1)) != 0)
{
COPY(*destinations[i], buffer);
}
//if there's an error on malloc(), we just skip the field and notify the user
else
{
printf("Error: malloc(%d)\n", buffer_position + 1);
}
}
//since we found a field that matches, no need to keep looking
//break the for loop, skip the while and process next line
goto next_iteration;
}
}
//no matches for the current line just skip it entirely
while(*++source && *source != '\n');
next_iteration:
++source;
}
}
My questions:
- Would it be considered better if the line delimiter was defined by the caller?
- By adding the options to not ignore spaces and tabs, and set case sensitivity on/off, would the parameter list be considered too big?