Programming/C

YAML 개요 및 libyaml 사용법

moxie2ks 2025. 3. 10. 18:01
728x90
반응형

개요

  • YAML은 사람이 읽을 수 있는 데이터 직렬화 언어이다. 구성 파일과 데이터가 저장되거나 전송되는 응용 프로그램에 일반적으로 사용된다.
  • YAML은 XML(Extensible Markup Language)과 동일한 많은 통신 응용 프로그램을 대상으로 하지만 SGML(Standard Generalized Markup Language)과는 의도적으로 다른 최소 구문을 가지고 있다.
  • 파이썬 스타일 들여쓰기를 사용하여 네스팅을 나타내며 대부분의 문자열 값에 대한 따옴표를 필요로 하지 않는다.
  • 사용자 지정 데이터 유형이 허용되지만 YAML은 기본적으로 스칼라(예: 문자열, int 및 float), 목록 및 연관 배열(map, dictionary 또는 hash)을 인코딩한다. 이러한 데이터 유형은 펄 프로그래밍 언어를 기반으로 하지만 일반적으로 사용되는 모든 고급 프로그래밍 언어는 매우 유사한 개념을 공유한다.
  • key-value pair를 표현하는 데 사용되는 콜론 중심 구문은 RFC 822에서 정의된 전자 메일 헤더에서 영감을 받았으며 문서 구분자 ---는 MIME(RFC 2046)에서 차용했다. 이스케이프 시퀀스는 C에서 재사용되며 여러 줄 문자열에 대한 공백 래핑은 HTML에서 영감을 받았다.
  • 목록과 해시는 중첩된 목록과 해시를 포함하여 트리 구조를 형성할 수 있으며 임의의 그래프는 SOAP의 XML과 유사한 YAML 별칭을 사용하여 나타낼 수 있다.
  • YAML은 SAX에서 영감을 받은 기능인 스트림으로 읽고 쓸 수 있다.
  • 많은 프로그래밍 언어에서 YAML을 읽고 쓰기 위한 지원이 가능하다.
  • YAML 파일의 공식 권장 파일 이름 확장자는 2006년부터 .yaml이다. 2024년 MIME 유형 application/yaml이 최종 확정되었다.

특징

장점 단점
가독성 - 들여쓰기와 개행을 사용하여 데이터 구조를 표현하여 사람이 읽기 쉽다. 복잡성 - 복잡한 데이터 구조를 표현하려면 구문이 복잡해지고 가독성이 떨어진다.
언어 독립성 - 다양한 프로그래밍 언어에서 사용할 수 있다. 구문 오류 - 들여쓰기나 특수 문자의 사용에 신경 써야 하며, 실수하기 쉽다.
복잡한 데이터 구조 지원 - 리스트, 맵 등의 복잡한 데이터 구조를 표현할 수 있다. 성능 - JSON이나 XML에 비해 파싱 속도가 느릴 수 있다.
재사용 가능한 데이터 구조 - 앵커와 별칭을 이용해 데이터의 재사용성을 높일 수 있다. 호환성 - 다른 데이터 포맷에 비해 지원하는 도구나 라이브러리가 적을 수 있다.
확장 가능성 - 사용자 정의 타입을 지원하여 데이터의 유연성과 확장성을 높인다.  

기본 문법

# 은 주석

--- 문서의 시작을 나타내며 선택 사항이다.

... 문서의 을 나타내며 선택 사항이다.

기본 표현

'key: value' 로 표현하며, ':' 다음에는 무조건 공백 문자가 와야한다.

':' 은 통상 'key' 가 된다고 볼 수 있다.

key: value

key:
  key_1:
    key_2:
      key_3:
        ..

자료형

int, string, boolean 을 지원한다.

int_type: 1
string_type: "1"
boolean_type: true

object 표현

key:
  key: value
  key: value

# 또는

key: {
  key: value,
  key: value
}

list 표현

key:
  - item
  - item

# 또는

key: [
  item, item
]

text 표현

|>

| 는 줄바꿈을 포함하고, > 는 줄바꿈을 무시한다.

예시

--- # 문서의 시작
# my test yaml syntax

name: wook
job: developer

basic_list:
- apple
- banana
- orange

another_list: [
  apple,
  banana,
  orange
]

object_list:
- color: red
  direction: left
- color: blue
  direction: right

basic_object:
  time: '12:34:11'
  date: '2019-04-30'

another_object: {
  time: '12:34:11',
  date: '2019-04-30'
}

comment_line_break: |
  Hello my name is wook.
  Im developer.

comment_single_line: >
  Hello world
  my first yml syntax.

... # 문서의 끝

YAML 파일의 파싱하기

.yaml파일을 파싱하기 위해, libyaml 라이브러리를 사용할 수 있다.

libyaml

libyaml은 YAML을 파싱하고 파싱된 값을 하기 위한 C 라이브러리이다.

Build

  1. libyaml 소스코드 다운로드 - https://pyyaml.org/download/libyaml/yaml-0.2.5.zip
  2. configure & make
