YAML
是专门用来写配置文件的语言,非常简洁和强大,比JSON
和xml
格式要方便很多。
YAML
语言(发音 /ˈjæməl/ )的设计目标,就是方便人类读写,它实质上是一种通用的数据串行化格式。
它的基本语法规则如下:
- key: value;kv之间有空格 k: v
- 大小写敏感
- 使用缩进表示层级关系
- 缩进时不允许使用Tab键,只允许使用空格。
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
- #表示注释,从这个字符一直到行尾,都会被解析器忽略。
- 字符串默认不使用引号表示。如果字符串之中包含空格或特殊字符,需要放在引号之中。
- 单引号和双引号都可以使用,双引号不会对特殊字符转义。
- 单引号之中如果还有单引号,必须连续使用两个单引号转义。
- 字符串可以写成多行,从第二行开始,必须有一个单空格缩进。换行符会被转为空格。
YAML
支持的数据结构有三种:
- 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
- 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
- 纯量(scalars):单个的、不可再分的值
一般常用yaml-cpp
和OpenCV
进行解析。
相比yaml-cpp
,OpenCV
的优点是可以在YAML
文件中存储矩阵,读出来就是cv::Mat
格式;缺点是OpenCV
要求YAML
文件有一个特殊的头,与标准的YAML
文件并不兼容。或者也可以理解为OpenCV
定义了一种新的YAML
格式。
git clone https://github.com/jbeder/yaml-cpp.git
cd yaml-cpp
mkdir build && cd build
cmake .. && make -j
sudo make install
find_package(yaml-cpp REQUIRED)
include_directories(${YAML_CPP_INCLUDE_DIR})
target_link_libraries(node_name yaml-cpp)
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
类型,你也可以转换成其它类型,它是一个模板方法。
YAML::Node类提供了三个方法用于判断当前Node的类型:IsScalar()
、IsSequence()
、IsMap()
每实例化出一个Node对象之后都要首先判断一下该Node对象的类型是什么,确定好类型之后再根据不同类型的特点进行对该Node对象的值操作。
如果对Node对象进行了不符合其类型特点的操作之后,代码会抛出异常。因此,如果Node对象的类型是不确定的情况下进行值操作时,一定要加上try catch接收异常
YAML::Node tmp_node_people = root_node["people"];
if (tmp_node_people.IsMap())
{
//如果确定是Map,那么可以放心的使用[]操作符
YAML::Node tmp_node_name = tmp_node_people["name"];
}
else if (tmp_node_people.IsScalar())
{
//如果确定是Scalar,可以放心的使用as方法
std::people_name = tmp_node_people.as<std::string>();
//但是需要注意的是,as<>中指定的类型如果是std::string不会有风险,因为一切都是字符串,转换没有问题,但是如果指定的是其他值,例如std::int,但实际yaml文件中该值是个字母,那么就会有异常抛出。因此,再使用as操作时,如果指定std::string之外的的类型时最好加上try catch。
}
else if (tmp_node_people.IsSequence())
{
//如果确定是Sequence,那么可以放心的使用[]操作符,当然里面必须是整数格式的元素下标,否则会抛出异常。
YAML::Node tmp_node_name = tmp_node_people[0];
}
else
{
//TODO:处理未知类型(未知类型是无法操作的)
}
配置文件config.yaml
:
name: frank
sex: male
age: 18
skills:
c++: 1
java: 1
android: 1
python: 1
yaml-cpp
API接口使用方式:
#include
#include "yaml-cpp/yaml.h"
#include
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;
}
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;
// 保存config为yaml文件
ofstream ofs("./testconfig.yaml");
config["score"] = 99;
ofs << config;
ofs.close();
return 0;
}
#include
#include
#include
#include
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是一个模板转换,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的一个子项
std::cout << node << endl;
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来删除它
}
output
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
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()
判断迭代器是否结束。
Scalar类型的访问:
YAML::Node tmp_node_age = root_node["age"];
//注意:这里如果确定此Scalar对象对应的值是int类型的可以直接操作,但如果不确定需要使用try catch以防抛出异常。
int tmp_age = tmp_node_age.as<int>();
Map类型的访问:
// Map类型即为键值对,yaml-cpp中将Map类型的节点,使用pair类型进行遍历
for (YAML::const_iterator it = root_node.begin(); it != root_node.end(); ++it)
{
YAML::Node tmp_key_node = it->first;
YAML::Node tmp_value_node = it->second;
key = tmp_key_node.as<std::string>();
//注意如果这里确定value是Scalar类型,是可以这样操作的,如果不确定,一定要先通过IsXXX()方法确定类型,然后操作
value = tmp_value_node<std::string>();
}
Sequence(序列)类型的访问:
YAML::Node tmp_node_list = root_node["list"];
for (int i = 0; i < tmp_node.size(); i++)
{
//注意:这里如果确定Sequence中每个元素都是Scalar类型的,可以使用as()进行访问,否则需要使用IsXXX()先判断类型
std::string tmp_meta = tmp_node_list[I].as<std::string>();
}
yaml
支持 Scalar
、List
、Map
类型,yaml-cpp
通过 NodeType
定义了 Node
的可能类型:
namespace YAML
{
struct NodeType
{
enum value
{
Undefined,
Null,
Scalar,
Sequence,
Map
};
};
}
对应:未定义、空、标量、序列、字典。
YAML::Node node1 = YAML::Load("[1,2,3,4]");
cout << " Type: " << node1.Type() << endl;
YAML::Node node2 = YAML::Load("1");
cout << " Type: " << node2.Type() << endl;
YAML::Node node3 = YAML::Load("{'id':1,'degree':'senior'}");
cout << " Type: " << node3.Type() << endl;
结果:
Type: 3
Type: 2
Type: 4
分别对应:Sequence、Scalar、Map。
YAML::Exception
YAML::TypedBadConversion // 类型转换错误
YAML::BadFile // 文件访问错误
YAML::BadSubscript // 子类型错误(例如:Node对象是个Scalar但你确使用了[]操作符进行访问)
日常开发中,经常用yaml
文件来做配置文件,除了读取配置参数,我们经常需要保存参数,yaml-cpp
自然也提供了相应的功能。
Node
可以使用文件流的方式进行读写,前面已经使用过了,保存一个node
可以用下面的方法:
std::ofstream ofs("config.yaml");
// ...
// 设置配置文件node数据
ofs << node << std::endl;
ofs.close();
这样,上面打印到cout
的内容会被输出到config.yaml
文件。
为了读取一个node
,你可以这么做:
std::ifstream ifs("config.yaml");
YAML::Node node = YAML::Load(ifs); // 读取config.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;