From 5ca715c1e0cd53aa9d8985e345a84bf836dba1bc Mon Sep 17 00:00:00 2001 From: Mickey Rose Date: Sun, 19 Aug 2012 16:20:08 +0200 Subject: [PATCH 1/2] transform expressions: disallow space-separated compound arguments, refs #1389 --- include/mapnik/transform_expression.hpp | 24 ++++++ .../mapnik/transform_expression_grammar.hpp | 13 ++-- src/transform_expression_grammar.cpp | 76 ++++++++++++++----- 3 files changed, 87 insertions(+), 26 deletions(-) diff --git a/include/mapnik/transform_expression.hpp b/include/mapnik/transform_expression.hpp index 53170f63f..901ff9951 100644 --- a/include/mapnik/transform_expression.hpp +++ b/include/mapnik/transform_expression.hpp @@ -31,6 +31,10 @@ #include #include +// fusion +#include +#include + // stl #include @@ -83,16 +87,36 @@ struct scale_node struct rotate_node { + typedef boost::fusion::vector2 coords_type; + expr_node angle_; expr_node cx_; expr_node cy_; + explicit rotate_node(expr_node const& angle) + : angle_(angle) {} + + rotate_node(expr_node const& angle, + expr_node const& cx, expr_node const& cy) + : angle_(angle), cx_(cx), cy_(cy) {} + rotate_node(expr_node const& angle, boost::optional const& cx, boost::optional const& cy) : angle_(angle) , cx_(cx ? *cx : value_null()) , cy_(cy ? *cy : value_null()) {} + + rotate_node(expr_node const& angle, + boost::optional const& center) + : angle_(angle) + { + if (center) + { + cx_ = boost::fusion::at_c<0>(*center); + cy_ = boost::fusion::at_c<1>(*center); + } + } }; struct skewX_node diff --git a/include/mapnik/transform_expression_grammar.hpp b/include/mapnik/transform_expression_grammar.hpp index 4d4afae9f..d389ec788 100644 --- a/include/mapnik/transform_expression_grammar.hpp +++ b/include/mapnik/transform_expression_grammar.hpp @@ -28,7 +28,6 @@ #include // spirit -#include #include namespace mapnik { @@ -40,20 +39,22 @@ namespace mapnik { : qi::grammar { explicit transform_expression_grammar(expression_grammar const& g); - typedef qi::locals, - boost::optional - > rotate_locals; + typedef qi::rule node_rule; typedef qi::rule list_rule; // rules - typename expression_grammar::rule_type expr; + qi::rule attr; + qi::rule atom; + qi::rule expr; + qi::rule sep_atom; + qi::rule sep_expr; qi::rule start; qi::rule transform_; qi::rule matrix; qi::rule translate; qi::rule scale; - qi::rule rotate; + qi::rule rotate; qi::rule skewX; qi::rule skewY; }; diff --git a/src/transform_expression_grammar.cpp b/src/transform_expression_grammar.cpp index 6e8d3944e..05f4e44a1 100644 --- a/src/transform_expression_grammar.cpp +++ b/src/transform_expression_grammar.cpp @@ -19,12 +19,16 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ - + +// mapnik #include // boost #include +// spirit +#include + namespace mapnik { namespace qi = boost::spirit::qi; @@ -39,8 +43,17 @@ transform_expression_grammar::transform_expression_grammar(expression_ using qi::_c; using qi::_3; using qi::_6; using qi::_val; using qi::char_; + using qi::double_; using qi::lit; using qi::no_case; + + // [http://www.w3.org/TR/SVG/coords.html#TransformAttribute] + + // The value of the ‘transform’ attribute is a , which + // is defined as a list of transform definitions, which are applied in + // the order provided. The individual transform definitions are + // separated by whitespace and/or a comma. + #if BOOST_VERSION > 104200 using qi::no_skip; start = transform_ % no_skip[char_(", ")] ; @@ -51,48 +64,71 @@ transform_expression_grammar::transform_expression_grammar(expression_ transform_ = matrix | translate | scale | rotate | skewX | skewY ; + // matrix( ) matrix = no_case[lit("matrix")] >> (lit('(') - >> expr >> -lit(',') - >> expr >> -lit(',') - >> expr >> -lit(',') - >> expr >> -lit(',') - >> expr >> -lit(',') - >> expr >> lit(')')) + >> ( atom >> sep_atom >> sep_atom >> sep_atom >> sep_atom + >> sep_atom >> lit(')') + | expr >> sep_expr >> sep_expr >> sep_expr >> sep_expr + >> sep_expr >> lit(')') + )) [ _val = construct(_1,_2,_3,_4,_5,_6) ]; + // translate( []) translate = no_case[lit("translate")] - >> (lit('(') - >> expr >> -lit(',') - >> -expr >> lit(')')) - [ _val = construct(_1,_2) ]; + >> lit('(') + >> ( ( atom >> -sep_atom >> lit(')') ) + [ _val = construct(_1,_2) ] + | ( expr >> -sep_expr >> lit(')') ) + [ _val = construct(_1,_2) ] + ); + // scale( []) scale = no_case[lit("scale")] - >> (lit('(') - >> expr >> -lit(',') - >> -expr >> lit(')')) - [ _val = construct(_1,_2) ]; + >> lit('(') + >> ( ( atom >> -sep_atom >> lit(')') ) + [ _val = construct(_1,_2) ] + | ( expr >> -sep_expr >> lit(')') ) + [ _val = construct(_1,_2) ] + ); + // rotate( [ ]) rotate = no_case[lit("rotate")] >> lit('(') - >> expr[_a = _1] >> -lit(',') - >> -(expr [_b = _1] >> -lit(',') >> expr[_c = _1]) - >> lit(')') - [ _val = construct(_a,_b,_c) ]; + >> ( ( atom >> -( sep_atom >> sep_atom ) >> lit(')') ) + [ _val = construct(_1,_2) ] + | ( expr >> -( sep_expr >> sep_expr ) >> lit(')') ) + [ _val = construct(_1,_2) ] + ); + // skewX() skewX = no_case[lit("skewX")] >> lit('(') >> expr [ _val = construct(_1) ] >> lit(')'); + // skewY() skewY = no_case[lit("skewY")] >> lit('(') >> expr [ _val = construct(_1) ] >> lit(')'); + // number or attribute + atom = double_ [ _val = _1 ] + | attr [ _val = construct(_1) ]; + + // Individual arguments in lists consiting solely of numbers and/or + // attributes are separated by whitespace and/or a comma. + sep_atom = -lit(',') >> atom [ _val = _1 ]; + + // Individual arguments in lists containing one or more compound + // expressions are separated by a comma. + sep_expr = lit(',') >> expr [ _val = _1 ]; + + attr = g.attr.alias(); expr = g.expr.alias(); } template struct mapnik::transform_expression_grammar; -} \ No newline at end of file +} From cbaf80f574ea80631fc8007bf2c84d1bd467a128 Mon Sep 17 00:00:00 2001 From: Mickey Rose Date: Mon, 20 Aug 2012 02:24:34 +0200 Subject: [PATCH 2/2] transform expressions: add parsing tests --- tests/python_tests/object_test.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/python_tests/object_test.py b/tests/python_tests/object_test.py index b16089dae..0dc23bc51 100644 --- a/tests/python_tests/object_test.py +++ b/tests/python_tests/object_test.py @@ -93,8 +93,19 @@ def test_shieldsymbolizer_init(): def test_shieldsymbolizer_modify(): s = mapnik.ShieldSymbolizer(mapnik.Expression('[Field Name]'), 'DejaVu Sans Bold', 6, mapnik.Color('#000000'), mapnik.PathExpression('../data/images/dummy.png')) # transform expression - s.transform = "rotate(30+[a]) scale(2*[sx] [sy])" - eq_(s.transform, "rotate((30+[a])) scale(2*[sx], [sy])") + def check_transform(expr, expect_str=None): + s.transform = expr + eq_(s.transform, expr if expect_str is None else expect_str) + check_transform("matrix(1 2 3 4 5 6)", "matrix(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)") + check_transform("matrix(1, 2, 3, 4, 5, 6 +7)", "matrix(1, 2, 3, 4, 5, (6+7))") + check_transform("rotate([a])") + check_transform("rotate([a] -2)", "rotate(([a]-2))") + check_transform("rotate([a] -2 -3)", "rotate([a], -2.0, -3.0)") + check_transform("rotate([a] -2 -3 -4)", "rotate(((([a]-2)-3)-4))") + check_transform("rotate([a] -2, 3, 4)", "rotate(([a]-2), 3, 4)") + check_transform("translate([tx]) rotate([a])") + check_transform("scale([sx], [sy]/2)") + # TODO check expected failures def test_polygonsymbolizer_init(): p = mapnik.PolygonSymbolizer()