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