How to use C++ to parse yaml files

1. Install yaml-cpp

   Install yaml-cpp library

git clone https://github.com/jbeder/yaml-cpp.git
cd yaml-cpp
mkdir build && cd build
cmake .. && make -j
sudo make install

2. yaml syntax

    YAML is a language specially used to write configuration files. It is very concise and powerful, much more convenient than JSON and xml formats.

The design goal of the YAML language (pronounced /ˈjæməl/) is to facilitate human reading and writing. It is essentially a universal data serialization format. Its basic grammar rules are as follows.

  • Case Sensitive
  • Use indentation to indicate hierarchical relationships
  • Tab key is not allowed when indenting, only spaces are allowed.
  • The number of indented spaces is not important, as long as the elements of the same level are aligned to the left
  • # Indicates a comment. From this character to the end of the line, it will be ignored by the parser.

There are three data structures supported by YAML.

  • Object: a collection of key-value pairs, also known as mapping/hashes/dictionary
  • Array: A set of values ​​arranged in order, also known as sequence/list
  • Scalars: a single, indivisible value

Object

    A set of key-value pairs of the object, represented by a colon structure, pay attention to a space after the colon.

    animal: pets

    Yaml also allows another way to write all key-value pairs as an inline object.

    hash: { name: Steve, foo: bar }

Array

A group of lines at the beginning of the conjunction line form an array. Note that there is still a space in the conjunction line.

- Cat
- Dog
- Goldfish

Equivalent to python list

[ 'Cat', 'Dog', 'Goldfish' ]

The child member of the data structure is an array, you can indent a space below the item.

-
 - Cat
 - Dog
 - Goldfish

Equivalent to Python list

[ [ 'Cat', 'Dog', 'Goldfish' ] ]

Arrays can also use inline notation.

animal: [Cat, Dog]

Equivalent to Python dictionary

{ animal: [ 'Cat', 'Dog' ] }

Composite structure

Objects and arrays can be combined to form a composite structure.

languages:
 - Ruby
 - Perl
 - Python 
websites:
 YAML: yaml.org 
 Ruby: ruby-lang.org 
 Python: python.org 
 Perl: use.perl.org 

Equivalent to Python dictionary

{ languages: [ 'Ruby', 'Perl', 'Python' ],
  websites: 
   { YAML: 'yaml.org',
     Ruby: 'ruby-lang.org',
     Python: 'python.org',
     Perl: 'use.perl.org' } }

Scalar

Scalar is the most basic and indivisible value

  • String
  • Boolean value
  • Integer
  • Floating point
  • Null
  • time
  • date

The value is expressed directly in literal form.

number: 12.30

Boolean value trueand falserepresent.

isSet: true

nullUse to ~indicate.

parent: ~

The time is in ISO8601 format.

iso8601: 2001-12-14t21:59:43.10-05:00

The date is represented by the year, month, and day in the compound iso8601 format.

date: 1976-07-31

YAML allows the use of two exclamation marks to coerce the data type.

e: !! p 123

f: !!str true

String

Strings are the most common and most complex data type. Strings are not expressed in quotation marks by default.

str: this is a line of string

If the string contains spaces or special characters, they need to be enclosed in quotation marks.

str:'Content: String'

Both single quotes and double quotes can be used, and double quotes will not escape special characters.

s1:'content\nstring'

s2: "Content\nString"

If there is a single quotation mark in the single quotation mark, it must be escaped with two single quotation marks consecutively.

str: 'labor''s day'

The string can be written on multiple lines, starting from the second line, there must be a single space indentation. Newline characters will be converted to spaces.

str: this is a paragraph

  Multi-line

  String

For multi-line strings, you can use | to preserve newlines, or use> to collapse newlines.

this: |
  Foo
  Bar
that: >
  Foo
  Bar

+Means to keep the newline at the end of the text block, -means to delete the newline at the end of the string.

s1: |
  Foo

s2: |+
  Foo

s3: |-
  Foo

HTML tags can be inserted in the string.

message: |
  <p style="color: red">
    段落
  </p>

Quote

Anchors &and aliases *can be used for reference.

defaults: &defaults
  adapter:  postgres
  host:     localhost

development:
  database: myapp_development
  <<: *defaults

test:
  database: myapp_test
  <<: *defaults

It is equivalent to the following code.

defaults:
  adapter:  postgres
  host:     localhost

development:
  database: myapp_development
  adapter:  postgres
  host:     localhost

test:
  database: myapp_test
  adapter:  postgres
  host:     localhost

&It is used to establish an anchor point ( defaults), which <<means that it is merged into the current data and is *used to reference the anchor point.

