Разбор с Boost Spirit, получение дополнительных предметов - PullRequest
2 голосов
/ 02 июля 2010

Это долго с большим количеством кода, поэтому я надеюсь, что Stack Overflow справится с этим.: P

Я пытаюсь написать SVG-парсер с Boost Spirit.У меня есть грамматика, которая заполняет вектор "Контурами", которые являются векторами "BezierPoints", которые могут представлять либо обычные точки, либо точки с элементами управления Безье.

Пока у меня есть это (не обрабатывая относительные команды рисованияпока):

#ifndef SVG_PARSER_HPP
#define SVG_PARSER_HPP

#include <vector>

#include "boost/spirit/include/qi.hpp"
#include "boost/spirit/include/phoenix.hpp"

#include "boost/fusion/include/adapt_struct.hpp"
#include "boost/fusion/include/std_pair.hpp"

namespace qi = boost::spirit::qi;
namespace phoenix = boost::phoenix;
namespace ascii = boost::spirit::ascii;

struct Point
{
    Point(const double nx = 0.0, const double ny = 0.0) : x(nx), y(ny)
    {}

    double x;
    double y;
};

BOOST_FUSION_ADAPT_STRUCT(
    Point,
    (double, x)
    (double, y)
)

struct BezierPoint
{
    BezierPoint(const double x = 0.0, const double y = 0.0) :
      point(x, y), control1(0.0, 0.0), control2(0.0, 0.0) {}

    BezierPoint(const Point &p) : point(p), control1(0.0, 0.0),
      control2(0.0, 0.0) {}

    Point point; // End point.  Start point is in the BezierPoint that
                 // came before it.

    // Todo: Set these to be coincident with point for non-curve points.
    Point control1;
    Point control2;
};

BOOST_FUSION_ADAPT_STRUCT(
    BezierPoint,
    (Point, control1)
    (Point, control2)
    (Point, point)
)

typedef std::vector<BezierPoint> BezierVec;
typedef std::vector<BezierVec> Contours;

template <typename Iterator>
struct PathGrammar : qi::grammar<Iterator, Contours()>
{
    ///////////////////////////
    // SVG is a damn monster //
    ///////////////////////////

    PathGrammar() : PathGrammar::base_type(path_data)
    {
        using qi::char_;
        using qi::double_;
        using qi::_val;
        using qi::_1;

        using phoenix::push_back;
        using phoenix::insert;
        using phoenix::begin;
        using phoenix::end;
        using phoenix::construct;
        using phoenix::val;

        using ascii::space;

        path_data = *space >> -moveto_drawto_command_groups
          >> *space;

        moveto_drawto_command_groups = moveto_drawto_command_group
          % *space;
        moveto_drawto_command_group = moveto[
           insert(_val, end(_val), begin(_1), end(_1))
          ] >> *space
          >> -drawto_commands[
           insert(_val, end(_val), begin(_1), end(_1))
          ];

        // Draw commands are (optionally) followed by a closepath
        // command.
        drawto_commands = (drawto_command[
           insert(_val, end(_val), begin(_1), end(_1))
          ] % *space) >> *space >> -closepath;
        drawto_command =  lineto | horizontal_lineto
          | vertical_lineto | curveto | smooth_curveto;

        moveto = ( char_('M') | char_('m') ) >> *space
          >> lineto_argument_sequence;

        closepath = (char_('Z') | char_('z'));

        lineto = ( char_('L') | char_('l') ) >> *space
          >> lineto_argument_sequence;
        lineto_argument_sequence = coordinate_pair[
           push_back(_val, construct<BezierPoint>(_1))
          ] % -comma_space;

        horizontal_lineto = ( char_('H') | char_('h') ) >> *space
          >> horizontal_lineto_argument_sequence;
        horizontal_lineto_argument_sequence = coordinate[
           push_back(_val, construct<BezierPoint>(_1, val(0.0)))
          ] % -comma_space;

        vertical_lineto = ( char_('V') | char_('v') ) >> *space
          >> vertical_lineto_argument_sequence;
        vertical_lineto_argument_sequence = coordinate[
           push_back(_val, construct<BezierPoint>(val(0.0), _1))
          ] % -comma_space;

        curveto = ( char_('C') | char_('c') ) >> *space
          >> curveto_argument_sequence;
        curveto_argument_sequence = curveto_argument % -comma_space;
        curveto_argument = coordinate_pair >> -comma_space
          >> coordinate_pair >> -comma_space >> coordinate_pair;

        smooth_curveto = ( char_('S') | char_('s') ) >> *space
          >> smooth_curveto_argument_sequence;
        smooth_curveto_argument_sequence = smooth_curveto_argument
          % -comma_space;
        smooth_curveto_argument = coordinate_pair >> -comma_space
          >> coordinate_pair;

        coordinate_pair = (double_ >> -comma_space >> double_);
        coordinate = double_;
        comma_space = (+space >> -char_(',') >> *space)
          | (char_(',') >> *space);
    }

