tlsa / libcyaml

C library for reading and writing YAML.
ISC License
258 stars 56 forks source link

How to parse complex list of map? #235

Closed kanat3 closed 3 months ago

kanat3 commented 3 months ago

suppose that yaml file looks like this:

start:
    - lang: en
      tasks:
        - name: List 1
          flag: main
          list:
            - name: Do this 1
    - lang: en
      tasks:
        - name: List 2
          list:
            - name: Do this 2
            - name: Do this 3

and schema looks like this:

static const cyaml_schema_field_t list_fields_schema[] = {
    CYAML_FIELD_STRING_PTR(
      "name", CYAML_FLAG_POINTER,
      struct list, name, 0, CYAML_UNLIMITED),
    CYAML_FIELD_END
};

static const cyaml_schema_value_t list_schema = {
  CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT,
      struct list, list_fields_schema),
};

static const cyaml_schema_field_t tasks_item_fields_schema[] = {
  CYAML_FIELD_STRING_PTR(
      "name", CYAML_FLAG_POINTER,
      struct tasks_item, name, 0, CYAML_UNLIMITED),

  CYAML_FIELD_ENUM(
      "flag", CYAML_FLAG_DEFAULT | CYAML_FLAG_OPTIONAL,
      struct tasks_item, flag, flag_strings,
      CYAML_ARRAY_LEN(flag_strings)),

  CYAML_FIELD_SEQUENCE(
      "list", CYAML_FLAG_POINTER,
      struct tasks_item, list,
      &list_schema, 0, CYAML_UNLIMITED),
    CYAML_FIELD_END
};

static const cyaml_schema_value_t tasks_item_schema = {
  CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT,
      struct tasks_item, tasks_item_fields_schema),
};

static const cyaml_schema_field_t tasks_fields_schema[] = {
    CYAML_FIELD_ENUM(
            "lang", CYAML_FLAG_DEFAULT,
            struct tasks, lang, lang_strings,
            CYAML_ARRAY_LEN(lang_strings)),

    CYAML_FIELD_SEQUENCE(
            "tasks", CYAML_FLAG_POINTER,
            struct tasks, tasks_item,
            &tasks_item_schema, 0, CYAML_UNLIMITED),
    CYAML_FIELD_END
};

static const cyaml_schema_value_t tasks_schema = {
      CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT,
      struct tasks, tasks_fields_schema),
};

static const cyaml_schema_field_t config_fields_schema[] = {
    CYAML_FIELD_SEQUENCE(
            "start", CYAML_FLAG_POINTER,
            struct config, tasks,
            &tasks_schema, 0, CYAML_UNLIMITED),
    CYAML_FIELD_END
};

static const cyaml_schema_value_t config_schema = {
    CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
            struct config, config_fields_schema),
};

I used this shema with libcyaml compiled from main branch (future v2) and it worked But than I switched on libcyaml 1.4.1 it failed with

libcyaml:    INFO: Load: [lang]
libcyaml:   DEBUG: Load: Event: SCALAR
libcyaml:   DEBUG: Load: Handle state in mapping (value)
libcyaml:   DEBUG: Load: Reading value of type 'ENUM'
libcyaml:    INFO: Load:   <en>
libcyaml:   DEBUG: Load: Event: SCALAR
libcyaml:   DEBUG: Load: Handle state in mapping (key)
libcyaml:    INFO: Load: [tasks]
libcyaml:   ERROR: Load: Unexpected key: tasks

where am I wrong with yaml schema?

tlsa commented 3 months ago

I'm not sure. I can't reproduce the problem using v1.4.1:

Using this C file:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#include <cyaml/cyaml.h>

enum flag {
    FLAG_MAIN,
    FLAG_OTHER,
};

enum lang {
    LANG_EN,
    LANG_DE,
};

struct list {
    char *name;
};

struct tasks_item {
    char *name;
    struct list *list;
    size_t list_count;
    enum flag flag;
};

struct tasks {
    enum lang lang;
    struct tasks_item *tasks_item;
    size_t tasks_item_count;
};

struct config {
    struct tasks *tasks;
    size_t tasks_count;
};

static const cyaml_schema_field_t list_fields_schema[] = {
    CYAML_FIELD_STRING_PTR(
            "name", CYAML_FLAG_POINTER,
            struct list, name, 0, CYAML_UNLIMITED),
    CYAML_FIELD_END
};

