Search code examples
c++boost-propertytree

Parse hex numbers along with decimal numbers from XML with boost::property_tree


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.)


Solution

  • 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;