The UCX properties parser can be used to parse line based key/value strings.
Supported Syntax
Key/value pairs must be line based and separated by a single character delimter. The parser supports up to three different characters which introduce comments. All characters starting with a comment character up to the end of the line are ignored. Blank lines are also ignored.
An example properties file looks like this:
# Comment line at start of file
key1 = value1
key2 = value2
# next is a blank line and will be ignored
keys_are_trimmed = and_so_are_values # also a comment
Basic Parsing
#include <cx/properties.h>
typedef struct cx_properties_config_s {
char delimiter;
char comment1;
char comment2;
char comment3;
// reserved for future use - not implemented in UCX 3.1
char continuation;
} CxPropertiesConfig;
void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config);
void cxPropertiesInitDefault(CxProperties *prop);
void cxPropertiesDestroy(CxProperties *prop);
void cxPropertiesReset(CxProperties *prop);
int cxPropertiesFilln(CxProperties *prop,
const char *buf, size_t len);
// where S is one of cxstring, cxmutstr, char*, const char*
int cxPropertiesFill(CxProperties *prop, S string);
CxPropertiesStatus cxPropertiesNext(CxProperties *prop,
cxstring *key, cxstring *value);
void cxPropertiesUseStack(CxProperties *prop,
char *buf, size_t capacity);
The first step is to initialize a CxProperties structure with a call to cxPropertiesInit() using the desired config. The shorthand cxPropertiesInitDefault() creates a default configuration with the equals sign '=' as delimiter and the hash-symbol '#' as comment symbol (the other two comment symbols remain unused in the default config).
The actual parsing is an interleaving invocation of the cxPropertiesFill() (or cxPropertiesFilln()) and cxPropertiesNext() functions. The cxPropertiesFill() function is a convenience function, that accepts UCX strings and normal zero-terminated C strings and behaves otherwise like cxPropertiesFilln().
Filling the input buffer is cost-free if there is no data already in the input buffer. In that case, the input buffer only stores the pointer to the original data without creating a copy. Calling cxPropertiesNext() will return with CX_PROPERTIES_NO_ERROR (= zero) for each key/value-pair that is successfully parsed, and stores the pointers and lengths for both the key and the value into the structures pointed to by the key and value arguments.
When all the data from the input buffer was successfully consumed, cxPropertiesNext() returns CX_PROPERTIES_NO_DATA.
If cxPropertiesNext() returns CX_PROPERTIES_INCOMPLETE_DATA it means that the input buffer is exhausted, but the last line did not contain a full key/value pair. In that case, you can call cxPropertiesFill() again to add more data and continue with cxPropertiesNext().
Note, that adding more data to a non-empty input buffer will lead to an allocation, unless you specified some stack memory with cxPropertiesUseStack(). The stack capacity must be large enough to contain the longest line in your data. If the internal buffer is not large enough to contain a single line, it is extended. If that is not possible for some reason, cxPropertiesNext() fails and returns CX_PROPERTIES_BUFFER_ALLOC_FAILED.
If you want to reuse a CxProperties structure with the same config, you can call cxPropertiesReset(), even if the last operation was a failure. Otherwise, you should always call cxPropertiesDestroy() when you are done with the parser.
List of Status Codes
Below is a full list of status codes for cxPropertiesNext().
Status Code
Meaning
CX_PROPERTIES_NO_ERROR
A key/value pair was found and returned.
CX_PROPERTIES_NO_DATA
The input buffer does not contain more data.
CX_PROPERTIES_INCOMPLETE_DATA
The input ends unexpectedly. This can happen when the last line does not terminate with a line break, or when the input ends with a parsed key but no value. Use cxPropertiesFill() to add more data before retrying.
CX_PROPERTIES_NULL_INPUT
The input buffer was never initialized. Probably you forgot to call cxPropertiesFill() at least once.
CX_PROPERTIES_INVALID_EMPTY_KEY
Only white-spaces were found on the left hand-side of the delimiter. Keys must not be empty.
CX_PROPERTIES_INVALID_MISSING_DELIMITER
A line contains data, but no delimiter.
CX_PROPERTIES_BUFFER_ALLOC_FAILED
More internal buffer was needed, but could not be allocated.
The basic idea of cxPropertiesLoad() is that key/value-pairs are extracted from a source and ingested by a sink. For the most common scenarios where properties data is read from a string or a file and put into a map, several functions are available. But you can specify your own sources and sinks, as well.
The following example shows a simple function which loads all properties data from a file. The chunk_size argument when creating the file source specifies how many bytes are read from the file and filled into the properties parser in one read/sink cycle.
#include <stdio.h>
#include <cx/properties.h>
int load_props_from_file(const char *filename, CxMap *map) {
FILE *f = fopen(filename, "r");
if (!f) return -1;
CxProperties prop;
cxPropertiesInitDefault(&prop);
CxPropertiesSink sink = cxPropertiesMapSink(map);
CxPropertiesSource src = cxPropertiesFileSource(f, 512);
CxPropertiesStatus status = cxPropertiesLoad(&prop, sink, src);
fclose(f);
return status;
}
// usage:
CxMap *map = cxHashMapCreateSimple(CX_STORE_POINTERS);
if (load_props_from_file("my-props.properties", map)) {
// error handling
} else {
// assuming my-props.properties contains the following line:
// my-key = some value
char *value = cxMapGet(map, "my-key");
}
You can create your own sources and sinks by initializing the respective structures.
For a source, only the read_func is mandatory, the other two functions are optional and used for initialization and cleanup, if required. The file source created by cxPropertiesFileSource(), for example, uses the read_init_func to allocate, and the read_clean_func to free the read buffer, respectively.
Since the default map sink created by cxPropertiesMapSink() stores char* pointers into a map, the following example uses a different sink, which stores them as cxmutstr values, automatically freeing them when the map gets destroyed. And instead of reading the data from a file with fread(), it uses mmap() to map the file into memory for reading.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <cx/properties.h>
#include <cx/hash_map.h>
static int prop_mmap(CxProperties *prop, CxPropertiesSource *src) {
struct stat s;
int fd = open(src->src, O_RDONLY);
if (fd < 0) return -1;
// re-use the data field to store the fd
// there are cleaner ways, but this is just for illustration
src->src = (void*) fd;
fstat(fd, &s);
// memory map the entire file
// and store the address and length in the properties source
src->data_ptr = mmap(0, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
src->data_size = s.st_size;
return src->data_ptr == NULL;
}
static int prop_read(CxProperties *prop, CxPropertiesSource *src,
cxstring *target) {
// copy the address and length of the mapped data to the target
target->ptr = src->data_ptr;
target->length = src->data_size;
// set the new size to zero to indicate that there is no more data
src->data_size = 0;
return 0;
}
static void prop_unmap(CxProperties *prop, CxPropertiesSource *src) {
// unmap the memory and close the file
munmap(src->data_ptr, src->data_size);
close((int)src->src);
}
static int prop_sink(CxProperties *prop, CxPropertiesSink *sink,
cxstring key, cxstring value) {
CxMap *map = sink->sink;
// copy the string and store it into the map
cxmutstr v = cx_strdup(value);
int r = cxMapPut(map, key, &v);
if (r != 0) cx_strfree(&v);
return r;
}
int load_props_from_file(const char *filename, CxMap *map) {
CxProperties prop;
cxPropertiesInitDefault(&prop);
CxPropertiesSource src;
src.src = (void*) filename;
src.read_init_func = prop_mmap;
src.read_func = prop_read;
src.read_clean_func = prop_unmap;
CxPropertiesSink sink;
sink.sink = map;
sink.sink_func = prop_sink;
return cxPropertiesLoad(&prop, sink, src);
}
int main() {
// in contrast to the default map sink,
// this one here stores the UCX strings by value
CxMap *map = cxHashMapCreateSimple(sizeof(cxmutstr));
// automatically free the UCX string when removed from the map
cxDefineDestructor(map, cx_strfree);
// use our custom load function to load the data from the file
if (load_props_from_file("my-props.properties", map)) {
fputs("Error reading properties.\n", stderr);
return 1;
}
// output the read key/value pairs for illustration
CxMapIterator iter = cxMapIterator(map);
cx_foreach(CxMapEntry *, entry, iter) {
cxstring k = cx_strn(entry->key->data, entry->key->len);
cxmutstr *v = entry->value;
printf("%.*s = %.*s\n",
(int) k.length, k.ptr, (int) v->length, v->ptr);
}
// freeing the map also frees the strings
// because we have registered cx_strfree() as destructor function
cxMapFree(map);
return 0;
}
Additional Status Codes
For sources and sinks there are three additional special status codes, which only appear as return values for cxPropertiesLoad().
Status Code
Meaning
CX_PROPERTIES_READ_INIT_FAILED
Initializing the properties source failed and the cx_properties_read_init_func returned non-zero.
CX_PROPERTIES_READ_FAILED
Reading from a properties source failed and the cx_properties_read_func returned non-zero.
CX_PROPERTIES_SINK_FAILED
Sinking a key/value-pair failed and the cx_properties_sink_func returned non-zero.