$ ./configure --prefix=<path to install>
$ make
# make install

API Synopsis

Parser API Synopsis

#include <yaml.h>

yaml_parser_t parser;
yaml_event_t event;

int done = 0;

/* Create the Parser object. */
yaml_parser_initialize(&parser);

/* Set a string input. */
char *input = "...";
size_t length = strlen(input);

yaml_parser_set_input_string(&parser, input, length);

/* Set a file input. */
FILE *input = fopen("...", "rb");

yaml_parser_set_input_file(&parser, input);

/* Set a generic reader. */
void *ext = ...;
int read_handler(void *ext, char *buffer, int size, int *length) {
    /* ... */
    *buffer = ...;
    *length = ...;
    /* ... */
    return error ? 0 : 1;
}

yaml_parser_set_input(&parser, read_handler, ext);

/* Read the event sequence. */
while (!done) {

    /* Get the next event. */
    if (!yaml_parser_parse(&parser, &event))
        goto error;

    /*
      ...
      Process the event.
      ...
    */

    /* Are we finished? */
    done = (event.type == YAML_STREAM_END_EVENT);

    /* The application is responsible for destroying the event object. */
    yaml_event_delete(&event);

}

/* Destroy the Parser object. */
yaml_parser_delete(&parser);

return 1;

/* On error. */
error:

/* Destroy the Parser object. */
yaml_parser_delete(&parser);

return 0;

Emitter API Synopsis

#include <yaml.h>

yaml_emitter_t emitter;
yaml_event_t event;

/* Create the Emitter object. */
yaml_emitter_initialize(&emitter);

/* Set a file output. */
FILE *output = fopen("...", "wb");

yaml_emitter_set_output_file(&emitter, output);

/* Set a generic writer. */
void *ext = ...;
int write_handler(void *ext, char *buffer, int size) {
    /*
       ...
       Write `size` bytes.
       ...
    */
    return error ? 0 : 1;
}

yaml_emitter_set_output(&emitter, write_handler, ext);

/* Create and emit the STREAM-START event. */
yaml_stream_start_event_initialize(&event, YAML_UTF8_ENCODING);
if (!yaml_emitter_emit(&emitter, &event))
    goto error;

/*
  ...
  Emit more events.
  ...
*/

/* Create and emit the STREAM-END event. */
yaml_stream_end_event_initialize(&event);
if (!yaml_emitter_emit(&emitter, &event))
    goto error;

/* Destroy the Emitter object. */
yaml_emitter_delete(&emitter);

return 1;

/* On error. */
error:

/* Destroy the Emitter object. */
yaml_emitter_delete(emitter);

return 0;

예제코드

test.yaml

---
db_server: "localhost"
db_username: "test"
db_password: "wibble"
national_rail_username: test
national_rail_password: wibble
...

main.c

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

typedef struct Conf {
    char db_server[100];
    char db_pass[100];
    char db_user[100];
    char rail_user[100];
    char rail_pass[100];
} Conf;

int readConf(char* filename, Conf* conf)
{
    FILE* fh = fopen(filename, "r");
    yaml_parser_t parser;
    yaml_token_t token;
    int state = 0;

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

    do {
        char* target_key;
        char* tk;
        yaml_parser_scan(&parser, &token);
        switch(token.type)
        {
            case YAML_KEY_TOKEN:     state = 0; break;
            case YAML_VALUE_TOKEN:   state = 1; break;
            case YAML_SCALAR_TOKEN:
                tk = (char*)token.data.scalar.value;
                if (state == 0)
                {
                    if (!strcmp(tk, "db_server"))
                        target_key = conf->db_server;
                    else if (!strcmp(tk, "db_password"))
                        target_key = conf->db_pass;
                    else if (!strcmp(tk, "db_username"))
                        target_key = conf->db_user;
                    else if (!strcmp(tk, "national_rail_username"))
                        target_key = conf->rail_user;
                    else if (!strcmp(tk, "national_rail_password"))
                        target_key = conf->rail_pass;
                    else
                        printf("Unrecognised key: %s\n", tk);
                }
                else
                    strcpy(target_key, tk);
                break;
           default: break;
        }
        if (token.type != YAML_STREAM_END_TOKEN)
            yaml_token_delete(&token);
    }
    while (token.type != YAML_STREAM_END_TOKEN);

    yaml_token_delete(&token);
    yaml_parser_delete(&parser);
    fclose(fh);

    return 0;
}  

int main()
{
    Conf conf = {0};
    readConf("test.yaml", &conf);
    printf("%s\n", conf.db_server);
    printf("%s\n", conf.db_user);
    printf("%s\n", conf.db_pass);
    printf("%s\n", conf.rail_user);
    printf("%s\n", conf.rail_pass);

    return 0;
}

컴파일 및 실행

$ gcc main.c -o test -I<installed libyaml path>/include -L<installed libyaml path>/lib -lyaml && ./test

결과

참조문헌

YAML

yaml (yml) 문법 정리

YAML documents parsing with libyaml in C

https://github.com/yaml/libyaml

728x90
반응형