    // Quadratic curves are not supported

    qi::rule<Iterator, Contours()> path_data;

    qi::rule<Iterator, Contours()> moveto_drawto_command_groups;
    qi::rule<Iterator, BezierVec()> moveto_drawto_command_group;

    qi::rule<Iterator, BezierVec()> drawto_commands;
    qi::rule<Iterator, BezierVec()> drawto_command;

    qi::rule<Iterator, BezierVec()> moveto;
    qi::rule<Iterator, BezierVec()> moveto_argument_sequence;

    qi::rule<Iterator> closepath;

    qi::rule<Iterator, BezierVec()> lineto;
    qi::rule<Iterator, BezierVec()> lineto_argument_sequence;

    qi::rule<Iterator, BezierVec()> horizontal_lineto;
    qi::rule<Iterator, BezierVec()>
      horizontal_lineto_argument_sequence;

    qi::rule<Iterator, BezierVec()> vertical_lineto;
    qi::rule<Iterator, BezierVec()> vertical_lineto_argument_sequence;

    qi::rule<Iterator, BezierVec()> curveto;
    qi::rule<Iterator, BezierVec()> curveto_argument_sequence;
    qi::rule<Iterator, BezierPoint()> curveto_argument;

    qi::rule<Iterator, BezierVec()> smooth_curveto;
    qi::rule<Iterator, BezierVec()> smooth_curveto_argument_sequence;
    qi::rule<Iterator, BezierPoint()> smooth_curveto_argument;

    qi::rule<Iterator, Point()> coordinate_pair;
    qi::rule<Iterator, double()> coordinate;
    qi::rule<Iterator> comma_space;
};

#endif

Грамматика вызывается так:

typedef string::const_iterator StrItr;

PathGrammar<StrItr> grammar;

Contours paths;

StrItr startIt = pathData.begin();
StrItr endIt = pathData.end();
qi::parse(startIt, endIt, grammar, paths);

BOOST_FOREACH(BezierVec v, paths)
{
    cout << "Path:" << endl;
    BOOST_FOREACH(BezierPoint p, v)
    {
        cout << '\t' << p.point.x << ", " << p.point.y << endl;
    }
}

И это моя текущая тестовая строка:

M26.591,0L0,22.348l25.46,23.479L12.306,100l36.067-23.619L85.008,28.43L26.591,0z M30.553,34.23
    l-8.487-10.467l9.052-5.234l25.601,8.77l-3.109,12.729L30.553,34.23z

Строка переформатирована, чтобы сделатьэто легче читать:

M 26.591,   0
L  0    ,  22.348
l 25.46 ,  23.479
L 12.306, 100
l 36.067, -23.619
L 85.008,  28.43
L 26.591,   0
z
M 30.553,  34.23
l -8.487, -10.467
l  9.052,  -5.234
l 25.601,   8.77
l -3.109,  12.729
L 30.553,  34.23
z

Вот вывод:

Path:
    77, 0
    26.591, 0
    76, 0
    0, 22.348
    108, 0
    25.46, 23.479
    76, 0
    12.306, 100
    108, 0
    36.067, -23.619
    76, 0
    85.008, 28.43
    76, 0
    26.591, 0
Path:
    77, 0
    30.553, 34.23
    108, 0
    -8.487, -10.467
    108, 0
    9.052, -5.234
    108, 0
    25.601, 8.77
    108, 0
    -3.109, 12.729
    76, 0
    30.553, 34.23

Грамматика видит точки, но продолжает вставлять все эти дополнительные точки, и я понятия не имею, гдеони приходят.

PS

Мне также интересно узнать пару правил.Сначала есть это правило:

qi::rule<Iterator, BezierVec()> drawto_commands;
qi::rule<Iterator, BezierVec()> drawto_command;

...

drawto_commands = (drawto_command[
   insert(_val, end(_val), begin(_1), end(_1))
  ] % *space) >> *space >> -closepath;

Я хочу получить результаты (drawto_command % *space) в виде одного вектора вместо вектора векторов.Насколько я могу сказать, я должен сделать это вручную с Фениксом.Это тот случай?

У меня похожая вещь с моими правилами moveto:

qi::rule<Iterator, BezierVec()> moveto_drawto_command_group;
qi::rule<Iterator, BezierVec()> moveto;
qi::rule<Iterator, BezierVec()> moveto_argument_sequence;

...

moveto_drawto_command_group = moveto[
   insert(_val, end(_val), begin(_1), end(_1))
  ] >> *space
  >> -drawto_commands[
   insert(_val, end(_val), begin(_1), end(_1))
  ];

У меня есть два правила, которые дают BezierVec, который я хочу объединить в один BezierVec длятретье правило.Пока что единственный способ сделать это - ручная вставка с помощью Phoenix.Нет ли более простого способа?

1 Ответ

5 голосов
/ 02 июля 2010

Дополнительные значения в выводе генерируются из символов ASCII 'M' == 77, 'L' == 76, 'l' == 108 и т. Д. Это происходит, когда вы сопоставляете те, которые используют char_('M') и т. Д., Что предоставляет сопоставленное значение как атрибут char. Компилятор успешно присваивает это значениям double в выходном массиве. Чтобы избежать этого, используйте либо lit('M'), либо просто 'M'. Ни один из них не предоставляет никаких атрибутов, что делает выражения нейтральными с точки зрения генерируемых значений.

Второе, что может быть улучшено, - это убрать конструкции *space повсеместно и переключиться на разбор фраз (см. Документацию по API-функции *1013* здесь ). Если вы добавите компонент синтаксического анализатора space в качестве параметра Skipper и добавите параметр типа шаблона space_type Skipper для всех правил, вы получите тот же эффект, что и в вашей грамматике с добавлением конструкций *space. Например:

qi::rule<Iterator, Contours(), space_type> path_data;

Если у вас есть части ввода, которые не могут содержать пробелы, они все равно могут быть встроены в директивы lexeme[]. См. здесь для получения дополнительной информации.


Ваш P.S .:

Чтобы объединить все векторы, возвращенные из drawto_command, вы можете использовать трюк, заставляющий Spirit.Qi добавить к предоставленному (атрибуту левой стороны) вектор:

 drawto_commands = +drawto_command >> -closepath;

, который уже предполагает, что вы переключились на разбор фраз, поэтому я удалил конструкции *space. Почему это работает? Ну, у Spirit.Qi есть специальное правило распространения атрибутов для последовательностей, заставляющее его добавлять все атрибуты, предоставляемые его элементами, в предоставленный контейнер, если все элементы этой последовательности либо представляют этот тип атрибута, либо контейнер этих типов атрибутов. Никаких семантических действий здесь не требуется. Обратите внимание, что это работает только для последовательностей, а не для отдельных элементов с правой стороны.

Ваш второй связанный вопрос может быть решен аналогичным образом:

moveto_drawto_command_group = moveto >> -drawto_commands;

снова, семантические действия не требуются.

...