all repos — mgba @ f836a67863e66499c812c8d963d37ff56ad10638

mGBA Game Boy Advance Emulator

src/third-party/inih/ini.c (view raw)

  1/* inih -- simple .INI file parser
  2
  3inih is released under the New BSD license (see LICENSE.txt). Go to the project
  4home page for more info:
  5
  6http://code.google.com/p/inih/
  7
  8*/
  9
 10#include <stdio.h>
 11#ifndef PSP2
 12#include <ctype.h>
 13#endif
 14#include <string.h>
 15
 16#include "ini.h"
 17
 18#if !INI_USE_STACK
 19#include <stdlib.h>
 20#endif
 21
 22#define MAX_SECTION 50
 23#define MAX_NAME 50
 24
 25/* Strip whitespace chars off end of given string, in place. Return s. */
 26static char* rstrip(char* s)
 27{
 28    char* p = s + strlen(s);
 29    while (p > s && isspace((unsigned char)(*--p)))
 30        *p = '\0';
 31    return s;
 32}
 33
 34/* Return pointer to first non-whitespace char in given string. */
 35static char* lskip(const char* s)
 36{
 37    while (*s && isspace((unsigned char)(*s)))
 38        s++;
 39    return (char*)s;
 40}
 41
 42/* Return pointer to first char c or ';' comment in given string, or pointer to
 43   null at end of string if neither found. ';' must be prefixed by a whitespace
 44   character to register as a comment. */
 45static char* find_char_or_comment(const char* s, char c)
 46{
 47    int was_whitespace = 0;
 48    while (*s && *s != c && !(was_whitespace && *s == ';')) {
 49        was_whitespace = isspace((unsigned char)(*s));
 50        s++;
 51    }
 52    return (char*)s;
 53}
 54
 55/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
 56static char* strncpy0(char* dest, const char* src, size_t size)
 57{
 58    strncpy(dest, src, size);
 59    dest[size - 1] = '\0';
 60    return dest;
 61}
 62
 63/* See documentation in header file. */
 64int ini_parse_file(FILE* file,
 65                   int (*handler)(void*, const char*, const char*,
 66                                  const char*),
 67                   void* user)
 68{
 69    /* Uses a fair bit of stack (use heap instead if you need to) */
 70#if INI_USE_STACK
 71    char line[INI_MAX_LINE];
 72#else
 73    char* line;
 74#endif
 75    char section[MAX_SECTION] = "";
 76    char prev_name[MAX_NAME] = "";
 77
 78    char* start;
 79    char* end;
 80    char* name;
 81    char* value;
 82    int lineno = 0;
 83    int error = 0;
 84
 85#if !INI_USE_STACK
 86    line = (char*)malloc(INI_MAX_LINE);
 87    if (!line) {
 88        return -2;
 89    }
 90#endif
 91
 92    /* Scan through file line by line */
 93    while (fgets(line, INI_MAX_LINE, file) != NULL) {
 94        lineno++;
 95
 96        start = line;
 97#if INI_ALLOW_BOM
 98        if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
 99                           (unsigned char)start[1] == 0xBB &&
100                           (unsigned char)start[2] == 0xBF) {
101            start += 3;
102        }
103#endif
104        start = lskip(rstrip(start));
105
106        if (*start == ';' || *start == '#') {
107            /* Per Python ConfigParser, allow '#' comments at start of line */
108        }
109#if INI_ALLOW_MULTILINE
110        else if (*prev_name && *start && start > line) {
111            /* Non-black line with leading whitespace, treat as continuation
112               of previous name's value (as per Python ConfigParser). */
113            if (!handler(user, section, prev_name, start) && !error)
114                error = lineno;
115        }
116#endif
117        else if (*start == '[') {
118            /* A "[section]" line */
119            end = find_char_or_comment(start + 1, ']');
120            if (*end == ']') {
121                *end = '\0';
122                strncpy0(section, start + 1, sizeof(section));
123                *prev_name = '\0';
124            }
125            else if (!error) {
126                /* No ']' found on section line */
127                error = lineno;
128            }
129        }
130        else if (*start && *start != ';') {
131            /* Not a comment, must be a name[=:]value pair */
132            end = find_char_or_comment(start, '=');
133            if (*end != '=') {
134                end = find_char_or_comment(start, ':');
135            }
136            if (*end == '=' || *end == ':') {
137                *end = '\0';
138                name = rstrip(start);
139                value = lskip(end + 1);
140                end = find_char_or_comment(value, '\0');
141                if (*end == ';')
142                    *end = '\0';
143                rstrip(value);
144
145                /* Valid name[=:]value pair found, call handler */
146                strncpy0(prev_name, name, sizeof(prev_name));
147                if (!handler(user, section, name, value) && !error)
148                    error = lineno;
149            }
150            else if (!error) {
151                /* No '=' or ':' found on name[=:]value line */
152                error = lineno;
153            }
154        }
155
156#if INI_STOP_ON_FIRST_ERROR
157        if (error)
158            break;
159#endif
160    }
161
162#if !INI_USE_STACK
163    free(line);
164#endif
165
166    return error;
167}
168
169/* See documentation in header file. */
170int ini_parse(const char* filename,
171              int (*handler)(void*, const char*, const char*, const char*),
172              void* user)
173{
174    FILE* file;
175    int error;
176
177    file = fopen(filename, "r");
178    if (!file)
179        return -1;
180    error = ini_parse_file(file, handler, user);
181    fclose(file);
182    return error;
183}