| 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 |
| 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 } |
|