1
2/*-------------------------------------------------------------------------*/
3/**
4   @file    iniparser.c
5   @author  N. Devillard
6   @brief   Parser for ini files.
7*/
8/*--------------------------------------------------------------------------*/
9/*---------------------------- Includes ------------------------------------*/
10#include <ctype.h>
11#include "iniparser.h"
12
13/*---------------------------- Defines -------------------------------------*/
14#define ASCIILINESZ         (1024)
15#define INI_INVALID_KEY     ((char*)-1)
16
17/*---------------------------------------------------------------------------
18                        Private to this module
19 ---------------------------------------------------------------------------*/
20/**
21 * This enum stores the status for each parsed line (internal use only).
22 */
23typedef enum _line_status_ {
24    LINE_UNPROCESSED,
25    LINE_ERROR,
26    LINE_EMPTY,
27    LINE_COMMENT,
28    LINE_SECTION,
29    LINE_VALUE
30} line_status ;
31
32/*-------------------------------------------------------------------------*/
33/**
34  @brief    Convert a string to lowercase.
35  @param    s   String to convert.
36  @return   ptr to statically allocated string.
37
38  This function returns a pointer to a statically allocated string
39  containing a lowercased version of the input string. Do not free
40  or modify the returned string! Since the returned string is statically
41  allocated, it will be modified at each function call (not re-entrant).
42 */
43/*--------------------------------------------------------------------------*/
44static char * strlwc(const char * s)
45{
46    static char l[ASCIILINESZ+1];
47    int i ;
48
49    if (s==NULL) return NULL ;
50    memset(l, 0, ASCIILINESZ+1);
51    i=0 ;
52    while (s[i] && i<ASCIILINESZ) {
53        l[i] = (char)tolower((int)s[i]);
54        i++ ;
55    }
56    l[ASCIILINESZ]=(char)0;
57    return l ;
58}
59
60/*-------------------------------------------------------------------------*/
61/**
62  @brief    Remove blanks at the beginning and the end of a string.
63  @param    s   String to parse.
64  @return   ptr to statically allocated string.
65
66  This function returns a pointer to a statically allocated string,
67  which is identical to the input string, except that all blank
68  characters at the end and the beg. of the string have been removed.
69  Do not free or modify the returned string! Since the returned string
70  is statically allocated, it will be modified at each function call
71  (not re-entrant).
72 */
73/*--------------------------------------------------------------------------*/
74static char * strstrip(const char * s)
75{
76    static char l[ASCIILINESZ+1];
77    char * last ;
78
79    if (s==NULL) return NULL ;
80
81    while (isspace((int)*s) && *s) s++;
82    memset(l, 0, ASCIILINESZ+1);
83    strcpy(l, s);
84    last = l + strlen(l);
85    while (last > l) {
86        if (!isspace((int)*(last-1)))
87            break ;
88        last -- ;
89    }
90    *last = (char)0;
91    return (char*)l ;
92}
93
94/*-------------------------------------------------------------------------*/
95/**
96  @brief    Get number of sections in a dictionary
97  @param    d   Dictionary to examine
98  @return   int Number of sections found in dictionary
99
100  This function returns the number of sections found in a dictionary.
101  The test to recognize sections is done on the string stored in the
102  dictionary: a section name is given as "section" whereas a key is
103  stored as "section:key", thus the test looks for entries that do not
104  contain a colon.
105
106  This clearly fails in the case a section name contains a colon, but
107  this should simply be avoided.
108
109  This function returns -1 in case of error.
110 */
111/*--------------------------------------------------------------------------*/
112int iniparser_getnsec(dictionary * d)
113{
114    int i ;
115    int nsec ;
116
117    if (d==NULL) return -1 ;
118    nsec=0 ;
119    for (i=0 ; i<d->size ; i++) {
120        if (d->key[i]==NULL)
121            continue ;
122        if (strchr(d->key[i], ':')==NULL) {
123            nsec ++ ;
124        }
125    }
126    return nsec ;
127}
128
129/*-------------------------------------------------------------------------*/
130/**
131  @brief    Get name for section n in a dictionary.
132  @param    d   Dictionary to examine
133  @param    n   Section number (from 0 to nsec-1).
134  @return   Pointer to char string
135
136  This function locates the n-th section in a dictionary and returns
137  its name as a pointer to a string statically allocated inside the
138  dictionary. Do not free or modify the returned string!
139
140  This function returns NULL in case of error.
141 */
142/*--------------------------------------------------------------------------*/
143char * iniparser_getsecname(dictionary * d, int n)
144{
145    int i ;
146    int foundsec ;
147
148    if (d==NULL || n<0) return NULL ;
149    foundsec=0 ;
150    for (i=0 ; i<d->size ; i++) {
151        if (d->key[i]==NULL)
152            continue ;
153        if (strchr(d->key[i], ':')==NULL) {
154            foundsec++ ;
155            if (foundsec>n)
156                break ;
157        }
158    }
159    if (foundsec<=n) {
160        return NULL ;
161    }
162    return d->key[i] ;
163}
164
165/*-------------------------------------------------------------------------*/
166/**
167  @brief    Dump a dictionary to an opened file pointer.
168  @param    d   Dictionary to dump.
169  @param    f   Opened file pointer to dump to.
170  @return   void
171
172  This function prints out the contents of a dictionary, one element by
173  line, onto the provided file pointer. It is OK to specify @c stderr
174  or @c stdout as output files. This function is meant for debugging
175  purposes mostly.
176 */
177/*--------------------------------------------------------------------------*/
178void iniparser_dump(dictionary * d, FILE * f)
179{
180    int     i ;
181
182    if (d==NULL || f==NULL) return ;
183    for (i=0 ; i<d->size ; i++) {
184        if (d->key[i]==NULL)
185            continue ;
186        if (d->val[i]!=NULL) {
187            fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
188        } else {
189            fprintf(f, "[%s]=UNDEF\n", d->key[i]);
190        }
191    }
192    return ;
193}
194
195/*-------------------------------------------------------------------------*/
196/**
197  @brief    Save a dictionary to a loadable ini file
198  @param    d   Dictionary to dump
199  @param    f   Opened file pointer to dump to
200  @return   void
201
202  This function dumps a given dictionary into a loadable ini file.
203  It is Ok to specify @c stderr or @c stdout as output files.
204 */
205/*--------------------------------------------------------------------------*/
206void iniparser_dump_ini(dictionary * d, FILE * f)
207{
208    int     i ;
209    int     nsec ;
210    char *  secname ;
211
212    if (d==NULL || f==NULL) return ;
213
214    nsec = iniparser_getnsec(d);
215    if (nsec<1) {
216        /* No section in file: dump all keys as they are */
217        for (i=0 ; i<d->size ; i++) {
218            if (d->key[i]==NULL)
219                continue ;
220            fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
221        }
222        return ;
223    }
224    for (i=0 ; i<nsec ; i++) {
225        secname = iniparser_getsecname(d, i) ;
226        iniparser_dumpsection_ini(d, secname, f) ;
227    }
228    fprintf(f, "\n");
229    return ;
230}
231
232/*-------------------------------------------------------------------------*/
233/**
234  @brief    Save a dictionary section to a loadable ini file
235  @param    d   Dictionary to dump
236  @param    s   Section name of dictionary to dump
237  @param    f   Opened file pointer to dump to
238  @return   void
239
240  This function dumps a given section of a given dictionary into a loadable ini
241  file.  It is Ok to specify @c stderr or @c stdout as output files.
242 */
243/*--------------------------------------------------------------------------*/
244void iniparser_dumpsection_ini(dictionary * d, char * s, FILE * f)
245{
246    int     j ;
247    char    keym[ASCIILINESZ+1];
248    int     seclen ;
249
250    if (d==NULL || f==NULL) return ;
251    if (! iniparser_find_entry(d, s)) return ;
252
253    seclen  = (int)strlen(s);
254    fprintf(f, "\n[%s]\n", s);
255    sprintf(keym, "%s:", s);
256    for (j=0 ; j<d->size ; j++) {
257        if (d->key[j]==NULL)
258            continue ;
259        if (!strncmp(d->key[j], keym, seclen+1)) {
260            fprintf(f,
261                    "%-30s = %s\n",
262                    d->key[j]+seclen+1,
263                    d->val[j] ? d->val[j] : "");
264        }
265    }
266    fprintf(f, "\n");
267    return ;
268}
269
270/*-------------------------------------------------------------------------*/
271/**
272  @brief    Get the number of keys in a section of a dictionary.
273  @param    d   Dictionary to examine
274  @param    s   Section name of dictionary to examine
275  @return   Number of keys in section
276 */
277/*--------------------------------------------------------------------------*/
278int iniparser_getsecnkeys(dictionary * d, char * s)
279{
280    int     seclen, nkeys ;
281    char    keym[ASCIILINESZ+1];
282    int j ;
283
284    nkeys = 0;
285
286    if (d==NULL) return nkeys;
287    if (! iniparser_find_entry(d, s)) return nkeys;
288
289    seclen  = (int)strlen(s);
290    sprintf(keym, "%s:", s);
291
292    for (j=0 ; j<d->size ; j++) {
293        if (d->key[j]==NULL)
294            continue ;
295        if (!strncmp(d->key[j], keym, seclen+1))
296            nkeys++;
297    }
298
299    return nkeys;
300
301}
302
303/*-------------------------------------------------------------------------*/
304/**
305  @brief    Get the number of keys in a section of a dictionary.
306  @param    d   Dictionary to examine
307  @param    s   Section name of dictionary to examine
308  @return   pointer to statically allocated character strings
309
310  This function queries a dictionary and finds all keys in a given section.
311  Each pointer in the returned char pointer-to-pointer is pointing to
312  a string allocated in the dictionary; do not free or modify them.
313
314  This function returns NULL in case of error.
315 */
316/*--------------------------------------------------------------------------*/
317char ** iniparser_getseckeys(dictionary * d, char * s)
318{
319
320    char **keys;
321
322    int i, j ;
323    char    keym[ASCIILINESZ+1];
324    int     seclen, nkeys ;
325
326    keys = NULL;
327
328    if (d==NULL) return keys;
329    if (! iniparser_find_entry(d, s)) return keys;
330
331    nkeys = iniparser_getsecnkeys(d, s);
332
333    keys = (char**) malloc(nkeys*sizeof(char*));
334
335    seclen  = (int)strlen(s);
336    sprintf(keym, "%s:", s);
337
338    i = 0;
339
340    for (j=0 ; j<d->size ; j++) {
341        if (d->key[j]==NULL)
342            continue ;
343        if (!strncmp(d->key[j], keym, seclen+1)) {
344            keys[i] = d->key[j];
345            i++;
346        }
347    }
348
349    return keys;
350
351}
352
353/*-------------------------------------------------------------------------*/
354/**
355  @brief    Get the string associated to a key
356  @param    d       Dictionary to search
357  @param    key     Key string to look for
358  @param    def     Default value to return if key not found.
359  @return   pointer to statically allocated character string
360
361  This function queries a dictionary for a key. A key as read from an
362  ini file is given as "section:key". If the key cannot be found,
363  the pointer passed as 'def' is returned.
364  The returned char pointer is pointing to a string allocated in
365  the dictionary, do not free or modify it.
366 */
367/*--------------------------------------------------------------------------*/
368char * iniparser_getstring(dictionary * d, const char * key, char * def)
369{
370    char * lc_key ;
371    char * sval ;
372
373    if (d==NULL || key==NULL)
374        return def ;
375
376    lc_key = strlwc(key);
377    sval = dictionary_get(d, lc_key, def);
378    return sval ;
379}
380
381/*-------------------------------------------------------------------------*/
382/**
383  @brief    Get the string associated to a key, convert to an int
384  @param    d Dictionary to search
385  @param    key Key string to look for
386  @param    notfound Value to return in case of error
387  @return   integer
388
389  This function queries a dictionary for a key. A key as read from an
390  ini file is given as "section:key". If the key cannot be found,
391  the notfound value is returned.
392
393  Supported values for integers include the usual C notation
394  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
395  are supported. Examples:
396
397  "42"      ->  42
398  "042"     ->  34 (octal -> decimal)
399  "0x42"    ->  66 (hexa  -> decimal)
400
401  Warning: the conversion may overflow in various ways. Conversion is
402  totally outsourced to strtol(), see the associated man page for overflow
403  handling.
404
405  Credits: Thanks to A. Becker for suggesting strtol()
406 */
407/*--------------------------------------------------------------------------*/
408int iniparser_getint(dictionary * d, const char * key, int notfound)
409{
410    char    *   str ;
411
412    str = iniparser_getstring(d, key, INI_INVALID_KEY);
413    if (str==INI_INVALID_KEY) return notfound ;
414    return (int)strtol(str, NULL, 0);
415}
416
417/*-------------------------------------------------------------------------*/
418/**
419  @brief    Get the string associated to a key, convert to a double
420  @param    d Dictionary to search
421  @param    key Key string to look for
422  @param    notfound Value to return in case of error
423  @return   double
424
425  This function queries a dictionary for a key. A key as read from an
426  ini file is given as "section:key". If the key cannot be found,
427  the notfound value is returned.
428 */
429/*--------------------------------------------------------------------------*/
430double iniparser_getdouble(dictionary * d, const char * key, double notfound)
431{
432    char    *   str ;
433
434    str = iniparser_getstring(d, key, INI_INVALID_KEY);
435    if (str==INI_INVALID_KEY) return notfound ;
436    return atof(str);
437}
438
439/*-------------------------------------------------------------------------*/
440/**
441  @brief    Get the string associated to a key, convert to a boolean
442  @param    d Dictionary to search
443  @param    key Key string to look for
444  @param    notfound Value to return in case of error
445  @return   integer
446
447  This function queries a dictionary for a key. A key as read from an
448  ini file is given as "section:key". If the key cannot be found,
449  the notfound value is returned.
450
451  A true boolean is found if one of the following is matched:
452
453  - A string starting with 'y'
454  - A string starting with 'Y'
455  - A string starting with 't'
456  - A string starting with 'T'
457  - A string starting with '1'
458
459  A false boolean is found if one of the following is matched:
460
461  - A string starting with 'n'
462  - A string starting with 'N'
463  - A string starting with 'f'
464  - A string starting with 'F'
465  - A string starting with '0'
466
467  The notfound value returned if no boolean is identified, does not
468  necessarily have to be 0 or 1.
469 */
470/*--------------------------------------------------------------------------*/
471int iniparser_getboolean(dictionary * d, const char * key, int notfound)
472{
473    char    *   c ;
474    int         ret ;
475
476    c = iniparser_getstring(d, key, INI_INVALID_KEY);
477    if (c==INI_INVALID_KEY) return notfound ;
478    if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') {
479        ret = 1 ;
480    } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') {
481        ret = 0 ;
482    } else {
483        ret = notfound ;
484    }
485    return ret;
486}
487
488/*-------------------------------------------------------------------------*/
489/**
490  @brief    Finds out if a given entry exists in a dictionary
491  @param    ini     Dictionary to search
492  @param    entry   Name of the entry to look for
493  @return   integer 1 if entry exists, 0 otherwise
494
495  Finds out if a given entry exists in the dictionary. Since sections
496  are stored as keys with NULL associated values, this is the only way
497  of querying for the presence of sections in a dictionary.
498 */
499/*--------------------------------------------------------------------------*/
500int iniparser_find_entry(
501    dictionary  *   ini,
502    const char  *   entry
503)
504{
505    int found=0 ;
506    if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) {
507        found = 1 ;
508    }
509    return found ;
510}
511
512/*-------------------------------------------------------------------------*/
513/**
514  @brief    Set an entry in a dictionary.
515  @param    ini     Dictionary to modify.
516  @param    entry   Entry to modify (entry name)
517  @param    val     New value to associate to the entry.
518  @return   int 0 if Ok, -1 otherwise.
519
520  If the given entry can be found in the dictionary, it is modified to
521  contain the provided value. If it cannot be found, -1 is returned.
522  It is Ok to set val to NULL.
523 */
524/*--------------------------------------------------------------------------*/
525int iniparser_set(dictionary * ini, const char * entry, const char * val)
526{
527    return dictionary_set(ini, strlwc(entry), val) ;
528}
529
530/*-------------------------------------------------------------------------*/
531/**
532  @brief    Delete an entry in a dictionary
533  @param    ini     Dictionary to modify
534  @param    entry   Entry to delete (entry name)
535  @return   void
536
537  If the given entry can be found, it is deleted from the dictionary.
538 */
539/*--------------------------------------------------------------------------*/
540void iniparser_unset(dictionary * ini, const char * entry)
541{
542    dictionary_unset(ini, strlwc(entry));
543}
544
545/*-------------------------------------------------------------------------*/
546/**
547  @brief    Load a single line from an INI file
548  @param    input_line  Input line, may be concatenated multi-line input
549  @param    section     Output space to store section
550  @param    key         Output space to store key
551  @param    value       Output space to store value
552  @return   line_status value
553 */
554/*--------------------------------------------------------------------------*/
555static line_status iniparser_line(
556    const char * input_line,
557    char * section,
558    char * key,
559    char * value)
560{
561    line_status sta ;
562    char        line[ASCIILINESZ+1];
563    int         len ;
564
565    strcpy(line, strstrip(input_line));
566    len = (int)strlen(line);
567
568    sta = LINE_UNPROCESSED ;
569    if (len<1) {
570        /* Empty line */
571        sta = LINE_EMPTY ;
572    } else if (line[0]=='#' || line[0]==';') {
573        /* Comment line */
574        sta = LINE_COMMENT ;
575    } else if (line[0]=='[' && line[len-1]==']') {
576        /* Section name */
577        sscanf(line, "[%[^]]", section);
578        strcpy(section, strstrip(section));
579        strcpy(section, strlwc(section));
580        sta = LINE_SECTION ;
581    } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
582           ||  sscanf (line, "%[^=] = '%[^\']'",   key, value) == 2
583           ||  sscanf (line, "%[^=] = %[^;#]",     key, value) == 2) {
584        /* Usual key=value, with or without comments */
585        strcpy(key, strstrip(key));
586        strcpy(key, strlwc(key));
587        strcpy(value, strstrip(value));
588        /*
589         * sscanf cannot handle '' or "" as empty values
590         * this is done here
591         */
592        if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
593            value[0]=0 ;
594        }
595        sta = LINE_VALUE ;
596    } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2
597           ||  sscanf(line, "%[^=] %[=]", key, value) == 2) {
598        /*
599         * Special cases:
600         * key=
601         * key=;
602         * key=#
603         */
604        strcpy(key, strstrip(key));
605        strcpy(key, strlwc(key));
606        value[0]=0 ;
607        sta = LINE_VALUE ;
608    } else {
609        /* Generate syntax error */
610        sta = LINE_ERROR ;
611    }
612    return sta ;
613}
614
615/*-------------------------------------------------------------------------*/
616/**
617  @brief    Parse an ini file and return an allocated dictionary object
618  @param    ininame Name of the ini file to read.
619  @return   Pointer to newly allocated dictionary
620
621  This is the parser for ini files. This function is called, providing
622  the name of the file to be read. It returns a dictionary object that
623  should not be accessed directly, but through accessor functions
624  instead.
625
626  The returned dictionary must be freed using iniparser_freedict().
627 */
628/*--------------------------------------------------------------------------*/
629dictionary * iniparser_load(const char * ininame)
630{
631    FILE * in ;
632
633    char line    [ASCIILINESZ+1] ;
634    char section [ASCIILINESZ+1] ;
635    char key     [ASCIILINESZ+1] ;
636    char tmp     [ASCIILINESZ+1] ;
637    char val     [ASCIILINESZ+1] ;
638
639    int  last=0 ;
640    int  len ;
641    int  lineno=0 ;
642    int  errs=0;
643
644    dictionary * dict ;
645
646    if ((in=fopen(ininame, "r"))==NULL) {
647        fprintf(stderr, "iniparser: cannot open %s\n", ininame);
648        return NULL ;
649    }
650
651    dict = dictionary_new(0) ;
652    if (!dict) {
653        fclose(in);
654        return NULL ;
655    }
656
657    memset(line,    0, ASCIILINESZ);
658    memset(section, 0, ASCIILINESZ);
659    memset(key,     0, ASCIILINESZ);
660    memset(val,     0, ASCIILINESZ);
661    last=0 ;
662
663    while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
664        lineno++ ;
665        len = (int)strlen(line)-1;
666        if (len==0)
667            continue;
668        /* Safety check against buffer overflows */
669        if (line[len]!='\n') {
670            fprintf(stderr,
671                    "iniparser: input line too long in %s (%d)\n",
672                    ininame,
673                    lineno);
674            dictionary_del(dict);
675            fclose(in);
676            return NULL ;
677        }
678        /* Get rid of \n and spaces at end of line */
679        while ((len>=0) &&
680                ((line[len]=='\n') || (isspace(line[len])))) {
681            line[len]=0 ;
682            len-- ;
683        }
684        /* Detect multi-line */
685        if (line[len]=='\\') {
686            /* Multi-line value */
687            last=len ;
688            continue ;
689        } else {
690            last=0 ;
691        }
692        switch (iniparser_line(line, section, key, val)) {
693            case LINE_EMPTY:
694            case LINE_COMMENT:
695            break ;
696
697            case LINE_SECTION:
698            errs = dictionary_set(dict, section, NULL);
699            break ;
700
701            case LINE_VALUE:
702            sprintf(tmp, "%s:%s", section, key);
703            errs = dictionary_set(dict, tmp, val) ;
704            break ;
705
706            case LINE_ERROR:
707            fprintf(stderr, "iniparser: syntax error in %s (%d):\n",
708                    ininame,
709                    lineno);
710            fprintf(stderr, "-> %s\n", line);
711            errs++ ;
712            break;
713
714            default:
715            break ;
716        }
717        memset(line, 0, ASCIILINESZ);
718        last=0;
719        if (errs<0) {
720            fprintf(stderr, "iniparser: memory allocation failure\n");
721            break ;
722        }
723    }
724    if (errs) {
725        dictionary_del(dict);
726        dict = NULL ;
727    }
728    fclose(in);
729    return dict ;
730}
731
732/*-------------------------------------------------------------------------*/
733/**
734  @brief    Free all memory associated to an ini dictionary
735  @param    d Dictionary to free
736  @return   void
737
738  Free all memory associated to an ini dictionary.
739  It is mandatory to call this function before the dictionary object
740  gets out of the current context.
741 */
742/*--------------------------------------------------------------------------*/
743void iniparser_freedict(dictionary * d)
744{
745    dictionary_del(d);
746}
747
748/* vim: set ts=4 et sw=4 tw=75 */
749