C++ 解析yaml文件的使用方法

1. 安裝yaml-cpp

   安裝yaml-cpp庫

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

2. yaml語法

    YAML 是專門用來寫配置文件的語言,非常簡潔和強大,比 JSON和xml格式要方便很多。

YAML 語言(發音 /ˈjæməl/ )的設計目標,就是方便人類讀寫。它實質上是一種通用的數據串行化格式。它的基本語法規則如下。

  • 大小寫敏感
  • 使用縮進表示層級關係
  • 縮進時不允許使用Tab鍵,只允許使用空格。
  • 縮進的空格數目不重要,只要相同層級的元素左側對齊即可
  • # 表示註釋,從這個字符一直到行尾,都會被解析器忽略。

YAML 支持的數據結構有三種。

  • 對象:鍵值對的集合,又稱爲映射(mapping)/ 哈希(hashes) / 字典(dictionary)
  • 數組:一組按次序排列的值,又稱爲序列(sequence) / 列表(list)
  • 純量(scalars):單個的、不可再分的值

對象

    對象的一組鍵值對,使用冒號結構表示,注意冒號後面有一個空格。

    animal: pets

    yaml 也允許另一種寫法,將所有鍵值對寫成一個行內對象。

    hash: { name: Steve, foo: bar }

數組

一組連詞線開頭的行,構成一個數組,注意連詞線仍舊有一個空格。

- Cat
- Dog
- Goldfish

相當於python列表

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

數據結構的子成員是一個數組,則可以在該項下面縮進一個空格。

-
 - Cat
 - Dog
 - Goldfish

相當於Python列表

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

數組也可以採用行內表示法。

animal: [Cat, Dog]

相當於Python字典

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

複合結構

對象和數組可以結合使用,形成複合結構。

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

相當於Python字典

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

純量

純量是最基本的、不可再分的值

  • 字符串
  • 布爾值
  • 整數
  • 浮點數
  • Null
  • 時間
  • 日期

數值直接以字面量的形式表示。

number: 12.30

布爾值用truefalse表示。

isSet: true

null~表示。

parent: ~

時間採用 ISO8601 格式。

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

日期採用複合 iso8601 格式的年、月、日表示。

date: 1976-07-31

YAML 允許使用兩個感嘆號,強制轉換數據類型。

e: !!str 123

f: !!str true

字符串

字符串是最常見,也是最複雜的一種數據類型。字符串默認不使用引號表示。

str: 這是一行字符串

如果字符串之中包含空格或特殊字符,需要放在引號之中。

str: '內容: 字符串'

單引號和雙引號都可以使用,雙引號不會對特殊字符轉義。

s1: '內容\n字符串'

s2: "內容\n字符串"

單引號之中如果還有單引號,必須連續使用兩個單引號轉義。

str: 'labor''s day'

字符串可以寫成多行,從第二行開始,必須有一個單空格縮進。換行符會被轉爲空格。

str: 這是一段

  多行

  字符串

多行字符串可以使用|保留換行符,也可以使用>摺疊換行。

this: |
  Foo
  Bar
that: >
  Foo
  Bar

+表示保留文字塊末尾的換行,-表示刪除字符串末尾的換行。

s1: |
  Foo

s2: |+
  Foo

s3: |-
  Foo

字符串之中可以插入 HTML 標記。

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

引用

錨點&和別名*,可以用來引用。

defaults: &defaults
  adapter:  postgres
  host:     localhost

development:
  database: myapp_development
  <<: *defaults

test:
  database: myapp_test
  <<: *defaults

等同於下面的代碼。

defaults:
  adapter:  postgres
  host:     localhost

development:
  database: myapp_development
  adapter:  postgres
  host:     localhost

test:
  database: myapp_test
  adapter:  postgres
  host:     localhost

&用來建立錨點(defaults),<<表示合併到當前數據,*用來引用錨點。

下面是另一個例子。

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

3. yaml的解析

3.1 Node

Node 是 yaml-cpp 中的核心概念,是最重要的數據結構,它用於存儲解析後的 yaml 信息。Node一共有以下幾種type:

Null 空節點

Sequence 序列,類似於一個Vector,對應YAML格式中的數組

Map 類似標準庫中的Map,對應YAML格式中的對象

Scalar 標量,對應YAML格式中的常量

生成 Node 的形式有很多種, loadFile() 是最常見的一種。

Node LoadFile(const std::string& filename)

filename 就是yaml文件的路徑。

有了 Node 之後,所有的信息都可以檢索到。比如 name.

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

as<string>()表示將解析的內容轉換成 string 類型。
你也可以轉換成其它類型。

它是一個模板方法。

3.2 yaml文件的解析

比如這樣一個配置文件config.yaml

name: frank
sex: male
age: 18

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

注意yaml 中的內容 ","":"後面一定要加空格
解析代碼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. node的增改查刪

以下直接上代碼,詳細的情況請看註釋。

#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來刪除它
}

如果你執行std::cout << node << endl;應該會得到以下結果:

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. yaml-cpp 中的迭代

yaml-cpp 中也可以通過迭代的方式,訪問 Node 中的內容。

比如,訪問 skills 下面的各個元素。

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

用 begin() 獲取迭代器,用 end() 判斷迭代器是否結束。

6. NodeType類型

yaml 支持 Scalar、List、Map 類型,yaml-cpp 通過 NodeType 定義了 Node 的可能類型。

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

對應未定義、空、標量、序列、字典。

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;

上面的代碼是爲了判斷 NodeType。

結果如下:

 Type: 3
 Type: 2
 Type: 4

分別對應 Sequence、Scalar、Map。

7. yaml-cpp 寫配置文件(yaml文件的保存與讀取)

日常開發中,經常用yaml文件來做配置文件,除了讀取配置參數,我們經常需要保存參數,yaml-cpp 自然也提供了相應的功能。

Node可以使用文件流的方式進行讀寫,前面已經使用過了,保存一個node可以用下面的方法:

std::ofstream fout("config.yaml");
...//設置配置文件node數據
fout << node <<std::endl;

fout.close();

這樣,上面打印到cout的內容會被輸出到config.yaml文件。

爲了讀取一個node,你可以這麼做:

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. 在ROS中使用yaml

在ROS中使用,需要對CMakeLists.txt修改,增加yaml-cpp庫,且使用一個roslib的package,設置yaml文件的路徑。

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

源文件中添加頭文件#include <ros/package.h>

#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);  

參考:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章