/* Copyright (c) 2000-2007 by Nicolas Devillard. * Copyright (x) 2009 by Tim Post * MIT License * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /** @addtogroup ciniparser * @{ */ /** * @file ciniparser.c * @author N. Devillard * @date Sep 2007 * @version 3.0 * @brief Parser for ini files. */ #include #include #define ASCIILINESZ (1024) #define INI_INVALID_KEY ((char*) NULL) /** * This enum stores the status for each parsed line (internal use only). */ typedef enum _line_status_ { LINE_UNPROCESSED, LINE_ERROR, LINE_EMPTY, LINE_COMMENT, LINE_SECTION, LINE_VALUE } line_status; /** * @brief Convert a string to lowercase. * @param s String to convert. * @return ptr to statically allocated string. * * This function returns a pointer to a statically allocated string * containing a lowercased version of the input string. Do not free * or modify the returned string! Since the returned string is statically * allocated, it will be modified at each function call (not re-entrant). */ static char *strlwc(const char *s) { static char l[ASCIILINESZ+1]; int i; if (s == NULL) return NULL; for (i = 0; s[i] && i < ASCIILINESZ; i++) l[i] = tolower(s[i]); l[i] = '\0'; return l; } /** * @brief Remove blanks at the beginning and the end of a string. * @param s String to parse. * @return ptr to statically allocated string. * * This function returns a pointer to a statically allocated string, * which is identical to the input string, except that all blank * characters at the end and the beg. of the string have been removed. * Do not free or modify the returned string! Since the returned string * is statically allocated, it will be modified at each function call * (not re-entrant). */ static char *strstrip(const char *s) { static char l[ASCIILINESZ+1]; unsigned int i, numspc; if (s == NULL) return NULL; while (isspace(*s)) s++; for (i = numspc = 0; s[i] && i < ASCIILINESZ; i++) { l[i] = s[i]; if (isspace(l[i])) numspc++; else numspc = 0; } l[i - numspc] = '\0'; return l; } /** * @brief Load a single line from an INI file * @param input_line Input line, may be concatenated multi-line input * @param section Output space to store section * @param key Output space to store key * @param value Output space to store value * @return line_status value */ static line_status ciniparser_line(char *input_line, char *section, char *key, char *value) { line_status sta; char line[ASCIILINESZ+1]; int len; strcpy(line, strstrip(input_line)); len = (int) strlen(line); if (len < 1) { /* Empty line */ sta = LINE_EMPTY; } else if (line[0] == '#') { /* Comment line */ sta = LINE_COMMENT; } else if (line[0] == '[' && line[len-1] == ']') { /* Section name */ sscanf(line, "[%[^]]", section); strcpy(section, strstrip(section)); strcpy(section, strlwc(section)); sta = LINE_SECTION; } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2 || sscanf (line, "%[^=] = '%[^\']'", key, value) == 2 || sscanf (line, "%[^=] = %[^;#]", key, value) == 2) { /* Usual key=value, with or without comments */ strcpy(key, strstrip(key)); strcpy(key, strlwc(key)); strcpy(value, strstrip(value)); /* * sscanf cannot handle '' or "" as empty values * this is done here */ if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) { value[0] = 0; } sta = LINE_VALUE; } else if (sscanf(line, "%[^=] = %[;#]", key, value) == 2 || sscanf(line, "%[^=] %[=]", key, value) == 2) { /* * Special cases: * key= * key=; * key=# */ strcpy(key, strstrip(key)); strcpy(key, strlwc(key)); value[0] = 0; sta = LINE_VALUE; } else { /* Generate syntax error */ sta = LINE_ERROR; } return sta; } /* The remaining public functions are documented in ciniparser.h */ int ciniparser_getnsec(dictionary *d) { int i; int nsec; if (d == NULL) return -1; nsec = 0; for (i = 0; i < d->size; i++) { if (d->key[i] == NULL) continue; if (strchr(d->key[i], ':') == NULL) { nsec ++; } } return nsec; } char *ciniparser_getsecname(dictionary *d, int n) { int i; int foundsec; if (d == NULL || n < 0) return NULL; if (n == 0) n ++; foundsec = 0; for (i = 0; i < d->size; i++) { if (d->key[i] == NULL) continue; if (! strchr(d->key[i], ':')) { foundsec++; if (foundsec >= n) break; } } if (foundsec == n) { return d->key[i]; } return (char *) NULL; } void ciniparser_dump(dictionary *d, FILE *f) { int i; if (d == NULL || f == NULL) return; for (i = 0; i < d->size; i++) { if (d->key[i] == NULL) continue; if (d->val[i] != NULL) { fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]); } else { fprintf(f, "[%s]=UNDEF\n", d->key[i]); } } return; } void ciniparser_dump_ini(dictionary *d, FILE *f) { int i, j; char keym[ASCIILINESZ+1]; int nsec; char *secname; int seclen; if (d == NULL || f == NULL) return; memset(keym, 0, ASCIILINESZ + 1); nsec = ciniparser_getnsec(d); if (nsec < 1) { /* No section in file: dump all keys as they are */ for (i = 0; i < d->size; i++) { if (d->key[i] == NULL) continue; fprintf(f, "%s = %s\n", d->key[i], d->val[i]); } return; } for (i = 0; i < nsec; i++) { secname = ciniparser_getsecname(d, i); seclen = (int)strlen(secname); fprintf(f, "\n[%s]\n", secname); snprintf(keym, ASCIILINESZ + 1, "%s:", secname); for (j = 0; j < d->size; j++) { if (d->key[j] == NULL) continue; if (!strncmp(d->key[j], keym, seclen+1)) { fprintf(f, "%-30s = %s\n", d->key[j]+seclen+1, d->val[j] ? d->val[j] : ""); } } } fprintf(f, "\n"); return; } char *ciniparser_getstring(dictionary *d, const char *key, char *def) { char *lc_key; char *sval; if (d == NULL || key == NULL) return def; lc_key = strlwc(key); sval = dictionary_get(d, lc_key, def); return sval; } int ciniparser_getint(dictionary *d, const char *key, int notfound) { char *str; str = ciniparser_getstring(d, key, INI_INVALID_KEY); if (str == INI_INVALID_KEY) return notfound; return (int) strtol(str, NULL, 10); } double ciniparser_getdouble(dictionary *d, char *key, double notfound) { char *str; str = ciniparser_getstring(d, key, INI_INVALID_KEY); if (str == INI_INVALID_KEY) return notfound; return atof(str); } int ciniparser_getboolean(dictionary *d, const char *key, int notfound) { char *c; int ret; c = ciniparser_getstring(d, key, INI_INVALID_KEY); if (c == INI_INVALID_KEY) return notfound; switch(c[0]) { case 'y': case 'Y': case '1': case 't': case 'T': ret = 1; break; case 'n': case 'N': case '0': case 'f': case 'F': ret = 0; break; default: ret = notfound; break; } return ret; } int ciniparser_find_entry(dictionary *ini, char *entry) { int found = 0; if (ciniparser_getstring(ini, entry, INI_INVALID_KEY) != INI_INVALID_KEY) { found = 1; } return found; } int ciniparser_set(dictionary *d, char *entry, char *val) { return dictionary_set(d, strlwc(entry), val); } void ciniparser_unset(dictionary *ini, char *entry) { dictionary_unset(ini, strlwc(entry)); } dictionary *ciniparser_load(const char *ininame) { FILE *in; char line[ASCIILINESZ+1]; char section[ASCIILINESZ+1]; char key[ASCIILINESZ+1]; char tmp[ASCIILINESZ+1]; char val[ASCIILINESZ+1]; int last = 0, len, lineno = 0, errs = 0; dictionary *dict; if ((in = fopen(ininame, "r")) == NULL) { fprintf(stderr, "ciniparser: cannot open %s\n", ininame); return NULL; } dict = dictionary_new(0); if (!dict) { fclose(in); return NULL; } memset(line, 0, ASCIILINESZ + 1); memset(section, 0, ASCIILINESZ + 1); memset(key, 0, ASCIILINESZ + 1); memset(val, 0, ASCIILINESZ + 1); last = 0; while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) { lineno++; len = (int) strlen(line)-1; /* Safety check against buffer overflows */ if (line[len] != '\n') { fprintf(stderr, "ciniparser: input line too long in %s (%d)\n", ininame, lineno); dictionary_del(dict); fclose(in); return NULL; } /* Get rid of \n and spaces at end of line */ while ((len >= 0) && ((line[len] == '\n') || (isspace(line[len])))) { line[len] = 0; len--; } /* Detect multi-line */ if (len >= 0 && line[len] == '\\') { /* Multi-line value */ last = len; continue; } switch (ciniparser_line(line, section, key, val)) { case LINE_EMPTY: case LINE_COMMENT: break; case LINE_SECTION: errs = dictionary_set(dict, section, NULL); break; case LINE_VALUE: snprintf(tmp, ASCIILINESZ + 1, "%s:%s", section, key); errs = dictionary_set(dict, tmp, val); break; case LINE_ERROR: fprintf(stderr, "ciniparser: syntax error in %s (%d):\n", ininame, lineno); fprintf(stderr, "-> %s\n", line); errs++; break; default: break; } memset(line, 0, ASCIILINESZ); last = 0; if (errs < 0) { fprintf(stderr, "ciniparser: memory allocation failure\n"); break; } } if (errs) { dictionary_del(dict); dict = NULL; } fclose(in); return dict; } void ciniparser_freedict(dictionary *d) { dictionary_del(d); } /** @} */