2

これは多くのコードで長くなるので、StackOverflowがそれに対処できることを願っています。:P

BoostSpiritを使用してSVGパーサーを作成しようとしています。「BezierPoints」のベクトルである「Contours」をベクトルに取り込む文法があります。これは、通常のポイントまたはベジェコントロールを使用したポイントのいずれかを表す場合があります。

これまでのところ、これはあります(相対描画コマンドはまだ処理していません):

#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を与える2つのルールがあり、3番目のルールのために1つのBezierVecに結合したいと思います。これまでのところ、これを行う唯一の方法は、Phoenixを使用して手動で挿入することであるように思われます。もっと簡単な方法はありませんか?

4

1 に答える 1

5

出力の追加の値は、ASCII文字、、、などから生成さ'M' == 77れます。これは、などを使用'L' == 76して値を一致させるときに発生し、一致した値を属性として公開します。コンパイラーはこれを出力配列の値に喜んで割り当てます。これを回避するには、、または単にを使用します。どちらも属性を公開しないため、生成された値に関して式はニュートラルになります。'l' == 108char_('M')chardoublelit('M')'M'

改善できる2つ目の方法は*space、あらゆる場所から構成を削除し、フレーズ解析に切り替えることです(phrase_parseAPI関数のドキュメントはこちらを参照してください)。spaceパーサーコンポーネントをスキッパーパラメーターとして指定し、スキッパーテンプレートタイプパラメーターをすべてのルールに追加すると、文法に構成を散在さspace_typeせるのと同じ効果が得られます。*space例えば:

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

空白を含めることが許可されていない入力の部分がある場合でも、それらをlexeme[]ディレクティブに埋め込むことができます。詳細については、こちらをご覧ください。


あなたのPS:

から返されたすべてのベクトルを組み合わせるにはdrawto_command、Spirit.Qiに提供された(左側の属性)ベクトルに追加するように強制するトリックを使用できます。

 drawto_commands = +drawto_command >> -closepath;

これは、フレーズ解析に切り替えたことをすでに想定しているため、*space構成を削除しました。なぜこれが機能するのですか?そうですね、Spirit.Qiにはシーケンス用の特別な属性伝播ルールがあり、このシーケンスのすべての要素がこの属性タイプまたはそれらの属性タイプのコンテナーを公開する場合、その要素によって公開されるすべての属性を提供されたコンテナーに追加するように強制します。ここではセマンティックアクションは必要ありません。これはシーケンスに対してのみ機能し、単一要素の右側の構成では機能しないことに注意してください。

2番目の関連する質問は、同様の方法で解決できます。

moveto_drawto_command_group = moveto >> -drawto_commands;

この場合も、セマンティックアクションは必要ありません。

于 2010-07-02T00:50:43.467 に答える