一个具体的例子看 boost::spirit::qi

这是来自 boost.spirit 的一个例子,如何 parse 最简单的 XML 的 tag。首先设计容器,

#include <boost/variant/recursive_variant.hpp>

namespace test {
struct mini_xml ;

typedef boost::variant<boost::recursive_wrapper<mini_xml>,
                       std::string> mini_xml_node ;
struct mini_xml {
  std::string name ;
  std::vector<mini_xml_node> children ;
} ;
}

这里面很重要的是使用了 boost.variant 这个东西(类似于 union),其好处是可以使用 static_visitor 进行遍历。为了让 spirit 对这个结构写入数据,我们需要让它成为一个 fusion 的 sequence,这只需要使用对应的宏(类似的例子见这里)就行了,

#include <boost/fusion/include/adapt_struct.hpp>

BOOST_FUSION_ADAPT_STRUCT(
   test::mini_xml,
   (std::string, name)
   (std::vector<test::mini_xml_node>, children)
)

然后我们就利用 spirit::qi 的 grammar 为此提供需要的 grammar。直观的说我们需要这么几个“语法元素”:每个 node 或者是 xml 或者就是简单的 text,而 xml 是被 tag 包围起来的里面是任意多个 node 的东西。

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>

namespace test {

template <class Iter>
struct mini_xml_grammar
  : qi::grammar<Iter, mini_xml(), ascii::space_type> {

  mini_xml_grammar () : mini_xml_grammar::base_type (xml) {
    using qi::lit ;
    using qi::lexeme ;
    using ascii::char_ ;
    using ascii::string ;
    using namespace qi::labels ;

    using phoenix::at_c ;
    using phoenix::push_back ;

    text = lexeme[+(char_ - '<')    [_val += _1]] ;
    node = (xml | text)             [_val = _1] ;

    start_tag =
         '<'
      >> !lit ('/')
      >> lexeme[+(char_ - '>')      [_val += _1]]
      >> '>'
      ;

    end_tag =
         "</"
      >> string(_r1)
      >> '>'
      ;

    xml =
         start_tag                  [at_c<0> (_val) = _1]
      >> *node                      [push_back (at_c<1> (_val), _1)]
      >> end_tag (at_c<0> (_val))
      ;
  }

  qi::rule <Iter, mini_xml(),        ascii::space_type> xml ;
  qi::rule <Iter, mini_xml_node(),   ascii::space_type> node ;
  qi::rule <Iter, std::string(),     ascii::space_type> text ;
  qi::rule <Iter, std::string(),     ascii::space_type> start_tag ;
  qi::rule <Iter, void(std::string), ascii::space_type> end_tag ;
} ;

}
[/sourceode]

当我们拿到了一个 test::xml 对象的时候,我们如果需要遍历整个 tree structure 就需要使用某种的递归,剥去第一层 test::mini_xml 之后,就是对 test::mini_xml_node 进行处理,它或者是对字符串输出,或者是递归的调用对应 mini_xml 的输出。


namespace test {
int const tabsize = 4;

void tab(int indent) {
  for (int i = 0; i < indent; ++i)
    std::cout << ' ';
}

struct mini_xml_printer {
  mini_xml_printer(int indent = 0)
    : indent(indent) {}

  void operator()(mini_xml const& xml) const;
  int indent;
};

struct mini_xml_node_printer : boost::static_visitor<> {
  mini_xml_node_printer(int indent = 0)
    : indent(indent) {}

  void
  operator()(mini_xml const& xml) const {
    mini_xml_printer(indent+tabsize)(xml);
  }

  void
  operator()(std::string const& text) const {
    tab(indent+tabsize);
    std::cout << "text: \"" << text << '"' << std::endl;
  }
  int indent;
};

void
mini_xml_printer::operator()(mini_xml const& xml) const {
  tab(indent);
  std::cout << "tag: " << xml.name << std::endl;
  tab(indent);
  std::cout << '{' << std::endl;

  BOOST_FOREACH(mini_xml_node const& node, xml.children) {
    boost::apply_visitor(mini_xml_node_printer(indent), node);
  }

  tab(indent);
  std::cout << '}' << std::endl;
}
}

值得注意的是像 parse_phrase 这类函数输入的 iterator 要求是 forward iterator,但是像 std::istream_iterator 提供的仅仅是 input iterator,这导致我们不能直接通过 parse_phrase 进行 parsing。所幸 spirit 提供了自己的 istream_iterator。

{
  // somewhere in main
  std::ifstream fin ;
  if (fn != "-")
    fin.open (fn.c_str ()) ;
  std::istream& in = (!fin ? std::cin : fin) ;
  // ...
  in >> std::noskipws ;
  boost::spirit::istream_iterator begin (in), end ;

  test::mini_xml ast ;
  typedef test::mini_xml_grammar<boost::spirit::istream_iterator>
    mini_xml_grammar ;
  mini_xml_grammar xml ;

  bool r = phrase_parse
    (begin, end, xml, boost::spirit::ascii::space, ast) ;

  if (r && begin == end) {
    test::mini_xml_printer printer ;
    printer (ast) ;
  }
  else
    std::cout << "fail" << std::endl ;

  return 0 ;
}

小结一下大致的思路:学会使用 boost.variant,很多这种 tree 结构里面都会用;记得弄成 default constructible 的,否则不好搞;很多问题还是递归的看!

——————
And Abram fell on his face: and God talked with him, saying,

Advertisements
一个具体的例子看 boost::spirit::qi

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s