#include "cx/properties.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
const CxPropertiesConfig cx_properties_config_default = {
'=',
'#',
'\0',
'\0',
'\\',
};
void cxPropertiesInit(
CxProperties *prop,
CxPropertiesConfig config
) {
memset(prop,
0,
sizeof(CxProperties));
prop->config = config;
}
void cxPropertiesDestroy(CxProperties *prop) {
cxBufferDestroy(&prop->input);
cxBufferDestroy(&prop->buffer);
}
void cxPropertiesReset(CxProperties *prop) {
CxPropertiesConfig config = prop->config;
cxPropertiesDestroy(prop);
cxPropertiesInit(prop, config);
}
int cxPropertiesFilln(
CxProperties *prop,
const char *buf,
size_t len
) {
if (cxBufferEof(&prop->input)) {
cxBufferDestroy(&prop->input);
cxBufferInit(&prop->input, (
void*) buf, len,
NULL,
CX_BUFFER_COPY_ON_WRITE |
CX_BUFFER_AUTO_EXTEND);
prop->input.size = len;
}
else {
if (cxBufferAppend(buf,
1, len, &prop->input) < len)
return -1;
}
return 0;
}
void cxPropertiesUseStack(
CxProperties *prop,
char *buf,
size_t capacity
) {
cxBufferInit(&prop->buffer, buf, capacity,
NULL,
CX_BUFFER_COPY_ON_EXTEND);
}
CxPropertiesStatus cxPropertiesNext(
CxProperties *prop,
cxstring *key,
cxstring *value
) {
if (prop->input.space ==
NULL) {
return CX_PROPERTIES_NULL_INPUT;
}
CxBuffer *current_buffer = &prop->input;
char comment1 = prop->config.comment1;
char comment2 = prop->config.comment2;
char comment3 = prop->config.comment3;
char delimiter = prop->config.delimiter;
char continuation = prop->config.continuation;
if (!cxBufferEof(&prop->buffer)) {
cxstring input = cx_strn(prop->input.space + prop->input.pos,
prop->input.size - prop->input.pos);
cxstring nl = cx_strchr(input,
'\n');
while (nl.length >
0) {
char previous = nl.ptr > input.ptr ? nl.ptr[
-1] : prop->buffer.space[prop->buffer.size
-1];
if (previous == continuation) {
nl = cx_strchr(cx_strsubs(nl,
1),
'\n');
}
else {
break;
}
}
if (nl.length >
0) {
size_t len_until_nl = (
size_t)(nl.ptr - input.ptr) +
1;
if (cxBufferAppend(input.ptr,
1,
len_until_nl, &prop->buffer) < len_until_nl) {
return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
}
prop->input.pos += len_until_nl;
current_buffer = &prop->buffer;
}
else {
if (cxBufferAppend(input.ptr,
1,
input.length, &prop->buffer) < input.length) {
return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
}
cxBufferReset(&prop->input);
return CX_PROPERTIES_INCOMPLETE_DATA;
}
}
while (!cxBufferEof(current_buffer)) {
const char *buf = current_buffer->space + current_buffer->pos;
size_t len = current_buffer->size - current_buffer->pos;
size_t delimiter_index =
0;
size_t comment_index =
0;
bool has_comment = false;
bool has_continuation = false;
size_t i =
0;
char c =
0;
for (; i < len; i++) {
c = buf[i];
if (c == comment1 || c == comment2 || c == comment3) {
if (comment_index ==
0) {
comment_index = i;
has_comment = true;
}
}
else if (c == delimiter) {
if (delimiter_index ==
0 && !has_comment) {
delimiter_index = i;
}
}
else if (delimiter_index >
0 && c == continuation && i
+1 < len && buf[i
+1] ==
'\n') {
has_continuation = true;
i++;
}
else if (c ==
'\n') {
break;
}
}
if (c !=
'\n') {
assert(current_buffer != &prop->buffer);
assert(cxBufferEof(&prop->buffer));
if (prop->buffer.space ==
NULL) {
cxBufferInit(&prop->buffer,
NULL,
256,
NULL,
CX_BUFFER_AUTO_EXTEND);
}
else {
cxBufferReset(&prop->buffer);
}
if (cxBufferAppend(buf,
1, len, &prop->buffer) < len) {
return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
}
cxBufferReset(&prop->input);
return CX_PROPERTIES_INCOMPLETE_DATA;
}
cxstring line = has_comment ?
cx_strn(buf, comment_index) :
cx_strn(buf, i);
if (delimiter_index ==
0) {
line = cx_strtrim(line);
if (line.length >
0) {
if (line.ptr[
0] == delimiter) {
return CX_PROPERTIES_INVALID_EMPTY_KEY;
}
else {
return CX_PROPERTIES_INVALID_MISSING_DELIMITER;
}
}
else {
if (current_buffer == &prop->buffer) {
assert(current_buffer->pos + i +
1 == current_buffer->size);
cxBufferReset(&prop->buffer);
current_buffer = &prop->input;
}
else {
current_buffer->pos += i +
1;
}
continue;
}
}
else {
cxstring k = cx_strn(buf, delimiter_index);
cxstring val = cx_strn(
buf + delimiter_index +
1,
line.length - delimiter_index -
1);
k = cx_strtrim(k);
val = cx_strtrim(val);
if (k.length >
0) {
current_buffer->pos += i +
1;
assert(current_buffer->pos <= current_buffer->size);
assert(current_buffer != &prop->buffer || current_buffer->pos == current_buffer->size);
if (has_continuation) {
char *ptr = (
char*)val.ptr;
if (current_buffer != &prop->buffer) {
if (prop->buffer.space ==
NULL) {
cxBufferInit(&prop->buffer,
NULL,
256,
NULL,
CX_BUFFER_AUTO_EXTEND);
}
prop->buffer.size =
0;
prop->buffer.pos =
0;
if (cxBufferWrite(val.ptr,
1, val.length, &prop->buffer) != val.length) {
return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
}
val.ptr = prop->buffer.space;
ptr = prop->buffer.space;
}
bool trim = false;
size_t x =
0;
for(
size_t j=
0;j<val.length;j++) {
c = ptr[j];
if (j
+1 < val.length && c ==
'\\' && ptr[j
+1] ==
'\n') {
j++;
trim = true;
continue;
}
if (j > x) {
if (trim) {
if (isspace((
unsigned char)c)) {
continue;
}
trim = false;
}
ptr[x] = c;
}
x++;
}
val.length = x;
}
*key = k;
*value = val;
return CX_PROPERTIES_NO_ERROR;
}
else {
return CX_PROPERTIES_INVALID_EMPTY_KEY;
}
}
}
assert(cxBufferEof(&prop->buffer));
assert(cxBufferEof(&prop->input));
return CX_PROPERTIES_NO_DATA;
}
#ifndef CX_PROPERTIES_LOAD_FILL_SIZE
#define CX_PROPERTIES_LOAD_FILL_SIZE 1024
#endif
const unsigned cx_properties_load_fill_size =
CX_PROPERTIES_LOAD_FILL_SIZE;
#ifndef CX_PROPERTIES_LOAD_BUF_SIZE
#define CX_PROPERTIES_LOAD_BUF_SIZE 256
#endif
const unsigned cx_properties_load_buf_size =
CX_PROPERTIES_LOAD_BUF_SIZE;
CxPropertiesStatus cx_properties_load(CxPropertiesConfig config,
const CxAllocator *allocator, cxstring filename, CxMap *target) {
if (allocator ==
NULL) {
allocator = cxDefaultAllocator;
}
const bool use_cstring = cxCollectionStoresPointers(target);
if (!use_cstring && cxCollectionElementSize(target) !=
sizeof(cxmutstr)) {
return CX_PROPERTIES_MAP_ERROR;
}
cxmutstr fname = cx_strdup(filename);
if (fname.ptr ==
NULL) {
return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
}
FILE *f = fopen(fname.ptr,
"r");
if (f ==
NULL) {
cx_strfree(&fname);
return CX_PROPERTIES_FILE_ERROR;
}
char linebuf[cx_properties_load_buf_size];
char fillbuf[cx_properties_load_fill_size];
CxPropertiesStatus status;
CxProperties parser;
cxPropertiesInit(&parser, config);
cxPropertiesUseStack(&parser, linebuf, cx_properties_load_buf_size);
status =
CX_PROPERTIES_NO_DATA;
size_t keys_found =
0;
while (true) {
size_t r = fread(fillbuf,
1, cx_properties_load_fill_size, f);
if (ferror(f)) {
status =
CX_PROPERTIES_FILE_ERROR;
break;
}
if (r ==
0) {
break;
}
if (cxPropertiesFilln(&parser, fillbuf, r)) {
status =
CX_PROPERTIES_BUFFER_ALLOC_FAILED;
break;
}
cxstring key, value;
while (true) {
status = cxPropertiesNext(&parser, &key, &value);
if (status !=
CX_PROPERTIES_NO_ERROR) {
break;
}
else {
cxmutstr v = cx_strdup_a(allocator, value);
if (v.ptr ==
NULL) {
status =
CX_PROPERTIES_MAP_ERROR;
break;
}
void *mv = use_cstring ? (
void*)v.ptr : &v;
if (cxMapPut(target, key, mv)) {
cx_strfree(&v);
status =
CX_PROPERTIES_MAP_ERROR;
break;
}
keys_found++;
}
}
if (status >
CX_PROPERTIES_OK) {
break;
}
}
fclose(f);
cxPropertiesDestroy(&parser);
cx_strfree(&fname);
if (status ==
CX_PROPERTIES_NO_DATA && keys_found >
0) {
return CX_PROPERTIES_NO_ERROR;
}
else {
return status;
}
}