// // Athena style config parser // (would be better to have "one" implementation instead of .. 4 :) // // // Author: Florian Wilkemeyer // // Copyright (c) RAthena Project (www.rathena.org) - Licensed under GNU GPL // For more information, see LICENCE in the main folder // #include #include #include #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, "",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, "", 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()