使用 libyaml 解析树状结构

Posted

技术标签:

【中文标题】使用 libyaml 解析树状结构【英文标题】:Using libyaml to parse tree like structure 【发布时间】:2019-10-24 15:27:34 【问题描述】:

我是 YAML 的新手,我想解析以下 yaml 文件:

basket :
 size : 10
 type : organic
 fruit1:
  mango : 5
  type : farm-fresh
 fruit2:
  peach : 43
  manufacturer : xyz
 color : brown
 design : netted
 ...

yaml 文件将遵循上述格式,带有任意随机字符串名称和值(字符串、浮点数、整数等)。 我想将这些值中的每一个存储在一个struct 中,它具有keyvalues 作为字符数组。

struct Input 
 char key[100]:
 char value[100];
; 

存在一个上述结构的数组来存储来自 yaml 文件的值。

所以来自 yaml 文件的数据应该存储为:

 //Input[x].key                  //Input[x].value
basket.size                       10
basket.fruit1.mango               5
basket.fruit2.manufacturer        xyz
basket.color                      brown
basket.desgin                     netted

我编写了一个应用程序来解析 yaml 文件,并将单个节点/叶子作为字符串输出。所以基于上面的yaml文件,我得到的节点值为basketsize543等。我遵循here定义的方法。这是迄今为止我发现学习 yaml 的好资源之一。

这种方法对我来说不是那么有用,因为我之前的节点与叶子之间没有任何关系,反之亦然。

libyaml 是否提供了一种方法来在树中维护这种关系,然后返回以响应查询。由于项目要求,我一定会使用libyaml。但也欢迎任何其他建议。

【问题讨论】:

【参考方案1】:

您链接的资源描述了几种解析 YAML 的方法。与教程所说的相反,基于标记的解析根本没有用,除非您正在实现语法高亮显示。对于所有其他情况,您希望使用基于事件的解析。所以我假设你尝试使用它。

libyaml 是否提供了一种在树中维护这种关系的方法

基于事件的解析是否维护了树结构(不确定你所说的树中的关系到底是什么意思),你会得到…开始...End 事件,用于描述输入结构的序列和映射。构建一个遍历事件流的struct Input 列表非常简单:

#include <yaml.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>

struct Input 
  char key[100];
  char value[100];
;

struct Input gen(const char *key, const char *value) 
  struct Input ret;
  strcpy(ret.key, key);
  strcpy(ret.value, value);
  return ret;


void append_all(yaml_parser_t *p, struct Input **target,
        char cur_key[100], size_t len) 
  yaml_event_t e;
  yaml_parser_parse(p, &e);
  switch (e.type) 
    case YAML_MAPPING_START_EVENT:
      yaml_event_delete(&e);
      yaml_parser_parse(p, &e);
      while (e.type != YAML_MAPPING_END_EVENT) 
        // assume scalar key
        assert(e.type == YAML_SCALAR_EVENT);
        if (len != 0) cur_key[len++] = '.';
        memcpy(cur_key + len, e.data.scalar.value,
            strlen(e.data.scalar.value) + 1);
        const size_t new_len = len + strlen(e.data.scalar.value);
        yaml_event_delete(&e);
        append_all(p, target, cur_key, new_len);
        if (len != 0) --len;
        cur_key[len] = '\0'; // remove key part
        yaml_parser_parse(p, &e);
      
      break;
    case YAML_SCALAR_EVENT:
      *(*target)++ = gen(cur_key, e.data.scalar.value);
      break;
    default: assert(false);
  
  yaml_event_delete(&e);


int main(int argc, char *argv[]) 
  yaml_parser_t p;
  yaml_event_t e;
  yaml_parser_initialize(&p);
  FILE *f = fopen("foo.yaml", "r");
  yaml_parser_set_input_file(&p, f);
  // skip stream start and document start
  yaml_parser_parse(&p, &e);
  yaml_event_delete(&e);
  yaml_parser_parse(&p, &e);
  yaml_event_delete(&e);

  char cur_key[100] = '\0';
  struct Input input[100];
  struct Input *input_end = input;
  append_all(&p, &input_end, cur_key, 0);

  // skip document end and stream end
  yaml_parser_parse(&p, &e);
  yaml_event_delete(&e);
  yaml_parser_parse(&p, &e);
  yaml_event_delete(&e);

  yaml_parser_delete(&p);
  fclose(f);

  // print out input items
  for (struct Input *cur = input; cur < input_end; ++cur) 
    printf("%s = %s\n", cur->key, cur->value);
  

【讨论】:

const 用于变量new_len 的任何原因:const size_t new_len = len + strlen(e.data.scalar.value); @gst 这是一种很好的做法,可以用作代码文档,这意味着“我不希望这个值改变”。应该总是有理由创建值const。如果您意识到在维护代码时需要更改 const 值(从而删除 const),它会提醒您之前的代码是在假设它不能更改并且可能需要审查的情况下编写的。

以上是关于使用 libyaml 解析树状结构的主要内容,如果未能解决你的问题,请参考以下文章

树状数组预备

DOM The Document Object Model 以树状结构组织的文档对象模型

详解树状结构图 vue-org-tree

ZJOI2017 树状数组

数据结构之树状数组从零认识树状数组

数据结构——树状数组