yaml-cpp是一个yml操作库。
YAML
YAML (YAML Ain’t a Markup Language,YAML不是一种标记语言)通常以.yml为后缀,是一种直观的能够被电脑识别的数据序列化格式,并且容易被人类阅读。
基本语法
yaml语法简单,适合做配置文件:
- 大小写敏感;
- 使用缩进表示层级关系;
- 缩进不允许使用tab,只允许空格
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
- '#'表示注释;
数据类型
YAML支持以下几种数据类型:
- 对象:键值对的集合,又称为映射(mapping)/哈希(hashes)/字典(dictionary);
- 数组:一组按次序排列的值,又称为序列(sequence)/列表(list);
- 标量(scalars):单个的、不可再分的值;
对象
对象键值对使用冒号结构表示 key: value
,冒号后面要加一个空格。
多层对象可表示为:
key: {key1: value1, key2: value2}
或者
key:
key1: value1
key2: value2
对于复杂的对象,可使用问号加一个空格代表一个复杂的 key,配合一个冒号加一个空格代表一个 value。以键为[complexkey1,complexkey2]
,值为 [complexvalue1,complexvalue2]
为例:
?
- complexkey1
- complexkey2
:
- complexvalue1
- complexvalue2
数组
以 - 开头的行表示构成一个数组,以一个数组对象为例:
companies:
-
id: 1
name: company1
price: 200W
-
id: 2
name: company2
price: 500W
也可以下方式表示:
companies: [{id: 1,name: company1,price: 200W},{id: 2,name: company2,price: 500W}]
标量
标量是最基本的,不可再分的值,包括:
- 字符串:有特殊字符(如空格等),可用单引号或双引号括起来;否则不需要;字符串可以拆成多行,换行符会转换为空格。
- 布尔值:TRUE/true/True或FALSE/false/False;
- 整数
- 浮点数
- Null:使用**~**表示null;
- 日期、时间:必须是ISO 8601格式的;
引用
& 用来建立锚点(defaults),<< 表示合并到当前数据,***** 用来引用锚点。
合并数据示例:
defaults: &defaults
adapter: postgres
host: localhost
development:
database: myapp_development
<<: *defaults
等价于:
defaults:
adapter: postgres
host: localhost
development:
database: myapp_development
adapter: postgres
host: localhost
单纯引用示例:
localhost:
host: &host 127.0.0.1
user:
host: *host
db: users
等价于:
localhost:
host: 127.0.0.1
user:
host: 127.0.0.1
db: users
yaml-cpp库
yaml-cpp是C++的开源库(https://github.com/jbeder/yaml-cpp);
生成器Emitter
YAML::Emitter可用于生成YAML格式文本,通过c_str()获取对应字符串:
YAML::BeginSeq ... YAML::EndSeq
:生成数组序列,其中间的内容即为数组元素;通过YAML::Flow可控制显示方式;YAML::BeginMap ... YAML::EndMap
:生成对象:YAML::Key
:用于输入键;YAML::Value
:用于输入值;
YAML::Anchor("AnchorName") ... YAML::Alias("AnchorName")
:分别用于生成锚点,和引用锚点。
Emitter还可直接输入容器(如vector作为数组)。
void buildYAML() {
YAML::Emitter out;
out << YAML::BeginMap;
out << YAML::Key << "name" << YAML::Value << "orchard";
out << YAML::Key << "fruits";
out << YAML::Value << YAML::BeginSeq << "apple" << "banana" << "pear" << YAML::EndSeq;
std::vector<int> vec{10, 20, 30};
out << YAML::Key << "weight" << YAML::Value << YAML::Flow << vec ;
std::map<std::string, int> others;
out << YAML::Key << "extra" << YAML::Value << YAML::Block;
others["pear"] = 10;
others["peach"] = 20;
out << others;
out << YAML::EndMap;
std::cout << out.c_str() << std::endl;
}
// name: orchard
// fruits:
// - apple
// - banana
// - pear
// weight: [10, 20, 30]
// extra:
// peach: 20
// pear: 10
节点Node
Node是yaml-cpp中核心概念,用于存储解析后的yaml信息:
Node LoadFile(const std::string& filename)
:从文件中加载;Node Load(...)
:从字符串或输入流中加载;
Node是一个类map结构,可通过operator["name"]
来获取子项(可级联,且不存在时不会出错):
.IsDefined()
:判断是否存在。.as<type>()
:获取对应值。.Type()
:获取对应的类型({ Undefined, Null, Scalar, Sequence, Map });
数组
数组可像STL中的列表一样访问:
YAML::Node primes = YAML::Load("[2, 3, 5, 7, 11]");
for (std::size_t i=0;i<primes.size();i++) {
std::cout << primes[i].as<int>() << "\n";
}
// or:
for (YAML::const_iterator it=primes.begin();it!=primes.end();++it) {
std::cout << it->as<int>() << "\n";
}
// or:
for (auto num : primes) {
std::cout << num.as<int>() << "\n";
}
对象
对象可像STL中的map一样访问:
YAML::Node lineup = YAML::Load("{1B: Prince Fielder, 2B: Rickie Weeks, LF: Ryan Braun}");
for(YAML::const_iterator it=lineup.begin();it!=lineup.end();++it) {
std::cout << "Playing at " << it->first.as<std::string>() << " is " << it->second.as<std::string>() << "\n";
}
// or:
for(auto ln : lineup){
std::cout << "Playing at " << ln.first.as<std::string>() << " is " << ln.second.as<std::string>() << "\n";
}
创建
通过Node也可方便地创建yaml:
void buildByNode() {
YAML::Node node; // starts out as null
node["key"] = "value"; // it now is a map node
node["seq"].push_back("first element"); // node["seq"] automatically becomes a sequence
node["seq"].push_back("second element");
node["mirror"] = node["seq"][0]; // this creates an alias
auto strOut = YAML::Dump(node);
std::cout<<strOut<<std::endl;
}
// key: value
// seq:
// - &1 first element
// - second element
// mirror: *1
解析
以读取config.yml为例:
common:
logLevel: 1
logPath: './logs'
abilities:
-
name: motion
ability: [run]
-
name: study
ability: [math, english]
plugin:
file: ./plugin/fast.so
param:
param1: value1
p2: v2
p3: test
读取代码:
void loadConfigs() {
YAML::Node root;
try {
root = YAML::LoadFile("./.config.yml");
} catch (YAML::ParserException &ex) {
std::cerr << "!! config parse failed: " << ex.what() << std::endl;
exit(-1);
} catch (YAML::BadFile &ex) {
std::cerr << "!! config load failed: " << ex.what() << std::endl;
exit(-1);
}
// common node:
auto nodeCommon = root["common"];
if (!nodeCommon.IsDefined()) {
std::cerr << "Node [common] not found, use default log-config" << std::endl;
return;
}
if (nodeCommon["logLevel"].IsDefined()) {
std::cout << "LogLevel: " << nodeCommon["logLevel"].as<int>() << std::endl;
}
if (nodeCommon["logPath"].IsDefined()) {
std::cout << "LogPath: " << nodeCommon["logPath"].as<std::string>() << std::endl;
}
// loadAbility(nodeCommon, ...)
// loadPlugin(root["plugin"], ...)
}
读取ability:
void loadAbility(const YAML::Node &nodeCommon, std::vector<CommonAbility> &allAbility) {
if (!nodeCommon["abilities"].IsDefined()) {
std::cerr << "Node [engine/abilities] not found") << std::endl;
return;
}
auto nodeAbility = nodeCommon["abilities"];
allAbility.reserve(nodeAbility.size());
for (auto eg : nodeAbility) {
auto subAbility = eg["ability"];
std::vector<std::string> vecAbility;
vecAbility.reserve(subAbility.size());
for (auto ab : subAbility) {
vecAbility.emplace_back(ab.as<std::string>());
}
allAbility.emplace_back(CommonAbility{name:eg["name"].as<std::string>(), abilities:vecAbility});
}
}
读取plugin:
void loadPlugins(const YAML::Node &plug, PluginInfo &config) {
if (!plug["file"].IsDefined()) {
std::cerr << "file of plugin [{}] not found!!", pl);
return;
}
plInfo.file = plug["file"].as<std::string>();
auto param = plug["param"];
if (param.IsDefined()) {
for (auto it = param.begin(); it != param.end(); ++it) {
plInfo.param[it->first.as<std::string>()] = it->second.as<std::string>();
}
}
}
通过iterator枚举对象时,first为键,second为值。