Here is another example.

- &showell Steve 
- Clark 
- Brian 
- Oren 
- *showell 

3. Analysis of yaml

3.1 Node

Node is the core concept in yaml-cpp and the most important data structure. It is used to store the parsed yaml information. Node has the following types:

Null node

Sequence sequence, similar to a Vector, corresponding to the array in YAML format

Map is similar to Map in the standard library, corresponding to objects in YAML format

Scalar scalar, corresponding to constants in YAML format

There are many ways to generate Node, loadFile() is the most common one.

Node LoadFile(const std::string& filename)

filename is the path of the yaml file.

With Node, all information can be retrieved. Such as name.

cout << "name:" << config["name"].as<string>() << endl;

as<string>() means to convert the parsed content into string type.
You can also convert to other types.

It is a template method.

3.2 Analysis of yaml file

For example, such a configuration file config.yaml

name: frank
sex: male
age: 18

skills: 
  c++: 1
  java: 1
  android: 1
  python: 1

Note that the content in yaml ","":" must be followed by a space.
Parse the code yaml_test.cpp

#include <iostream>
#include "yaml-cpp/yaml.h"
#include <fstream>

using namespace std;

int main(int argc,char** argv)
{
    YAML::Node config;
    try{
         config = YAML::LoadFile("../config.yaml");
    }catch(YAML::BadFile &e){
        std::cout<<"read error!"<<std::endl;
        return -1;
    }
    
    cout << "Node type " << config.Type() << endl;
    cout << "skills type " << config["skills"].Type() << endl;

    //可以用string类型作为下表,读取参数
    string age = "age";
    cout << "age when string is label:" << config[age].as<int>() << endl;

    cout << "name:" << config["name"].as<string>() << endl;
    cout << "sex:" << config["sex"].as<string>() << endl;
    cout << "age:" << config["age"].as<int>() << endl;

    //读取不存在的node值,报YAML::TypedBadConversion异常
    try{
        string label = config["label"].as<string>();
    }catch(YAML::TypedBadConversion<string> &e){
        std::cout<<"label node is NULL"<<std::endl;
    }//TypedBadConversion是模板类,读取什么类型的参数就传入什么类型


    cout << "skills c++:" << config["skills"]["c++"].as<int>() << endl;
    cout << "skills java:" << config["skills"]["java"].as<int>() << endl;
    cout << "skills android:" << config["skills"]["android"].as<int>() << endl;
    cout << "skills python:" << config["skills"]["python"].as<int>() << endl;

    for(YAML::const_iterator it= config["skills"].begin(); it != config["skills"].end();++it)
    {
        cout << it->first.as<string>() << ":" << it->second.as<int>() << endl;
    }

    YAML::Node test1 = YAML::Load("[1,2,3,4]");
    cout << " Type: " << test1.Type() << endl;

    YAML::Node test2 = YAML::Load("1");
    cout << " Type: " << test2.Type() << endl;

    YAML::Node test3 = YAML::Load("{'id':1,'degree':'senior'}");
    cout << " Type: " << test3.Type() << endl;

    ofstream fout("./testconfig.yaml"); //保存config为yaml文件

    config["score"] = 99;//添加新元素

    fout << config;

    fout.close();


    return 0;
}

4. Add, modify, and delete node

Directly upload the code below, please see the comments for details.

#include <fstream>
#include <yaml-cpp/yaml.h>
#include <iostream>
#include <assert.h>

