]> git.ozlabs.org Git - ccan/blob - ccan/ciniparser/ciniparser.c
Tim Post's iniparser module.
[ccan] / ccan / ciniparser / ciniparser.c
1 /* Copyright (c) 2000-2007 by Nicolas Devillard.
2  * Copyright (x) 2009 by Tim Post <tinkertim@gmail.com>
3  * MIT License
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  */
23
24 /** @addtogroup ciniparser
25  * @{
26  */
27 /**
28  *  @file ciniparser.c
29  *  @author N. Devillard
30  *  @date Sep 2007
31  *  @version 3.0
32  *  @brief Parser for ini files.
33  */
34
35 #include <ctype.h>
36 #include "ciniparser.h"
37
38 #define ASCIILINESZ      (1024)
39 #define INI_INVALID_KEY  ((char*) NULL)
40
41 /**
42  * This enum stores the status for each parsed line (internal use only).
43  */
44 typedef enum _line_status_ {
45         LINE_UNPROCESSED,
46         LINE_ERROR,
47         LINE_EMPTY,
48         LINE_COMMENT,
49         LINE_SECTION,
50         LINE_VALUE
51 } line_status;
52
53
54 /**
55  * @brief Convert a string to lowercase.
56  * @param s String to convert.
57  * @return ptr to statically allocated string.
58  *
59  * This function returns a pointer to a statically allocated string
60  * containing a lowercased version of the input string. Do not free
61  * or modify the returned string! Since the returned string is statically
62  * allocated, it will be modified at each function call (not re-entrant).
63  */
64 static char *strlwc(const char *s)
65 {
66         static char l[ASCIILINESZ+1];
67         int i;
68
69         if (s == NULL)
70                 return NULL;
71
72         memset(l, 0, ASCIILINESZ+1);
73         i=0;
74
75         while (s[i] && i < ASCIILINESZ) {
76                 l[i] = (char)tolower((int)s[i]);
77                 i++;
78         }
79
80         l[ASCIILINESZ] = (char) 0;
81
82         return l;
83 }
84
85 /**
86  * @brief Remove blanks at the beginning and the end of a string.
87  * @param s String to parse.
88  * @return ptr to statically allocated string.
89  *
90  * This function returns a pointer to a statically allocated string,
91  * which is identical to the input string, except that all blank
92  * characters at the end and the beg. of the string have been removed.
93  * Do not free or modify the returned string! Since the returned string
94  * is statically allocated, it will be modified at each function call
95  * (not re-entrant).
96  */
97 static char *strstrip(char *s)
98 {
99         static char l[ASCIILINESZ+1];
100         char *last;
101
102         if (s == NULL)
103                 return NULL;
104
105         while (isspace((int)*s) && *s)
106                 s++;
107
108         memset(l, 0, ASCIILINESZ+1);
109         strcpy(l, s);
110         last = l + strlen(l);
111
112         while (last > l) {
113                 if (!isspace((int)*(last-1)))
114                         break;
115                 last --;
116         }
117
118         *last = (char) 0;
119
120         return (char *) l;
121 }
122
123 /**
124  * @brief Load a single line from an INI file
125  * @param input_line Input line, may be concatenated multi-line input
126  * @param section Output space to store section
127  * @param key Output space to store key
128  * @param value Output space to store value
129  * @return line_status value
130  */
131 static
132 line_status ciniparser_line(char *input_line, char *section,
133         char *key, char *value)
134 {
135         line_status sta;
136         char line[ASCIILINESZ+1];
137         int      len;
138
139         strcpy(line, strstrip(input_line));
140         len = (int) strlen(line);
141
142         sta = LINE_UNPROCESSED;
143         if (len < 1) {
144                 /* Empty line */
145                 sta = LINE_EMPTY;
146         } else if (line[0] == '#') {
147                 /* Comment line */
148                 sta = LINE_COMMENT;
149         } else if (line[0] == '[' && line[len-1] == ']') {
150                 /* Section name */
151                 sscanf(line, "[%[^]]", section);
152                 strcpy(section, strstrip(section));
153                 strcpy(section, strlwc(section));
154                 sta = LINE_SECTION;
155         } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
156                    ||  sscanf (line, "%[^=] = '%[^\']'", key, value) == 2
157                    ||  sscanf (line, "%[^=] = %[^;#]", key, value) == 2) {
158                 /* Usual key=value, with or without comments */
159                 strcpy(key, strstrip(key));
160                 strcpy(key, strlwc(key));
161                 strcpy(value, strstrip(value));
162                 /*
163                  * sscanf cannot handle '' or "" as empty values
164                  * this is done here
165                  */
166                 if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
167                         value[0] = 0;
168                 }
169                 sta = LINE_VALUE;
170         } else if (sscanf(line, "%[^=] = %[;#]", key, value) == 2
171                 ||  sscanf(line, "%[^=] %[=]", key, value) == 2) {
172                 /*
173                  * Special cases:
174                  * key=
175                  * key=;
176                  * key=#
177                  */
178                 strcpy(key, strstrip(key));
179                 strcpy(key, strlwc(key));
180                 value[0] = 0;
181                 sta = LINE_VALUE;
182         } else {
183                 /* Generate syntax error */
184                 sta = LINE_ERROR;
185         }
186         return sta;
187 }
188
189 /* The remaining public functions are documented in ciniparser.h */
190
191 int ciniparser_getnsec(dictionary *d)
192 {
193         int i;
194         int nsec;
195
196         if (d == NULL)
197                 return -1;
198
199         nsec = 0;
200         for (i = 0; i < d->size; i++) {
201                 if (d->key[i] == NULL)
202                         continue;
203                 if (strchr(d->key[i], ':') == NULL) {
204                         nsec ++;
205                 }
206         }
207
208         return nsec;
209 }
210
211 char *ciniparser_getsecname(dictionary *d, int n)
212 {
213         int i;
214         int foundsec;
215
216         if (d == NULL || n < 0)
217                 return NULL;
218
219         if (n == 0)
220                 n ++;
221
222         foundsec = 0;
223
224         for (i = 0; i < d->size; i++) {
225                 if (d->key[i] == NULL)
226                         continue;
227                 if (! strchr(d->key[i], ':')) {
228                         foundsec++;
229                         if (foundsec >= n)
230                                 break;
231                 }
232         }
233
234         if (foundsec == n) {
235                 return d->key[i];
236         }
237
238         return (char *) NULL;
239 }
240
241 void ciniparser_dump(dictionary *d, FILE *f)
242 {
243         int i;
244
245         if (d == NULL || f == NULL)
246                 return;
247
248         for (i = 0; i < d->size; i++) {
249                 if (d->key[i] == NULL)
250                         continue;
251                 if (d->val[i] != NULL) {
252                         fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
253                 } else {
254                         fprintf(f, "[%s]=UNDEF\n", d->key[i]);
255                 }
256         }
257
258         return;
259 }
260
261 void ciniparser_dump_ini(dictionary *d, FILE *f)
262 {
263         int i, j;
264         char keym[ASCIILINESZ+1];
265         int nsec;
266         char *secname;
267         int seclen;
268
269         if (d == NULL || f == NULL)
270                 return;
271
272         memset(keym, 0, ASCIILINESZ + 1);
273
274         nsec = ciniparser_getnsec(d);
275         if (nsec < 1) {
276                 /* No section in file: dump all keys as they are */
277                 for (i = 0; i < d->size; i++) {
278                         if (d->key[i] == NULL)
279                                 continue;
280                         fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
281                 }
282                 return;
283         }
284
285         for (i = 0; i < nsec; i++) {
286                 secname = ciniparser_getsecname(d, i);
287                 seclen  = (int)strlen(secname);
288                 fprintf(f, "\n[%s]\n", secname);
289                 snprintf(keym, ASCIILINESZ + 1, "%s:", secname);
290                 for (j = 0; j < d->size; j++) {
291                         if (d->key[j] == NULL)
292                                 continue;
293                         if (!strncmp(d->key[j], keym, seclen+1)) {
294                                 fprintf(f, "%-30s = %s\n",
295                                         d->key[j]+seclen+1,
296                                         d->val[j] ? d->val[j] : "");
297                         }
298                 }
299         }
300         fprintf(f, "\n");
301
302         return;
303 }
304
305 char *ciniparser_getstring(dictionary *d, const char *key, char *def)
306 {
307         char *lc_key;
308         char *sval;
309
310         if (d == NULL || key == NULL)
311                 return def;
312
313         lc_key = strlwc(key);
314         sval = dictionary_get(d, lc_key, def);
315
316         return sval;
317 }
318
319 int ciniparser_getint(dictionary *d, const char *key, int notfound)
320 {
321         char *str;
322
323         str = ciniparser_getstring(d, key, INI_INVALID_KEY);
324
325         if (str == INI_INVALID_KEY)
326                 return notfound;
327
328         return (int) strtol(str, NULL, 10);
329 }
330
331 double ciniparser_getdouble(dictionary *d, char *key, double notfound)
332 {
333         char *str;
334
335         str = ciniparser_getstring(d, key, INI_INVALID_KEY);
336
337         if (str == INI_INVALID_KEY)
338                 return notfound;
339
340         return atof(str);
341 }
342
343 int ciniparser_getboolean(dictionary *d, const char *key, int notfound)
344 {
345         char *c;
346         int ret;
347
348         c = ciniparser_getstring(d, key, INI_INVALID_KEY);
349         if (c == INI_INVALID_KEY)
350                 return notfound;
351
352         switch(c[0]) {
353         case 'y': case 'Y': case '1': case 't': case 'T':
354                 ret = 1;
355                 break;
356         case 'n': case 'N': case '0': case 'f': case 'F':
357                 ret = 0;
358                 break;
359         default:
360                 ret = notfound;
361                 break;
362         }
363
364         return ret;
365 }
366
367 int ciniparser_find_entry(dictionary *ini, char *entry)
368 {
369         int found = 0;
370
371         if (ciniparser_getstring(ini, entry, INI_INVALID_KEY) != INI_INVALID_KEY) {
372                 found = 1;
373         }
374
375         return found;
376 }
377
378 int ciniparser_set(dictionary *d, char *entry, char *val)
379 {
380         return dictionary_set(d, strlwc(entry), val);
381 }
382
383 void ciniparser_unset(dictionary *ini, char *entry)
384 {
385         dictionary_unset(ini, strlwc(entry));
386 }
387
388 dictionary *ciniparser_load(const char *ininame)
389 {
390         FILE *in;
391         char line[ASCIILINESZ+1];
392         char section[ASCIILINESZ+1];
393         char key[ASCIILINESZ+1];
394         char tmp[ASCIILINESZ+1];
395         char val[ASCIILINESZ+1];
396         int  last = 0, len, lineno = 0, errs = 0;
397         dictionary *dict;
398
399         if ((in = fopen(ininame, "r")) == NULL) {
400                 fprintf(stderr, "ciniparser: cannot open %s\n", ininame);
401                 return NULL;
402         }
403
404         dict = dictionary_new(0);
405         if (!dict) {
406                 fclose(in);
407                 return NULL;
408         }
409
410         memset(line, 0, ASCIILINESZ + 1);
411         memset(section, 0, ASCIILINESZ + 1);
412         memset(key, 0, ASCIILINESZ + 1);
413         memset(val, 0, ASCIILINESZ + 1);
414         last = 0;
415
416         while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
417                 lineno++;
418                 len = (int) strlen(line)-1;
419                 /* Safety check against buffer overflows */
420                 if (line[len] != '\n') {
421                         fprintf(stderr,
422                                         "ciniparser: input line too long in %s (%d)\n",
423                                         ininame,
424                                         lineno);
425                         dictionary_del(dict);
426                         fclose(in);
427                         return NULL;
428                 }
429
430                 /* Get rid of \n and spaces at end of line */
431                 while ((len >= 0) &&
432                                 ((line[len] == '\n') || (isspace(line[len])))) {
433                         line[len] = 0;
434                         len--;
435                 }
436
437                 /* Detect multi-line */
438                 if (line[len] == '\\') {
439                         /* Multi-line value */
440                         last = len;
441                         continue;
442                 } else {
443                         last = 0;
444                 }
445
446                 switch (ciniparser_line(line, section, key, val)) {
447                 case LINE_EMPTY:
448                 case LINE_COMMENT:
449                         break;
450
451                 case LINE_SECTION:
452                         errs = dictionary_set(dict, section, NULL);
453                         break;
454
455                 case LINE_VALUE:
456                         snprintf(tmp, ASCIILINESZ + 1, "%s:%s", section, key);
457                         errs = dictionary_set(dict, tmp, val);
458                         break;
459
460                 case LINE_ERROR:
461                         fprintf(stderr, "ciniparser: syntax error in %s (%d):\n",
462                                         ininame, lineno);
463                         fprintf(stderr, "-> %s\n", line);
464                         errs++;
465                         break;
466
467                 default:
468                         break;
469                 }
470                 memset(line, 0, ASCIILINESZ);
471                 last = 0;
472                 if (errs < 0) {
473                         fprintf(stderr, "ciniparser: memory allocation failure\n");
474                         break;
475                 }
476         }
477
478         if (errs) {
479                 dictionary_del(dict);
480                 dict = NULL;
481         }
482
483         fclose(in);
484
485         return dict;
486 }
487
488 void ciniparser_freedict(dictionary *d)
489 {
490         dictionary_del(d);
491 }
492
493 /** @}
494  */