#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/queue.h>
#include <assert.h>
#include <yaml.h>
#include "yaml-path.h"
#define YAML_PATH_MAX_SECTION_LEN 1024
#define YAML_PATH_MAX_SECTIONS 255
#define YAML_PATH_MAX_LEN YAML_PATH_MAX_SECTION_LEN * YAML_PATH_MAX_SECTIONS
typedef enum yaml_path_section_type {
YAML_PATH_SECTION_ROOT,
YAML_PATH_SECTION_ANCHOR,
YAML_PATH_SECTION_KEY,
YAML_PATH_SECTION_INDEX,
YAML_PATH_SECTION_SLICE,
} yaml_path_section_type_t;
typedef struct yaml_path_section {
yaml_path_section_type_t type;
int level;
union {
const char *key;
const char *anchor;
int index;
struct {int start, end, stride;} slice;
} data;
TAILQ_ENTRY(yaml_path_section) entries;
yaml_node_type_t node_type;
int counter;
bool valid;
bool next_valid;
} yaml_path_section_t;
typedef TAILQ_HEAD(path_section_list, yaml_path_section) path_section_list_t;
typedef struct yaml_path {
path_section_list_t sections_list;
size_t sections_count;
size_t sequence_level;
size_t current_level;
size_t start_level;
yaml_path_error_t error;
} yaml_path_t;
static void
yaml_path_error_set (yaml_path_t *path, yaml_path_error_type_t error_type, const char *message, size_t pos)
{
assert(path != NULL);
path->error.type = error_type;
path->error.message = message;
path->error.pos = pos;
}
static void
yaml_path_sections_remove (yaml_path_t *path)
{
assert(path != NULL);
while (!TAILQ_EMPTY(&path->sections_list)) {
yaml_path_section_t *el = TAILQ_FIRST(&path->sections_list);
TAILQ_REMOVE(&path->sections_list, el, entries);
path->sections_count--;
switch (el->type) {
case YAML_PATH_SECTION_KEY:
free((void *)el->data.key);
break;
case YAML_PATH_SECTION_ANCHOR:
free((void *)el->data.anchor);
break;
case YAML_PATH_SECTION_SLICE:
if (path->sequence_level == el->level)
path->sequence_level = 0;
break;
default:
break;
}
free(el);
}
}
static yaml_path_section_t*
yaml_path_section_create (yaml_path_t *path, yaml_path_section_type_t section_type)
{
yaml_path_section_t *el = malloc(sizeof(*el));
assert(el != NULL);
memset(el, 0, sizeof(*el));
path->sections_count++;
el->level = path->sections_count;
el->type = section_type;
el->node_type = YAML_SCALAR_NODE;
TAILQ_INSERT_TAIL(&path->sections_list, el, entries);
if (el->type == YAML_PATH_SECTION_SLICE && !path->sequence_level) {
path->sequence_level = el->level;
}
return el;
}
static size_t
yaml_path_section_snprint (yaml_path_section_t *section, char *s, size_t max_len)
{
assert(section != NULL);
if (s == NULL)
return -1;
size_t len = 0;
switch (section->type) {
case YAML_PATH_SECTION_ROOT:
len = snprintf(s, max_len, "$");
break;
case YAML_PATH_SECTION_KEY:
len = snprintf(s, max_len, ".%s", section->data.key);
break;
case YAML_PATH_SECTION_ANCHOR:
len = snprintf(s, max_len, "&%s", section->data.anchor);
break;
case YAML_PATH_SECTION_INDEX:
len = snprintf(s, max_len, "[%d]", section->data.index);
break;
case YAML_PATH_SECTION_SLICE:
len = snprintf(s, max_len, "[%d:%d:%d]", section->data.slice.start, section->data.slice.end, section->data.slice.stride);
break;
default:
len = snprintf(s, max_len, "<?>");
break;
}
return len;
}
static void
_parse (yaml_path_t *path, char *s_path) {
char *sp = s_path;
char *spe = NULL;
assert(path != NULL);
if (s_path == NULL || !s_path[0]) {
yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Path is empty", 0);
return;
}
while (*sp != '\0') {
switch (*sp) {
case '.':
case '[':
if (path->sections_count == 0) {
yaml_path_section_create(path, YAML_PATH_SECTION_ROOT);
}
if (*sp == '.') {
// Key
spe = sp + 1;
while (*spe != '.' && *spe != '[' && *spe != '\0')
spe++;
if (spe == sp+1) {
yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment key is missing", sp - s_path);
goto error;
}
yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_KEY);
sec->data.key = strndup(sp + 1, spe-sp - 1);
sp = spe-1;
} else if (*sp == '[') {
spe = sp+1;
if (*spe == '\'') {
// Key
sp = spe;
spe++;
while (*spe != '\'' && *spe != '\0')
spe++;
if (spe == sp+1) {
yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment key is missing", sp - s_path);
goto error;
}
if (*spe == '\0') {
yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment key is invalid (unxepected end of string, missing ''')", sp - s_path);
goto error;
}
spe++;
if (*spe == '\0') {
yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment key is invalid (unxepected end of string, missing ']')", sp - s_path);
goto error;
}
if (*spe != ']') {
yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment key is invalid (invalid character)", spe - s_path);
goto error;
}
yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_KEY);
sec->data.key = strndup(sp + 1, spe-sp - 2);
sp = spe;
} else {
// Index or Slice
int idx = strtol(spe, &spe, 10);
if (*spe == ']') {
// Index
yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_INDEX);
sec->data.index = idx;
sp = spe;
} else if (*spe == ':') {
// Slice
int idx_start = idx;
sp = spe++;
idx = strtol(spe, &spe, 10);
if (*spe == ':') {
int idx_end = (spe == sp+1 ? __INT_MAX__ : idx);
sp = spe++;
idx = strtol(spe, &spe, 10);
if (*spe == ']' && (idx > 0 || spe == sp+1)) {
yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_SLICE);
sec->data.slice.start = idx_start;
sec->data.slice.end = idx_end;
sec->data.slice.stride = idx > 0 ? idx : 1;
sp = spe;
} else if (*spe == ']' && idx <= 0) {
yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment slice stride can not be less than 1", spe - s_path - 1);
goto error;
} else {
yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment slice stride is invalid (invalid character)", spe - s_path);
goto error;
}
} else if (*spe == ']') {
yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_SLICE);
sec->data.slice.start = idx_start;
sec->data.slice.end = (spe == sp+1 ? __INT_MAX__ : idx);
sec->data.slice.stride = 1;
sp = spe;
} else {
yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment slice end index is invalid (invalid character)", spe - s_path);
goto error;
}
} else if (*spe == '\0') {
yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment index is invalid (unxepected end of string, missing ']')", spe - s_path);
goto error;
} else {
yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Segment index is invalid (invalid character)", spe - s_path);
goto error;
}
}
}
break;
default:
if (path->sections_count == 0) {
spe = sp + 1;
if (*sp == '$' && (*spe == '.' || *spe == '[' || *spe == '\0')) {
yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_ROOT);
} else if (*sp == '&') {
// Anchor
sp++;
while (*spe != '.' && *spe != '[' && *spe != '\0')
spe++;
yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_ANCHOR);
sec->data.anchor = strndup(sp, spe-sp);
} else {
// Key
while (*spe != '.' && *spe != '[' && *spe != '\0')
spe++;
yaml_path_section_t *sec = yaml_path_section_create(path, YAML_PATH_SECTION_ROOT);
sec = yaml_path_section_create(path, YAML_PATH_SECTION_KEY);
sec->data.key = strndup(sp, spe-sp);
}
sp = spe-1;
}
break;
}
sp++;
}
if (path->sections_count == 0) {
yaml_path_error_set(path, YAML_PATH_ERROR_PARSE, "Invalid path segments", 0);
}
return;
error:
yaml_path_sections_remove(path);
}
static yaml_path_section_t*
yaml_path_section_get_at_level (yaml_path_t *path, size_t level)
{
assert(path != NULL);
yaml_path_section_t *el;
TAILQ_FOREACH(el, &path->sections_list, entries) {
if (el->level == level)
return el;
}
return NULL;
}
static yaml_path_section_t*
yaml_path_section_get_first (yaml_path_t *path)
{
assert(path != NULL);
return yaml_path_section_get_at_level(path, 1);
}
static yaml_path_section_t*
yaml_path_section_get_current (yaml_path_t *path)
{
assert(path != NULL);
if (!path->start_level)
return NULL;
return yaml_path_section_get_at_level(path, path->current_level - path->start_level + 1);
}
static bool
yaml_path_sections_prev_are_valid (yaml_path_t *path)
{
assert(path != NULL);
int valid = true;
yaml_path_section_t *el;
TAILQ_FOREACH(el, &path->sections_list, entries) {
if (el->level < path->current_level - path->start_level + 1)
valid = el->valid && valid;
}
return valid;
}
static bool
yaml_path_section_current_is_last (yaml_path_t *path)
{
assert(path != NULL);
yaml_path_section_t *sec = yaml_path_section_get_current(path);
if (sec == NULL)
return false;
return sec->level == path->sections_count;
}
static bool
yaml_path_section_current_is_mandatory_sequence (yaml_path_t *path)
{
assert(path != NULL);
yaml_path_section_t *sec = yaml_path_section_get_current(path);
if (sec == NULL)
return false;
return (sec->type == YAML_PATH_SECTION_SLICE && sec->level == path->sequence_level);
}
static bool
yaml_path_is_valid (yaml_path_t *path)
{
assert(path != NULL);
bool valid = true;
yaml_path_section_t *el;
TAILQ_FOREACH(el, &path->sections_list, entries) {
valid = el->valid && valid;
}
return valid;
}
/* Public */
yaml_path_t*
yaml_path_create (void)
{
yaml_path_t *ypath = malloc(sizeof(*ypath));
assert(ypath != NULL);
memset (ypath, 0, sizeof(*ypath));
TAILQ_INIT(&ypath->sections_list);
return ypath;
}
int
yaml_path_parse (yaml_path_t *path, char *s_path)
{
if (path == NULL)
return -1;
yaml_path_sections_remove(path);
memset(&path->error, 0, sizeof(path->error));
_parse(path, s_path);
if (path->sections_count == 0)
return -2;
return 0;
}
void
yaml_path_destroy (yaml_path_t *path)
{
if (path == NULL)
return;
yaml_path_sections_remove(path);
free(path);
}
/* API */
const yaml_path_error_t*
yaml_path_error_get (yaml_path_t *path)
{
if (path == NULL)
return NULL;
return &path->error;
}
size_t
yaml_path_snprint (yaml_path_t *path, char *s, size_t max_len)
{
if (s == NULL)
return -1;
if (path == NULL)
return 0;
size_t len = 0;
yaml_path_section_t *el;
TAILQ_FOREACH(el, &path->sections_list, entries) {
len += yaml_path_section_snprint(el, s + (len < max_len ? len : max_len), max_len - (len < max_len ? len : max_len));
}
return len;
}
int
yaml_path_filter_event (yaml_path_t *path, yaml_parser_t *parser, yaml_event_t *event, yaml_path_filter_mode_t mode)
{
int res = 0;
const char *anchor = NULL;
if (path == NULL || parser == NULL || event == NULL)
goto exit;
switch(event->type) {
case YAML_MAPPING_START_EVENT:
anchor = (const char *)event->data.mapping_start.anchor;
break;
case YAML_SEQUENCE_START_EVENT:
anchor = (const char *)event->data.sequence_start.anchor;
break;
case YAML_SCALAR_EVENT:
anchor = (const char *)event->data.scalar.anchor;
break;
default:
break;
}
if (!path->start_level) {
switch (yaml_path_section_get_first(path)->type) {
case YAML_PATH_SECTION_ROOT:
if (event->type == YAML_DOCUMENT_START_EVENT) {
path->start_level = 1;
yaml_path_section_get_first(path)->valid = true;
}
break;
case YAML_PATH_SECTION_ANCHOR:
if (anchor != NULL) {
if (!strcmp(yaml_path_section_get_first(path)->data.anchor, anchor)) {
path->start_level = path->current_level;
if (yaml_path_section_get_current(path))
yaml_path_section_get_current(path)->node_type = YAML_SCALAR_NODE;
}
}
break;
default:
//TODO: This path is invalid
break;
}
} else {
//TODO: ?
}
yaml_path_section_t *current_section = yaml_path_section_get_current(path);
if (!current_section) {
} else {
switch (event->type) {
case YAML_DOCUMENT_START_EVENT:
case YAML_MAPPING_START_EVENT:
case YAML_SEQUENCE_START_EVENT:
case YAML_ALIAS_EVENT:
case YAML_SCALAR_EVENT:
switch (current_section->node_type) {
case YAML_SCALAR_NODE:
current_section->valid = true;
break;
case YAML_MAPPING_NODE:
if (current_section->type == YAML_PATH_SECTION_KEY) {
if (current_section->counter % 2) {
current_section->valid = current_section->next_valid;
current_section->next_valid = false;
} else {
current_section->next_valid = !strcmp(current_section->data.key, (const char *)event->data.scalar.value);
current_section->valid = false;
}
} else {
current_section->valid = false;
}
break;
case YAML_SEQUENCE_NODE:
if (current_section->type == YAML_PATH_SECTION_INDEX) {
current_section->valid = current_section->data.index == current_section->counter;
} else if (current_section->type == YAML_PATH_SECTION_SLICE) {
current_section->valid = current_section->data.slice.start <= current_section->counter &&
current_section->data.slice.end > current_section->counter &&
(current_section->data.slice.start + current_section->counter) % current_section->data.slice.stride == 0;
} else {
current_section->valid = false;
}
break;
default:
break;
}
current_section->counter++;
default:
break;
}
}
switch (event->type) {
case YAML_STREAM_START_EVENT:
case YAML_STREAM_END_EVENT:
case YAML_NO_EVENT:
res = 1;
break;
case YAML_DOCUMENT_START_EVENT:
if (path->start_level == 1)
path->current_level++;
res = 1;
break;
case YAML_DOCUMENT_END_EVENT:
if (path->start_level == 1)
path->current_level--;
res = 1;
break;
case YAML_MAPPING_START_EVENT:
case YAML_SEQUENCE_START_EVENT:
if (current_section) {
if (yaml_path_section_current_is_last(path))
res = yaml_path_is_valid(path);
} else {
if (path->current_level > path->start_level) {
if (mode == YAML_PATH_FILTER_RETURN_ALL)
res = yaml_path_is_valid(path);
}
}
path->current_level++;
current_section = yaml_path_section_get_current(path);
if (current_section && yaml_path_section_current_is_mandatory_sequence(path)) {
res = yaml_path_sections_prev_are_valid(path);
}
if (current_section) {
current_section->node_type = event->type == YAML_MAPPING_START_EVENT ? YAML_MAPPING_NODE : YAML_SEQUENCE_NODE;
current_section->counter = 0;
}
break;
case YAML_MAPPING_END_EVENT:
case YAML_SEQUENCE_END_EVENT:
if (current_section) {
if (yaml_path_section_current_is_mandatory_sequence(path))
res = yaml_path_sections_prev_are_valid(path);
}
path->current_level--;
current_section = yaml_path_section_get_current(path);
if (current_section) {
if (yaml_path_section_current_is_last(path))
res = yaml_path_is_valid(path);
} else {
if (path->current_level > path->start_level) {
if (mode == YAML_PATH_FILTER_RETURN_ALL)
res = yaml_path_is_valid(path);
}
}
break;
case YAML_ALIAS_EVENT:
case YAML_SCALAR_EVENT:
if (!current_section) {
if ((mode == YAML_PATH_FILTER_RETURN_ALL && path->current_level > path->start_level) || path->current_level == path->start_level)
res = yaml_path_is_valid(path);
} else {
res = yaml_path_is_valid(path) && yaml_path_section_current_is_last(path);
}
break;
default:
break;
}
exit:
return res;
}