45

I want a simple tutorial to show me how to load a yaml file and parse the data. Expat style would be great but any solution that actually shows me the data in some form would be useful.

So far I ran multiple tests in the yaml-0.1.1 source code for C and I either get an error, no output whatsoever, or in the run-emitter.c case. It reads in the yaml file and prints it to STDOUT, it does not produce the text via libyaml functions/structs. In the cases with an error I don't know if it was because the file was bad or my build is incorrect (I didn't modify anything...) The file was copied from yaml.org

Can anyone point me to a tutorial? (I googled for at least 30 minutes reading anything that looked relevant) or a name of a lib that has a good tutorial or example. Maybe you can tell me which libyaml test loads in files and does something with it or why I got errors. This document does not explain how to use the file--only how to load it:

http://pyyaml.org/wiki/LibYAML#Documentation

Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
  • 8
    Please, C and C++ are not the same. There are excellent examples of solutions for each, but as you see they are very different. I guess from the tags you want C++ more than C. If you want C++, ask for C++. Asking for C solutions can often make a program more confusing. – Chris Lutz Mar 07 '09 at 07:17
  • 1
    Does anyone know what "expat" style means in the question? OP said: "Expat style would be great but any solution that actually shows me the data in some form would be useful." – Gabriel Staples Jun 24 '22 at 00:59

5 Answers5

71

Try yaml-cpp (as suggested by this question) for a C++ parser.

Disclosure: I'm the author.

Example syntax (from the Tutorial):

YAML::Node config = YAML::LoadFile("config.yaml");

if (config["lastLogin"]) {
  std::cout << "Last logged in: " << config["lastLogin"].as<DateTime>() << "\n";
}

const std::string username = config["username"].as<std::string>();
const std::string password = config["password"].as<std::string>();
login(username, password);
config["lastLogin"] = getCurrentDateTime();

std::ofstream fout("config.yaml");
fout << config;
Community
  • 1
  • 1
