conf_io support YAML

This commit is contained in:
lixianjing 2025-04-14 09:47:30 +08:00
parent d580a30260
commit 3fd716f7d7
12 changed files with 1240 additions and 64 deletions

View File

@ -1,5 +1,8 @@
# 最新动态 # 最新动态
2025/04/14
* conf_io 支持 YAML格式。
2025/04/13 2025/04/13
* 完善slist/dlist(感谢兆坤提供补丁) * 完善slist/dlist(感谢兆坤提供补丁)

View File

@ -1,8 +1,8 @@
# 读写 XML/JSON/INI 和 UBJSON 等格式的数据文件 # 读写 XML/JSON/INI/YAML 和 UBJSON 等格式的数据文件
> 将抽象放进代码,细节放进元数据。 > 将抽象放进代码,细节放进元数据。
开发应用程序,会经常使用各种数据文件(如配置数据和元数据),常见的数据文件格式有 INI、XML、JSON 和 UBJSON对一个复杂的应用程序其中可能会同时使用多种不同格式的数据文件。 开发应用程序,会经常使用各种数据文件(如配置数据和元数据),常见的数据文件格式有 INI、YAML、XML、JSON 和 UBJSON对一个复杂的应用程序其中可能会同时使用多种不同格式的数据文件。
通常操作这些数据文件的函数各不相同对于程序员来说即是学习负担也是记忆负担。AWTK 提供了一套统一的接口函数,同一套接口函数,可以操作不同的格式的数据文件。 通常操作这些数据文件的函数各不相同对于程序员来说即是学习负担也是记忆负担。AWTK 提供了一套统一的接口函数,同一套接口函数,可以操作不同的格式的数据文件。
@ -315,6 +315,42 @@ tk_object_t* conf_ubjson_load(const char* url, bool_t create_if_not_exist);
ret_t conf_ubjson_save_as(tk_object_t* obj, const char* url); ret_t conf_ubjson_save_as(tk_object_t* obj, const char* url);
``` ```
### 3.5 YAML 格式
* 打开
```c
/**
* @method conf_yaml_load
* 从指定 URL 加载 YAML 对象。
*
* @annotation ["constructor"]
*
* @param {const char*} url 路径(通常是文件路径)。
* @param {bool_t} create_if_not_exist 如果不存在是否创建。
*
* @return {tk_object_t*} 返回配置对象。
*/
tk_object_t* conf_yaml_load(const char* url, bool_t create_if_not_exist);
```
* 保存
```c
/**
* @method conf_yaml_save_as
* 将 doc 对象保存到指定 URL。
* @annotation ["static"]
*
* @param {tk_object_t*} obj doc 对象。
* @param {const char*} url 保存的位置。
*
* @return {ret_t} 返回 RET_OK 表示成功,否则表示失败
*/
ret_t conf_yaml_save_as(tk_object_t* obj, const char* url);
```
## 完整示例 ## 完整示例
* 文件读写 * 文件读写

View File

