但是,从一开始就将数据读入属性树似乎不支持检查未定义/拼写错误的选项
嗯。这并不完全正确。您可以制作自己的解析函数来添加逻辑。如果需要,请使用属性树转换器。
这是一个扩展示例,显示了要验证的三个不同类型的参数:
enum class restricted { value1, value2 };
struct params {
int i0 = 1;
restricted r1 = restricted::value2;
std::string s2 = "some default";
};
我们希望有一个这样的解析函数:
params read_inifile(std::string filename) {
params p;
pt::ptree tree;
std::ifstream file(filename);
read_ini(file, tree);
p.i0 = tree.get("ops1.i0", 1);
p.r1 = tree.get("ops1.r1", restricted::value2);
p.s2 = tree.get("ops1.s2", "some default");
return p;
}
可流式传输的类型
要翻译和验证枚举,您只需要实现流式操作符:
static inline std::istream& operator>>(std::istream& is, restricted& r) {
std::string v;
if (is >> std::ws >> v) {
if (boost::iequals("value1", v))
r = restricted::value1;
else if (boost::iequals("value2", v))
r = restricted::value2;
else
throw std::runtime_error("invalid restricted value");
}
return is;
}
static inline std::ostream& operator<<(std::ostream& os, restricted r) {
switch(r) {
case restricted::value1: return os << "value1";
case restricted::value2: return os << "value2";
default: return os << "invalid";
}
}
自定义翻译器
假设i0 需要自定义验证。在这个例子中,我们要求它是一个奇数:
namespace translators {
template <typename T>
struct must_be_odd {
typedef T internal_type;
typedef T external_type;
boost::optional<T> get_value(const std::string& str) const {
if (str.empty()) return boost::none;
T v = boost::lexical_cast<T>(str);
if (v % 2 == 0)
throw std::runtime_error("value must be odd");
return boost::make_optional(v);
}
boost::optional<std::string> put_value(const T& i0) {
assert(i0 % 2); // assert that the value was odd
return boost::lexical_cast<std::string>(i0);
}
};
static const must_be_odd<int> i0;
}
现在我们可以简单地提供翻译器(在这里,更像是自定义验证器,例如 Boost Program Options 也有它们):
p.i0 = tree.get("ops1.i0", 1, translators::i0);
看Live On Coliru
不支持的选项
这是一个多一点的工作。您必须根据已知集迭代树检查结果路径。这是一个相当通用的实现(它应该与任何(宽)字符串类型的区分大小写的树一起正常工作):
template <typename Tree,
typename Path = typename Tree::path_type,
typename Key = typename Path::key_type,
typename Cmp = typename Tree::key_compare>
std::size_t unsupported(Tree const& tree, std::set<Key, Cmp> const& supported, Path prefix = "") {
if (tree.size()) {
std::size_t n = 0;
for (auto& node : tree) {
Path sub = prefix;
sub /= node.first;
n += unsupported(node.second, supported, sub);
}
return n;
} else {
if (!supported.count(prefix.dump()) && tree.template get_value_optional<std::string>())
return 1;
}
return 0;
}
你可以这样使用它:
if (auto n = unsupported(tree, {"ops1.i0", "ops1.r1", "ops2.s2"})) {
throw std::runtime_error(std::to_string(n) + " unsupported options");
}
完整演示
Live On Coliru
#include <boost/algorithm/string.hpp>
#include <iostream>
#include <set>
enum class restricted { value1, value2 };
static inline std::istream& operator>>(std::istream& is, restricted& r) {
std::string v;
if (is >> std::ws >> v) {
if (boost::iequals("value1", v))
r = restricted::value1;
else if (boost::iequals("value2", v))
r = restricted::value2;
else
throw std::runtime_error("invalid restricted value");
}
return is;
}
static inline std::ostream& operator<<(std::ostream& os, restricted r) {
switch(r) {
case restricted::value1: return os << "value1";
case restricted::value2: return os << "value2";
default: return os << "invalid";
}
}
struct params {
int i0 = 1;
restricted r1 = restricted::value2;
std::string s2 = "some default";
};
#include <boost/property_tree/ini_parser.hpp>
#include <boost/lexical_cast.hpp>
#include <fstream>
namespace pt = boost::property_tree;
namespace translators {
template <typename T>
struct must_be_odd {
typedef T internal_type;
typedef T external_type;
boost::optional<T> get_value(const std::string& str) const {
if (str.empty()) return boost::none;
T v = boost::lexical_cast<T>(str);
if (v % 2 == 0)
throw std::runtime_error("value must be odd");
return boost::make_optional(v);
}
boost::optional<std::string> put_value(const T& i0) {
assert(i0 % 2); // assert that the value was odd
return boost::lexical_cast<std::string>(i0);
}
};
static const must_be_odd<int> i0;
}
template <typename Tree,
typename Path = typename Tree::path_type,
typename Key = typename Path::key_type,
typename Cmp = typename Tree::key_compare>
std::size_t unsupported(Tree const& tree, std::set<Key, Cmp> const& supported, Path prefix = "") {
if (tree.size()) {
std::size_t n = 0;
for (auto& node : tree) {
Path sub = prefix;
sub /= node.first;
n += unsupported(node.second, supported, sub);
}
return n;
} else {
if (!supported.count(prefix.dump()) && tree.template get_value_optional<std::string>())
return 1;
}
return 0;
}
params read_inifile(std::string filename) {
params p;
try {
pt::ptree tree;
std::ifstream file(filename);
read_ini(file, tree);
p.i0 = tree.get("ops1.i0", 1, translators::i0);
p.r1 = tree.get("ops1.r1", restricted::value2);
p.s2 = tree.get("ops1.s2", "some default");
if (auto n = unsupported(tree, {"ops1.i0", "ops1.r1", "ops2.s2"})) {
throw std::runtime_error(std::to_string(n) + " unsupported options");
}
} catch (std::exception const& e) {
std::cerr << "error: " << e.what() << "\n";
throw std::runtime_error("read_inifile");
}
return p;
}
pt::ptree to_ptree(params const& p) {
pt::ptree tree;
tree.put("ops1.i0", p.i0, translators::i0);
tree.put("ops1.r1", p.r1);
tree.put("ops1.s2", p.s2);
return tree;
}
int main() {
params const p = read_inifile("./testini.ini"); // get options from filename
write_ini("./used0.ini", to_ptree(p)); // save options to used.ini
std::cout << p.i0 << std::endl;
}
对于像这样的输入
[ops1]
i0=17
i99=oops
[oops1]
also=oops
打印
error: 2 unsupported options
terminate called after throwing an instance of 'std::runtime_error'
what(): read_inifile
还有changing 17 to 18 prints:
error: value must be odd
terminate called after throwing an instance of 'std::runtime_error'
what(): read_inifile
在有效输入时,used0.ini 将按预期写入:
[ops1]
i0=1
r1=value2
s2=some default