ucx/properties.c

changeset 992
f421aef8f865
parent 943
9b5948aa5b90
equal deleted inserted replaced
991:ab3125bd8b5f 992:f421aef8f865
27 */ 27 */
28 28
29 #include "cx/properties.h" 29 #include "cx/properties.h"
30 30
31 #include <assert.h> 31 #include <assert.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <ctype.h>
32 35
33 const CxPropertiesConfig cx_properties_config_default = { 36 const CxPropertiesConfig cx_properties_config_default = {
34 '=', 37 '=',
35 '#', 38 '#',
36 '\0', 39 '\0',
37 '\0', 40 '\0',
38 '\\', 41 '\\',
39 }; 42 };
40 43
41 void cxPropertiesInit( 44 void cxPropertiesInit(
42 CxProperties *prop, 45 CxProperties *prop,
92 return CX_PROPERTIES_NULL_INPUT; 95 return CX_PROPERTIES_NULL_INPUT;
93 } 96 }
94 97
95 // a pointer to the buffer we want to read from 98 // a pointer to the buffer we want to read from
96 CxBuffer *current_buffer = &prop->input; 99 CxBuffer *current_buffer = &prop->input;
97 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
98 // check if we have rescued data 107 // check if we have rescued data
99 if (!cxBufferEof(&prop->buffer)) { 108 if (!cxBufferEof(&prop->buffer)) {
100 // check if we can now get a complete line 109 // check if we can now get a complete line
101 cxstring input = cx_strn(prop->input.space + prop->input.pos, 110 cxstring input = cx_strn(prop->input.space + prop->input.pos,
102 prop->input.size - prop->input.pos); 111 prop->input.size - prop->input.pos);
103 cxstring nl = cx_strchr(input, '\n'); 112 cxstring nl = cx_strchr(input, '\n');
113 while (nl.length > 0) {
114 // check for line continuation
115 char previous = nl.ptr > input.ptr ? nl.ptr[-1] : prop->buffer.space[prop->buffer.size-1];
116 if (previous == continuation) {
117 // this nl is a line continuation, check the next newline
118 nl = cx_strchr(cx_strsubs(nl, 1), '\n');
119 } else {
120 break;
121 }
122 }
123
104 if (nl.length > 0) { 124 if (nl.length > 0) {
105 // we add as much data to the rescue buffer as we need 125 // we add as much data to the rescue buffer as we need
106 // to complete the line 126 // to complete the line
107 size_t len_until_nl = (size_t)(nl.ptr - input.ptr) + 1; 127 size_t len_until_nl = (size_t)(nl.ptr - input.ptr) + 1;
108 128
125 // reset the input buffer (make way for a re-fill) 145 // reset the input buffer (make way for a re-fill)
126 cxBufferReset(&prop->input); 146 cxBufferReset(&prop->input);
127 return CX_PROPERTIES_INCOMPLETE_DATA; 147 return CX_PROPERTIES_INCOMPLETE_DATA;
128 } 148 }
129 } 149 }
130 150
131 char comment1 = prop->config.comment1;
132 char comment2 = prop->config.comment2;
133 char comment3 = prop->config.comment3;
134 char delimiter = prop->config.delimiter;
135
136 // get one line and parse it 151 // get one line and parse it
137 while (!cxBufferEof(current_buffer)) { 152 while (!cxBufferEof(current_buffer)) {
138 const char *buf = current_buffer->space + current_buffer->pos; 153 const char *buf = current_buffer->space + current_buffer->pos;
139 size_t len = current_buffer->size - current_buffer->pos; 154 size_t len = current_buffer->size - current_buffer->pos;
140 155
143 * delimiter and comment chars 158 * delimiter and comment chars
144 */ 159 */
145 size_t delimiter_index = 0; 160 size_t delimiter_index = 0;
146 size_t comment_index = 0; 161 size_t comment_index = 0;
147 bool has_comment = false; 162 bool has_comment = false;
163 bool has_continuation = false;
148 164
149 size_t i = 0; 165 size_t i = 0;
150 char c = 0; 166 char c = 0;
151 for (; i < len; i++) { 167 for (; i < len; i++) {
152 c = buf[i]; 168 c = buf[i];
157 } 173 }
158 } else if (c == delimiter) { 174 } else if (c == delimiter) {
159 if (delimiter_index == 0 && !has_comment) { 175 if (delimiter_index == 0 && !has_comment) {
160 delimiter_index = i; 176 delimiter_index = i;
161 } 177 }
178 } else if (delimiter_index > 0 && c == continuation && i+1 < len && buf[i+1] == '\n') {
179 has_continuation = true;
180 i++;
162 } else if (c == '\n') { 181 } else if (c == '\n') {
163 break; 182 break;
164 } 183 }
165 } 184 }
166 185
221 buf + delimiter_index + 1, 240 buf + delimiter_index + 1,
222 line.length - delimiter_index - 1); 241 line.length - delimiter_index - 1);
223 k = cx_strtrim(k); 242 k = cx_strtrim(k);
224 val = cx_strtrim(val); 243 val = cx_strtrim(val);
225 if (k.length > 0) { 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 // move value to the rescue buffer
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 // value.ptr is now inside the rescue buffer and we can
265 // remove the continuation character from the value
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 // skip continuation and newline character
272 j++;
273 trim = true; // enable trim in the next line
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 }
226 *key = k; 289 *key = k;
227 *value = val; 290 *value = val;
228 current_buffer->pos += i + 1; 291
229 assert(current_buffer->pos <= current_buffer->size);
230 return CX_PROPERTIES_NO_ERROR; 292 return CX_PROPERTIES_NO_ERROR;
231 } else { 293 } else {
232 return CX_PROPERTIES_INVALID_EMPTY_KEY; 294 return CX_PROPERTIES_INVALID_EMPTY_KEY;
233 } 295 }
234 } 296 }
239 assert(cxBufferEof(&prop->input)); 301 assert(cxBufferEof(&prop->input));
240 302
241 return CX_PROPERTIES_NO_DATA; 303 return CX_PROPERTIES_NO_DATA;
242 } 304 }
243 305
244 static int cx_properties_sink_map( 306 #ifndef CX_PROPERTIES_LOAD_FILL_SIZE
245 cx_attr_unused CxProperties *prop, 307 #define CX_PROPERTIES_LOAD_FILL_SIZE 1024
246 CxPropertiesSink *sink, 308 #endif
247 cxstring key, 309 const unsigned cx_properties_load_fill_size = CX_PROPERTIES_LOAD_FILL_SIZE;
248 cxstring value 310 #ifndef CX_PROPERTIES_LOAD_BUF_SIZE
249 ) { 311 #define CX_PROPERTIES_LOAD_BUF_SIZE 256
250 CxMap *map = sink->sink; 312 #endif
251 CxAllocator *alloc = sink->data; 313 const unsigned cx_properties_load_buf_size = CX_PROPERTIES_LOAD_BUF_SIZE;
252 cxmutstr v = cx_strdup_a(alloc, value); 314
253 int r = cxMapPut(map, key, v.ptr); 315 CxPropertiesStatus cx_properties_load(CxPropertiesConfig config,
254 if (r != 0) cx_strfree_a(alloc, &v); 316 const CxAllocator *allocator, cxstring filename, CxMap *target) {
255 return r; 317 if (allocator == NULL) {
256 } 318 allocator = cxDefaultAllocator;
257 319 }
258 CxPropertiesSink cxPropertiesMapSink(CxMap *map) { 320
259 CxPropertiesSink sink; 321 // sanity check for the map
260 sink.sink = map; 322 const bool use_cstring = cxCollectionStoresPointers(target);
261 sink.data = (void*) cxDefaultAllocator; 323 if (!use_cstring && cxCollectionElementSize(target) != sizeof(cxmutstr)) {
262 sink.sink_func = cx_properties_sink_map; 324 return CX_PROPERTIES_MAP_ERROR;
263 return sink; 325 }
264 } 326
265 327 // create a duplicate to guarantee zero-termination
266 static int cx_properties_read_string( 328 cxmutstr fname = cx_strdup(filename);
267 CxProperties *prop, 329 if (fname.ptr == NULL) {
268 CxPropertiesSource *src, 330 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
269 cxstring *target 331 }
270 ) { 332
271 if (prop->input.space == src->src) { 333 // open the file
272 // when the input buffer already contains the string 334 FILE *f = fopen(fname.ptr, "r");
273 // we have nothing more to provide 335 if (f == NULL) {
274 target->length = 0; 336 cx_strfree(&fname);
337 return CX_PROPERTIES_FILE_ERROR;
338 }
339
340 // initialize the parser
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 // read/fill/parse loop
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 // cleanup and exit
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;
275 } else { 395 } else {
276 target->ptr = src->src; 396 return status;
277 target->length = src->data_size; 397 }
278 } 398 }
279 return 0;
280 }
281
282 static int cx_properties_read_file(
283 cx_attr_unused CxProperties *prop,
284 CxPropertiesSource *src,
285 cxstring *target
286 ) {
287 target->ptr = src->data_ptr;
288 target->length = fread(src->data_ptr, 1, src->data_size, src->src);
289 return ferror((FILE*)src->src);
290 }
291
292 static int cx_properties_read_init_file(
293 cx_attr_unused CxProperties *prop,
294 CxPropertiesSource *src
295 ) {
296 src->data_ptr = cxMallocDefault(src->data_size);
297 if (src->data_ptr == NULL) return 1;
298 return 0;
299 }
300
301 static void cx_properties_read_clean_file(
302 cx_attr_unused CxProperties *prop,
303 CxPropertiesSource *src
304 ) {
305 cxFreeDefault(src->data_ptr);
306 }
307
308 CxPropertiesSource cxPropertiesStringSource(cxstring str) {
309 CxPropertiesSource src;
310 src.src = (void*) str.ptr;
311 src.data_size = str.length;
312 src.data_ptr = NULL;
313 src.read_func = cx_properties_read_string;
314 src.read_init_func = NULL;
315 src.read_clean_func = NULL;
316 return src;
317 }
318
319 CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) {
320 CxPropertiesSource src;
321 src.src = (void*) str;
322 src.data_size = len;
323 src.data_ptr = NULL;
324 src.read_func = cx_properties_read_string;
325 src.read_init_func = NULL;
326 src.read_clean_func = NULL;
327 return src;
328 }
329
330 CxPropertiesSource cxPropertiesCstrSource(const char *str) {
331 CxPropertiesSource src;
332 src.src = (void*) str;
333 src.data_size = strlen(str);
334 src.data_ptr = NULL;
335 src.read_func = cx_properties_read_string;
336 src.read_init_func = NULL;
337 src.read_clean_func = NULL;
338 return src;
339 }
340
341 CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) {
342 CxPropertiesSource src;
343 src.src = file;
344 src.data_size = chunk_size;
345 src.data_ptr = NULL;
346 src.read_func = cx_properties_read_file;
347 src.read_init_func = cx_properties_read_init_file;
348 src.read_clean_func = cx_properties_read_clean_file;
349 return src;
350 }
351
352 CxPropertiesStatus cxPropertiesLoad(
353 CxProperties *prop,
354 CxPropertiesSink sink,
355 CxPropertiesSource source
356 ) {
357 assert(source.read_func != NULL);
358 assert(sink.sink_func != NULL);
359
360 // initialize reader
361 if (source.read_init_func != NULL) {
362 if (source.read_init_func(prop, &source)) {
363 return CX_PROPERTIES_READ_INIT_FAILED; // LCOV_EXCL_LINE
364 }
365 }
366
367 // transfer the data from the source to the sink
368 CxPropertiesStatus status;
369 CxPropertiesStatus kv_status = CX_PROPERTIES_NO_DATA;
370 bool found = false;
371 while (true) {
372 // read input
373 cxstring input;
374 if (source.read_func(prop, &source, &input)) { // LCOV_EXCL_START
375 status = CX_PROPERTIES_READ_FAILED;
376 break;
377 } // LCOV_EXCL_STOP
378
379 // no more data - break
380 if (input.length == 0) {
381 if (found) {
382 // something was found, check the last kv_status
383 if (kv_status == CX_PROPERTIES_INCOMPLETE_DATA) {
384 status = CX_PROPERTIES_INCOMPLETE_DATA;
385 } else {
386 status = CX_PROPERTIES_NO_ERROR;
387 }
388 } else {
389 // nothing found
390 status = CX_PROPERTIES_NO_DATA;
391 }
392 break;
393 }
394
395 // set the input buffer and read the k/v-pairs
396 cxPropertiesFill(prop, input);
397
398 do {
399 cxstring key, value;
400 kv_status = cxPropertiesNext(prop, &key, &value);
401 if (kv_status == CX_PROPERTIES_NO_ERROR) {
402 found = true;
403 if (sink.sink_func(prop, &sink, key, value)) {
404 kv_status = CX_PROPERTIES_SINK_FAILED; // LCOV_EXCL_LINE
405 }
406 }
407 } while (kv_status == CX_PROPERTIES_NO_ERROR);
408
409 if (kv_status > CX_PROPERTIES_OK) {
410 status = kv_status;
411 break;
412 }
413 }
414
415 if (source.read_clean_func != NULL) {
416 source.read_clean_func(prop, &source);
417 }
418
419 return status;
420 }

mercurial