YAML学习笔记

本文详细介绍了YAML的语法规则,包括缩进、注释、清单、散列表等基本结构。同时,重点讲解了libyaml库在C语言中解析YAML文件的方法,包括基于token和基于event的解析方式,并给出了示例代码。此外,还提到了YAML的高级特性如引用和合并。
摘要由CSDN通过智能技术生成

语法

  • YAML 使用可打印的 Unicode 字符,可使用 UTF-8 或 UTF-16。

  • 使用空白字符为文件缩进来表示结构;不过不能使用跳格字符 (TAB)。

  • 注解由井字号( # )开始,可以出现在一行中的任何位置,而且范围只有一行(也就是一般所谓的单行注解)

  • 每个清单成员以单行表示,并用短杠 + 空白( - )起始。或使用方括号( [ ] ),并用逗号 + 空白( , )分开成员。

  • 每个散列表的成员用冒号 + 空白( : )分开键值和内容。或使用大括号( { } ),并用逗号 + 空白( , )分开。

  • 散列表的键值可以用问号 ( ? ) 起始,用来明确的表示多个词汇组成的键值。

  • 字符串平常并不使用引号,但必要的时候可以用双引号 ( " ) 或单引号 ( ' ) 框住。

  • 使用双引号表示字符串时,可用倒斜线( \ )开始的转义字符(这跟 C 语言类似)表示特殊字符。

  • 区块的字符串用缩进和修饰符(非必要)来和其他数据分隔,有新行保留(preserve)(使用符号 | )或新行折叠(flod)(使用符号 > )两种方式。

  • 在单一文件中,可用连续三个连字号(---)区分多个文件。

  • 另外,还有选择性的连续三个点号( ... )用来表示文件结尾。

  • 重复的内容可使从参考标记星号 ( * ) 复制到锚点标记( & )。

  • 指定格式可以使用两个惊叹号 ( !! ),后面接上名称。

  • 文件中的单一文件可以使用指导指令,使用方法是百分比符号 ( % )。有两个指导指令在 YAML1.1 版中被定义:

    • % YAML 指导指令,用来识别文件的 YAML 版本。
    • % TAG 指导指令,被用在 URI 的前缀标记。这个方法在标记节点的类型时相当有用。
  • YAML 在使用逗号及冒号时,后面都必须接一个空白字符,所以可以在字符串或数值中自由加入分隔符号(例如:5,280 或 http://www.wikipedia.org)而不需要使用引号。

  • 另外还有两个特殊符号在 YAML 中被保留,有可能在未来的版本被使用 --( @ )和( ` )

libyaml库解析YAML文件

libyaml是一个用于解析YAML文件的C语言库,可以从包管理器中获得。要使用它需要包含<yaml.h>文件并且在gcc中加上链接器标志 -lyaml,详细信息查看头文件/usr/include/yaml.h

示例文档:

# config/public.yaml

title   : Finex 2011
img_url : /finex/html/img/
css_url : /finex/html/style/
js_url  : /finex/html/js/

template_dir: html/templ/

default_act : idx    # used for invalid/missing act=

pages:
  - act   : idx
    title : Welcome
    html  : public/welcome.phtml
  - act   : reg
    title : Register
    html  : public/register.phtml
  - act   : log
    title : Log in
    html  : public/login.phtml
  - act   : out
    title : Log out
    html  : public/logout.phtml

yaml_parser_t

libyaml使用的主要对象就是解析器本身,这是一个yaml_parser_t类型的对象。它必须手动分配(通常在栈上)并且使用以下函数初始化和释放:

int yaml_parser_initialize(yaml_parser_t *)
void yaml_parser_delete(yaml_parser_t *)

打开一个特定的文件,使用函数:(还有一些函数用于从字符串或通用读处理程序中读取输入,并且设置输入文件的编码方式,在这里不做讨论)

void yaml_parser_set_input_file(yaml_parser_t *parser, FILE *file)

基于Token和基于Event的解析

使用libyaml解析一个YAML文档有两种方式:token-basedevent-based。概念上,最简单的方式是基于token,通过使用以下函数:

int yaml_parser_scan(yaml_parser_t *parser, yaml_token_t *token)
void yaml_token_delete(yaml_token_t *token)

我们能从YAML文档中依次获得每一个token,完整的yaml_token_t结构在<yaml.h>文件中。但就我们的目的而言,仅需要.type.data.scalar.value两个字段,它能告诉我们token的类型以及数据(如果类型是YAML_SCALAR_TOKEN)

以下代码是使用示例:

#include <stdio.h>
#include <yaml.h>

int main(void)
{
  FILE *fh = fopen("config/public.yaml", "r");
  yaml_parser_t parser;
  yaml_token_t  token;   /* new variable */

  /* Initialize parser */
  if(!yaml_parser_initialize(&parser))
    fputs("Failed to initialize parser!\n", stderr);
  if(fh == NULL)
    fputs("Failed to open file!\n", stderr);

  /* Set input file */
  yaml_parser_set_input_file(&parser, fh);

  /* BEGIN new code */
  do {
    yaml_parser_scan(&parser, &token);
    switch(token.type)
    {
    /* Stream start/end */
    case YAML_STREAM_START_TOKEN: puts("STREAM START"); break;
    case YAML_STREAM_END_TOKEN:   puts("STREAM END");   break;
    /* Token types (read before actual token) */
    case YAML_KEY_TOKEN:   printf("(Key token)   "); break;
    case YAML_VALUE_TOKEN: printf("(Value token) "); break;
    /* Block delimeters */
    case YAML_BLOCK_SEQUENCE_START_TOKEN: puts("<b>Start Block (Sequence)</b>"); break;
    case YAML_BLOCK_ENTRY_TOKEN:          puts("<b>Start Block (Entry)</b>");    break;
    case YAML_BLOCK_END_TOKEN:            puts("<b>End block</b>");              break;
    /* Data */
    case YAML_BLOCK_MAPPING_START_TOKEN:  puts("[Block mapping]");            break;
    case YAML_SCALAR_TOKEN:  printf("scalar %s \n", token.data.scalar.value); break;
    /* Others */
    default:
      printf("Got token of type %d\n", token.type);
    }
    if(token.type != YAML_STREAM_END_TOKEN)
      yaml_token_delete(&token);
  } while(token.type != YAML_STREAM_END_TOKEN);
  yaml_token_delete(&token);
  /* END new code */

  /* Cleanup */
  yaml_parser_delete(&parser);
  fclose(fh);
  return 0;
}

对于简单的文档,显然基于token的解析是有意义的。然而,更合理的范例是基于event解析。这是通过类似的函数实现的

int yaml_parser_parse(yaml_parser_t *parser, yaml_event_t *event)
void yaml_event_delete(yaml_event_t *event)

使用这些函数实现的代码及输出如下所示:

#include <stdio.h>
#include <yaml.h>

int main(void)
{
  FILE *fh = fopen("config/public.yaml", "r");
  yaml_parser_t parser;
  yaml_event_t  event;   /* New variable */

  /* Initialize parser */
  if(!yaml_parser_initialize(&parser))
    fputs("Failed to initialize parser!\n", stderr);
  if(fh == NULL)
    fputs("Failed to open file!\n", stderr);

  /* Set input file */
  yaml_parser_set_input_file(&parser, fh);

  /* START new code */
  do {
    if (!yaml_parser_parse(&parser, &event)) {
       printf("Parser error %d\n", parser.error);
       exit(EXIT_FAILURE);
    }

    switch(event.type)
    { 
    case YAML_NO_EVENT: puts("No event!"); break;
    /* Stream start/end */
    case YAML_STREAM_START_EVENT: puts("STREAM START"); break;
    case YAML_STREAM_END_EVENT:   puts("STREAM END");   break;
    /* Block delimeters */
    case YAML_DOCUMENT_START_EVENT: puts("<b>Start Document</b>"); break;
    case YAML_DOCUMENT_END_EVENT:   puts("<b>End Document</b>");   break;
    case YAML_SEQUENCE_START_EVENT: puts("<b>Start Sequence</b>"); break;
    case YAML_SEQUENCE_END_EVENT:   puts("<b>End Sequence</b>");   break;
    case YAML_MAPPING_START_EVENT:  puts("<b>Start Mapping</b>");  break;
    case YAML_MAPPING_END_EVENT:    puts("<b>End Mapping</b>");    break;
    /* Data */
    case YAML_ALIAS_EVENT:  printf("Got alias (anchor %s)\n", event.data.alias.anchor); break;
    case YAML_SCALAR_EVENT: printf("Got scalar (value %s)\n", event.data.scalar.value); break;
    }
    if(event.type != YAML_STREAM_END_EVENT)
      yaml_event_delete(&event);
  } while(event.type != YAML_STREAM_END_EVENT);
  yaml_event_delete(&event);
  /* END new code */

  /* Cleanup */
  yaml_parser_delete(&parser);
  fclose(fh);
  return 0;
}
STREAM START 
Start Document 
  Start Mapping 
    Got scalar (value title) 
    Got scalar (value Finex 2011) 
    Got scalar (value img_url) 
    Got scalar (value /finex/html/img/) 
    Got scalar (value css_url) 
    Got scalar (value /finex/html/style/) 
    Got scalar (value js_url) 
    Got scalar (value /finex/html/js/) 
    Got scalar (value template_dir) 
    Got scalar (value html/templ/) 
    Got scalar (value pages) 
    Start Sequence 
      Start Mapping 
        Got scalar (value act) 
        Got scalar (value idx) 
        Got scalar (value title) 
        Got scalar (value Welcome) 
        Got scalar (value html) 
        Got scalar (value public/welcome.phtml) 
      End Mapping 
      Start Mapping 
        Got scalar (value act) 
        Got scalar (value reg) 
        Got scalar (value title) 
        Got scalar (value Register) 
        Got scalar (value html) 
        Got scalar (value public/register.phtml) 
      End Mapping 
      Start Mapping 
        Got scalar (value act) 
        Got scalar (value log) 
        Got scalar (value title) 
        Got scalar (value Log in) 
        Got scalar (value html) 
        Got scalar (value public/login.phtml) 
      End Mapping 
      Start Mapping 
        Got scalar (value act) 
        Got scalar (value out) 
        Got scalar (value title) 
        Got scalar (value Log out) 
        Got scalar (value html) 
        Got scalar (value public/logout.phtml) 
      End Mapping 
    End Sequence 
  End Mapping 
End Document 
STREAM END 

关于这个有两件主要的事情要注意。第一,输出的结构更好,通用的End Block令牌现在是具体的事件。第二,代码更简单,不仅如此,switch分支有每一种事件类型,在基于token的代码中,switch分支的声明是不完整的,还应该包含许多其他的token,并且大多数我都不理解。

而且,基于event的代码更适合面向对象编程,当在其他语言中使用libyaml时你可能会看到

基于Document解析

实际上还有第三种方式解析YAML,基于以下函数:

int yaml_parser_load(yaml_parser_t *parser, yaml_document_t *document)
void yaml_document_delete(yaml_document_t *document)

它允许你加载一个单独的文档到结构中,并且使用各种yaml_document_*函数。这是很有用的,因为YAML文档可能分散在多个文件中,或者单文件包含许多YAML文档。

YAML的基本组件

YAML 提供缩进/区块以及内置(inline)两种格式,来表示清单和散列表。以下展示几种 YAML 的基本原件。

清单(数组)

--- # 最喜爱的电影  
- Casablanca  
- North by Northwest  
- Notorious 

另外还有一种内置格式(inline format)可以选择──用方括号围住,并用逗号 + 空白区隔(类似 JSON 的语法)

 --- # 购物清单  
 [milk, pumpkin pie, eggs, juice]

散列表

键值和数据由冒号及空白字符分开。区块形式(常用于YAML数据文档中)使用缩进和换行符分隔 key:value 对。内置形式(常用于YAML数据流中)在大括号中使用逗号+空白字符分隔 key:value 对。

 --- # 區塊形式
   name: John Smith
   age: 33
 --- # 內置形式
 {name: John Smith, age: 33}

层次结构化的元素

清单中使用散列表

- {name: John Smith, age: 33}
- name: Mary Smith
  age: 27

散列表中使用清单

men: [John Smith, Bill Jones]
women:
  - Mary Smith
  - Susan Williams

YAML的高级组件

YAML最常被提到的特色有两个:关系树和数据形态

树状结构之间的交互引用

数据合并和参考

为了维持文件的简洁,并避免数据输入的错误,YAML 提供了结点参考(*)和散列合并(<<)参考到其他结点标签的锚点标记(&)。参考会将树状结构加入锚点标记的内容,并可以在所有数据结构中运作,合并只有散列表可以使用,可以将键值自锚点标记复制到指定的散列表中。

当数据被 instantiate 合并和参考会被剖析器自动展开。

#眼部雷射手術之標準程序
---
- step:  &id001                  # 定義錨點標籤 &id001
    instrument:      Lasik 2000
    pulseEnergy:     5.4
    pulseDuration:   12
    repetition:      1000
    spotSize:        1mm

- step:
     <<: *id001                  # 合併鍵值:使用在錨點標籤定義的內容
     spotSize:       2mm         # 覆寫"spotSize"鍵值

- step:
     <<: *id001                  # 合併鍵值:使用在錨點標籤定義的內容
     pulseEnergy:    500.0       # 覆寫鍵值
     alert: >                    # 加入其他鍵值
           warn patient of 
           audible pop

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值