1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 #include "cx/properties.h"
30
31 #include <assert.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <ctype.h>
35
36 const CxPropertiesConfig cx_properties_config_default = {
37 '=',
38 '#',
39 '\0',
40 '\0',
41 '\\',
42 };
43
44 void cxPropertiesInit(
45 CxProperties *prop,
46 CxPropertiesConfig config
47 ) {
48 memset(prop,
0,
sizeof(CxProperties));
49 prop->config = config;
50 }
51
52 void cxPropertiesDestroy(CxProperties *prop) {
53 cxBufferDestroy(&prop->input);
54 cxBufferDestroy(&prop->buffer);
55 }
56
57 void cxPropertiesReset(CxProperties *prop) {
58 CxPropertiesConfig config = prop->config;
59 cxPropertiesDestroy(prop);
60 cxPropertiesInit(prop, config);
61 }
62
63 int cxPropertiesFilln(
64 CxProperties *prop,
65 const char *buf,
66 size_t len
67 ) {
68 if (cxBufferEof(&prop->input)) {
69
70 cxBufferDestroy(&prop->input);
71 cxBufferInit(&prop->input, (
void*) buf, len,
72 NULL,
CX_BUFFER_COPY_ON_WRITE |
CX_BUFFER_AUTO_EXTEND);
73 prop->input.size = len;
74 }
else {
75 if (cxBufferAppend(buf,
1, len, &prop->input) < len)
return -1;
76 }
77 return 0;
78 }
79
80 void cxPropertiesUseStack(
81 CxProperties *prop,
82 char *buf,
83 size_t capacity
84 ) {
85 cxBufferInit(&prop->buffer, buf, capacity,
NULL,
CX_BUFFER_COPY_ON_EXTEND);
86 }
87
88 CxPropertiesStatus cxPropertiesNext(
89 CxProperties *prop,
90 cxstring *key,
91 cxstring *value
92 ) {
93
94 if (prop->input.space ==
NULL) {
95 return CX_PROPERTIES_NULL_INPUT;
96 }
97
98
99 CxBuffer *current_buffer = &prop->input;
100
101 char comment1 = prop->config.comment1;
102 char comment2 = prop->config.comment2;
103 char comment3 = prop->config.comment3;
104 char delimiter = prop->config.delimiter;
105 char continuation = prop->config.continuation;
106
107
108 if (!cxBufferEof(&prop->buffer)) {
109
110 cxstring input = cx_strn(prop->input.space + prop->input.pos,
111 prop->input.size - prop->input.pos);
112 cxstring nl = cx_strchr(input,
'\n');
113 while (nl.length >
0) {
114
115 char previous = nl.ptr > input.ptr ? nl.ptr[
-1] : prop->buffer.space[prop->buffer.size
-1];
116 if (previous == continuation) {
117
118 nl = cx_strchr(cx_strsubs(nl,
1),
'\n');
119 }
else {
120 break;
121 }
122 }
123
124 if (nl.length >
0) {
125
126
127 size_t len_until_nl = (
size_t)(nl.ptr - input.ptr) +
1;
128
129 if (cxBufferAppend(input.ptr,
1,
130 len_until_nl, &prop->buffer) < len_until_nl) {
131 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
132 }
133
134
135 prop->input.pos += len_until_nl;
136
137
138 current_buffer = &prop->buffer;
139 }
else {
140
141 if (cxBufferAppend(input.ptr,
1,
142 input.length, &prop->buffer) < input.length) {
143 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
144 }
145
146 cxBufferReset(&prop->input);
147 return CX_PROPERTIES_INCOMPLETE_DATA;
148 }
149 }
150
151
152 while (!cxBufferEof(current_buffer)) {
153 const char *buf = current_buffer->space + current_buffer->pos;
154 size_t len = current_buffer->size - current_buffer->pos;
155
156
157
158
159
160 size_t delimiter_index =
0;
161 size_t comment_index =
0;
162 bool has_comment = false;
163 bool has_continuation = false;
164
165 size_t i =
0;
166 char c =
0;
167 for (; i < len; i++) {
168 c = buf[i];
169 if (c == comment1 || c == comment2 || c == comment3) {
170 if (comment_index ==
0) {
171 comment_index = i;
172 has_comment = true;
173 }
174 }
else if (c == delimiter) {
175 if (delimiter_index ==
0 && !has_comment) {
176 delimiter_index = i;
177 }
178 }
else if (delimiter_index >
0 && c == continuation && i
+1 < len && buf[i
+1] ==
'\n') {
179 has_continuation = true;
180 i++;
181 }
else if (c ==
'\n') {
182 break;
183 }
184 }
185
186 if (c !=
'\n') {
187
188 assert(current_buffer != &prop->buffer);
189
190 assert(cxBufferEof(&prop->buffer));
191 if (prop->buffer.space ==
NULL) {
192
193 cxBufferInit(&prop->buffer,
NULL,
256,
NULL,
CX_BUFFER_AUTO_EXTEND);
194 }
else {
195
196
197 cxBufferReset(&prop->buffer);
198 }
199 if (cxBufferAppend(buf,
1, len, &prop->buffer) < len) {
200 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
201 }
202
203 cxBufferReset(&prop->input);
204 return CX_PROPERTIES_INCOMPLETE_DATA;
205 }
206
207 cxstring line = has_comment ?
208 cx_strn(buf, comment_index) :
209 cx_strn(buf, i);
210
211 if (delimiter_index ==
0) {
212
213 line = cx_strtrim(line);
214
215 if (line.length >
0) {
216 if (line.ptr[
0] == delimiter) {
217 return CX_PROPERTIES_INVALID_EMPTY_KEY;
218 }
else {
219 return CX_PROPERTIES_INVALID_MISSING_DELIMITER;
220 }
221 }
else {
222
223
224 if (current_buffer == &prop->buffer) {
225
226 assert(current_buffer->pos + i +
1 == current_buffer->size);
227
228 cxBufferReset(&prop->buffer);
229
230 current_buffer = &prop->input;
231 }
else {
232
233 current_buffer->pos += i +
1;
234 }
235 continue;
236 }
237 }
else {
238 cxstring k = cx_strn(buf, delimiter_index);
239 cxstring val = cx_strn(
240 buf + delimiter_index +
1,
241 line.length - delimiter_index -
1);
242 k = cx_strtrim(k);
243 val = cx_strtrim(val);
244 if (k.length >
0) {
245 current_buffer->pos += i +
1;
246 assert(current_buffer->pos <= current_buffer->size);
247 assert(current_buffer != &prop->buffer || current_buffer->pos == current_buffer->size);
248
249 if (has_continuation) {
250 char *ptr = (
char*)val.ptr;
251 if (current_buffer != &prop->buffer) {
252
253 if (prop->buffer.space ==
NULL) {
254 cxBufferInit(&prop->buffer,
NULL,
256,
NULL,
CX_BUFFER_AUTO_EXTEND);
255 }
256 prop->buffer.size =
0;
257 prop->buffer.pos =
0;
258 if (cxBufferWrite(val.ptr,
1, val.length, &prop->buffer) != val.length) {
259 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
260 }
261 val.ptr = prop->buffer.space;
262 ptr = prop->buffer.space;
263 }
264
265
266 bool trim = false;
267 size_t x =
0;
268 for(
size_t j=
0;j<val.length;j++) {
269 c = ptr[j];
270 if (j
+1 < val.length && c ==
'\\' && ptr[j
+1] ==
'\n') {
271
272 j++;
273 trim = true;
274 continue;
275 }
276 if (j > x) {
277 if (trim) {
278 if (isspace((
unsigned char)c)) {
279 continue;
280 }
281 trim = false;
282 }
283 ptr[x] = c;
284 }
285 x++;
286 }
287 val.length = x;
288 }
289 *key = k;
290 *value = val;
291
292 return CX_PROPERTIES_NO_ERROR;
293 }
else {
294 return CX_PROPERTIES_INVALID_EMPTY_KEY;
295 }
296 }
297 }
298
299
300 assert(cxBufferEof(&prop->buffer));
301 assert(cxBufferEof(&prop->input));
302
303 return CX_PROPERTIES_NO_DATA;
304 }
305
306 #ifndef CX_PROPERTIES_LOAD_FILL_SIZE
307 #define CX_PROPERTIES_LOAD_FILL_SIZE 1024
308 #endif
309 const unsigned cx_properties_load_fill_size =
CX_PROPERTIES_LOAD_FILL_SIZE;
310 #ifndef CX_PROPERTIES_LOAD_BUF_SIZE
311 #define CX_PROPERTIES_LOAD_BUF_SIZE 256
312 #endif
313 const unsigned cx_properties_load_buf_size =
CX_PROPERTIES_LOAD_BUF_SIZE;
314
315 CxPropertiesStatus cx_properties_load(CxPropertiesConfig config,
316 const CxAllocator *allocator, cxstring filename, CxMap *target) {
317 if (allocator ==
NULL) {
318 allocator = cxDefaultAllocator;
319 }
320
321
322 const bool use_cstring = cxCollectionStoresPointers(target);
323 if (!use_cstring && cxCollectionElementSize(target) !=
sizeof(cxmutstr)) {
324 return CX_PROPERTIES_MAP_ERROR;
325 }
326
327
328 cxmutstr fname = cx_strdup(filename);
329 if (fname.ptr ==
NULL) {
330 return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
331 }
332
333
334 FILE *f = fopen(fname.ptr,
"r");
335 if (f ==
NULL) {
336 cx_strfree(&fname);
337 return CX_PROPERTIES_FILE_ERROR;
338 }
339
340
341 char linebuf[cx_properties_load_buf_size];
342 char fillbuf[cx_properties_load_fill_size];
343 CxPropertiesStatus status;
344 CxProperties parser;
345 cxPropertiesInit(&parser, config);
346 cxPropertiesUseStack(&parser, linebuf, cx_properties_load_buf_size);
347
348
349 status =
CX_PROPERTIES_NO_DATA;
350 size_t keys_found =
0;
351 while (true) {
352 size_t r = fread(fillbuf,
1, cx_properties_load_fill_size, f);
353 if (ferror(f)) {
354 status =
CX_PROPERTIES_FILE_ERROR;
355 break;
356 }
357 if (r ==
0) {
358 break;
359 }
360 if (cxPropertiesFilln(&parser, fillbuf, r)) {
361 status =
CX_PROPERTIES_BUFFER_ALLOC_FAILED;
362 break;
363 }
364 cxstring key, value;
365 while (true) {
366 status = cxPropertiesNext(&parser, &key, &value);
367 if (status !=
CX_PROPERTIES_NO_ERROR) {
368 break;
369 }
else {
370 cxmutstr v = cx_strdup_a(allocator, value);
371 if (v.ptr ==
NULL) {
372 status =
CX_PROPERTIES_MAP_ERROR;
373 break;
374 }
375 void *mv = use_cstring ? (
void*)v.ptr : &v;
376 if (cxMapPut(target, key, mv)) {
377 cx_strfree(&v);
378 status =
CX_PROPERTIES_MAP_ERROR;
379 break;
380 }
381 keys_found++;
382 }
383 }
384 if (status >
CX_PROPERTIES_OK) {
385 break;
386 }
387 }
388
389
390 fclose(f);
391 cxPropertiesDestroy(&parser);
392 cx_strfree(&fname);
393 if (status ==
CX_PROPERTIES_NO_DATA && keys_found >
0) {
394 return CX_PROPERTIES_NO_ERROR;
395 }
else {
396 return status;
397 }
398 }
399