Search code examples
c++boost-spirit-qi

boost::spirit::qi preserving white space


I am using this code to parse "k1=v1;k2=v2;k3=v3;kn=vn" string into a map.

    qi::phrase_parse(
      begin,end,
      *(*~qi::char_('=') >> '=' >> *~qi::char_(';') >> -qi::lit(';')),
      qi::ascii::space, dict);

The above code would remove space chars, e.g. "some_key=1 2 3" becomes some_key -> 123

I can't figure out how to remove or what to replace with the fourth parameter: qi::ascii::space

Bacically, I want to preserve the original string (key and value) after splitting by '='.

I do not have much experience/knowledge with spirit. It does require investing time to learn.


Solution

  • If you want no skipper, simply use qi::parse instead of qi::phrase_parse:

    qi::parse(
      begin,end,
      *(*~qi::char_(";=") >> '=' >> *~qi::char_(';') >> -qi::lit(';')),
      dict);
    

    However, you likely DO want to selectively skip whitespace. The easiest way is usually to have a general skipper, and then mark the lexeme areas (where you don't allow the skipper):

    qi::phrase_parse(
      begin, end,
      *(qi::lexeme[+(qi::graph - '=')]
         >> '='
         >> qi::lexeme[*~qi::char_(';')] >> (qi::eoi|';')),
      qi::ascii::space, dict);
    

    The linked answer does give more techniques/backgrounds on how to work with skippers in Qi

    DEMO TIME

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/adapted/std_pair.hpp>
    #include <map>
    #include <iomanip>
    namespace qi = boost::spirit::qi;
    
    int main() {
        for (std::string const& input : { 
                R"()",
                R"(foo=bar)",
                R"(foo=bar;)",
                R"( foo = bar ; )",
                R"( foo = bar ;
    foo 
    = qux; baz =
    
        quux 
    corge grault
     thud
    
    ; x=)",
                // failing:
                R"(;foo = bar;)",
            })
        {
            std::cout << "-------------------------\n";
            auto f=begin(input), l=end(input);
    
            std::multimap<std::string, std::string> dict;
    
            bool ok = qi::phrase_parse(f, l,
              (qi::lexeme[+(qi::graph - '=' - ';')]
                 >> '='
                 >> qi::lexeme[*~qi::char_(';')]
              ) % ';',
              qi::space,
              dict);
    
            if (ok) {
                std::cout << "Parsed " << dict.size() << " elements:\n";
                for (auto& [k,v]: dict) {
                    std::cout << " - " << std::quoted(k) << " -> " << std::quoted(v) << "\n";
                }
            } else {
                std::cout << "Parse failed\n";
            }
    
            if (f!=l) {
                std::cout << "Remaining input: " << std::quoted(std::string(f,l)) << "\n";
            }
        }
    
    }
    

    Prints

    -------------------------
    Parse failed
    -------------------------
    Parsed 1 elements:
     - "foo" -> "bar"
    -------------------------
    Parsed 1 elements:
     - "foo" -> "bar"
    Remaining input: ";"
    -------------------------
    Parsed 1 elements:
     - "foo" -> "bar "
    Remaining input: "; "
    -------------------------
    Parsed 4 elements:
     - "baz" -> "quux 
    corge grault
     thud
    
    "
     - "foo" -> "bar "
     - "foo" -> "qux"
     - "x" -> ""
    -------------------------
    Parse failed
    Remaining input: ";foo = bar;"