static const cyaml_schema_value_t list_schema = {
    CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT,
            struct list, list_fields_schema),
};

static const cyaml_strval_t flag_strings[] = {
    {"main" , FLAG_MAIN},
    {"other", FLAG_OTHER},
};

static const cyaml_schema_field_t tasks_item_fields_schema[] = {
    CYAML_FIELD_STRING_PTR(
            "name", CYAML_FLAG_POINTER,
            struct tasks_item, name, 0, CYAML_UNLIMITED),

    CYAML_FIELD_ENUM(
            "flag", CYAML_FLAG_DEFAULT | CYAML_FLAG_OPTIONAL,
            struct tasks_item, flag, flag_strings,
            CYAML_ARRAY_LEN(flag_strings)),

    CYAML_FIELD_SEQUENCE(
            "list", CYAML_FLAG_POINTER,
            struct tasks_item, list,
            &list_schema, 0, CYAML_UNLIMITED),
    CYAML_FIELD_END
};

static const cyaml_schema_value_t tasks_item_schema = {
    CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT,
            struct tasks_item, tasks_item_fields_schema),
};

static const cyaml_strval_t lang_strings[] = {
    {"en", LANG_EN},
    {"de", LANG_DE},
};

static const cyaml_schema_field_t tasks_fields_schema[] = {
    CYAML_FIELD_ENUM(
            "lang", CYAML_FLAG_DEFAULT,
            struct tasks, lang, lang_strings,
            CYAML_ARRAY_LEN(lang_strings)),

    CYAML_FIELD_SEQUENCE(
            "tasks", CYAML_FLAG_POINTER,
            struct tasks, tasks_item,
            &tasks_item_schema, 0, CYAML_UNLIMITED),
    CYAML_FIELD_END
};

static const cyaml_schema_value_t tasks_schema = {
    CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT,
    struct tasks, tasks_fields_schema),
};

static const cyaml_schema_field_t config_fields_schema[] = {
    CYAML_FIELD_SEQUENCE(
            "start", CYAML_FLAG_POINTER,
            struct config, tasks,
            &tasks_schema, 0, CYAML_UNLIMITED),
    CYAML_FIELD_END
};

static const cyaml_schema_value_t config_schema = {
    CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
            struct config, config_fields_schema),
};

int main(void)
{
    cyaml_err_t err;
    struct config *config;
    static const char *yaml =
            "start:\n"
            "- lang: en\n"
            "  tasks:\n"
            "    - name: List 1\n"
            "      flag: main\n"
            "      list:\n"
            "        - name: Do this 1\n"
            "- lang: en\n"
            "  tasks:\n"
            "    - name: List 2\n"
            "      list:\n"
            "        - name: Do this 2\n"
            "        - name: Do this 3\n";
    static const cyaml_config_t cyaml_config = {
            .log_level = CYAML_LOG_NOTICE,
            .mem_fn = cyaml_mem,
            .log_fn = cyaml_log,
            .flags = CYAML_CFG_IGNORED_KEY_WARNING,
    };

    printf("LibCYAML version: %s\n\n", cyaml_version_str);

    err = cyaml_load_data(
            (uint8_t *)yaml, strlen(yaml),
            &cyaml_config, &config_schema,
            (void **)&config, NULL);
    if (err != CYAML_OK) {
        fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err));
        return EXIT_FAILURE;
    }

    printf("config:\n");
    for (size_t i = 0; i < config->tasks_count; i++) {
        printf("- lang: %i\n", config->tasks[i].lang);
        printf("  tasks:\n");
        for (size_t j = 0; j < config->tasks[i].tasks_item_count; j++) {
            printf("  - name: %s\n", config->tasks[i].tasks_item->name);
        }
    }

    err = cyaml_free(&cyaml_config, &config_schema, config, 0);
    if (err != CYAML_OK) {
        fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err));
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

I get the following output:

LibCYAML version: 1.4.1

config:
- lang: 0
  tasks:
  - name: List 1
- lang: 0
  tasks:
  - name: List 2

I used your schemas and your YAML document. I had to make up the C structures and enums because they weren't provided. I also made up the call to cyaml_load_data. So maybe compare those with what you're doing.

kanat3 commented 3 months ago

Omg, I had a problem with the header files. This works now. Thank you!