int main()
{
    YAML::Node node;  
    assert(node.IsNull());  //初始化的节点是Null类型
    node["key"] = "value";  //当你给它赋值键值对,它转变为Map类型
    //node.force_insert("key", "value");//这个操作和上面等价,但是它不会检查是否存在"key"键,不推荐使用
    if(node["mascot"])
        std::cout << node["mascot"].as<std::string>() << "\n";//单纯的查询操作不会增加一个key,当然上面的if不会执行

    node["number"] = 255;
    assert(node.IsMap());   //node是一个Map
    node["seq"].push_back("first element");
    node["seq"].push_back("second element");//node的seq下是Sequence类型,有两个参数

    YAML::Node node_2;  
    node_2.push_back("first item");//如果你不给node_2键值对,它是一个sequence类型
    node_2.push_back("second_item");
    node_2.push_back("third_item");
    std::vector<int> v = {1,3,5,7,9};//给node_2插入了一个Sequence
    node_2.push_back(v);
    assert(node_2.IsSequence());//当然,node_2仍然是一个Sequence

    assert(node_2[0].as<std::string>() == "first item");
    //对于Sequence类型,你可以使用它的下标来访问
    //注意这里as<T>是一个模板转换,node_2[0]的type是NodeType::Scalar
    auto it = node_2.begin();
    for(; it != node_2.end(); it++)
        std::cout << *(it) << std::endl;
    //当然,你也可以用迭代器来访问
    //他们的类型分别是NodeType::Scalar,NodeType::Scalar,NodeType::Scalar,NodeType::Sequence
    //取值时记得使用as进行模板转换
    node_2["key"] = "value";
    assert(node_2.IsMap());//一旦node_2接收到键值对,它转变为Map类型
    assert(node_2[0].as<std::string>() == "first item");//此时,Sequence时的下标变为它的key值
    node["node_2"] = node_2;//将node_2作为node的一个子项
    node["pointer_to_first_element"] = node["seq"][0];//你也可以给已有的node设置一个别名,类似于一个指针
    assert(node["pointer_to_first_element"].as<std::string>() == "first element");//你可以通过这个指针访问那个node

    node.remove(node["seq"][0]);//你可以通过指定一个node来删除它
    node.remove("pointer_to_first_element");//你也可以通过指定key来删除它
}

If you execute it, you std::cout << node << endl;should get the following results:

key: value
number: 255
seq:
  - first element
  - second element
node_2:
  0: first item
  1: second_item
  2: third_item
  3:
    - 1
    - 3
    - 5
    - 7
    - 9
  key: value

5. Iteration in yaml-cpp

In yaml-cpp, you can also access the content in Node in an iterative manner.

For example, visit the various elements under skills.

for(YAML::const_iterator it= config["skills"].begin(); it != config["skills"].end();++it)
{
    cout << it->first.as<string>() << ":" << it->second.as<int>() << endl;
}

Use begin() to get the iterator, and end() to determine whether the iterator is over.

6. NodeType type

yaml supports Scalar, List, and Map types, and yaml-cpp defines the possible types of Node through NodeType.

namespace YAML {
struct NodeType {
  enum value { Undefined, Null, Scalar, Sequence, Map };
};
}

Corresponds to undefined, empty, scalar, sequence, dictionary.

YAML::Node test1 = YAML::Load("[1,2,3,4]");
cout << " Type: " << test1.Type() << endl;

YAML::Node test2 = YAML::Load("1");
cout << " Type: " << test2.Type() << endl;

YAML::Node test3 = YAML::Load("{'id':1,'degree':'senior'}");
cout << " Type: " << test3.Type() << endl;

The above code is to determine the NodeType.

The results are as follows:

 Type: 3
 Type: 2
 Type: 4

Correspond to Sequence, Scalar, and Map respectively.

7. yaml-cpp write configuration file (save and read yaml file)

In daily development, yaml files are often used as configuration files. In addition to reading configuration parameters, we often need to save parameters, and yaml-cpp naturally also provides corresponding functions.

Node can use the file stream method to read and write. It has been used before. To save a node, you can use the following method:

std::ofstream fout("config.yaml");
...//设置配置文件node数据
fout << node <<std::endl;

fout.close();

In this way, the content printed to cout above will be output to the config.yaml file.

To read a node, you can do this:

std::ifstream file("config.yaml");
YAML::Node node = YAML::Load(file);//读取来自test.yaml的node文件
std::cout << node <<std::endl;
//或者
YAML::Node node_2 = YAML::LoadFile("config.yaml");//也可以这样读取文件
std::cout << node_2["node_2"] <<std::endl;//可以直接用下标访问
for(auto it = node_2.begin(); it != node_2.end(); it++)
    std::cout << it->first << it->second << std::endl;//也可以用迭代器访问

8. Use yaml in ROS

To use in ROS, you need to modify CMakeLists.txt, add the yaml-cpp library, and use a roslib package to set the path of the yaml file.

link_libraries(yaml-cpp)
find_package(catkin REQUIRED COMPONENTS
  roslib
)

Add the header file #include <ros/package.h> to the source file

#include <ros/package.h>
......
std::string dir_package, dir_package_file;
dir_package = ros::package::getPath("xxx");
dir_package_file = dir_package + "/config/test.yaml";
loadYamlFile(dir_package_file);  

reference:

https://en.wikipedia.org/wiki/YAML
https://yaml.org/spec/1.2/spec.html

https://www.ruanyifeng.com/blog/2016/07/yaml.html

https://www.cnblogs.com/huodaozhe/p/12026327.html

https://blog.csdn.net/briblue/article/details/89515470

Guess you like

Origin blog.csdn.net/sunlin972913894/article/details/103038082
Recommended