Rusty's cleanup to ciniparser.c's strstrip and strlwc
[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         for (i = 0; s[i] && i < ASCIILINESZ; i++)
73                 l[i] = tolower(s[i]);
74         l[i] = '\0';
75         return l;
76 }
77
78 /**
79  * @brief Remove blanks at the beginning and the end of a string.
80  * @param s String to parse.
81  * @return ptr to statically allocated string.
82  *
83  * This function returns a pointer to a statically allocated string,
84  * which is identical to the input string, except that all blank
85  * characters at the end and the beg. of the string have been removed.
86  * Do not free or modify the returned string! Since the returned string
87  * is statically allocated, it will be modified at each function call
88  * (not re-entrant).
89  */
90 static char *strstrip(const char *s)
91 {
92         static char l[ASCIILINESZ+1];
93         unsigned int i, numspc;
94
95         if (s == NULL)
96                 return NULL;
97
98         while (isspace(*s))
99                 s++;
100
101         for (i = numspc = 0; s[i] && i < ASCIILINESZ; i++) {
102                 l[i] = s[i];
103                 if (isspace(l[i]))
104                         numspc++;
105                 else
106                         numspc = 0;
107         }
108         l[i - numspc] = '\0';
109         return l;
110 }
111
112 /**
113  * @brief Load a single line from an INI file
114  * @param input_line Input line, may be concatenated multi-line input
115  * @param section Output space to store section
116  * @param key Output space to store key
117  * @param value Output space to store value
118  * @return line_status value
119  */
120 static
121 line_status ciniparser_line(char *input_line, char *section,
122         char *key, char *value)
123 {
124         line_status sta;
125         char line[ASCIILINESZ+1];
126         int      len;
127
128         strcpy(line, strstrip(input_line));
129         len = (int) strlen(line);
130
131         sta = LINE_UNPROCESSED;
132         if (len < 1) {
133                 /* Empty line */
134                 sta = LINE_EMPTY;
135         } else if (line[0] == '#') {
136                 /* Comment line */
137                 sta = LINE_COMMENT;
138         } else if (line[0] == '[' && line[len-1] == ']') {
139                 /* Section name */
140                 sscanf(line, "[%[^]]", section);
141                 strcpy(section, strstrip(section));
142                 strcpy(section, strlwc(section));
143                 sta = LINE_SECTION;
144         } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
145                    ||  sscanf (line, "%[^=] = '%[^\']'", key, value) == 2
146                    ||  sscanf (line, "%[^=] = %[^;#]", key, value) == 2) {
147                 /* Usual key=value, with or without comments */
148                 strcpy(key, strstrip(key));
149                 strcpy(key, strlwc(key));
150                 strcpy(value, strstrip(value));
151                 /*
152                  * sscanf cannot handle '' or "" as empty values
153                  * this is done here
154                  */
155                 if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
156                         value[0] = 0;
157                 }
158                 sta = LINE_VALUE;
159         } else if (sscanf(line, "%[^=] = %[;#]", key, value) == 2
160                 ||  sscanf(line, "%[^=] %[=]", key, value) == 2) {
161                 /*
162                  * Special cases:
163                  * key=
164                  * key=;
165                  * key=#
166                  */
167                 strcpy(key, strstrip(key));
168                 strcpy(key, strlwc(key));
169                 value[0] = 0;
170                 sta = LINE_VALUE;
171         } else {
172                 /* Generate syntax error */
173                 sta = LINE_ERROR;
174         }
175         return sta;
176 }
177
178 /* The remaining public functions are documented in ciniparser.h */
179
180 int ciniparser_getnsec(dictionary *d)
181 {
182         int i;
183         int nsec;
184
185         if (d == NULL)
186                 return -1;
187
188         nsec = 0;
189         for (i = 0; i < d->size; i++) {
190                 if (d->key[i] == NULL)
191                         continue;
192                 if (strchr(d->key[i], ':') == NULL) {
193                         nsec ++;
194                 }
195         }
196
197         return nsec;
198 }
199
200 char *ciniparser_getsecname(dictionary *d, int n)
201 {
202         int i;
203         int foundsec;
204
205         if (d == NULL || n < 0)
206                 return NULL;
207
208         if (n == 0)
209                 n ++;
210
211         foundsec = 0;
212
213         for (i = 0; i < d->size; i++) {
214                 if (d->key[i] == NULL)
215                         continue;
216                 if (! strchr(d->key[i], ':')) {
217                         foundsec++;
218                         if (foundsec >= n)
219                                 break;
220                 }
221         }
222
223         if (foundsec == n) {
224                 return d->key[i];
225         }
226
227         return (char *) NULL;
228 }
229
230 void ciniparser_dump(dictionary *d, FILE *f)
231 {
232         int i;
233
234         if (d == NULL || f == NULL)
235                 return;
236
237         for (i = 0; i < d->size; i++) {
238                 if (d->key[i] == NULL)
239                         continue;
240                 if (d->val[i] != NULL) {
241                         fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
242                 } else {
243                         fprintf(f, "[%s]=UNDEF\n", d->key[i]);
244                 }
245         }
246
247         return;
248 }
249
250 void ciniparser_dump_ini(dictionary *d, FILE *f)
251 {
252         int i, j;
253         char keym[ASCIILINESZ+1];
254         int nsec;
255         char *secname;
256         int seclen;
257
258         if (d == NULL || f == NULL)
259                 return;
260
261         memset(keym, 0, ASCIILINESZ + 1);
262
263         nsec = ciniparser_getnsec(d);
264         if (nsec < 1) {
265                 /* No section in file: dump all keys as they are */
266                 for (i = 0; i < d->size; i++) {
267                         if (d->key[i] == NULL)
268                                 continue;
269                         fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
270                 }
271                 return;
272         }
273
274         for (i = 0; i < nsec; i++) {
275                 secname = ciniparser_getsecname(d, i);
276                 seclen  = (int)strlen(secname);
277                 fprintf(f, "\n[%s]\n", secname);
278                 snprintf(keym, ASCIILINESZ + 1, "%s:", secname);
279                 for (j = 0; j < d->size; j++) {
280                         if (d->key[j] == NULL)
281                                 continue;
282                         if (!strncmp(d->key[j], keym, seclen+1)) {
283                                 fprintf(f, "%-30s = %s\n",
284                                         d->key[j]+seclen+1,
285                                         d->val[j] ? d->val[j] : "");
286                         }
287                 }
288         }
289         fprintf(f, "\n");
290
291         return;
292 }
293
294 char *ciniparser_getstring(dictionary *d, const char *key, char *def)
295 {
296         char *lc_key;
297         char *sval;
298
299         if (d == NULL || key == NULL)
300                 return def;
301
302         lc_key = strlwc(key);
303         sval = dictionary_get(d, lc_key, def);
304
305         return sval;
306 }
307
308 int ciniparser_getint(dictionary *d, const char *key, int notfound)
309 {
310         char *str;
311
312         str = ciniparser_getstring(d, key, INI_INVALID_KEY);
313
314         if (str == INI_INVALID_KEY)
315                 return notfound;
316
317         return (int) strtol(str, NULL, 10);
318 }
319
320 double ciniparser_getdouble(dictionary *d, char *key, double notfound)
321 {
322         char *str;
323
324         str = ciniparser_getstring(d, key, INI_INVALID_KEY);
325
326         if (str == INI_INVALID_KEY)
327                 return notfound;
328
329         return atof(str);
330 }
331
332 int ciniparser_getboolean(dictionary *d, const char *key, int notfound)
333 {
334         char *c;
335         int ret;
336
337         c = ciniparser_getstring(d, key, INI_INVALID_KEY);
338         if (c == INI_INVALID_KEY)
339                 return notfound;
340
341         switch(c[0]) {
342         case 'y': case 'Y': case '1': case 't': case 'T':
343                 ret = 1;
344                 break;
345         case 'n': case 'N': case '0': case 'f': case 'F':
346                 ret = 0;
347                 break;
348         default:
349                 ret = notfound;
350                 break;
351         }
352
353         return ret;
354 }
355
356 int ciniparser_find_entry(dictionary *ini, char *entry)
357 {
358         int found = 0;
359
360         if (ciniparser_getstring(ini, entry, INI_INVALID_KEY) != INI_INVALID_KEY) {
361                 found = 1;
362         }
363
364         return found;
365 }
366
367 int ciniparser_set(dictionary *d, char *entry, char *val)
368 {
369         return dictionary_set(d, strlwc(entry), val);
370 }
371
372 void ciniparser_unset(dictionary *ini, char *entry)
373 {
374         dictionary_unset(ini, strlwc(entry));
375 }
376
377 dictionary *ciniparser_load(const char *ininame)
378 {
379         FILE *in;
380         char line[ASCIILINESZ+1];
381         char section[ASCIILINESZ+1];
382         char key[ASCIILINESZ+1];
383         char tmp[ASCIILINESZ+1];
384         char val[ASCIILINESZ+1];
385         int  last = 0, len, lineno = 0, errs = 0;
386         dictionary *dict;
387
388         if ((in = fopen(ininame, "r")) == NULL) {
389                 fprintf(stderr, "ciniparser: cannot open %s\n", ininame);
390                 return NULL;
391         }
392
393         dict = dictionary_new(0);
394         if (!dict) {
395                 fclose(in);
396                 return NULL;
397         }
398
399         memset(line, 0, ASCIILINESZ + 1);
400         memset(section, 0, ASCIILINESZ + 1);
401         memset(key, 0, ASCIILINESZ + 1);
402         memset(val, 0, ASCIILINESZ + 1);
403         last = 0;
404
405         while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
406                 lineno++;
407                 len = (int) strlen(line)-1;
408                 /* Safety check against buffer overflows */
409                 if (line[len] != '\n') {
410                         fprintf(stderr,
411                                         "ciniparser: input line too long in %s (%d)\n",
412                                         ininame,
413                                         lineno);
414                         dictionary_del(dict);
415                         fclose(in);
416                         return NULL;
417                 }
418
419                 /* Get rid of \n and spaces at end of line */
420                 while ((len >= 0) &&
421                                 ((line[len] == '\n') || (isspace(line[len])))) {
422                         line[len] = 0;
423                         len--;
424                 }
425
426                 /* Detect multi-line */
427                 if (line[len] == '\\') {
428                         /* Multi-line value */
429                         last = len;
430                         continue;
431                 } else {
432                         last = 0;
433                 }
434
435                 switch (ciniparser_line(line, section, key, val)) {
436                 case LINE_EMPTY:
437                 case LINE_COMMENT:
438                         break;
439
440                 case LINE_SECTION:
441                         errs = dictionary_set(dict, section, NULL);
442                         break;
443
444                 case LINE_VALUE:
445                         snprintf(tmp, ASCIILINESZ + 1, "%s:%s", section, key);
446                         errs = dictionary_set(dict, tmp, val);
447                         break;
448
449                 case LINE_ERROR:
450                         fprintf(stderr, "ciniparser: syntax error in %s (%d):\n",
451                                         ininame, lineno);
452                         fprintf(stderr, "-> %s\n", line);
453                         errs++;
454                         break;
455
456                 default:
457                         break;
458                 }
459                 memset(line, 0, ASCIILINESZ);
460                 last = 0;
461                 if (errs < 0) {
462                         fprintf(stderr, "ciniparser: memory allocation failure\n");
463                         break;
464                 }
465         }
466
467         if (errs) {
468                 dictionary_del(dict);
469                 dict = NULL;
470         }
471
472         fclose(in);
473
474         return dict;
475 }
476
477 void ciniparser_freedict(dictionary *d)
478 {
479         dictionary_del(d);
480 }
481
482 /** @}
483  */