michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @file iniparser.c michael@0: @author N. Devillard michael@0: @date Sep 2007 michael@0: @version 3.0 michael@0: @brief Parser for ini files. michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: /* michael@0: $Id: iniparser.c,v 2.19 2011-03-02 20:15:13 ndevilla Exp $ michael@0: $Revision: 2.19 $ michael@0: $Date: 2011-03-02 20:15:13 $ michael@0: */ michael@0: /*---------------------------- Includes ------------------------------------*/ michael@0: #include michael@0: #include "iniparser.h" michael@0: michael@0: /*---------------------------- Defines -------------------------------------*/ michael@0: #define ASCIILINESZ (1024) michael@0: #define INI_INVALID_KEY ((char*)-1) michael@0: michael@0: /*--------------------------------------------------------------------------- michael@0: Private to this module michael@0: ---------------------------------------------------------------------------*/ michael@0: /** michael@0: * This enum stores the status for each parsed line (internal use only). michael@0: */ michael@0: typedef enum _line_status_ { michael@0: LINE_UNPROCESSED, michael@0: LINE_ERROR, michael@0: LINE_EMPTY, michael@0: LINE_COMMENT, michael@0: LINE_SECTION, michael@0: LINE_VALUE michael@0: } line_status ; michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Convert a string to lowercase. michael@0: @param s String to convert. michael@0: @return ptr to statically allocated string. michael@0: michael@0: This function returns a pointer to a statically allocated string michael@0: containing a lowercased version of the input string. Do not free michael@0: or modify the returned string! Since the returned string is statically michael@0: allocated, it will be modified at each function call (not re-entrant). michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: static char * strlwc(char * s) michael@0: { michael@0: static char l[ASCIILINESZ+1]; michael@0: int i ; michael@0: michael@0: if (s==NULL) return NULL ; michael@0: memset(l, 0, ASCIILINESZ+1); michael@0: i=0 ; michael@0: while (s[i] && i l) { michael@0: if (!isspace((int)*(last-1))) michael@0: break ; michael@0: last -- ; michael@0: } michael@0: *last = (char)0; michael@0: return (char*)l ; michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Get number of sections in a dictionary michael@0: @param d Dictionary to examine michael@0: @return int Number of sections found in dictionary michael@0: michael@0: This function returns the number of sections found in a dictionary. michael@0: The test to recognize sections is done on the string stored in the michael@0: dictionary: a section name is given as "section" whereas a key is michael@0: stored as "section:key", thus the test looks for entries that do not michael@0: contain a colon. michael@0: michael@0: This clearly fails in the case a section name contains a colon, but michael@0: this should simply be avoided. michael@0: michael@0: This function returns -1 in case of error. michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: int iniparser_getnsec(dictionary * d) michael@0: { michael@0: int i ; michael@0: int nsec ; michael@0: michael@0: if (d==NULL) return -1 ; michael@0: nsec=0 ; michael@0: for (i=0 ; isize ; i++) { michael@0: if (d->key[i]==NULL) michael@0: continue ; michael@0: if (strchr(d->key[i], ':')==NULL) { michael@0: nsec ++ ; michael@0: } michael@0: } michael@0: return nsec ; michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Get name for section n in a dictionary. michael@0: @param d Dictionary to examine michael@0: @param n Section number (from 0 to nsec-1). michael@0: @return Pointer to char string michael@0: michael@0: This function locates the n-th section in a dictionary and returns michael@0: its name as a pointer to a string statically allocated inside the michael@0: dictionary. Do not free or modify the returned string! michael@0: michael@0: This function returns NULL in case of error. michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: char * iniparser_getsecname(dictionary * d, int n) michael@0: { michael@0: int i ; michael@0: int foundsec ; michael@0: michael@0: if (d==NULL || n<0) return NULL ; michael@0: foundsec=0 ; michael@0: for (i=0 ; isize ; i++) { michael@0: if (d->key[i]==NULL) michael@0: continue ; michael@0: if (strchr(d->key[i], ':')==NULL) { michael@0: foundsec++ ; michael@0: if (foundsec>n) michael@0: break ; michael@0: } michael@0: } michael@0: if (foundsec<=n) { michael@0: return NULL ; michael@0: } michael@0: return d->key[i] ; michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Dump a dictionary to an opened file pointer. michael@0: @param d Dictionary to dump. michael@0: @param f Opened file pointer to dump to. michael@0: @return void michael@0: michael@0: This function prints out the contents of a dictionary, one element by michael@0: line, onto the provided file pointer. It is OK to specify @c stderr michael@0: or @c stdout as output files. This function is meant for debugging michael@0: purposes mostly. michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: void iniparser_dump(dictionary * d, FILE * f) michael@0: { michael@0: int i ; michael@0: michael@0: if (d==NULL || f==NULL) return ; michael@0: for (i=0 ; isize ; i++) { michael@0: if (d->key[i]==NULL) michael@0: continue ; michael@0: if (d->val[i]!=NULL) { michael@0: fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]); michael@0: } else { michael@0: fprintf(f, "[%s]=UNDEF\n", d->key[i]); michael@0: } michael@0: } michael@0: return ; michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Save a dictionary to a loadable ini file michael@0: @param d Dictionary to dump michael@0: @param f Opened file pointer to dump to michael@0: @return void michael@0: michael@0: This function dumps a given dictionary into a loadable ini file. michael@0: It is Ok to specify @c stderr or @c stdout as output files. michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: void iniparser_dump_ini(dictionary * d, FILE * f) michael@0: { michael@0: int i, j ; michael@0: char keym[ASCIILINESZ+1]; michael@0: int nsec ; michael@0: char * secname ; michael@0: int seclen ; michael@0: michael@0: if (d==NULL || f==NULL) return ; michael@0: michael@0: nsec = iniparser_getnsec(d); michael@0: if (nsec<1) { michael@0: /* No section in file: dump all keys as they are */ michael@0: for (i=0 ; isize ; i++) { michael@0: if (d->key[i]==NULL) michael@0: continue ; michael@0: fprintf(f, "%s = %s\n", d->key[i], d->val[i]); michael@0: } michael@0: return ; michael@0: } michael@0: for (i=0 ; isize ; j++) { michael@0: if (d->key[j]==NULL) michael@0: continue ; michael@0: if (!strncmp(d->key[j], keym, seclen+1)) { michael@0: fprintf(f, michael@0: "%-30s = %s\n", michael@0: d->key[j]+seclen+1, michael@0: d->val[j] ? d->val[j] : ""); michael@0: } michael@0: } michael@0: } michael@0: fprintf(f, "\n"); michael@0: return ; michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Get the string associated to a key michael@0: @param d Dictionary to search michael@0: @param key Key string to look for michael@0: @param def Default value to return if key not found. michael@0: @return pointer to statically allocated character string michael@0: michael@0: This function queries a dictionary for a key. A key as read from an michael@0: ini file is given as "section:key". If the key cannot be found, michael@0: the pointer passed as 'def' is returned. michael@0: The returned char pointer is pointing to a string allocated in michael@0: the dictionary, do not free or modify it. michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: char * iniparser_getstring(dictionary * d, char * key, char * def) michael@0: { michael@0: char * lc_key ; michael@0: char * sval ; michael@0: michael@0: if (d==NULL || key==NULL) michael@0: return def ; michael@0: michael@0: lc_key = strlwc(key); michael@0: sval = dictionary_get(d, lc_key, def); michael@0: return sval ; michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Get the string associated to a key, convert to an int michael@0: @param d Dictionary to search michael@0: @param key Key string to look for michael@0: @param notfound Value to return in case of error michael@0: @return integer michael@0: michael@0: This function queries a dictionary for a key. A key as read from an michael@0: ini file is given as "section:key". If the key cannot be found, michael@0: the notfound value is returned. michael@0: michael@0: Supported values for integers include the usual C notation michael@0: so decimal, octal (starting with 0) and hexadecimal (starting with 0x) michael@0: are supported. Examples: michael@0: michael@0: "42" -> 42 michael@0: "042" -> 34 (octal -> decimal) michael@0: "0x42" -> 66 (hexa -> decimal) michael@0: michael@0: Warning: the conversion may overflow in various ways. Conversion is michael@0: totally outsourced to strtol(), see the associated man page for overflow michael@0: handling. michael@0: michael@0: Credits: Thanks to A. Becker for suggesting strtol() michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: int iniparser_getint(dictionary * d, char * key, int notfound) michael@0: { michael@0: char * str ; michael@0: michael@0: str = iniparser_getstring(d, key, INI_INVALID_KEY); michael@0: if (str==INI_INVALID_KEY) return notfound ; michael@0: return (int)strtol(str, NULL, 0); michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Get the string associated to a key, convert to a double michael@0: @param d Dictionary to search michael@0: @param key Key string to look for michael@0: @param notfound Value to return in case of error michael@0: @return double michael@0: michael@0: This function queries a dictionary for a key. A key as read from an michael@0: ini file is given as "section:key". If the key cannot be found, michael@0: the notfound value is returned. michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: double iniparser_getdouble(dictionary * d, char * key, double notfound) michael@0: { michael@0: char * str ; michael@0: michael@0: str = iniparser_getstring(d, key, INI_INVALID_KEY); michael@0: if (str==INI_INVALID_KEY) return notfound ; michael@0: return atof(str); michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Get the string associated to a key, convert to a boolean michael@0: @param d Dictionary to search michael@0: @param key Key string to look for michael@0: @param notfound Value to return in case of error michael@0: @return integer michael@0: michael@0: This function queries a dictionary for a key. A key as read from an michael@0: ini file is given as "section:key". If the key cannot be found, michael@0: the notfound value is returned. michael@0: michael@0: A true boolean is found if one of the following is matched: michael@0: michael@0: - A string starting with 'y' michael@0: - A string starting with 'Y' michael@0: - A string starting with 't' michael@0: - A string starting with 'T' michael@0: - A string starting with '1' michael@0: michael@0: A false boolean is found if one of the following is matched: michael@0: michael@0: - A string starting with 'n' michael@0: - A string starting with 'N' michael@0: - A string starting with 'f' michael@0: - A string starting with 'F' michael@0: - A string starting with '0' michael@0: michael@0: The notfound value returned if no boolean is identified, does not michael@0: necessarily have to be 0 or 1. michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: int iniparser_getboolean(dictionary * d, char * key, int notfound) michael@0: { michael@0: char * c ; michael@0: int ret ; michael@0: michael@0: c = iniparser_getstring(d, key, INI_INVALID_KEY); michael@0: if (c==INI_INVALID_KEY) return notfound ; michael@0: if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') { michael@0: ret = 1 ; michael@0: } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') { michael@0: ret = 0 ; michael@0: } else { michael@0: ret = notfound ; michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Finds out if a given entry exists in a dictionary michael@0: @param ini Dictionary to search michael@0: @param entry Name of the entry to look for michael@0: @return integer 1 if entry exists, 0 otherwise michael@0: michael@0: Finds out if a given entry exists in the dictionary. Since sections michael@0: are stored as keys with NULL associated values, this is the only way michael@0: of querying for the presence of sections in a dictionary. michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: int iniparser_find_entry( michael@0: dictionary * ini, michael@0: char * entry michael@0: ) michael@0: { michael@0: int found=0 ; michael@0: if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) { michael@0: found = 1 ; michael@0: } michael@0: return found ; michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Set an entry in a dictionary. michael@0: @param ini Dictionary to modify. michael@0: @param entry Entry to modify (entry name) michael@0: @param val New value to associate to the entry. michael@0: @return int 0 if Ok, -1 otherwise. michael@0: michael@0: If the given entry can be found in the dictionary, it is modified to michael@0: contain the provided value. If it cannot be found, -1 is returned. michael@0: It is Ok to set val to NULL. michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: int iniparser_set(dictionary * ini, char * entry, char * val) michael@0: { michael@0: return dictionary_set(ini, strlwc(entry), val) ; michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Delete an entry in a dictionary michael@0: @param ini Dictionary to modify michael@0: @param entry Entry to delete (entry name) michael@0: @return void michael@0: michael@0: If the given entry can be found, it is deleted from the dictionary. michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: void iniparser_unset(dictionary * ini, char * entry) michael@0: { michael@0: dictionary_unset(ini, strlwc(entry)); michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Load a single line from an INI file michael@0: @param input_line Input line, may be concatenated multi-line input michael@0: @param section Output space to store section michael@0: @param key Output space to store key michael@0: @param value Output space to store value michael@0: @return line_status value michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: static line_status iniparser_line( michael@0: char * input_line, michael@0: char * section, michael@0: char * key, michael@0: char * value) michael@0: { michael@0: line_status sta ; michael@0: char line[ASCIILINESZ+1]; michael@0: int len ; michael@0: michael@0: strcpy(line, strstrip(input_line)); michael@0: len = (int)strlen(line); michael@0: michael@0: sta = LINE_UNPROCESSED ; michael@0: if (len<1) { michael@0: /* Empty line */ michael@0: sta = LINE_EMPTY ; michael@0: } else if (line[0]=='#' || line[0]==';') { michael@0: /* Comment line */ michael@0: sta = LINE_COMMENT ; michael@0: } else if (line[0]=='[' && line[len-1]==']') { michael@0: /* Section name */ michael@0: sscanf(line, "[%[^]]", section); michael@0: strcpy(section, strstrip(section)); michael@0: strcpy(section, strlwc(section)); michael@0: sta = LINE_SECTION ; michael@0: } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2 michael@0: || sscanf (line, "%[^=] = '%[^\']'", key, value) == 2 michael@0: || sscanf (line, "%[^=] = %[^;#]", key, value) == 2) { michael@0: /* Usual key=value, with or without comments */ michael@0: strcpy(key, strstrip(key)); michael@0: strcpy(key, strlwc(key)); michael@0: strcpy(value, strstrip(value)); michael@0: /* michael@0: * sscanf cannot handle '' or "" as empty values michael@0: * this is done here michael@0: */ michael@0: if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) { michael@0: value[0]=0 ; michael@0: } michael@0: sta = LINE_VALUE ; michael@0: } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2 michael@0: || sscanf(line, "%[^=] %[=]", key, value) == 2) { michael@0: /* michael@0: * Special cases: michael@0: * key= michael@0: * key=; michael@0: * key=# michael@0: */ michael@0: strcpy(key, strstrip(key)); michael@0: strcpy(key, strlwc(key)); michael@0: value[0]=0 ; michael@0: sta = LINE_VALUE ; michael@0: } else { michael@0: /* Generate syntax error */ michael@0: sta = LINE_ERROR ; michael@0: } michael@0: return sta ; michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Parse an ini file and return an allocated dictionary object michael@0: @param ininame Name of the ini file to read. michael@0: @return Pointer to newly allocated dictionary michael@0: michael@0: This is the parser for ini files. This function is called, providing michael@0: the name of the file to be read. It returns a dictionary object that michael@0: should not be accessed directly, but through accessor functions michael@0: instead. michael@0: michael@0: The returned dictionary must be freed using iniparser_freedict(). michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: dictionary * iniparser_load(char * ininame) michael@0: { michael@0: FILE * in ; michael@0: michael@0: char line [ASCIILINESZ+1] ; michael@0: char section [ASCIILINESZ+1] ; michael@0: char key [ASCIILINESZ+1] ; michael@0: char tmp [ASCIILINESZ+1] ; michael@0: char val [ASCIILINESZ+1] ; michael@0: michael@0: int last=0 ; michael@0: int len ; michael@0: int lineno=0 ; michael@0: int errs=0; michael@0: michael@0: dictionary * dict ; michael@0: michael@0: if ((in=fopen(ininame, "r"))==NULL) { michael@0: fprintf(stderr, "iniparser: cannot open %s\n", ininame); michael@0: return NULL ; michael@0: } michael@0: michael@0: dict = dictionary_new(0) ; michael@0: if (!dict) { michael@0: fclose(in); michael@0: return NULL ; michael@0: } michael@0: michael@0: memset(line, 0, ASCIILINESZ); michael@0: memset(section, 0, ASCIILINESZ); michael@0: memset(key, 0, ASCIILINESZ); michael@0: memset(val, 0, ASCIILINESZ); michael@0: last=0 ; michael@0: michael@0: while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) { michael@0: lineno++ ; michael@0: len = (int)strlen(line)-1; michael@0: if (len==0) michael@0: continue; michael@0: /* Safety check against buffer overflows */ michael@0: if (line[len]!='\n') { michael@0: fprintf(stderr, michael@0: "iniparser: input line too long in %s (%d)\n", michael@0: ininame, michael@0: lineno); michael@0: dictionary_del(dict); michael@0: fclose(in); michael@0: return NULL ; michael@0: } michael@0: /* Get rid of \n and spaces at end of line */ michael@0: while ((len>=0) && michael@0: ((line[len]=='\n') || (isspace(line[len])))) { michael@0: line[len]=0 ; michael@0: len-- ; michael@0: } michael@0: /* Detect multi-line */ michael@0: if (line[len]=='\\') { michael@0: /* Multi-line value */ michael@0: last=len ; michael@0: continue ; michael@0: } else { michael@0: last=0 ; michael@0: } michael@0: switch (iniparser_line(line, section, key, val)) { michael@0: case LINE_EMPTY: michael@0: case LINE_COMMENT: michael@0: break ; michael@0: michael@0: case LINE_SECTION: michael@0: errs = dictionary_set(dict, section, NULL); michael@0: break ; michael@0: michael@0: case LINE_VALUE: michael@0: sprintf(tmp, "%s:%s", section, key); michael@0: errs = dictionary_set(dict, tmp, val) ; michael@0: break ; michael@0: michael@0: case LINE_ERROR: michael@0: fprintf(stderr, "iniparser: syntax error in %s (%d):\n", michael@0: ininame, michael@0: lineno); michael@0: fprintf(stderr, "-> %s\n", line); michael@0: errs++ ; michael@0: break; michael@0: michael@0: default: michael@0: break ; michael@0: } michael@0: memset(line, 0, ASCIILINESZ); michael@0: last=0; michael@0: if (errs<0) { michael@0: fprintf(stderr, "iniparser: memory allocation failure\n"); michael@0: break ; michael@0: } michael@0: } michael@0: if (errs) { michael@0: dictionary_del(dict); michael@0: dict = NULL ; michael@0: } michael@0: fclose(in); michael@0: return dict ; michael@0: } michael@0: michael@0: /*-------------------------------------------------------------------------*/ michael@0: /** michael@0: @brief Free all memory associated to an ini dictionary michael@0: @param d Dictionary to free michael@0: @return void michael@0: michael@0: Free all memory associated to an ini dictionary. michael@0: It is mandatory to call this function before the dictionary object michael@0: gets out of the current context. michael@0: */ michael@0: /*--------------------------------------------------------------------------*/ michael@0: void iniparser_freedict(dictionary * d) michael@0: { michael@0: dictionary_del(d); michael@0: } michael@0: michael@0: /* vim: set ts=4 et sw=4 tw=75 */