Search code examples
c++boost-spirit-qi

How to extract multiple structures from a string with boost::spirit


I have some complicated structures and i want to extract their data from a text using boost::spirit library (I've selected this one for efficiency purpose).

but i will ask my question in simpler way.

assume, we have two structures like these:

struct person 
{
   std::string name;
   uint8_t age; 
};

and

struct fruit
{
   std::string color;
   std::double average_weight;
};

and our text that included these data is presented below:

"... (jane, 23) (david, 19) (mary, 30) [yello,100] [green, 60.6] [red, 30.5]"

now, the problem is "extracting these data in suitable format"

for example by call handler for each struct or push_back them on vector.

any help would be greatly appreciated!

is there any code sample about that?!


Solution

  • call handlers for parsed structures.

    #include <string>
    
    #define BOOST_RESULT_OF_USE_DECLTYPE
    
    #include <boost/fusion/adapted.hpp>
    #include <boost/spirit/home/qi.hpp>
    
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;
    namespace phoenix = boost::phoenix;
    namespace fusion = boost::fusion;
    
    struct person
    {
        std::string name;
        uint8_t age;
    };
    
    BOOST_FUSION_ADAPT_STRUCT
    (
        person,
        (std::string, name)
        (uint8_t, age)
    );
    
    struct fruit
    {
        std::string color;
        double average_weight;
    };
    
    BOOST_FUSION_ADAPT_STRUCT
    (
        fruit,
        (std::string, color)
        (double, average_weight)
    );
    
    
    template <typename _Iterator>
    struct parser : 
        qi::grammar<_Iterator, void(), ascii::space_type>
    {
        parser() :
            parser::base_type(main)
        {
            main = 
                *(
                    _person[ ([](const person &person_)
                            { 
                                // Add handler here
                            }) ]
                    | _fruit[ ([](const fruit &fruit_)
                            { 
                                // Add handler here
                            }) ]
    
                );
    
            _person = qi::lit('(') >> *(qi::char_ - ',') >> ',' >> qi::ushort_ >> ')';
            _fruit = qi::lit('[') >> *(qi::char_ - ',') >> ',' >> qi::double_ >> ']';
        }
    
        qi::rule<_Iterator, void(), ascii::space_type> main;
        qi::rule<_Iterator, person(), ascii::space_type> _person;
        qi::rule<_Iterator, fruit(), ascii::space_type> _fruit;
    };
    
    
    int main()
    {
        typedef std::string::const_iterator iterator;
    
        std::string input_ = "(jane, 23000) (david, 19) (mary, 30) [yello,100] [green, 60.6] [red, 30.5]";
    
        iterator iterator_ = std::begin(input_);
    
        bool result_ = qi::phrase_parse(iterator_, iterator(std::end(input_)),  parser<iterator>(), ascii::space)
            && iterator_ == std::end(input_);
    
        return 0;
    }
    

    P.S. Not all compiler can build that code because of lambdas in semantic actions. (msvs don't) In this case you have to use something else (phoenix::bind for example)

    store parsed structures in a vector

    typedef boost::variant <
        person,
        fruit
    > variant;
    
    template <typename _Iterator>
    struct parser : 
        qi::grammar<_Iterator, std::vector < variant > (), ascii::space_type>
    {
        parser() :
            parser::base_type(main)
        {
            main = *(_person | _fruit);
    
            _person = qi::lit('(') >> *(qi::char_ - ',') >> ',' >> qi::ushort_ >> ')';
            _fruit = qi::lit('[') >> *(qi::char_ - ',') >> ',' >> qi::double_ >> ']';
        }
    
        qi::rule<_Iterator, std::vector < variant > (), ascii::space_type> main;
        qi::rule<_Iterator, person(), ascii::space_type> _person;
        qi::rule<_Iterator, fruit(), ascii::space_type> _fruit;
    };