Измените, как boost:: property_tree reads переводит строки для bool

Я потерялся в файлах заголовков для boost_tree boost и, учитывая отсутствие документации по нижним уровням, я решил спросить, какой простой способ перегрузить транслятор потока, чтобы изменить, как значения Boolean анализируются.

Проблема заключается в том, что на входной стороне дерева свойств есть пользователи, и они могут изменять файлы конфигурации. Булевское значение может быть указано несколькими способами, например:

dosomething.enabled=true
dosomething.enabled=trUE
dosomething.enabled=yes
dosomething.enabled=ON
dosomething.enabled=1

По умолчанию используется проверка на 0 или 1, а затем использование

std::ios_base::boolalpha 

чтобы заставить поток попытаться проанализировать значение соответствующим образом для текущей локали... что может быть безумным, если мы попытаемся отправить файл конфигурации международным клиентам.

Итак, что самый простой способ переопределить это поведение или только bool? Не только проще всего реализовать, но и проще всего использовать, так что пользователям моего класса, которые производятся от iptree, не нужно делать что-то особенное для булевых значений.

Спасибо!

Ответ 1

Вы можете специализировать boost::property_tree::translator_between, чтобы дерево свойств использовало собственный переводчик для типа значения bool. Эта специализация должна быть видимой (т.е. #includ ed) клиентами, желающими настроить поведение. Вот рабочий пример:

#include <iostream>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/algorithm/string/predicate.hpp>

// Custom translator for bool (only supports std::string)
struct BoolTranslator
{
    typedef std::string internal_type;
    typedef bool        external_type;

    // Converts a string to bool
    boost::optional<external_type> get_value(const internal_type& str)
    {
        if (!str.empty())
        {
            using boost::algorithm::iequals;

            if (iequals(str, "true") || iequals(str, "yes") || str == "1")
                return boost::optional<external_type>(true);
            else
                return boost::optional<external_type>(false);
        }
        else
            return boost::optional<external_type>(boost::none);
    }

    // Converts a bool to string
    boost::optional<internal_type> put_value(const external_type& b)
    {
        return boost::optional<internal_type>(b ? "true" : "false");
    }
};

/*  Specialize translator_between so that it uses our custom translator for
    bool value types. Specialization must be in boost::property_tree
    namespace. */
namespace boost {
namespace property_tree {

template<typename Ch, typename Traits, typename Alloc> 
struct translator_between<std::basic_string< Ch, Traits, Alloc >, bool>
{
    typedef BoolTranslator type;
};

} // namespace property_tree
} // namespace boost

int main()
{
    boost::property_tree::iptree pt;

    read_json("test.json", pt);
    int i = pt.get<int>("number");
    int b = pt.get<bool>("enabled");
    std::cout << "i=" << i << " b=" << b << "\n";
}

test.json:

{
    "number" : 42,
    "enabled" : "Yes"
}

Вывод:

i=42 b=1

Обратите внимание, что в этом примере предполагается, что дерево свойств нечувствительно к регистру и использует std::string. Если вы хотите, чтобы BoolTranslator был более общим, вам нужно сделать шаблон BoolTranslator и предоставить специализации для широких строк и сравнения с регистром.

Ответ 2

Существует также хороший пример в theboostcpplibraries.com.

Основываясь на этом, я написал для настраиваемого анализатора (объявление опущено):

boost::optional<bool> string_to_bool_translator::get_value(const std::string &s) {
    auto tmp = boost::to_lower_copy(s);
    if (tmp == "true" || tmp == "1" || tmp == "y" || tmp == "on") {
       return boost::make_optional(true);
    } else if (tmp == "false" || tmp == "0" || tmp == "n" || tmp == "off") {
      return boost::make_optional(false);
    } else {
        return boost::none;
    }
} 

Только для bool и std::string, но легко расширяемый.

Затем

boost::property_tree::ptree pt;
...
string_to_bool_translator tr;
auto optional_value = pt.get_optional<bool>(key, tr);