Jesse Beder
  • 33,081
  • 21
  • 109
  • 146
  • 3
    What is the setup required to use [yaml-cpp](https://github.com/jbeder/yaml-cpp)? Do I just have to download the header files and include them in my C++ program? – Srini Jun 01 '16 at 18:45
  • @BruceWayne you have to build the library and link it to your project. – Jesse Beder Jun 01 '16 at 20:47
  • 4
    Unfortunately yaml-cpp is hardly maintained. Releases are unfrequent, sometimes buggy (which distros such as Debian worsen by not updating enough from upstream), and as of early 2018 the latest release still uses `auto_ptr` which forbids the use of yaml-cpp in any C++17 project. – akim Jan 28 '18 at 14:27
  • 9
    I am proven wrong, the 0.6.0 release was made a couple of hours after my previous comment. – akim Jan 28 '18 at 17:23
  • 3
    @akim How do you build this thing? I've spent hours on this and I can't seem to get it to build. I keep getting undefined reference its driving me crazy. – darksky Jan 28 '18 at 19:56
  • I have not tried yet. I'm waiting for it to appear in the distros I depend upon :/ I don't want to have to vendor it. – akim Jan 30 '18 at 06:20
  • @akim: I don't think you're wrong; IME, yaml-cpp **is** not maintained _at all_. From what I can tell, the latest 0.6.0 and 0.6.1 releases [don't even build](https://github.com/jbeder/yaml-cpp/issues/554), neither on Linux nor on macOS. I would love to be proven wrong. – ssc Mar 05 '18 at 18:37
  • @ssc that's not true; please see the issue you're linking to. – Jesse Beder Mar 06 '18 at 04:39
  • And to be clear: I don't work a lot on this project any more, but it *does* still work, and I do accept patches that are easy to verify, and I'd *love* if someone stepped up to be a more active maintainer :) – Jesse Beder Mar 06 '18 at 04:40
  • 1
    @JesseBeder: submitted the best patch I could muster, see issue. PS: what is `getCurrentDateTime()` ? – ssc Mar 06 '18 at 11:16
  • 1
    @ssc thanks! And that was just an example of a function someone might have written. – Jesse Beder Mar 09 '18 at 22:16
  • As of Dec 5, 2019, I found the yaml-cpp library essentially unusable. It threw exception after exception on valid yaml code, double check by external validators. In the end I'm back to looking for a more stable c++ yaml library. – granitdev Dec 05 '19 at 21:13
  • 1
    @granitdev have you filed a bug? – Jesse Beder Dec 06 '19 at 02:33
  • I went through the tutorial, I can't seem to find how to use the ```DateTime``` MSVC says it is an undeclared identified... is there a namespace missing? – Biaspoint Aug 27 '20 at 19:57
  • In 2020, we got vcpkg support, so it's as simple as `vcpkg install yaml-cpp` – automorphic Dec 01 '21 at 01:43
  • 3
    Is there a full example including headers and libraries? – surrutiaquir Jun 23 '22 at 21:25
  • For anyone wanting a potential solution for what `getCurrentDateTime()` might look like on Linux, see my C answer here: [Retrieve Linux Time using struct timespec](https://stackoverflow.com/a/71889097/4561887). My original source code for it is in my eRCaGuy_hello_world test repo as `timing_clock_gettime_full_demo.c`. The code works perfectly in C++ too. The `time_str` variable I create prints out as `Fri Apr 15 14:05:12 2022 -0700`, which is the same format `git` uses when you `git commit`. Print the same thing at the command-line with `date +"%a %b %-d %H:%M:%S %Y %z"`. – Gabriel Staples Jun 24 '22 at 01:22
  • 2
    please provide config file for this – Amarnath Reddy Surapureddy Jul 25 '22 at 04:55
  • How does the config.yaml look like? – Rony Tesler Jan 15 '23 at 17:13
12

I have written a tutorial at http://wpsoftware.net/andrew/pages/libyaml.html.

This covers the basics of using libyaml in C, using token-based and event-based parsing. It includes sample code for outputting the contents of a YAML file.

apoelstra
  • 129
  • 1
  • 2
10

C example - parsing YAML tree to a glib "N-ary tree":

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

void process_layer(yaml_parser_t *parser, GNode *data);
gboolean dump(GNode *n, gpointer data);



int main (int argc, char **argv) {
    char *file_path = "test.yaml";
    GNode *cfg = g_node_new(file_path);
    yaml_parser_t parser;

    FILE *source = fopen(file_path, "rb");
    yaml_parser_initialize(&parser);
    yaml_parser_set_input_file(&parser, source);
    process_layer(&parser, cfg); // Recursive parsing
    yaml_parser_delete(&parser);
    fclose(source);

    printf("Results iteration:\n");
    g_node_traverse(cfg, G_PRE_ORDER, G_TRAVERSE_ALL, -1, dump, NULL);
    g_node_destroy(cfg);

    return(0);
}



enum storage_flags { VAR, VAL, SEQ }; // "Store as" switch

void process_layer(yaml_parser_t *parser, GNode *data) {
    GNode *last_leaf = data;
    yaml_event_t event;
    int storage = VAR; // mapping cannot start with VAL definition w/o VAR key

    while (1) {
        yaml_parser_parse(parser, &event);

        // Parse value either as a new leaf in the mapping
        //  or as a leaf value (one of them, in case it's a sequence)
        if (event.type == YAML_SCALAR_EVENT) {
            if (storage) g_node_append_data(last_leaf, g_strdup((gchar*) event.data.scalar.value));
            else last_leaf = g_node_append(data, g_node_new(g_strdup((gchar*) event.data.scalar.value)));
            storage ^= VAL; // Flip VAR/VAL switch for the next event
        }

        // Sequence - all the following scalars will be appended to the last_leaf
        else if (event.type == YAML_SEQUENCE_START_EVENT) storage = SEQ;
        else if (event.type == YAML_SEQUENCE_END_EVENT) storage = VAR;

        // depth += 1
        else if (event.type == YAML_MAPPING_START_EVENT) {
            process_layer(parser, last_leaf);
            storage ^= VAL; // Flip VAR/VAL, w/o touching SEQ
        }

        // depth -= 1
        else if (
            event.type == YAML_MAPPING_END_EVENT
            || event.type == YAML_STREAM_END_EVENT
        ) break;

        yaml_event_delete(&event);
    }
}


gboolean dump(GNode *node, gpointer data) {
    int i = g_node_depth(node);
    while (--i) printf(" ");
    printf("%s\n", (char*) node->data);
    return(FALSE);
}
mk-fg
  • 195
  • 2
  • 5
6

As an alternative to yaml-cpp and libyaml there is rapidyaml. Here's an example.

#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

#include <ryml_std.hpp>
#include <ryml.hpp>

std::string get_file_contents(const char *filename)
{
    std::ifstream in(filename, std::ios::in | std::ios::binary);
    if (!in) {
        std::cerr << "could not open " << filename << std::endl;
        exit(1);
    }
    std::ostringstream contents;
    contents << in.rdbuf();
    return contents.str();
}

int main(int argc, char const *argv[]) 
{
    std::string contents = get_file_contents("config.yaml");
    ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(contents));
    ryml::NodeRef foo = tree["foo"];
    for (ryml::NodeRef const& child : foo.children()) {
        std::cout << "key: " << child.key() << " val: " << child.val() << std::endl;
    }
    
    ryml::NodeRef array = tree["matrix"]["array"];
    for (ryml::NodeRef const& child : array.children()) {
        double val;
        child >> val;
        std::cout << "float val: " << std::setprecision (18) << val << std::endl;
    }
    return 0;
}

config.yaml

foo:
  bar: a
  barbar: b
  barbarbar: c

matrix:
  array:
    - 0.045533736417839546
    - 0.16564066086021373
    - 0.028658520327566304
    - 0.009133486414620372
    - -0.5801749091384203

Update

Be aware that this project uses submodules recursively, that is why "Download ZIP" option on GitHub will not work. Use git

git clone --recurse-submodules -j8 https://github.com/biojppm/rapidyaml.git

Considering you have rapidyaml cloned in thirdparty/rapidyaml here is a minimal cmake configuration .

CMakeLists.txt

cmake_minimum_required(VERSION 3.14)

project(so_answer VERSION 0.0.1 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_subdirectory(thirdparty/rapidyaml)

add_executable(example example.cpp)
target_include_directories(example PRIVATE thirdparty/rapidyaml/src)
target_link_libraries(example ryml)
Mikolasan
  • 626
  • 10
  • 19
5

A Google Code Search (now defunct) for "yaml load lang:c++" gave this as the first link: demo.cc:

#include <iyaml++.hh>
#include <tr1/memory>
#include <iostream>
#include <stdexcept>

using namespace std;

// What should libyaml++ do when a YAML entity is parsed?
// NOTE:  if any of the event handlers is not defined, a respective default
// no-op handler will be used.  For example, not defining on_eos() is
// equivalent to defining void on_eos() { }.
class my_handler : public yaml::event_handler {
    void on_string(const std::string& s) { cout << "parsed string:  " << s << endl; }
    void on_integer(const std::string& s) { cout << "parsed integer:  " << s << endl; }
    void on_sequence_begin() { cout << "parsed sequence-begin." << endl; }
    void on_mapping_begin() { cout << "parsed mapping-begin." << endl; }
    void on_sequence_end() { cout << "parsed sequence-end." << endl; }
    void on_mapping_end() { cout << "parsed mapping-end." << endl; }
    void on_document() { cout << "parsed document." << endl; }
    void on_pair() { cout << "parsed pair." << endl; }
    void on_eos() { cout << "parsed eos." << endl; }
};

// ok then, now that i know how to behave on each YAML entity encountered, just
// give me a stream to parse!
int main(int argc, char* argv[])
{
    tr1::shared_ptr<my_handler> handler(new my_handler());
    while( cin ) {
        try { yaml::load(cin, handler); } // throws on syntax error

        catch( const runtime_error& e ) {
            cerr << e.what() << endl;
        }
    }
    return 0;
}
ChrisN
  • 16,635
  • 9
  • 57
  • 81
jfs
  • 399,953
  • 195
  • 994
  • 1,670