Search code examples
c++boostboost-propertytree

Boost property tree specify allowed values


I wanted to use boosts property tree as handling the settings of my c++ app since it seems to be widely used in this scenario.

My question: when changing values in the property tree (through xml parsing or manually), is there a way to specify a list of allowed values of a key in advance? E.g. if I wanted to do a simple "Yes/No" setting, do I have to check the values with an if - condition or can I somehow teach my tree to only accept the two values "Yes" and "No" for the specific key in advance, so that it throws an exception on error.


Solution

  • You can use translators for this. A nice blog post I remember that describes this to get custom date format parsing in an XML-backed property tree was here:

    Let's take your example:

    enum class YesNo { No, Yes };
    

    In this case the calling code could look like:

    static YesNoTranslator trans;
    
    int main() {
    
        std::istringstream iss(R"(
                <?xml version="1.0"?>
                <demo>
                    <positive>Yes</positive>
                    <negative>No</negative>
                    <invalid>Bogus</invalid>
                </demo>
            )");
    
        ptree pt;
        read_xml(iss, pt);
    
    
        for (auto&& field : { "demo.positive", "demo.negative", "demo.invalid" })
        {
            try {
                std::cout << "With 'No' default: '" << field << "':\t" << pt.get(field, YesNo::No, trans) << "\n";
                std::cout << "Without default:   '" << field << "':\t" << pt.get<YesNo>(field, trans)     << "\n";
            } catch(std::exception const& e) {
                std::cout << "Error parsing '"      << field << "':\t" << e.what()                        << "\n";
            }
        }
    }
    

    Full Demo

    Live On Coliru

    #include <boost/property_tree/ptree.hpp>
    #include <boost/property_tree/xml_parser.hpp>
    #include <sstream>
    #include <iostream>
    
    using boost::property_tree::ptree;
    
    enum class YesNo { No, Yes };
    
    static inline std::ostream& operator<<(std::ostream& os, YesNo v) {
        switch(v) {
            case YesNo::Yes: return os << "Yes";
            case YesNo::No:  return os << "No";
        }
        return os << "??";
    }
    
    struct YesNoTranslator {
        typedef std::string  internal_type;
        typedef YesNo        external_type;
    
        boost::optional<external_type> get_value(internal_type const& v) {
            if (v == "Yes") return YesNo::Yes;
            if (v == "No")  return YesNo::No;
    
            return boost::none;
        }
    
        boost::optional<internal_type> put_value(external_type const& v) {
            switch(v) {
                case YesNo::Yes: return std::string("Yes");
                case YesNo::No:  return std::string("No");
                default: throw std::domain_error("YesNo");
            }
        }
    };
    
    static YesNoTranslator trans;
    
    int main() {
    
        std::istringstream iss(R"(
                <?xml version="1.0"?>
                <demo>
                    <positive>Yes</positive>
                    <negative>No</negative>
                    <invalid>Bogus</invalid>
                </demo>
            )");
    
        ptree pt;
        read_xml(iss, pt);
    
    
        for (auto&& field : { "demo.positive", "demo.negative", "demo.invalid" })
        {
            try {
                std::cout << "With 'No' default: '" << field << "':\t" << pt.get(field, YesNo::No, trans) << "\n";
                std::cout << "Without default:   '" << field << "':\t" << pt.get<YesNo>(field, trans)     << "\n";
            } catch(std::exception const& e) {
                std::cout << "Error parsing '"      << field << "':\t" << e.what()                        << "\n";
            }
        }
    
    }
    

    Which prints

    With 'No' default: 'demo.positive': Yes
    Without default:   'demo.positive': Yes
    With 'No' default: 'demo.negative': No
    Without default:   'demo.negative': No
    With 'No' default: 'demo.invalid':  No
    Without default:   'demo.invalid':  Error parsing 'demo.invalid':   conversion of data to type "5YesNo" failed