1

I am parsing an XML file with boost::property_tree. The data I need to parse includes regular decimal numbers such as 42 and hex numbers such as 0xF1. For example:

<hex>0xF1</hex>
<dec>42</dec>

It's easy to parse decimal numbers and convert them to int with ptree::get<int>(). However, the same call on the hex number fails.

I can work around this by parsing the hex number as a std::string and then using std::istringstream and std::hex to convert it to int. Demonstrating with code:

#include <iostream>
#include <string>
#include <sstream>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>

using std::string;
namespace pt = boost::property_tree;

int main() {
    pt::ptree tree;

    try {
        pt::read_xml("debug.xml", tree, pt::xml_parser::no_comments);
    } catch (const pt::xml_parser_error&) {}

    int hexnum;

    // Doesn't work (throws exception)
    try {
        hexnum = tree.get<int>("hex");
    } catch (const pt::ptree_bad_data&) {
        std::cout << "caught bad ptree data exception";
    }

    // Workaround: parse as a string, then convert the string
    string hexstring;

    try {
         hexstring = tree.get<string>("hex");
         std::istringstream iss(hexstring);
         iss >> std::hex >> hexnum;
         if (!iss) throw std::ios_base::failure("invalid hex string");
    } catch (const pt::ptree_error&) {
        // get() failed
    } catch (const std::ios_base::failure& fail) {
        std::cout << fail.what();
    }

    // Parsing a regular decimal number is straightforward
    int decnum;

    try {
        decnum = tree.get<int>("dec");
    } catch (const pt::ptree_error&) {}

    return 0;
}

Is there a more elegant way to do this, similar to how I can convert a std::string into a number using std::istringstream with std::hex, std::oct, or std::dec? The documentation shows that there is a stream_translator.hpp header file which looks promising as a way to do this -- but there's not much documentation for this file either on the Boost website or in the header file itself. The workaround with std::istringstream is acceptable, but stream_translator.hpp makes me wonder if boost::property_tree provides a way to do this.

I need to be able to easily switch between parsing hex and decimal numbers, just as it's easy to do so with iss >> std::hex or iss >> std::dec on std::istringstream iss.

(In case it matters, my compiler is VS2005. Yes, '05 not '15.)

Null
  • 1,950
  • 9
  • 30
  • 33

2 Answers2

1

The code of stream_translator looks pretty simple: it has only two methods. I guess you can write your own translator and setup a hex flag. Something like this:

/// Implementation of Translator that uses the stream overloads.
template <typename Ch, typename Traits, typename Alloc, typename E>
class stream_translator
{
    typedef customize_stream<Ch, Traits, E> customized;
public:
    typedef std::basic_string<Ch, Traits, Alloc> internal_type;
    typedef E external_type;

    explicit stream_translator(std::locale loc = std::locale())
        : m_loc(loc)
    {}

    boost::optional<E> get_value(const internal_type &v) {
        std::basic_istringstream<Ch, Traits, Alloc> iss(v);
        iss.imbue(m_loc);
        iss.setf(std::ios_base::hex, std::ios_base::basefield);
        E e;
        customized::extract(iss, e);
        if(iss.fail() || iss.bad() || iss.get() != Traits::eof()) {
            return boost::optional<E>();
        }
        return e;
    }
    boost::optional<internal_type> put_value(const E &v) {
        std::basic_ostringstream<Ch, Traits, Alloc> oss;
        oss.imbue(m_loc);
        oss.setf(std::ios_base::hex, std::ios_base::basefield);
        customized::insert(oss, v);
        if(oss) {
            return oss.str();
        }
        return boost::optional<internal_type>();
    }

private:
    std::locale m_loc;
};

ptree itself has get method that accepts a translator. So you can pass your own translator there.

    template<class Type, class Translator>
    Type get(const path_type &path,
             const Type &default_value,
             Translator tr) const;
alexeibs
  • 535
  • 3
  • 7
  • This works (+1) but isn't any easier than the workaround. I was hoping there'd be a way to do it without defining an entire translator. – Null Jun 16 '16 at 21:08
  • I don't know I just looked at the code of boost::property_tree and I found this solution the most obvious. – alexeibs Jun 16 '16 at 21:14
1

Just use the new stoi/stol/stoll with its "base" set to 0, which will do the auto detection of the numeric base.

darkbit
  • 31
  • 3