@ -27,6 +27,8 @@
#include "tkc/data_reader_factory.h" #include "tkc/data_reader_factory.h"
#include "tkc/data_writer_factory.h" #include "tkc/data_writer_factory.h"
#define INI_COMMENT_CHAR '#'
typedef enum _parser_state_t { typedef enum _parser_state_t {
STATE_NONE = 0, STATE_NONE = 0,
STATE_BEFORE_GROUP, STATE_BEFORE_GROUP,
@ -176,65 +178,6 @@ conf_doc_t* conf_doc_load_ini(const char* data) {
return doc; return doc;
} }
static ret_t conf_doc_save_str(const char* p, str_t* str) {
return_value_if_fail(p != NULL, RET_BAD_PARAMS);
while (*p) {
if (*p == '#' || *p == '\\' || *p == '\n') {
return_value_if_fail(str_append_char(str, '\\') == RET_OK, RET_OOM);
}
return_value_if_fail(str_append_char(str, *p) == RET_OK, RET_OOM);
p++;
}
return RET_OK;
}
static ret_t conf_doc_save_value(const value_t* v, str_t* str) {
char buff[64] = {0};
return_value_if_fail(str != NULL, RET_BAD_PARAMS);
switch (v->type) {
case VALUE_TYPE_STRING: {
const char* p = value_str(v);
return_value_if_fail(p != NULL, RET_BAD_PARAMS);
return conf_doc_save_str(p, str);
}
case VALUE_TYPE_WSTRING: {
str_t s;
ret_t ret = RET_OK;
str_init(&s, 0);
str_from_wstr(&s, value_wstr(v));
ret = conf_doc_save_str(s.str, str);
str_reset(&s);
return ret;
}
case VALUE_TYPE_FLOAT32: {
tk_snprintf(buff, sizeof(buff) - 1, "%f", value_float32(v));
break;
}
case VALUE_TYPE_FLOAT:
case VALUE_TYPE_DOUBLE: {
tk_snprintf(buff, sizeof(buff) - 1, "%lf", value_double(v));
break;
}
case VALUE_TYPE_INT64: {
tk_snprintf(buff, sizeof(buff) - 1, "%lld", value_int64(v));
break;
}
case VALUE_TYPE_UINT64: {
tk_snprintf(buff, sizeof(buff) - 1, "%llu", value_uint64(v));
break;
}
default: {
tk_snprintf(buff, sizeof(buff) - 1, "%d", value_int(v));
break;
}
}
return str_append(str, buff);
}
static ret_t conf_doc_save_leaf_node(conf_node_t* node, str_t* str) { static ret_t conf_doc_save_leaf_node(conf_node_t* node, str_t* str) {
value_t v; value_t v;
const char* key = conf_node_get_name(node); const char* key = conf_node_get_name(node);
@ -242,7 +185,7 @@ static ret_t conf_doc_save_leaf_node(conf_node_t* node, str_t* str) {
conf_node_get_value(node, &v); conf_node_get_value(node, &v);
return_value_if_fail(str_append_more(str, " ", key, " = ", NULL) == RET_OK, RET_OOM); return_value_if_fail(str_append_more(str, " ", key, " = ", NULL) == RET_OK, RET_OOM);
return_value_if_fail(conf_doc_save_value(&v, str) == RET_OK, RET_OOM); return_value_if_fail(conf_node_save_value(str, &v, INI_COMMENT_CHAR) == RET_OK, RET_OOM);
return_value_if_fail(str_append(str, "\n") == RET_OK, RET_OOM); return_value_if_fail(str_append(str, "\n") == RET_OK, RET_OOM);
return RET_OK; return RET_OK;

View File

@ -1327,3 +1327,64 @@ ret_t conf_doc_foreach(conf_doc_t* doc, conf_doc_on_visit_t on_visit, void* ctx)
return_value_if_fail(doc && on_visit, RET_BAD_PARAMS); return_value_if_fail(doc && on_visit, RET_BAD_PARAMS);
return conf_node_foreach_sibling(NULL, conf_node_get_first_child(doc->root), on_visit, ctx); return conf_node_foreach_sibling(NULL, conf_node_get_first_child(doc->root), on_visit, ctx);
} }
static ret_t conf_doc_save_str(const char* p, str_t* str, char comment_char) {
return_value_if_fail(p != NULL, RET_BAD_PARAMS);
while (*p) {
char c = str_escape_char(*p);
if (c != *p || *p == comment_char || *p == '\\' || *p == '\n') {
return_value_if_fail(str_append_char(str, '\\') == RET_OK, RET_OOM);
}
return_value_if_fail(str_append_char(str, c) == RET_OK, RET_OOM);
p++;
}
return RET_OK;
}
ret_t conf_node_save_value(str_t* str, const value_t* v, char comment_char) {
char buff[64] = {0};
return_value_if_fail(str != NULL, RET_BAD_PARAMS);
switch (v->type) {
case VALUE_TYPE_STRING: {
const char* p = value_str(v);
return_value_if_fail(p != NULL, RET_BAD_PARAMS);
return conf_doc_save_str(p, str, comment_char);
}
case VALUE_TYPE_WSTRING: {
str_t s;
ret_t ret = RET_OK;
str_init(&s, 0);
str_from_wstr(&s, value_wstr(v));
ret = conf_doc_save_str(s.str, str, comment_char);
str_reset(&s);
return ret;
}
case VALUE_TYPE_FLOAT32: {
tk_snprintf(buff, sizeof(buff) - 1, "%f", value_float32(v));
break;
}
case VALUE_TYPE_FLOAT:
case VALUE_TYPE_DOUBLE: {
tk_snprintf(buff, sizeof(buff) - 1, "%lf", value_double(v));
break;
}
case VALUE_TYPE_INT64: {
tk_snprintf(buff, sizeof(buff) - 1, "%lld", value_int64(v));
break;
}
case VALUE_TYPE_UINT64: {
tk_snprintf(buff, sizeof(buff) - 1, "%llu", value_uint64(v));
break;
}
default: {
tk_snprintf(buff, sizeof(buff) - 1, "%d", value_int(v));
break;
}
}
return str_append(str, buff);
}

View File

@ -662,6 +662,9 @@ struct _conf_node_t {
conf_node_t* first_child; conf_node_t* first_child;
binary_data_t binary_data; binary_data_t binary_data;
} value; } value;
/*for yaml only*/
uint8_t leading_spaces;
}; };
/** /**
@ -967,6 +970,9 @@ ret_t conf_doc_foreach(conf_doc_t* doc, conf_doc_on_visit_t on_visit, void* ctx)
ret_t conf_doc_set_ex(conf_doc_t* doc, conf_node_t* node, const char* path, const value_t* v); ret_t conf_doc_set_ex(conf_doc_t* doc, conf_node_t* node, const char* path, const value_t* v);
ret_t conf_doc_get_value_extend_type(conf_doc_t* doc, conf_node_t* node, value_t* v); ret_t conf_doc_get_value_extend_type(conf_doc_t* doc, conf_node_t* node, value_t* v);
/*ini/yaml使用*/
ret_t conf_node_save_value(str_t* str, const value_t* v, char comment_char);
END_C_DECLS END_C_DECLS
#endif /*TK_CONF_NODE_H*/ #endif /*TK_CONF_NODE_H*/

410
src/conf_io/conf_yaml.c Normal file
View File

