|
|
@@ -0,0 +1,494 @@
|
|
|
+/* Copyright (c) 2000-2007 by Nicolas Devillard.
|
|
|
+ * Copyright (x) 2009 by Tim Post <tinkertim@gmail.com>
|
|
|
+ * 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 <ctype.h>
|
|
|
+#include "ciniparser.h"
|
|
|
+
|
|
|
+#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;
|
|
|
+
|
|
|
+ memset(l, 0, ASCIILINESZ+1);
|
|
|
+ i=0;
|
|
|
+
|
|
|
+ while (s[i] && i < ASCIILINESZ) {
|
|
|
+ l[i] = (char)tolower((int)s[i]);
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ l[ASCIILINESZ] = (char) 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(char *s)
|
|
|
+{
|
|
|
+ static char l[ASCIILINESZ+1];
|
|
|
+ char *last;
|
|
|
+
|
|
|
+ if (s == NULL)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ while (isspace((int)*s) && *s)
|
|
|
+ s++;
|
|
|
+
|
|
|
+ memset(l, 0, ASCIILINESZ+1);
|
|
|
+ strcpy(l, s);
|
|
|
+ last = l + strlen(l);
|
|
|
+
|
|
|
+ while (last > l) {
|
|
|
+ if (!isspace((int)*(last-1)))
|
|
|
+ break;
|
|
|
+ last --;
|
|
|
+ }
|
|
|
+
|
|
|
+ *last = (char) 0;
|
|
|
+
|
|
|
+ return (char *) 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);
|
|
|
+
|
|
|
+ sta = LINE_UNPROCESSED;
|
|
|
+ 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 (line[len] == '\\') {
|
|
|
+ /* Multi-line value */
|
|
|
+ last = len;
|
|
|
+ continue;
|
|
|
+ } else {
|
|
|
+ last = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+}
|
|
|
+
|
|
|
+/** @}
|
|
|
+ */
|