summaryrefslogblamecommitdiff
path: root/src/common/raconf.c
blob: e73154f50523fc769229f6f7f264aafed0e88120 (plain) (tree)
1
2
3
4
  
                             

                                                                   






















                                                                           
              


  





                                          



  

























































































                                                                                                              


                   























































































































































































































































                                                                                                                                                                      



                                                                    

































                                                                                   


                      















                                                                                                                 

                        











                                                                                    


                        





                                                                                     
 




                                  


                         











                                                                                     



                       



                                                                                                 
 
                                  
 




                                  


                       



















                                                                                                                   


                          




















                                                                                                                     


                           



















                                                                                                                     



                         



















                                                                                                                                 

                         
//
// 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()