boost::spirit::karma output of string in quotation

2019-02-27 00:28发布

问题:

I am trying to escape a string in quotation marks using boost::spirit::karma. This works fine if it's just a string. However, for a string in a boost::variant in a std::vector, it does not. Just printing the string does work however, I do not quite understand why.

Line (1) works fine, but doesn't do what I want. Line (2) should do it, but doesn't.

#include <iostream>
#include <string>
#include <boost/variant.hpp>
#include <boost/spirit/include/karma.hpp>
namespace karma = boost::spirit::karma;

typedef std::vector<boost::variant<int, std::string>> ParameterList;
typedef boost::variant<int, std::string, ParameterList> Parameter;

main()
{
    using karma::int_;
    using boost::spirit::ascii::string;
    using karma::eol;
    using karma::lit;

    std::string generated;
    std::back_insert_iterator<std::string> sink(generated);

    // (1)
    karma::rule<std::back_insert_iterator<std::string>, ParameterList()> parameterListRule = (int_ | string) % lit(", "); // This works!

    // (2)
    //karma::rule<std::back_insert_iterator<std::string>, ParameterList()> parameterListRule = (int_ | (lit('"') << string << lit('"'))) % lit(", "); // This does not work

    karma::rule<std::back_insert_iterator<std::string>, Parameter()> parameterRule = (int_ | (lit('"') << string << lit('"')) | parameterListRule) << eol; // This does work, even though it also escapes the string in a pair of quotation marks

    karma::generate(sink, parameterRule, 1); // Works
    karma::generate(sink, parameterRule, "foo"); // Works
    karma::generate(sink, parameterRule, Parameter(ParameterList{1, "foo"})); // Only works using rule (1), not with (2)
    std::cout << generated;
}

回答1:

If you iterate your data types, you should iterate your rules.

#include <iostream>
#include <string>
#include <boost/variant.hpp>
#include <boost/spirit/include/karma.hpp>
namespace karma = boost::spirit::karma;

typedef boost::variant<int, std::string> Item;
typedef std::vector<Item> ParameterList;
typedef boost::variant<int, std::string, ParameterList> Parameter;

int main()
{
  using karma::int_;
  using boost::spirit::ascii::string;
  using karma::eol;
  using karma::lit;

  std::string generated;
  std::back_insert_iterator<std::string> sink(generated);

  karma::rule<std::back_insert_iterator<std::string>, Item()> itemRule =
      int_ | (lit('"') << string << lit('"'));

  karma::rule<std::back_insert_iterator<std::string>, ParameterList()>
    parameterListRule =  itemRule % lit(", ");

  karma::rule<std::back_insert_iterator<std::string>, Parameter()>
    parameterRule = (int_ | (lit('"') << string << lit('"')) | parameterListRule) << eol;

  karma::generate(sink, parameterRule, 1);
  karma::generate(sink, parameterRule, "foo");
  karma::generate(sink, parameterRule, Parameter(ParameterList {1, "foo"}));
  std::cout << generated;

  return 0;
}


回答2:

Edited In case recursion was not the goal, here's an edited version that solves the issue and the quote escaping: Live on Coliru (or just source here)

Hmm. It looks like you might have been after a recursive attribute/rule:

typedef boost::make_recursive_variant<int, std::string, std::vector<boost::recursive_variant_> >::type Parameter;

Just in that case, here's a simple approach to generating that:

gen = int_ | string | gen % ", ";

Now, your title suggests that strings containing double-quotes should escape these. I suggest

str = '"' << *('\\' << char_('"') | char_) << '"';
gen = int_ | str | gen % ", ";

Now the following test cases

for (Parameter p : Parameters { 
        1, 
        "foo",
        Parameters { 1, "foo" },
        Parameters { 1, "escape: \"foo\"", Parameters { "2", "bar" } } 
   })
{
    std::cout << karma::format(gen, p) << '\n';
}

result in:

1
"foo"
1, "foo"
1, "escape: \"foo\"", "2", "bar"

If recursion is really a feature, you'd want to see the grouping of nested Parameter lists:

gen = int_ | str | '{' << gen % ", " << '}';

Now prints

1
"foo"
{1, "foo"}
{1, "escape: \"foo\"", {"2", "bar"}}

Full sample program:

#include <boost/variant.hpp>
#include <boost/spirit/include/karma.hpp>

namespace karma = boost::spirit::karma;
typedef boost::make_recursive_variant<int, std::string, std::vector<boost::recursive_variant_> >::type Parameter;
typedef std::vector<Parameter> Parameters;

int main()
{
    typedef boost::spirit::ostream_iterator It;

    karma::rule<It, Parameter()>   gen;
    karma::rule<It, std::string()> str;

    str = '"' << *('\\' << karma::char_('"') | karma::char_) << '"';
    gen = (karma::int_ | str | '{' << gen % ", " << '}');

    for (Parameter p : Parameters { 
            1, 
            "foo",
            Parameters { 1, "foo" },
            Parameters { 1, "escape: \"foo\"", Parameters { "2", "bar" } } 
       })
    {
        std::cout << karma::format(gen, p) << '\n';
    }
}


回答3:

#include <iostream>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/karma_right_alignment.hpp>

using namespace boost;

void foo(char* buffer, uint32_t lhOid) {
    boost::spirit::karma::generate(buffer, boost::spirit::right_align(20)[boost::spirit::karma::int_], lhOid);
    *buffer = '\0';
}

int main() {
    char arr[21];
    foo(arr, 1234);
    std::cout.write(arr, 21) << std::endl;
    return 0;
}