2024 年 03 月 11 日
虽然使用 LibYAML 能够实现几乎全部的 YAML 文档解析任务,但当 YAML 文档较为复杂时,解析代码颇为繁冗。LibCYAML 库基于 LibYAML,基于模式(Schema)驱动的方式将 YAML 文档解析为 C 数据结构,用法较 LibYAML 更为简易。
在 Debian 或 Ubuntu 之类的系统中,可使用以下命令安装 LibCYAML 开发包:
$ sudo apt install libcyaml-dev
假设 C 程序中存在一个字符串常量:
char *foo = "Foo";
如何将其保存为 YAML 文档?这是一个简单的问题,若不借助 LibCYAML,反而更容易解决:
FILE *fp = fopen("foo.yaml", "r");
if (!fp) {
(stderr, "Failed to open file!");
fprintf(EXIT_FAILURE);
exit}
(fp, "foo: %s\n", foo);
fprintf(fp); fclose
但是,若借助 LibCYAML 解决该问题,则有助于初步理解 LibCYAML 库的工作过程,且该工作过程在使用 LibCYAML 解析 YAML 文档时同样需要。
上述字符串常量可保存为 YAML 的一个映射,不过必须将该字符串常量封装至一个结构体内,否则无法为 LibCYAML 提供 C 数据结构的 YAML 模式。以下是封装上述字符串常量的结构体:
struct root {
char *foo;
};
上述结构体只有一个域,对应的 YAML 模式可写为
static const cyaml_schema_field_t root_mapping_schema[] = {
("foo", CYAML_FLAG_POINTER,
CYAML_FIELD_STRING_PTRstruct root, foo, 0, CYAML_UNLIMITED),
CYAML_FIELD_END};
上述代码可以令 LibCYAML 知悉 struct root
类型的
foo
成员是指针类型,指向一个长度无限制的字符串。
struct root
本身对应的 YAML 模式是映射,可写为
static const cyaml_schema_value_t root_schema = {
(CYAML_FLAG_POINTER, struct root, root_mapping_schema)
CYAML_VALUE_MAPPING};
对于 C 结构体:
struct root foo_wrapper = {.foo = "Foo"};
以下代码可将其转化为符合上述 YAML 模式的结果:
static const cyaml_config_t config = {
.log_fn = cyaml_log, /* Use the default logging function. */
.mem_fn = cyaml_mem, /* Use the default memory allocator. */
.log_level = CYAML_LOG_WARNING, /* Logging errors and warnings only. */
};
= cyaml_save_file("foo.yaml", &config,
cyaml_err_t err &root_schema, &foo_wrapper, 0);
if (err != CYAML_OK) {
(EXIT_FAILURE);
exit}
输出结果为 foo.yaml 文件,其内容应为
foo: Foo
下面是完整的代码:
#include <stdlib.h>
#include <stdio.h>
#include <cyaml/cyaml.h>
struct root {
char *foo;
};
static const cyaml_schema_field_t root_mapping_schema[] = {
("foo", CYAML_FLAG_POINTER,
CYAML_FIELD_STRING_PTRstruct root, foo, 0, CYAML_UNLIMITED),
CYAML_FIELD_END};
static const cyaml_schema_value_t root_schema = {
(CYAML_FLAG_POINTER, struct root, root_mapping_schema)
CYAML_VALUE_MAPPING};
int main(void)
{
struct root foo_wrapper = {.foo = "Foo"};
/* Create our CYAML configuration. */
static const cyaml_config_t config = {
.log_fn = cyaml_log, /* Use the default logging function. */
.mem_fn = cyaml_mem, /* Use the default memory allocator. */
.log_level = CYAML_LOG_WARNING, /* Logging errors and warnings only. */
};
= cyaml_save_file("foo.yaml", &config,
cyaml_err_t err &root_schema, &foo_wrapper, 0);
if (err != CYAML_OK) {
(EXIT_FAILURE);
exit}
return EXIT_SUCCESS;
}
假设将上述内容保存为 foo.c 文件,以下命令可完成程序编译、运行以及结果查看:
.c -o foo -lcyaml
$ gcc foo./foo
$ .yaml
$ cat foo: Foo foo
由上述示例可见,LibCYAML 可将 C 结构体的成员(域)转化为 YAML 文档中的映射(键值对),但该结构体自身不会出现在 YAML 文档中,亦即从 YAML 视角看, C 结构体是包含一个或多个映射的容器,像 YAML 文档的起止符。
在为 C 数据结构编写 YAML 模式时,要注意区分域模式和值模式,前者类型为
cyaml_schema_field_t
,用于构造结构体各成员的模式,后者类型为
cyaml_schema_value_t
,用于构造结构体、数组以及基本类型的模式。
假设有一个 C 数组
int a[] = {1, 2, 3, 4};
该如何将其转化为 YAML 的序列呢?
首先写出数组 a
的成员对应的 YAML 模式。a
的成员皆为整形数,因此相应的 YAML 值模式为
static const cyaml_schema_value_t entry_schema = {
(CYAML_FLAG_DEFAULT, int)
CYAML_VALUE_INT};
数组 a
本身对应的 YAML 值模式为
static const cyaml_schema_value_t root_schema = {
(CYAML_FLAG_POINTER, int, &entry_schema, 0, CYAML_UNLIMITED)
CYAML_VALUE_SEQUENCE};
以下代码实现了 C 数组向 YAML 序列的转换过程:
int a[] = {1, 2, 3, 4};
static const cyaml_config_t config = {
.log_fn = cyaml_log,
.mem_fn = cyaml_mem,
.log_level = CYAML_LOG_WARNING,
};
= cyaml_save_file("foo.yaml", &config, &root_schema, a, 4);
cyaml_err_t err if (err != CYAML_OK) {
(EXIT_FAILURE);
exit}
LibCYAML 开发者已为 YAML 数据的解析提供了一篇简单的入门文档,我无需越俎代庖,详见「https://github.com/tlsa/libcyaml/blob/main/docs/guide.md」。
不过,有一个困扰我很久的问题,即如何解析无名的映射序列。LibCYAML 作者在该项目的 issue 列表中回答了这个问题,他的答案为
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <cyaml/cyaml.h>
enum gender {
, FEMALE, UNKNOWN
MALE};
struct person {
char *name;
unsigned age;
enum gender gdr;
};
static const cyaml_strval_t gender_strings[] = {
{"male", MALE},
{"female", FEMALE},
{"unknown", UNKNOWN},
};
static const cyaml_schema_field_t entry_fields_schema[] = {
("name", CYAML_FLAG_POINTER,
CYAML_FIELD_STRING_PTRstruct person, name, 0, CYAML_UNLIMITED),
("age", CYAML_FLAG_DEFAULT,
CYAML_FIELD_UINTstruct person, age),
("gender", CYAML_FLAG_DEFAULT,
CYAML_FIELD_ENUMstruct person, gdr, gender_strings,
(gender_strings)),
CYAML_ARRAY_LEN
CYAML_FIELD_END};
static const cyaml_schema_value_t entry_schema = {
(CYAML_FLAG_DEFAULT,
CYAML_VALUE_MAPPINGstruct person, entry_fields_schema),
};
static const cyaml_schema_value_t people_schema = {
(CYAML_FLAG_POINTER, struct person,
CYAML_VALUE_SEQUENCE&entry_schema, 0, CYAML_UNLIMITED)
};
int main(void)
{
;
cyaml_err_t errstruct person *people;
unsigned people_count;
static const char *yaml =
"- name: \"Jason\"\n"
" age: 23\n"
" gender: male\n"
"- name: \"Lisa\"\n"
" age: 26\n"
" gender: female\n"
"- name: \"A\"\n"
" age: 20\n"
" gender: unknown\n";
static const cyaml_config_t config = {
.log_level = CYAML_LOG_NOTICE,
.mem_fn = cyaml_mem,
.log_fn = cyaml_log,
.flags = CYAML_CFG_IGNORED_KEY_WARNING,
};
= cyaml_load_data(
err (uint8_t *)yaml, strlen(yaml),
&config, &people_schema,
(void **)&people, &people_count);
if (err != CYAML_OK) {
(stderr, "ERROR: %s\n", cyaml_strerror(err));
fprintfreturn EXIT_FAILURE;
}
("People count: %u\n", people_count);
printffor (unsigned i = 0; i < people_count; i++) {
("Person:\n");
printf("\tname: %s\n", people[i].name);
printf("\tage: %u\n", people[i].age);
printf("\tgender: %i\n", people[i].gdr);
printf
(people[i].name);
free}
(people);
free
return EXIT_SUCCESS;
}
请参考 cyaml.h 中的注释。此外, Zrythm 项目也为 LibCYAML 的用法给出了一些介绍,详见「https://docs.zrythm.org/cyaml_schemas.html」。