//
// Athena style config parser
// (would be better to have "one" implementation instead of .. 4 :)
//
//
// Author: Florian Wilkemeyer <fw@f-ws.de>
//
// Copyright (c) RAthena Project (www.rathena.org) - Licensed under GNU GPL
// For more information, see LICENCE in the main folder
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../common/cbasetypes.h"
#include "../common/showmsg.h"
#include "../common/db.h"
#include "../common/malloc.h"
#include "../common/raconf.h"
#define SECTION_LEN 32
#define VARNAME_LEN 64
struct raconf {
DBMap *db;
};
struct conf_value {
int64 intval;
bool bval;
double floatval;
size_t strval_len; // not includung \0
char strval[16];
};
static struct conf_value *makeValue(const char *key, char *val, size_t val_len) {
struct conf_value *v;
char *p;
size_t sz;
sz = sizeof(struct conf_value);
if (val_len >= sizeof(v->strval))
sz += (val_len - sizeof(v->strval) + 1);
v = (struct conf_value *)aCalloc(1, sizeof(struct conf_value));
if (v == NULL) {
ShowFatalError("raconf: makeValue => Out of Memory while allocating new node.\n");
return NULL;
}
memcpy(v->strval, val, val_len);
v->strval[val_len+1] = '\0';
v->strval_len = val_len;
// Parse boolean value:
if ((val_len == 4) && (strncmpi("true", val, 4) == 0))
v->bval = true;
else if ((val_len == 3) && (strncmpi("yes", val, 3) == 0))
v->bval = true;
else if ((val_len == 3) && (strncmpi("oui", val, 3) == 0))
v->bval = true;
else if ((val_len == 2) && (strncmpi("si", val, 2) == 0))
v->bval = true;
else if ((val_len == 2) && (strncmpi("ja", val, 2) == 0))
v->bval = true;
else if ((val_len == 1) && (*val == '1'))
v->bval = true;
else if ((val_len == 5) && (strncmpi("false", val, 5) == 0))
v->bval = false;
else if ((val_len == 2) && (strncmpi("no", val, 2) == 0))
v->bval = false;
else if ((val_len == 3) && (strncmpi("non", val, 3) == 0))
v->bval = false;
else if ((val_len == 2) && (strncmpi("no", val, 2) == 0))
v->bval = false;
else if ((val_len == 4) && (strncmpi("nein", val, 4) == 0))
v->bval = false;
else if ((val_len == 1) && (*val == '0'))
v->bval = false;
else
v->bval = false; // assume false.
// Parse number
// Supported formats:
// prefix: 0x hex .
// postix: h for hex
// b for bin (dual)
if ((val_len >= 1 && (val[val_len] == 'h')) || (val_len >= 2 && (val[0] == '0' && val[1] == 'x'))) {//HEX!
if (val[val_len] == 'h') {
val[val_len]= '\0';
v->intval = strtoull(val, NULL, 16);
val[val_len] = 'h';
} else
v->intval = strtoull(&val[2], NULL, 16);
} else if (val_len >= 1 && (val[val_len] == 'b')) { //BIN
val[val_len] = '\0';
v->intval = strtoull(val, NULL, 2);
val[val_len] = 'b';
} else if (*val >='0' && *val <= '9') { // begins with normal digit, so assume its dec.
// is it float?
bool is_float = false;
for (p = val; *p != '\0'; p++) {
if (*p == '.') {
v->floatval = strtod(val, NULL);
v->intval = (int64) v->floatval;
is_float = true;
break;
}
}
if (is_float == false) {
v->intval = strtoull(val, NULL, 10);
v->floatval = (double) v->intval;
}
} else {
// Everything else: lets use boolean for fallback
if (v->bval == true)
v->intval = 1;
else
v->intval = 0;
}
return v;
}//end: makeValue()
static bool configParse(raconf inst, const char *fileName)
{
FILE *fp;
char line[4096];
char currentSection[SECTION_LEN];
char *p;
char c;
int linecnt;
size_t linelen;
size_t currentSection_len;
fp = fopen(fileName, "r");
if (fp == NULL) {
ShowError("configParse: cannot open '%s' for reading.\n", fileName);
return false;
}
// Start with empty section:
currentSection[0] = '\0';
currentSection_len = 0;
//
linecnt = 0;
while (1) {
linecnt++;
if (fgets(line, sizeof(line), fp) != line)
break;
linelen = strlen(line);
p = line;
// Skip whitespaces from beginning (space and tab)
_line_begin_skip_whities:
c = *p;
if (c == ' ' || c == '\t') {
p++;
linelen--;
goto _line_begin_skip_whities;
}
// Remove linebreaks as (cr or lf) and whitespaces from line end!
_line_end_skip_whities_and_breaks:
c = p[linelen-1];
if (c == '\r' || c == '\n' || c == ' ' || c == '\t') {
p[--linelen] = '\0';
goto _line_end_skip_whities_and_breaks;
}
// Empty line?
// or line starts with comment (commented out)?
if (linelen == 0 || (p[0] == '/' && p[1] == '/') || p[0] == ';')
continue;
// Variable names can contain:
// A-Za-z-_.0-9
//
// Sections start with [ .. ] (INI Style)
//
c = *p;
// check what we have.. :)
if (c == '[') { // got section!
// Got Section!
// Search for ]
char *start = (p+1);
while (1) {
++p;
c = *p;
if (c == '\0') {
ShowError("Syntax Error: unterminated Section name in %s:%u (expected ']')\n", fileName, linecnt);
fclose(fp);
return false;
} else if (c == ']') { // closing backet (section name termination)
if ((p - start + 1) > (sizeof(currentSection))) {
ShowError("Syntax Error: Section name in %s:%u is too large (max Supported length: %u chars)\n", fileName, linecnt, sizeof(currentSection)-1);
fclose(fp);
return false;
}
// Set section!
*p = '\0'; // add termination here.
memcpy(currentSection, start, (p-start)+1); // we'll copy \0, too! (we replaced the ] backet with \0.)
currentSection_len = (p-start);
break;
} else if ((c >= '0' && c <= '9') || (c == '-') || (c == ' ') || (c == '_') || (c == '.') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
// skip .. (allowed char / specifier)
continue;
} else {
ShowError("Syntax Error: Invalid Character '%c' in %s:%u (offset %u) for Section name.\n", c, fileName, linecnt, (p-line));
fclose(fp);
return false;
}
}//endwhile: parse section name
} else if ((c >= '0' && c <= '9') || (c == '-') || (c == '_') || (c == '.') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
// Got variable!
// Search for '=' or ':' wich termiantes the name
char *start = p;
char *valuestart = NULL;
size_t start_len;
while (1) {
++p;
c = *p;
if (c == '\0') {
ShowError("Syntax Error: unterminated Variable name in %s:%u\n", fileName, linecnt);
fclose(fp);
return false;
} else if ((c == '=') || (c == ':')) {
// got name termination
*p = '\0'; // Terminate it so (start) will hold the pointer to the name.
break;
} else if ((c >= '0' && c <= '9') || (c == '-') || (c == '_') || (c == '.') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
// skip .. allowed char
continue;
} else {
ShowError("Syntax Error: Invalid Character '%c' in %s:%u (offset %u) for Variable name.\n", c, fileName, linecnt, (p-line));
fclose(fp);
return false;
}
}//endwhile: parse var name
start_len = (p-start);
if (start_len >= VARNAME_LEN) {
ShowError("%s:%u Variable length exceeds limit of %u Characters.\n", fileName, linecnt, VARNAME_LEN-1);
fclose(fp);
return false;
} else if (start_len == 0) {
ShowError("%s:%u Empty Variable name is not allowed.\n", fileName, linecnt);
fclose(fp);
return false;
}
valuestart = (p+1);
// Skip whitespace from begin of value (tab and space)
_skip_value_begin_whities:
c = *valuestart;
if (c == ' ' || c == '\t') {
valuestart++;
goto _skip_value_begin_whities;
}
// Scan for value termination,
// wich can be \0 or comment start (// or ; (INI) )
//
p = valuestart;
while (1) {
c = *p;
if (c == '\0') {
// Terminated by line end.
break;
} else if (c == '/' && p[1] == '/') {
// terminated by c++ style comment.
*p = '\0';
break;
} else if (c == ';') {
// terminated by ini style comment.
*p = '\0';
break;
}
p++;
}//endwhile: search var value end.
// Strip whitespaces from end of value.
if (valuestart != p) { // not empty!
p--;
_strip_value_end_whities:
c = *p;
if (c == ' ' || c == '\t') {
*p = '\0';
p--;
goto _strip_value_end_whities;
}
p++;
}
// Buildin Hook:
if (stricmp(start, "import") == 0) {
if (configParse(inst, valuestart) != true) {
ShowError("%s:%u - Import of '%s' failed!\n", fileName, linecnt, valuestart);
}
} else {
// put it to db.
struct conf_value *v, *o;
char key[(SECTION_LEN+VARNAME_LEN+1+1) ]; //+1 for delimiter, +1 for termination.
size_t section_len;
if (*currentSection == '\0') { // empty / none
strncpy(key, "<unnamed>",9);
section_len = 9;
} else {
strncpy(key, currentSection, currentSection_len);
section_len = currentSection_len;
}
key[section_len] = '.'; // Delim
strncpy(&key[section_len+1], start, start_len);
key[section_len + start_len + 1] = '\0';
v = makeValue(key, valuestart, (p-valuestart));
// Try to get the old one before
o = strdb_get(inst->db, key);
if (o != NULL) {
strdb_remove(inst->db, key);
aFree(o); //
}
strdb_put(inst->db, key, v);
}
} else {
ShowError("Syntax Error: unexpected Character '%c' in %s:%u (offset %u)\n", c, fileName, linecnt, (p-line));
fclose(fp);
return false;
}
}
fclose(fp);
return true;
}//end: configParse()
#define MAKEKEY(dest, section, key) { size_t section_len, key_len; \
if(section == NULL || *section == '\0'){ \
strncpy(dest, "<unnamed>", 9); \
section_len = 9; \
}else{ \
section_len = strlen(section); \
strncpy(dest, section, section_len); \
} \
\
dest[section_len] = '.'; \
\
key_len = strlen(key); \
strncpy(&dest[section_len+1], key, key_len); \
dest[section_len + key_len + 1] = '\0'; \
}
raconf raconf_parse(const char *file_name)
{
struct raconf *rc;
rc = aCalloc(1, sizeof(struct raconf));
if (rc == NULL) {
ShowFatalError("raconf_parse: failed to allocate memory for new handle\n");
return NULL;
}
rc->db = strdb_alloc(DB_OPT_BASE | DB_OPT_DUP_KEY, 98);
//
if (configParse(rc, file_name) != true) {
ShowError("Failed to Parse Configuration file '%s'\n", file_name);
}
return rc;
}//end: raconf_parse()
void raconf_destroy(raconf rc)
{
DBIterator *iter;
struct conf_value *v;
// Clear all entrys in db.
iter = db_iterator(rc->db);
for (v = (struct conf_value *)dbi_first(iter); dbi_exists(iter); v = (struct conf_value *)dbi_next(iter)) {
aFree(v);
}
dbi_destroy(iter);
db_destroy(rc->db);
aFree(rc);
}//end: raconf_destroy()
bool raconf_getbool(raconf rc, const char *section, const char *key, bool _default)
{
char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1];
struct conf_value *v;
MAKEKEY(keystr, section, key);
v = strdb_get(rc->db, keystr);
if (v == NULL)
return _default;
else
return v->bval;
}//end: raconf_getbool()
float raconf_getfloat(raconf rc,const char *section, const char *key, float _default)
{
char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1];
struct conf_value *v;
MAKEKEY(keystr, section, key);
v = strdb_get(rc->db, keystr);
if (v == NULL)
return _default;
else
return (float)v->floatval;
}//end: raconf_getfloat()
int64 raconf_getint(raconf rc, const char *section, const char *key, int64 _default)
{
char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1];
struct conf_value *v;
MAKEKEY(keystr, section, key);
v = strdb_get(rc->db, keystr);
if (v == NULL)
return _default;
else
return v->intval;
}//end: raconf_getint()
const char *raconf_getstr(raconf rc, const char *section, const char *key, const char *_default)
{
char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1];
struct conf_value *v;
MAKEKEY(keystr, section, key);
v = strdb_get(rc->db, keystr);
if (v == NULL)
return _default;
else
return v->strval;
}//end: raconf_getstr()
bool raconf_getboolEx(raconf rc, const char *section, const char *fallback_section, const char *key, bool _default)
{
char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1];
struct conf_value *v;
MAKEKEY(keystr, section, key);
v = strdb_get(rc->db, keystr);
if (v == NULL) {
MAKEKEY(keystr, fallback_section, key);
v = strdb_get(rc->db, keystr);
if (v == NULL) {
return _default;
} else {
return v->bval;
}
} else {
return v->bval;
}
}//end: raconf_getboolEx()
float raconf_getfloatEx(raconf rc,const char *section, const char *fallback_section, const char *key, float _default)
{
char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1];
struct conf_value *v;
MAKEKEY(keystr, section, key);
v = strdb_get(rc->db, keystr);
if (v == NULL) {
MAKEKEY(keystr, fallback_section, key);
v = strdb_get(rc->db, keystr);
if (v == NULL) {
return _default;
} else {
return (float)v->floatval;
}
} else {
return (float)v->floatval;
}
}//end: raconf_getfloatEx()
int64 raconf_getintEx(raconf rc, const char *section, const char *fallback_section, const char *key, int64 _default)
{
char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1];
struct conf_value *v;
MAKEKEY(keystr, section, key);
v = strdb_get(rc->db, keystr);
if (v == NULL) {
MAKEKEY(keystr, fallback_section, key);
v = strdb_get(rc->db, keystr);
if (v == NULL) {
return _default;
} else {
return v->intval;
}
} else {
return v->intval;
}
}//end: raconf_getintEx()
const char *raconf_getstrEx(raconf rc, const char *section, const char *fallback_section, const char *key, const char *_default)
{
char keystr[SECTION_LEN + VARNAME_LEN + 1 + 1];
struct conf_value *v;
MAKEKEY(keystr, section, key);
v = strdb_get(rc->db, keystr);
if (v == NULL) {
MAKEKEY(keystr, fallback_section, key);
v = strdb_get(rc->db, keystr);
if (v == NULL) {
return _default;
} else {
return v->strval;
}
} else {
return v->strval;
}
}//end: raconf_getstrEx()