読者です 読者をやめる 読者になる 読者になる

Boost.Spirit.QiでHTMLっぽいフォーマットの解析

C++

こんな感じの、文字色と太字の有無だけを指定できる簡易フォーマットです。

Hello<specified color=255,255,255 bold=true>World</specified>Akira

色と太字の有無と文字列を持つ文字列クラスのvectorに情報を格納します。

#include <iostream>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>

struct Color {
    int r, g, b;

    Color() : r(0), g(0), b(0) {}
};

struct SpecifiedString {
    Color color;
    bool isBold;
    std::string str;

    SpecifiedString() {}

    template <class Container>
    SpecifiedString(const Container& str)
        : isBold(false), str(str.begin(), str.end()) {}

    template <class Container>
    SpecifiedString& operator=(const Container& s)
    {
        str.append(s.begin(), s.end());
        color = Color();
        isBold = false;
        return *this;
    }

    void print() const
    {
        std::cout << "color=(" << color.r << " " << color.g << " " << color.b << ")" << std::endl;
        std::cout << "bold=" << isBold << std::endl;
        std::cout << "text=" << str << std::endl;
    }
};

BOOST_FUSION_ADAPT_STRUCT(
    Color,
    (int, r)
    (int, g)
    (int, b)
)

BOOST_FUSION_ADAPT_STRUCT(
    SpecifiedString,
    (Color, color)
    (bool, isBold)
    (std::string, str)
)

namespace qi = boost::spirit::qi;

struct disper {
    void operator()(const SpecifiedString& s) const
    {
        s.print();
        std::cout << std::endl;
    }
};

class HtmlLikeParser {
    std::vector<SpecifiedString> strings;
public:
    bool parse(const std::string& input)
    {
        std::string::const_iterator iter = input.begin();

        qi::rule<std::string::const_iterator, Color()> color =
            qi::int_ >> ',' >> qi::int_ >> ',' >> qi::int_;

        qi::rule<std::string::const_iterator, SpecifiedString()> specified =
            qi::lit("<specified") >>
                qi::lit(" color=") >> color >>
                qi::lit(" bold=") >> qi::bool_ >>
            qi::lit(">") >>
            +(qi::char_ - '<') >>
            qi::lit("</specified>");

        qi::rule<std::string::const_iterator, SpecifiedString()> text =
            (+(qi::char_ - '<'))[qi::_val = qi::_1];

        return qi::parse(iter, input.end(),
                         qi::repeat[specified | text],
                         strings);
    }

    void print_result() const
    {
        boost::for_each(strings, disper());
    }
};

int main()
{
    const std::string input("Hello<specified color=255,255,255 bold=true>World</specified>Akira");
    
    HtmlLikeParser parser;

    if (!parser.parse(input)) {
        std::cout << "parse error!" << std::endl;
        return 1;
    }

    parser.print_result();
}
color=(0 0 0)
bold=0
text=Hello

color=(255 255 255)
bold=1
text=World

color=(0 0 0)
bold=0
text=Akira

*char_ をSpecifiedStringに変換するのにだいぶ苦戦しましたが、vectorを受け取れるコンストラクタと代入演算子を用意しといて、セマンティックアクションで_val = _1としたらいけました。