使用boost :: {program_options,property_tree}读/写inifiles

时间:2018-05-08 12:17:33

标签: c++ boost boost-program-options boost-propertytree

利用提升,我想

  1. 从inifile中读取选项,如果在inifile中遇到未知选项,则中止
  2. 稍后将其保存在另一个inifile中。
  3. 第一部分可以使用boost :: program_options:

    完成
    try{
        inifile_options.add_options()
        ("ops1.i0", po::value<int>(&p.nx)->default_value(1), "test integer")
        ;
    
        po::variables_map vm;
        po::store(po::parse_config_file(pthfnini, inifile_options), vm);
        po::notify(vm);
    }   
    catch(exception& e){
        cerr << "error: " << e.what() << "\n";
        errorflag=1;
    }
    

    据我所知,使用boost :: program_options编写inifile是不可能的,但是boost :: property_tree可以工作:

    pt::ptree iniPropTree;
    pt::ini_parser::write_ini("./used0.ini",iniPropTree);
    

    现在的问题是如何将po :: variables_map中存储的数据转换为pt :: ptree?

    阅读增强文档让我觉得这是不可能的。以下是唯一可行的方法吗?

    iniPropTree.put<int>("ops1.i0",vm["ops1.i0"].as<int>();
    

    它为我的品味引入了相当多的冗余。但是,从一开始就将数据读入属性树似乎不支持检查未定义/拼写错误的选项。

    或者,是否可以迭代variables_map的内容并以某种方式推断每个元素的相应数据类型?

    完整的代码在这里:

    /*
     * g++ iniOps_test.cpp -Wall -std=c++11 -O3 -lboost_system -lboost_program_options -o iniOps_test.exe
     * 
     */
    
    // C++11 & Boost libraries
    #include <boost/program_options.hpp>            // po::options_description, po::variables_map, ...
    #include <boost/property_tree/ptree.hpp>        // pt::ptree
    #include <boost/property_tree/ini_parser.hpp>   // write_ini()
    #include <iostream>                             // cout
    #include <fstream>                              // ofstream, ifstream
    
    
    // namespaces
    namespace po = boost::program_options;
    namespace pt = boost::property_tree;
    using namespace std;
    
    
    struct params{
        std::string inipthfn;
        int i0;
    };
    
    
    void read_inifile(params &p, po::variables_map &vm){
    
        // initialize variables
        int errorflag=0;
        std::ifstream pthfnini("./testini.ini");
        po::options_description inifile_options("Allowed inifile options");
    
        try{
            inifile_options.add_options()
            ("ops1.i0", po::value<int>(&p.i0)->default_value(1), "test integer")
            ;
    
            ;
            po::store(po::parse_config_file(pthfnini, inifile_options), vm);
            po::notify(vm);
        }
        catch(exception& e){
            cerr << "error: " << e.what() << "\n";
            errorflag=1;
        }
    
        pthfnini.close();
        if(errorflag){ std::cout<<"--- program shutdown due to error in read_inifile ---"<<std::endl; exit(1); }
    }
    
    
    int main(){
    
        params p;
        po::variables_map vm;
        pt::ptree iniPropTree;
    
        read_inifile(p,vm);                                     // get options from inifile
    
        // ??? conversion from vm -> pt ???
    
        pt::ini_parser::write_ini("./used0.ini",iniPropTree);   // save options to used.ini
        cout << p.i0 << endl;
    
        return 0;
    }
    

    inifile“testini.ini”的内容是:

    [ops1]
    i0=2
    

3 个答案:

答案 0 :(得分:1)

这里存在一个概念问题。

命令行参数本质上是文本的。

变量映射中的值不是。使用的类型在value-semantics中配置(options-description的一部分)。

如果您的所有选项都具有相同类型,则可以&#34;作弊&#34;并对转换进行硬编码:

pt::ptree to_ptree(po::variables_map const& vm) {
    pt::ptree tree;
    for (auto& v : vm) {
        if (!v.second.empty() && !v.second.defaulted())
            tree.put(v.first, v.second.as<int>());
    }

    return tree;
}

保存:

[ops1]
i0=1

如果您需要更多灵活性,至少需要访问选项说明。这不是图书馆的预期用途,您很快就会遇到未实施的部分。

答案 1 :(得分:1)

  

但是,从一开始就将数据读入属性树似乎不支持检查未定义/拼写错误的选项

好。这并非完全正确。您可以创建自己的解析函数来添加逻辑。如果需要,请使用Property Tree Translators。

这是一个扩展示例,显示了要验证的不同类型的三个参数:

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

答案 2 :(得分:0)

在再次提出这个问题之后,我找到了一个合适的紧凑解决方案:

关键是编写一个函数,根据数据类型将条目从variables_map转换为propTree(thx到sehe,以便将我放在正确的轨道上):

void translate_variables_map_to_ptree(po::variables_map &vm, pt::ptree &propTree){

    for(po::variables_map::iterator it=vm.begin(); it!=vm.end(); it++){
        if( it->second.value().type() == typeid(int) ){ propTree.put<int>(it->first,vm[it->first].as<int>()); }
        else if( it->second.value().type() == typeid(float) ){ propTree.put<float>(it->first,vm[it->first].as<float>()); }
        else if( it->second.value().type() == typeid(double) ){ propTree.put<double>(it->first,vm[it->first].as<double>()); }
        else if( it->second.value().type() == typeid(std::string) ){ propTree.put<std::string>(it->first,vm[it->first].as<std::string>()); }
        else if( it->second.value().type() == typeid(size_t) ){ propTree.put<size_t>(it->first,vm[it->first].as<size_t>()); }
        else{ printf("Error: unknown datatype. Abort!\n"); exit(EXIT_FAILURE); }
    }
}

完整的工作示例写入包含所有读取信息的正确的inifile:

/*
 * g++ iniOps_test.cpp -Wall -std=c++11 -O3 -lboost_system -lboost_program_options -o iniOps_test.exe
 * 
 */

// C++11 & Boost libraries
#include <boost/program_options.hpp>            // po::options_description, po::variables_map, ...
#include <boost/property_tree/ptree.hpp>        // pt::ptree
#include <boost/property_tree/ini_parser.hpp>   // write_ini()
#include <iostream>                             // cout
#include <fstream>                              // ofstream, ifstream


// namespaces
namespace po = boost::program_options;
namespace pt = boost::property_tree;
using namespace std;


struct params{
    std::string s0;
    int i0;
};


void read_inifile(params &p, po::variables_map &vm){

    // initialize variables
    int errorflag=0;
    std::ifstream pthfnini("./testini.ini");
    po::options_description inifile_options("Allowed inifile options");

    try{
        inifile_options.add_options()
        ("ops1.i0", po::value<int>(&p.i0)->default_value(1), "test integer")
        ("ops1.s0", po::value<std::string>(&p.s0)->default_value("default"), "test string")
        ;

        ;
        po::store(po::parse_config_file(pthfnini, inifile_options), vm);
        po::notify(vm);
    }
    catch(exception& e){
        cerr << "error: " << e.what() << "\n";
        errorflag=1;
    }

    pthfnini.close();
    if(errorflag){ std::cout<<"--- program shutdown due to error in read_inifile ---"<<std::endl; exit(1); }
}

void translate_variables_map_to_ptree(po::variables_map &vm, pt::ptree &propTree){

    for(po::variables_map::iterator it=vm.begin(); it!=vm.end(); it++){
        if( it->second.value().type() == typeid(int) ){ propTree.put<int>(it->first,vm[it->first].as<int>()); }
        else if( it->second.value().type() == typeid(float) ){ propTree.put<float>(it->first,vm[it->first].as<float>()); }
        else if( it->second.value().type() == typeid(double) ){ propTree.put<double>(it->first,vm[it->first].as<double>()); }
        else if( it->second.value().type() == typeid(std::string) ){ propTree.put<std::string>(it->first,vm[it->first].as<std::string>()); }
        else if( it->second.value().type() == typeid(size_t) ){ propTree.put<size_t>(it->first,vm[it->first].as<size_t>()); }
        else{ printf("Error: unknown datatype. Abort!\n"); exit(EXIT_FAILURE); }
    }
}


int main(){

    params p;
    po::variables_map vm;
    pt::ptree iniPropTree;

    read_inifile(p,vm);                                     // get options from inifile
    translate_variables_map_to_ptree(vm,iniPropTree);       // conversion from vm -> pt 
    pt::ini_parser::write_ini("./used0.ini",iniPropTree);   // save options to used.ini

    cout << p.i0 << endl;
    cout << p.s0 << endl;

    return 0;
}

从读取命令行获取variables_map vm,还可以使用以下命令更新属性树中的值(从读取inifile):

string opsName = "ops1.i0"; if(vm.count(opsName)) p.i0 = vm[opsName].as<int>();