Line data Source code
1 : /** \file ini.cpp
2 : * \author Jared R. Males
3 : * \brief Implementation of the The inih ini-style, file parser modified for mxlib.
4 : *
5 : * \ingroup mxApp_files
6 : *
7 : */
8 :
9 : #ifndef INI_COMMENT_CHAR
10 : #define INI_COMMENT_CHAR ( '#' )
11 : #endif
12 :
13 : /* Nonzero to allow multi-line value parsing, in the style of Python's
14 : ConfigParser. If allowed, ini_parse() will call the handler with the same
15 : name for each subsequent line parsed. */
16 : #ifndef INI_ALLOW_MULTILINE
17 : #define INI_ALLOW_MULTILINE 1
18 : #endif
19 :
20 : /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
21 : the file. See http://code.google.com/p/inih/issues/detail?id=21 */
22 : #ifndef INI_ALLOW_BOM
23 : #define INI_ALLOW_BOM 1
24 : #endif
25 :
26 : /* Nonzero to use stack, zero to use heap (malloc/free). */
27 : #ifndef INI_USE_STACK
28 : #define INI_USE_STACK 0
29 : #endif
30 :
31 : /* Stop parsing on first error (default is to stop parsing). */
32 : #ifndef INI_STOP_ON_FIRST_ERROR
33 : #define INI_STOP_ON_FIRST_ERROR 0
34 : #endif
35 :
36 : /* Maximum line length for any line in INI file. */
37 : #ifndef INI_MAX_LINE
38 : #define INI_MAX_LINE 1024
39 : #endif
40 :
41 : #define MAX_SECTION 50
42 : #define MAX_NAME 50
43 :
44 : #if !INI_USE_STACK
45 : #include <stdlib.h>
46 : #endif
47 :
48 : #include "app/ini.hpp"
49 :
50 : /* Strip whitespace chars off end of given string, in place. Return s. */
51 468 : static char *rstrip( char *s )
52 : {
53 468 : char *p = s + strlen( s );
54 686 : while( p > s && isspace( (unsigned char)( *--p ) ) )
55 218 : *p = '\0';
56 468 : return s;
57 : }
58 :
59 : /* Return pointer to first non-whitespace char in given string. */
60 340 : static char *lskip( const char *s )
61 : {
62 348 : while( *s && isspace( (unsigned char)( *s ) ) )
63 8 : s++;
64 340 : return (char *)s;
65 : }
66 :
67 : /* Return pointer to first char c or ';' comment in given string, or pointer to
68 : null at end of string if neither found. ';' must be prefixed by a whitespace
69 : character to register as a comment. */
70 300 : static char *find_char_or_comment( const char *s, char c )
71 : {
72 300 : int was_whitespace = 0;
73 1928 : while( *s && *s != c && !( was_whitespace && *s == INI_COMMENT_CHAR ) )
74 : {
75 1628 : was_whitespace = isspace( (unsigned char)( *s ) );
76 1628 : s++;
77 : }
78 300 : return (char *)s;
79 : }
80 :
81 : /* Version of strncpy that ensures dest (size bytes) is null-terminated. */
82 171 : static char *strncpy0( char *dest, const char *src, size_t size )
83 : {
84 171 : strncpy( dest, src, size );
85 171 : dest[size - 1] = '\0';
86 171 : return dest;
87 : }
88 :
89 34 : int ini_parse_file( FILE *file, int ( *handler )( void *, const char *, const char *, const char * ), void *user )
90 : {
91 : /* Uses a fair bit of stack (use heap instead if you need to) */
92 : #if INI_USE_STACK
93 : char line[INI_MAX_LINE];
94 : #else
95 : char *line;
96 : #endif
97 34 : char section[MAX_SECTION] = "";
98 34 : char prev_name[MAX_NAME] = "";
99 :
100 : char *start;
101 : char *end;
102 : char *name;
103 : char *value;
104 34 : int lineno = 0;
105 34 : int error = 0;
106 :
107 : #if !INI_USE_STACK
108 34 : line = (char *)malloc( INI_MAX_LINE );
109 34 : if( !line )
110 : {
111 0 : return -2;
112 : }
113 : #endif
114 :
115 : /* Scan through file line by line */
116 246 : while( fgets( line, INI_MAX_LINE, file ) != NULL )
117 : {
118 212 : lineno++;
119 :
120 212 : start = line;
121 : #if INI_ALLOW_BOM
122 212 : if( lineno == 1 && (unsigned char)start[0] == 0xEF && (unsigned char)start[1] == 0xBB &&
123 0 : (unsigned char)start[2] == 0xBF )
124 : {
125 0 : start += 3;
126 : }
127 : #endif
128 212 : start = lskip( rstrip( start ) );
129 :
130 212 : if( *start == INI_COMMENT_CHAR || *start == '#' )
131 : {
132 : /* Per Python ConfigParser, allow '#' comments at start of line */
133 : }
134 : #if INI_ALLOW_MULTILINE
135 212 : else if( *prev_name && *start && start > line )
136 : {
137 : /* Non-black line with leading whitespace, treat as continuation
138 : of previous name's value (as per Python ConfigParser). */
139 2 : if( !handler( user, section, prev_name, start ) && !error )
140 0 : error = lineno;
141 : }
142 : #endif
143 210 : else if( *start == '[' )
144 : {
145 : /* A "[section]" line */
146 44 : end = find_char_or_comment( start + 1, ']' );
147 44 : if( *end == ']' )
148 : {
149 43 : *end = '\0';
150 43 : strncpy0( section, start + 1, sizeof( section ) );
151 43 : *prev_name = '\0';
152 : }
153 1 : else if( !error )
154 : {
155 : /* No ']' found on section line */
156 1 : error = lineno;
157 : }
158 : }
159 166 : else if( *start && *start != INI_COMMENT_CHAR )
160 : {
161 : /* Not a comment, must be a name[=:]value pair */
162 128 : end = find_char_or_comment( start, '=' );
163 128 : if( *end != '=' )
164 : {
165 0 : end = find_char_or_comment( start, ':' );
166 : }
167 128 : if( *end == '=' || *end == ':' )
168 : {
169 128 : *end = '\0';
170 128 : name = rstrip( start );
171 128 : value = lskip( end + 1 );
172 128 : end = find_char_or_comment( value, '\0' );
173 128 : if( *end == INI_COMMENT_CHAR )
174 0 : *end = '\0';
175 128 : rstrip( value );
176 :
177 : /* Valid name[=:]value pair found, call handler */
178 128 : strncpy0( prev_name, name, sizeof( prev_name ) );
179 128 : if( !handler( user, section, name, value ) && !error )
180 0 : error = lineno;
181 : }
182 0 : else if( !error )
183 : {
184 : /* No '=' or ':' found on name[=:]value line */
185 0 : error = lineno;
186 : }
187 : }
188 :
189 : #if INI_STOP_ON_FIRST_ERROR
190 : if( error )
191 : break;
192 : #endif
193 : }
194 :
195 : #if !INI_USE_STACK
196 34 : free( line );
197 : #endif
198 :
199 34 : return error;
200 : }
201 :
202 37 : int ini_parse( const char *filename, int ( *handler )( void *, const char *, const char *, const char * ), void *user )
203 : {
204 : FILE *file;
205 : int error;
206 :
207 37 : file = fopen( filename, "r" );
208 37 : if( !file )
209 3 : return -1;
210 34 : error = ini_parse_file( file, handler, user );
211 34 : fclose( file );
212 34 : return error;
213 : }
|