@ -0,0 +1,410 @@
/**
* File: yaml.c
* Author: AWTK Develop Team
* Brief: yaml
*
* Copyright (c) 2020 - 2025 Guangzhou ZHIYUAN Electronics Co.,Ltd.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* License file for more details.
*
*/
/**
* History:
* ================================================================
* 2025-04-13 Li XianJing <xianjimli@hotmail.com> created
*
*/
#include "tkc/mem.h"
#include "tkc/utils.h"
#include "conf_io/conf_yaml.h"
#include "tkc/data_reader_mem.h"
#include "tkc/data_writer_wbuffer.h"
#include "tkc/data_reader_factory.h"
#include "tkc/data_writer_factory.h"
/*root的leading_spaces为0其它实际的节点均加上 YAML_LEADING_SPACE_OFFSET */
#define YAML_LEADING_SPACE_OFFSET 2
#define YAML_SPACES_PER_LEVEL 2
#define YAML_SEP_CHAR ':'
#define YAML_COMMENT_CHAR '#'
#define YAML_LIST_START_CHAR '-'
typedef struct _yaml_parser_t {
str_t str;
const char* data;
const char* cursor;
conf_doc_t* doc;
conf_node_t* current_node;
} yaml_parser_t;
static ret_t yaml_parser_deinit(yaml_parser_t* parser);
static ret_t yaml_parser_init(yaml_parser_t* parser, const char* data) {
conf_doc_t* doc = NULL;
return_value_if_fail(parser != NULL && data != NULL, RET_BAD_PARAMS);
memset(parser, 0x00, sizeof(*parser));
doc = conf_doc_create(100);
return_value_if_fail(doc != NULL, RET_OOM);
doc->max_deep_level = 20;
doc->root = conf_doc_create_node(doc, CONF_NODE_ROOT_NAME);
if (doc->root == NULL) {
TKMEM_FREE(doc);
return RET_OOM;
}
parser->doc = doc;
goto_error_if_fail(str_init(&parser->str, 128) != NULL);
parser->data = data;
parser->cursor = data;
parser->current_node = doc->root;
return RET_OK;
error:
yaml_parser_deinit(parser);
return RET_FAIL;
}
static ret_t yaml_parser_deinit(yaml_parser_t* parser) {
return_value_if_fail(parser != NULL, RET_BAD_PARAMS);
if (parser->doc != NULL) {
conf_doc_destroy(parser->doc);
parser->doc = NULL;
}
str_reset(&parser->str);
memset(parser, 0x00, sizeof(*parser));
return RET_OK;
}
static uint32_t yaml_parser_skip_leading_space(yaml_parser_t* parser) {
uint32_t leading_spaces = 0;
const char* p = parser->cursor;
while (*p) {
if (*p == ' ') {
leading_spaces++;
} else if (*p == '\t') {
leading_spaces += YAML_SPACES_PER_LEVEL;
} else if (*p == '\r' || *p == '\n') {
leading_spaces = 0;
} else {
break;
}
p++;
}
parser->cursor = p;
return leading_spaces;
}
static ret_t yaml_parser_skip_to_line_end(yaml_parser_t* parser) {
parser->cursor = tk_skip_to_chars(parser->cursor, "\r\n");
parser->cursor = tk_skip_chars(parser->cursor, "\r\n");
return RET_OK;
}
static const char* yaml_parser_parse_value(yaml_parser_t* parser, uint32_t csep) {
char c = '\0';
uint32_t n = 0;
str_t* s = &(parser->str);
const char* p = parser->cursor;
if (*p != csep) {
return NULL;
} else {
p++;
}
str_clear(s);
do {
if (*p == '\\') {
p++;
if (*p == YAML_COMMENT_CHAR) {
c = *p;
p++;
} else {
c = str_unescape_char(p, &n);
p += n;
}
} else if (*p == YAML_COMMENT_CHAR) {
break;
} else if (*p == '\r' || *p == '\n' || *p == '\0') {
break;
} else {
c = *p++;
}
str_append_char(s, c);
} while(1);
parser->cursor = p;
str_trim(s, " \t\r\n");
yaml_parser_skip_to_line_end(parser);
return s->str;
}
static const char* yaml_parser_parse_name(yaml_parser_t* parser) {
const char* p = parser->cursor;
str_t* s = &parser->str;
str_clear(s);
do {
char c = *p;
if (c == YAML_SEP_CHAR) {
break;
} else if (c == '\0') {
log_warn("yaml unexpected end\n");
break;
} else if (c == '\r' || c == '\n') {
log_warn("yaml unexpected line end\n");
break;
} else if (tk_isalpha(c) || tk_isdigit(c) || c == '_') {
str_append_char(s, *p);
} else {
log_warn("yaml invalid char '%c' for name\n", c);
}
p++;
} while(1);
parser->cursor = p;
str_trim(s, " \t\r\n");
return s->str;
}
static conf_node_t* yaml_parser_get_parent_node(yaml_parser_t* parser, uint32_t leading_spaces) {
conf_node_t* parent = parser->current_node;
while (parent->leading_spaces >= leading_spaces) {
parent = parent->parent;
}
return parent;
}
static ret_t yaml_parser_parse_line(yaml_parser_t* parser) {
ret_t ret = RET_OK;
conf_node_t* node = NULL;
conf_node_t* parent = NULL;
const char* name = NULL;
const char* value = NULL;
char index_name[32] = {0};
bool_t is_list = FALSE;
uint32_t leading_spaces = yaml_parser_skip_leading_space(parser) + YAML_LEADING_SPACE_OFFSET;
parent = yaml_parser_get_parent_node(parser, leading_spaces);
ENSURE(parent != NULL);
if (parser->cursor[0] == YAML_COMMENT_CHAR) {
return yaml_parser_skip_to_line_end(parser);
} else if (parser->cursor[0] == '\r' || parser->cursor[0] == '\n') {
return yaml_parser_skip_to_line_end(parser);
} else if (parser->cursor[0] == '\0') {
return RET_EOS;
} else if (parser->cursor[0] == YAML_LIST_START_CHAR) {
uint32_t index = conf_node_count_children(parent);
tk_snprintf(index_name, TK_NAME_LEN, "%u", index);
name = index_name;
is_list = TRUE;
parent->node_type = CONF_NODE_ARRAY;
} else {
name = yaml_parser_parse_name(parser);
}
node = conf_doc_create_node(parser->doc, name);
return_value_if_fail(node != NULL, RET_OOM);
node->leading_spaces = leading_spaces;
value = yaml_parser_parse_value(parser, is_list ? YAML_LIST_START_CHAR : YAML_SEP_CHAR);
if (value != NULL && *value) {
value_t v;
value_set_str(&v, value);
conf_node_set_value(node, &v);
}
ret = conf_doc_append_child(parser->doc, parent, node);
if (ret == RET_OK) {
parser->current_node = node;
} else {
conf_doc_destroy_node(parser->doc, node);
}
return ret;
}
static ret_t yaml_parser_parse(yaml_parser_t* parser) {
do {
ret_t ret = yaml_parser_parse_line(parser);
if (ret == RET_EOS) {
break;
} else if (ret != RET_OK) {
return ret;
}
} while (1);
return RET_OK;
}
conf_doc_t* conf_doc_load_yaml(const char* data) {
ret_t ret = RET_OK;
yaml_parser_t parser;
conf_doc_t* doc = NULL;
return_value_if_fail(yaml_parser_init(&parser, data) == RET_OK, NULL);
ret = yaml_parser_parse(&parser);
if (ret == RET_OK) {
doc = parser.doc;
parser.doc = NULL;
}
yaml_parser_deinit(&parser);
return doc;
}
static ret_t conf_doc_save_yaml_node_name_value(conf_node_t* node, str_t* str, uint32_t levels) {
value_t v;
ret_t ret = RET_OK;
const char* key = conf_node_get_name(node);
if (levels > 0) {
ret = str_append_n_chars(str, ' ', levels * 2);
return_value_if_fail(ret == RET_OK, ret);
}
if (node->parent->node_type == CONF_NODE_ARRAY) {
return_value_if_fail(str_append_char(str, YAML_LIST_START_CHAR) == RET_OK, RET_OOM);
} else {
return_value_if_fail(str_append(str, key) == RET_OK, RET_OOM);
return_value_if_fail(str_append_char(str, YAML_SEP_CHAR) == RET_OK, RET_OOM);
}
ret = conf_node_get_value(node, &v);
if (ret == RET_OK) {
return_value_if_fail(str_append(str, " ") == RET_OK, RET_OOM);
return_value_if_fail(conf_node_save_value(str, &v, YAML_COMMENT_CHAR) == RET_OK, RET_OOM);
}
return_value_if_fail(str_append(str, "\n") == RET_OK, RET_OOM);
return RET_OK;
}
static ret_t conf_doc_save_yaml_node(conf_node_t* node, str_t* str, uint32_t levels) {
return_value_if_fail(node != NULL && str != NULL, RET_BAD_PARAMS);
while (node != NULL) {
conf_doc_save_yaml_node_name_value(node, str, levels);
if (node->value_type == CONF_NODE_VALUE_NODE) {
conf_node_t* iter = conf_node_get_first_child(node);
conf_doc_save_yaml_node(iter, str, levels + 1);
}
node = node->next;
}
return RET_OK;
}
ret_t conf_doc_save_yaml(conf_doc_t* doc, str_t* str) {
conf_node_t* node = NULL;
return_value_if_fail(doc != NULL && str != NULL, RET_BAD_PARAMS);
str_clear(str);
node = conf_node_get_first_child(doc->root);
return_value_if_fail(node != NULL, RET_BAD_PARAMS);
return conf_doc_save_yaml_node(node, str, 0);
}
static conf_doc_t* conf_doc_load_yaml_reader(data_reader_t* reader) {
char* data = NULL;
int32_t rsize = 0;
conf_doc_t* doc = NULL;
uint64_t size = data_reader_get_size(reader);
return_value_if_fail(reader != NULL && size > 0, NULL);
data = TKMEM_ALLOC(size + 1);
return_value_if_fail(data != NULL, NULL);
memset(data, 0x00, size + 1);
rsize = data_reader_read(reader, 0, data, size);
if (rsize > 0) {
doc = conf_doc_load_yaml(data);
}
TKMEM_FREE(data);
return doc;
}
static ret_t conf_doc_save_yaml_writer(conf_doc_t* doc, data_writer_t* writer) {
str_t str;
return_value_if_fail(writer != NULL, RET_BAD_PARAMS);
goto_error_if_fail(str_init(&str, 1024) != NULL);
goto_error_if_fail(conf_doc_save_yaml(doc, &str) == RET_OK);
goto_error_if_fail(data_writer_write(writer, 0, str.str, str.size) == str.size);
str_reset(&str);
return RET_OK;
error:
str_reset(&str);
return RET_FAIL;
}
tk_object_t* conf_yaml_load(const char* url, bool_t create_if_not_exist) {
return conf_obj_create(conf_doc_save_yaml_writer, conf_doc_load_yaml_reader, url,
create_if_not_exist);
}
ret_t conf_yaml_save_as(tk_object_t* obj, const char* url) {
data_writer_t* writer = NULL;
conf_doc_t* doc = conf_obj_get_doc(obj);
return_value_if_fail(doc != NULL && url != NULL, RET_BAD_PARAMS);
writer = data_writer_factory_create_writer(data_writer_factory(), url);
return_value_if_fail(writer != NULL, RET_BAD_PARAMS);
conf_doc_save_yaml_writer(doc, writer);
data_writer_destroy(writer);
return RET_OK;
}
tk_object_t* conf_yaml_create(void) {
return conf_yaml_load(NULL, TRUE);
}
tk_object_t* conf_yaml_load_from_buff(const void* buff, uint32_t size, bool_t create_if_not_exist) {
char url[MAX_PATH + 1] = {0};
return_value_if_fail(buff != NULL, NULL);
data_reader_mem_build_url(buff, size, url);
return conf_yaml_load(url, create_if_not_exist);
}
ret_t conf_yaml_save_to_buff(tk_object_t* obj, wbuffer_t* wb) {
char url[MAX_PATH + 1] = {0};
return_value_if_fail(obj != NULL && wb != NULL, RET_BAD_PARAMS);
wbuffer_init_extendable(wb);
data_writer_wbuffer_build_url(wb, url);
return conf_yaml_save_as(obj, url);
}

