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
布爾值用true
和false
表示。
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