171
src/conf_io/conf_yaml.h Normal file
View File

@ -0,0 +1,171 @@
/**
* File: yaml.h
* Author: AWTK Develop Team
* Brief: yaml
*
* Copyright (c) 2020 - 2025 Guangzhou ZHIYUAN Electronics Co.,Ltd.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* License file for more details.
*
*/
/**
* History:
* ================================================================
* 2025-04-13 Li XianJing <xianjimli@hotmail.com> created
*
*/
#ifndef TK_CONF_YAML_H
#define TK_CONF_YAML_H
#include "tkc/str.h"
#include "tkc/buffer.h"
#include "conf_io/conf_obj.h"
BEGIN_C_DECLS
/**
* @class conf_yaml_t
* @parent tk_object_t
* @annotation ["fake"]
*
* conf yaml对象
*
*
*
*```c
* char filename[MAX_PATH + 1] = {0};
* path_prepend_temp_path(filename, "test.yaml");
*
* const char *yaml_data1 = "root:\n"
* " name:awplc\n"
* " age:18\n"
* " weight:60.5\n";
* ENSURE(file_write(filename, yaml_data1, strlen(yaml_data1)) == RET_OK);
*
* // 从文件加载
* tk_object_t *yaml = conf_yaml_load(filename, FALSE);
*
* // 获取数据。
* ENSURE(tk_str_eq(tk_object_get_prop_str(yaml, "root.name"), "awplc"));
* ENSURE(tk_object_get_prop_int(yaml, "root.age", 0) == 18);
* ENSURE(tk_object_get_prop_double(yaml, "root.weight", 0) == 60.5);
*
* // 销毁对象
* TK_OBJECT_UNREF(yaml);
*
* // 从内存加载
* yaml = conf_yaml_load_from_buff(yaml_data1, strlen(yaml_data1), FALSE);
*
* // 获取数据
* ENSURE(tk_str_eq(tk_object_get_prop_str(yaml, "root.name"), "awplc"));
* ENSURE(tk_object_get_prop_int(yaml, "root.age", 0) == 18);
* ENSURE(tk_object_get_prop_double(yaml, "root.weight", 0) == 60.5);
*
* // 设置数据
* ENSURE(tk_object_set_prop_int(yaml, "root.age", 20) == RET_OK);
* ENSURE(tk_object_get_prop_int(yaml, "root.age", 0) == 20);
*
* // 保存到文件
* ENSURE(conf_yaml_save_as(yaml, filename) == RET_OK);
* ENSURE(file_exist(filename) == TRUE);
*
* // 销毁对象
* TK_OBJECT_UNREF(yaml);
*```
*/
/**
* @method conf_yaml_create
* conf对象
* @annotation ["constructor"]
*
* @return {tk_object_t*}
*/
tk_object_t* conf_yaml_create(void);
/**
* @method conf_yaml_load
* URL加载YAML对象
*
* @annotation ["constructor"]
*
* @param {const char*} url ()
* @param {bool_t} create_if_not_exist
*
* @return {tk_object_t*}
*/
tk_object_t* conf_yaml_load(const char* url, bool_t create_if_not_exist);
/**
* @method conf_yaml_load_from_buff
* YAML对象
* @annotation ["constructor"]
*
* @param {const void*} buff
* @param {uint32_t} size
* @param {bool_t} create_if_not_exist
*
* @return {tk_object_t*}
*/
tk_object_t* conf_yaml_load_from_buff(const void* buff, uint32_t size, bool_t create_if_not_exist);
/**
* @method conf_yaml_save_to_buff
* obj保存为YAML格式到内存
*
* @param {tk_object_t*} obj doc对象
* @param {wbuffer_t*} wb (使wbuffer_deyamlt)
*
* @return {ret_t} RET_OK表示成功
*/
ret_t conf_yaml_save_to_buff(tk_object_t* obj, wbuffer_t* wb);
/**
* @method conf_yaml_save_as
* doc对象保存到指定URL
* @annotation ["static"]
*
* @param {tk_object_t*} obj doc对象
* @param {const char*} url
*
* @return {ret_t} RET_OK表示成功
*/
ret_t conf_yaml_save_as(tk_object_t* obj, const char* url);
/*public for test*/
/**
* @method conf_doc_load_yaml
*
* yaml格式的conf doc对象
*
* @annotation ["global"]
*
* @param {const char*} data
*
* @return {conf_doc_t*} conf_doc对象
*/
conf_doc_t* conf_doc_load_yaml(const char* data);
/**
* @method conf_doc_save_yaml
*
* conf doc对象为yaml格式
* @annotation ["global"]
*
* @param {conf_doc_t*} doc conf doc对象
* @param {str_t*} str
*
* @return {ret_t} RET_OK表示成功
*/
ret_t conf_doc_save_yaml(conf_doc_t* doc, str_t* str);
END_C_DECLS
#endif /*TK_CONF_YAML_H*/

View File

@ -370,7 +370,7 @@ ret_t str_encode_xml_entity_with_len(str_t* str, const char* text, uint32_t len)
} }
/*https://en.wikipedia.org/wiki/Escape_sequences_in_C*/ /*https://en.wikipedia.org/wiki/Escape_sequences_in_C*/
static char str_escape_char(char c) { char str_escape_char(char c) {
switch (c) { switch (c) {
case '\a': { case '\a': {
c = 'a'; c = 'a';
@ -412,7 +412,7 @@ static char str_escape_char(char c) {
return c; return c;
} }
static char str_unescape_char(const char* s, uint32_t* nr) { char str_unescape_char(const char* s, uint32_t* nr) {
char c = 0; char c = 0;
const char* start = s; const char* start = s;
return_value_if_fail(s != NULL && nr != NULL, 0); return_value_if_fail(s != NULL && nr != NULL, 0);

View File

@ -853,6 +853,25 @@ ret_t str_append_format_padding(str_t* str, uint32_t size, const char* format, .
*/ */
ret_t str_append_json_pair(str_t* str, const char* key, const value_t* value); ret_t str_append_json_pair(str_t* str, const char* key, const value_t* value);
/**
* @method str_escape_char
*
* > '\n'"n"
* @param {char} c
* @return {char}
*/
char str_escape_char(char c);
/**
* @method str_unescape_char
*
* > "n"'\n'
* @param {const char*} s
* @param {uint32_t*} nr
* @return {char}
*/
char str_unescape_char(const char* s, uint32_t* nr);
#define STR_DESTROY(str) \ #define STR_DESTROY(str) \
if (str != NULL) { \ if (str != NULL) { \
str_destroy(str); \ str_destroy(str); \

464
tests/conf_yaml_test.cc Normal file
View File

@ -0,0 +1,464 @@
#include "gtest/gtest.h"
#include "conf_io/conf_yaml.h"
TEST(Yaml, basic1) {
value_t v;
conf_node_t* node = NULL;
conf_doc_t* doc = conf_doc_load_yaml("hello:");
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
node = conf_node_find_child(doc->root, "hello");
ASSERT_EQ(node != NULL, true);
ASSERT_STREQ(conf_node_get_name(node), "hello");
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "hello:\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, basic2) {
value_t v;
conf_node_t* node = NULL;
conf_doc_t* doc = conf_doc_load_yaml("name: jim");
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
node = conf_node_find_child(doc->root, "name");
ASSERT_EQ(node != NULL, true);
ASSERT_STREQ(conf_node_get_name(node), "name");
ASSERT_STREQ(conf_node_get_child_value_str(doc->root, "name", ""), "jim");
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "name: jim\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, basic3) {
value_t v;
conf_doc_t* doc = conf_doc_load_yaml("person:\r\n name: jim\n age:100");
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
ASSERT_STREQ(conf_doc_get_str(doc, "person.name", ""), "jim");
ASSERT_EQ(conf_doc_get_int(doc, "person.age", 0), 100);
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "person:\n name: jim\n age: 100\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, basic4) {
value_t v;
conf_doc_t* doc = conf_doc_load_yaml("jim:\r\n age:100\ntom:\n age:99\n");
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 2);
ASSERT_EQ(conf_doc_get_int(doc, "jim.age", 0), 100);
ASSERT_EQ(conf_doc_get_int(doc, "tom.age", 0), 99);
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "jim:\n age: 100\ntom:\n age: 99\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, basic5) {
value_t v;
conf_doc_t* doc = conf_doc_load_yaml("jim:\r\n age:100\n weight: 60");
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
ASSERT_EQ(conf_doc_get(doc, "jim.#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 2);
ASSERT_EQ(conf_doc_get_int(doc, "jim.age", 0), 100);
ASSERT_EQ(conf_doc_get_int(doc, "jim.weight", 0), 60);
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "jim:\n age: 100\n weight: 60\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, basic6) {
value_t v;
conf_doc_t* doc = conf_doc_load_yaml("jim:\r\n age:100\n weight: 60\ntom:\n age:99\n weight:70");
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 2);
ASSERT_EQ(conf_doc_get(doc, "jim.#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 2);
ASSERT_EQ(conf_doc_get(doc, "tom.#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 2);
ASSERT_EQ(conf_doc_get_int(doc, "jim.age", 0), 100);
ASSERT_EQ(conf_doc_get_int(doc, "tom.age", 0), 99);
ASSERT_EQ(conf_doc_get_int(doc, "jim.weight", 0), 60);
ASSERT_EQ(conf_doc_get_int(doc, "tom.weight", 0), 70);
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "jim:\n age: 100\n weight: 60\ntom:\n age: 99\n weight: 70\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, basic7) {
value_t v;
const char* data = "plan_request_params:\n\
planning_attempts: 1\n\
planning_pipeline: ompl\n\
max_velocity_scaling_factor: 1.0\n\
max_acceleration_scaling_factor: 0.5 ";
conf_doc_t* doc = conf_doc_load_yaml(data);
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
ASSERT_EQ(conf_doc_get(doc, "plan_request_params.#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 4);
ASSERT_EQ(conf_doc_get_int(doc, "plan_request_params.planning_attempts", 0), 1);
ASSERT_STREQ(conf_doc_get_str(doc, "plan_request_params.planning_pipeline", ""), "ompl");
ASSERT_FLOAT_EQ(conf_doc_get_float(doc, "plan_request_params.max_velocity_scaling_factor", 0), 1.0f);
ASSERT_FLOAT_EQ(conf_doc_get_float(doc, "plan_request_params.max_acceleration_scaling_factor", 0), 0.5f);
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "plan_request_params:\n planning_attempts: 1\n planning_pipeline: ompl\n max_velocity_scaling_factor: 1.0\n max_acceleration_scaling_factor: 0.5\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, list1) {
value_t v;
const char* data = "planning_pipelines:\n\
pipeline_names:\n\
- ompl\n";
conf_doc_t* doc = conf_doc_load_yaml(data);
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
ASSERT_EQ(conf_doc_get(doc, "planning_pipelines.#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
ASSERT_EQ(conf_doc_get(doc, "planning_pipelines.pipeline_names.#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "planning_pipelines:\n pipeline_names:\n - ompl\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, list2) {
value_t v;
const char* data = "planning_pipelines:\n\
pipeline_names:\n\
- ompl\n\
- kdl\n\
- ikfast\n";
conf_doc_t* doc = conf_doc_load_yaml(data);
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
ASSERT_EQ(conf_doc_get(doc, "planning_pipelines.#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
ASSERT_EQ(conf_doc_get(doc, "planning_pipelines.pipeline_names.#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 3);
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "planning_pipelines:\n pipeline_names:\n - ompl\n - kdl\n - ikfast\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, comment1) {
value_t v;
conf_node_t* node = NULL;
conf_doc_t* doc = conf_doc_load_yaml("name: jim #comment");
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
node = conf_node_find_child(doc->root, "name");
ASSERT_EQ(node != NULL, true);
ASSERT_STREQ(conf_node_get_name(node), "name");
ASSERT_STREQ(conf_node_get_child_value_str(doc->root, "name", ""), "jim");
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "name: jim\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, comment2) {
value_t v;
conf_node_t* node = NULL;
conf_doc_t* doc = conf_doc_load_yaml("#comment\nname: jim #comment");
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
node = conf_node_find_child(doc->root, "name");
ASSERT_EQ(node != NULL, true);
ASSERT_STREQ(conf_node_get_name(node), "name");
ASSERT_STREQ(conf_node_get_child_value_str(doc->root, "name", ""), "jim");
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "name: jim\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, comment3) {
value_t v;
const char* data = "planning_pipelines:#comment\n\
pipeline_names:#comment\n\
- ompl#comment\n\
#comment\n\
- kdl#comment\n\
- ikfast #comment\n";
conf_doc_t* doc = conf_doc_load_yaml(data);
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
ASSERT_EQ(conf_doc_get(doc, "planning_pipelines.#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
ASSERT_EQ(conf_doc_get(doc, "planning_pipelines.pipeline_names.#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 3);
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "planning_pipelines:\n pipeline_names:\n - ompl\n - kdl\n - ikfast\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, error1) {
value_t v;
conf_node_t* node = NULL;
conf_doc_t* doc = conf_doc_load_yaml("hello");
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
node = conf_node_find_child(doc->root, "hello");
ASSERT_EQ(node != NULL, true);
ASSERT_STREQ(conf_node_get_name(node), "hello");
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "hello:\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, error2) {
value_t v;
conf_node_t* node = NULL;
conf_doc_t* doc = conf_doc_load_yaml("hel\tlo");
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
node = conf_node_find_child(doc->root, "hello");
ASSERT_EQ(node != NULL, true);
ASSERT_STREQ(conf_node_get_name(node), "hello");
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "hello:\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, error3) {
value_t v;
conf_node_t* node = NULL;
conf_doc_t* doc = conf_doc_load_yaml("hel#lo");
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
node = conf_node_find_child(doc->root, "hello");
ASSERT_EQ(node != NULL, true);
ASSERT_STREQ(conf_node_get_name(node), "hello");
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "hello:\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, escape1) {
value_t v;
conf_node_t* node = NULL;
conf_doc_t* doc = conf_doc_load_yaml("name: hello\\nworld");
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
node = conf_node_find_child(doc->root, "name");
ASSERT_EQ(node != NULL, true);
ASSERT_STREQ(conf_node_get_name(node), "name");
const char* value = conf_node_get_child_value_str(doc->root, "name", "");
ASSERT_STREQ(value, "hello\nworld");
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "name: hello\\nworld\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, escape2) {
value_t v;
conf_node_t* node = NULL;
conf_doc_t* doc = conf_doc_load_yaml("name: \\#hello");
ASSERT_EQ(conf_doc_get(doc, "#size", &v), RET_OK);
ASSERT_EQ(value_int(&v), 1);
node = conf_node_find_child(doc->root, "name");
ASSERT_EQ(node != NULL, true);
ASSERT_STREQ(conf_node_get_name(node), "name");
const char* value = conf_node_get_child_value_str(doc->root, "name", "");
ASSERT_STREQ(value, "#hello");
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "name: \\#hello\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, set1) {
conf_doc_t* doc = conf_doc_load_yaml("name: \\#hello");
conf_doc_set_str(doc, "name", "tom");
conf_doc_set_int(doc, "age", 100);
conf_doc_set_float(doc, "weight", 60);
conf_doc_set_bool(doc, "male", true);
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "name: tom\nage: 100\nweight: 60.000000\nmale: 1\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, set2) {
conf_doc_t* doc = conf_doc_load_yaml("name: \\#hello");
conf_doc_set_int(doc, "tom.age", 100);
conf_doc_set_float(doc, "tom.weight", 60);
conf_doc_set_bool(doc, "tom.male", true);
str_t str;
str_init(&str, 100);
conf_doc_save_yaml(doc, &str);
ASSERT_STREQ(str.str, "name: \\#hello\ntom:\n age: 100\n weight: 60.000000\n male: 1\n");
str_reset(&str);
conf_doc_destroy(doc);
}
TEST(Yaml, set3) {
wbuffer_t wb;
tk_object_t* conf = conf_yaml_create();
ASSERT_NE(conf, (tk_object_t*)NULL);
ASSERT_EQ(tk_object_set_prop_int(conf, "awtk.value", 123), RET_OK);
ASSERT_EQ(tk_object_get_prop_int(conf, "awtk.value", 0), 123);
ASSERT_EQ(conf_yaml_save_to_buff(conf, &wb), RET_OK);
TK_OBJECT_UNREF(conf);
conf = conf_yaml_load_from_buff(wb.data, wb.cursor, FALSE);
ASSERT_EQ(tk_object_get_prop_int(conf, "awtk.value", 0), 123);
TK_OBJECT_UNREF(conf);
wbuffer_deinit(&wb);
}
TEST(Yaml, file) {
tk_object_t* conf = conf_yaml_load("file://./tests/testdata/test.yaml", TRUE);
ASSERT_STREQ(tk_object_get_prop_str(conf, "plan_request_params.planning_pipeline"), "ompl");
ASSERT_STREQ(tk_object_get_prop_str(conf, "plan_request_params.planning_attempts"), "1");
ASSERT_STREQ(tk_object_get_prop_str(conf, "plan_request_params.max_velocity_scaling_factor"), "1.0");
ASSERT_STREQ(tk_object_get_prop_str(conf, "plan_request_params.max_acceleration_scaling_factor"), "1.0");
ASSERT_STREQ(tk_object_get_prop_str(conf, "planning_pipelines.pipeline_names.[0]"), "ompl");
ASSERT_STREQ(tk_object_get_prop_str(conf, "planning_pipelines.pipeline_names.[1]"), "kdl");
TK_OBJECT_UNREF(conf);
}

View File

@ -1137,3 +1137,47 @@ TEST(Str, append_format_ex) {
str_reset(&s); str_reset(&s);
} }
TEST(Str, unescape_char1) {
str_t s;
uint32_t n = 0;
str_init(&s, 0);
const char* data = "abc\\r\\nd";
const char* p = data;
while (*p != '\0') {
if (*p == '\\') {
p++;
char c = str_unescape_char(p, &n);
str_append_char(&s, c);
p += n;
} else {
str_append_char(&s, *p);
p++;
}
}
ASSERT_STREQ(s.str, "abc\r\nd");
str_reset(&s);
}
TEST(Str, escape_char1) {
str_t s;
str_init(&s, 0);
const char* data = "abc\r\nd";
const char* p = data;
while (*p != '\0') {
char c = str_escape_char(*p);
if (*p != c) {
str_append_char(&s, '\\');
str_append_char(&s, c);
} else {
str_append_char(&s, *p);
}
p++;
}
ASSERT_STREQ(s.str, "abc\\r\\nd");
str_reset(&s);
}

19
tests/testdata/test.yaml vendored Normal file
View File

@ -0,0 +1,19 @@
planning_scene_monitor_options:
name: "planning_scene_monitor"
robot_description: "robot_description"
joint_state_topic: "/joint_states"
attached_collision_object_topic: "/planning_scene_monitor"
publish_planning_scene_topic: "/publish_planning_scene"
monitored_planning_scene_topic: "/monitored_planning_scene"
wait_for_initial_state_timeout: 10.0
planning_pipelines:
pipeline_names:
- ompl
- kdl
plan_request_params:
planning_attempts: 1
planning_pipeline: ompl
max_velocity_scaling_factor: 1.0
max_acceleration_scaling_factor: 1.0