Merge pull request #3251 from lightmare/svg-parser-test

Fix #3249 and make parse_svg_value more strict
This commit is contained in:
Artem Pavlenko 2016-01-25 09:58:14 +01:00
commit f17b11a1e5
4 changed files with 146 additions and 138 deletions

View file

@ -4,7 +4,7 @@ sudo: false
git: git:
depth: 10 depth: 10
submodules: true submodules: false
env: env:
global: global:
@ -43,6 +43,15 @@ before_install:
- export MASON_PUBLISH=${MASON_PUBLISH:-false} - export MASON_PUBLISH=${MASON_PUBLISH:-false}
- if [[ ${TRAVIS_BRANCH} != 'master' ]]; then export MASON_PUBLISH=false; fi - if [[ ${TRAVIS_BRANCH} != 'master' ]]; then export MASON_PUBLISH=false; fi
- if [[ ${TRAVIS_PULL_REQUEST} != 'false' ]]; then export MASON_PUBLISH=false; fi - if [[ ${TRAVIS_PULL_REQUEST} != 'false' ]]; then export MASON_PUBLISH=false; fi
- git submodule update --init --depth=10 ||
git submodule foreach 'test "$sha1" = "`git rev-parse HEAD`" ||
git ls-remote origin "refs/pull/*/head" |
while read hash ref; do
if test "$hash" = "$sha1"; then
git config --add remote.origin.fetch "+$ref:$ref";
fi
done'
- git submodule update --init --depth=10
install: install:
- if [[ $(uname -s) == 'Linux' ]]; then - if [[ $(uname -s) == 'Linux' ]]; then

View file

@ -137,29 +137,36 @@ double parse_double(T & error_messages, const char* str)
template <typename T, int DPI = 90> template <typename T, int DPI = 90>
double parse_svg_value(T & error_messages, const char* str, bool & percent) double parse_svg_value(T & error_messages, const char* str, bool & percent)
{ {
using namespace boost::spirit::qi;
using skip_type = boost::spirit::ascii::space_type; using skip_type = boost::spirit::ascii::space_type;
using boost::phoenix::ref; using boost::phoenix::ref;
double_type double_; qi::double_type double_;
char_type char_; qi::lit_type lit;
_1_type _1; qi::_1_type _1;
double val = 0.0; double val = 0.0;
symbols<char, double> units; qi::symbols<char, double> units;
units.add units.add
("px", 1.0)
("pt", DPI/72.0) ("pt", DPI/72.0)
("pc", DPI/6.0) ("pc", DPI/6.0)
("mm", DPI/25.4) ("mm", DPI/25.4)
("cm", DPI/2.54) ("cm", DPI/2.54)
("in", (double)DPI) ("in", (double)DPI)
; ;
if (!phrase_parse(str, str + std::strlen(str), const char* cur = str; // phrase_parse modifies the first iterator
double_[ref(val) = _1, ref(percent) = false] const char* end = str + std::strlen(str);
if (!qi::phrase_parse(cur, end,
double_[ref(val) = _1][ref(percent) = false]
> - (units[ ref(val) *= _1] > - (units[ ref(val) *= _1]
| |
char_('%')[ref(val) *= 0.01, ref(percent) = true]), lit('%')[ref(val) *= 0.01][ref(percent) = true]),
skip_type())) skip_type()))
{ {
error_messages.emplace_back("Failed to parse SVG value: \"" + std::string(str) + "\""); error_messages.emplace_back("Failed to parse SVG value: '" + std::string(str) + "'");
}
else if (cur != end)
{
error_messages.emplace_back("Failed to parse SVG value: '" + std::string(str) +
"', trailing garbage: '" + cur + "'");
} }
return val; return val;
} }

@ -1 +1 @@
Subproject commit 24844dead6d23fd541aba147b69703a605fc7351 Subproject commit e4178f3ce9798f93024a49d6cd0a87d98ec0f6b0

View file

@ -34,6 +34,42 @@
#include <fstream> #include <fstream>
#include <iterator> #include <iterator>
namespace // internal
{
struct test_parser
{
mapnik::svg_storage_type path;
mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage;
mapnik::svg::svg_path_adapter svg_path;
mapnik::svg::svg_converter_type svg;
mapnik::svg::svg_parser p;
test_parser()
: stl_storage(path.source())
, svg_path(stl_storage)
, svg(svg_path, path.attributes())
, p(svg)
{}
mapnik::svg::svg_parser* operator->()
{
return &p;
}
};
template <typename C>
std::string join(C const& container)
{
std::string result;
for (auto const& str : container)
{
if (!result.empty()) result += "\n ";
result += str;
}
return result;
}
}
TEST_CASE("SVG parser") { TEST_CASE("SVG parser") {
SECTION("SVG i/o") SECTION("SVG i/o")
@ -49,142 +85,112 @@ TEST_CASE("SVG parser") {
SECTION("SVG::parse i/o") SECTION("SVG::parse i/o")
{ {
std::string svg_name("FAIL"); std::string svg_name("FAIL");
char const* expected_errors[] =
using namespace mapnik::svg;
mapnik::svg_storage_type path;
vertex_stl_adapter<svg_path_storage> stl_storage(path.source());
svg_path_adapter svg_path(stl_storage);
svg_converter_type svg(svg_path, path.attributes());
svg_parser p(svg);
if (!p.parse(svg_name))
{ {
auto const& errors = p.error_messages(); "Unable to open 'FAIL'"
REQUIRE(errors.size() == 1); };
REQUIRE(errors[0] == "Unable to open 'FAIL'");
} test_parser p;
REQUIRE(!p->parse(svg_name));
REQUIRE(join(p->error_messages()) == join(expected_errors));
} }
SECTION("SVG::parse_from_string syntax error") SECTION("SVG::parse_from_string syntax error")
{ {
std::string svg_name("./test/data/svg/invalid.svg"); std::string svg_name("./test/data/svg/invalid.svg");
char const* expected_errors[] =
{
"Unable to parse '<?xml version=\"1.0\"?>\n<svg width=\"12cm\" height=\"4cm\" viewBox=\"0 0 1200 400\"\nxmlns=\"http://www.w3.org/2000/svg\" version=\"1.2\" baseProfile=\"tiny\">\n'"
};
std::ifstream in(svg_name.c_str()); std::ifstream in(svg_name.c_str());
std::string svg_str((std::istreambuf_iterator<char>(in)), std::string svg_str((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>()); std::istreambuf_iterator<char>());
using namespace mapnik::svg; test_parser p;
mapnik::svg_storage_type path; REQUIRE(!p->parse_from_string(svg_str));
vertex_stl_adapter<svg_path_storage> stl_storage(path.source()); REQUIRE(join(p->error_messages()) == join(expected_errors));
svg_path_adapter svg_path(stl_storage);
svg_converter_type svg(svg_path, path.attributes());
svg_parser p(svg);
if (!p.parse_from_string(svg_str))
{
auto const& errors = p.error_messages();
REQUIRE(errors.size() == 1);
REQUIRE(errors[0] == "Unable to parse '<?xml version=\"1.0\"?>\n<svg width=\"12cm\" height=\"4cm\" viewBox=\"0 0 1200 400\"\nxmlns=\"http://www.w3.org/2000/svg\" version=\"1.2\" baseProfile=\"tiny\">\n'");
}
} }
SECTION("SVG::parse_from_string syntax error") SECTION("SVG::parse_from_string syntax error")
{ {
std::string svg_name("./test/data/svg/invalid.svg"); std::string svg_name("./test/data/svg/invalid.svg");
char const* expected_errors[] =
using namespace mapnik::svg;
mapnik::svg_storage_type path;
vertex_stl_adapter<svg_path_storage> stl_storage(path.source());
svg_path_adapter svg_path(stl_storage);
svg_converter_type svg(svg_path, path.attributes());
svg_parser p(svg);
if (!p.parse(svg_name))
{ {
auto const& errors = p.error_messages(); "svg_parser::parse - Unable to parse './test/data/svg/invalid.svg'"
REQUIRE(errors.size() == 1); };
REQUIRE(errors[0] == "svg_parser::parse - Unable to parse './test/data/svg/invalid.svg'");
} test_parser p;
REQUIRE(!p->parse(svg_name));
REQUIRE(join(p->error_messages()) == join(expected_errors));
} }
SECTION("SVG parser color <fail>") SECTION("SVG parser color <fail>")
{ {
std::string svg_name("./test/data/svg/color_fail.svg"); std::string svg_name("./test/data/svg/color_fail.svg");
char const* expected_errors[] =
{
"Failed to parse color: \"fail\"",
"Failed to parse SVG value: 'fail'",
"Failed to parse color: \"fail\"",
};
std::ifstream in(svg_name.c_str()); std::ifstream in(svg_name.c_str());
std::string svg_str((std::istreambuf_iterator<char>(in)), std::string svg_str((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>()); std::istreambuf_iterator<char>());
using namespace mapnik::svg; test_parser p;
mapnik::svg_storage_type path; REQUIRE(!p->parse_from_string(svg_str));
vertex_stl_adapter<svg_path_storage> stl_storage(path.source()); REQUIRE(join(p->error_messages()) == join(expected_errors));
svg_path_adapter svg_path(stl_storage);
svg_converter_type svg(svg_path, path.attributes());
svg_parser p(svg);
if (!p.parse_from_string(svg_str))
{
auto const& errors = p.error_messages();
REQUIRE(errors.size() == 3);
REQUIRE(errors[0] == "Failed to parse color: \"fail\"");
REQUIRE(errors[1] == "Failed to parse SVG value: \"fail\"");
REQUIRE(errors[2] == "Failed to parse color: \"fail\"");
}
} }
SECTION("SVG - cope with erroneous geometries") SECTION("SVG - cope with erroneous geometries")
{ {
std::string svg_name("./test/data/svg/errors.svg"); std::string svg_name("./test/data/svg/errors.svg");
char const* expected_errors[] =
{
"parse_rect: Invalid width",
"Failed to parse SVG value: 'FAIL'",
"parse_rect: Invalid height",
"parse_rect: Invalid rx",
"parse_rect: Invalid ry",
"Failed to parse SVG value: '100invalidunit', trailing garbage: 'validunit'",
"unable to parse invalid svg <path>",
"unable to parse invalid svg <path> with id 'fail-path'",
"unable to parse invalid svg <path> with id 'fail-path'",
"parse_circle: Invalid radius",
"Failed to parse <polygon> 'points'",
"Failed to parse <polyline> 'points'",
"parse_ellipse: Invalid rx",
"parse_ellipse: Invalid ry",
};
std::ifstream in(svg_name.c_str()); std::ifstream in(svg_name.c_str());
std::string svg_str((std::istreambuf_iterator<char>(in)), std::string svg_str((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>()); std::istreambuf_iterator<char>());
using namespace mapnik::svg; test_parser p;
mapnik::svg_storage_type path; REQUIRE(!p->parse_from_string(svg_str));
vertex_stl_adapter<svg_path_storage> stl_storage(path.source()); REQUIRE(join(p->error_messages()) == join(expected_errors));
svg_path_adapter svg_path(stl_storage);
svg_converter_type svg(svg_path, path.attributes());
svg_parser p(svg);
if (!p.parse_from_string(svg_str))
{
auto const& errors = p.error_messages();
REQUIRE(errors.size() == 13);
REQUIRE(errors[0] == "parse_rect: Invalid width");
REQUIRE(errors[1] == "Failed to parse SVG value: \"FAIL\"");
REQUIRE(errors[2] == "parse_rect: Invalid height");
REQUIRE(errors[3] == "parse_rect: Invalid rx");
REQUIRE(errors[4] == "parse_rect: Invalid ry");
REQUIRE(errors[5] == "unable to parse invalid svg <path>");
REQUIRE(errors[6] == "unable to parse invalid svg <path> with id 'fail-path'");
REQUIRE(errors[7] == "unable to parse invalid svg <path> with id 'fail-path'");
REQUIRE(errors[8] == "parse_circle: Invalid radius");
REQUIRE(errors[9] == "Failed to parse <polygon> 'points'");
REQUIRE(errors[10] == "Failed to parse <polyline> 'points'");
REQUIRE(errors[11] == "parse_ellipse: Invalid rx");
REQUIRE(errors[12] == "parse_ellipse: Invalid ry");
}
} }
SECTION("SVG parser double % <fail>") SECTION("SVG parser double % <fail>")
{ {
std::string svg_name("./test/data/svg/gradient-radial-error.svg"); std::string svg_name("./test/data/svg/gradient-radial-error.svg");
char const* expected_errors[] =
{
"Failed to parse SVG value: 'FAIL'"
};
std::ifstream in(svg_name.c_str()); std::ifstream in(svg_name.c_str());
std::string svg_str((std::istreambuf_iterator<char>(in)), std::string svg_str((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>()); std::istreambuf_iterator<char>());
using namespace mapnik::svg; test_parser p;
mapnik::svg_storage_type path; REQUIRE(!p->parse_from_string(svg_str));
vertex_stl_adapter<svg_path_storage> stl_storage(path.source()); REQUIRE(join(p->error_messages()) == join(expected_errors));
svg_path_adapter svg_path(stl_storage);
svg_converter_type svg(svg_path, path.attributes());
svg_parser p(svg);
if (!p.parse_from_string(svg_str))
{
auto const& errors = p.error_messages();
REQUIRE(errors.size() == 1);
REQUIRE(errors[0] == "Failed to parse SVG value: \"FAIL\"");
}
} }
SECTION("SVG parser display=none") SECTION("SVG parser display=none")
@ -320,16 +326,10 @@ TEST_CASE("SVG parser") {
std::ifstream in(svg_name.c_str()); std::ifstream in(svg_name.c_str());
std::string svg_str((std::istreambuf_iterator<char>(in)), std::string svg_str((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>()); std::istreambuf_iterator<char>());
test_parser p;
using namespace mapnik::svg; REQUIRE(p->parse_from_string(svg_str));
mapnik::svg_storage_type path; auto width = p.svg.width();
vertex_stl_adapter<svg_path_storage> stl_storage(path.source()); auto height = p.svg.height();
svg_path_adapter svg_path(stl_storage);
svg_converter_type svg(svg_path, path.attributes());
svg_parser p(svg);
p.parse_from_string(svg_str);
auto width = svg.width();
auto height = svg.height();
REQUIRE(width == 100); REQUIRE(width == 100);
REQUIRE(height == 100); REQUIRE(height == 100);
} }
@ -604,41 +604,33 @@ TEST_CASE("SVG parser") {
SECTION("SVG missing <gradient> def") SECTION("SVG missing <gradient> def")
{ {
std::string svg_name("./test/data/svg/gradient-nodef.svg"); std::string svg_name("./test/data/svg/gradient-nodef.svg");
using namespace mapnik::svg; char const* expected_errors[] =
mapnik::svg_storage_type path; {
vertex_stl_adapter<svg_path_storage> stl_storage(path.source()); "Failed to find gradient fill: MyGradient",
svg_path_adapter svg_path(stl_storage); "Failed to find gradient stroke: MyGradient",
svg_converter_type svg(svg_path, path.attributes()); };
svg_parser p(svg);
REQUIRE(!p.parse(svg_name)); test_parser p;
auto const& errors = p.error_messages(); REQUIRE(!p->parse(svg_name));
REQUIRE(errors.size() == 2); REQUIRE(join(p->error_messages()) == join(expected_errors));
REQUIRE(errors[0] == "Failed to find gradient fill: MyGradient");
REQUIRE(errors[1] == "Failed to find gradient stroke: MyGradient");
} }
SECTION("SVG missing <gradient> id") SECTION("SVG missing <gradient> id")
{ {
std::string svg_name("./test/data/svg/gradient-no-id.svg"); std::string svg_name("./test/data/svg/gradient-no-id.svg");
char const* expected_errors[] =
{
"Failed to find gradient fill: MyGradient",
"Failed to find gradient stroke: MyGradient",
};
std::ifstream in(svg_name.c_str()); std::ifstream in(svg_name.c_str());
std::string svg_str((std::istreambuf_iterator<char>(in)), std::string svg_str((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>()); std::istreambuf_iterator<char>());
using namespace mapnik::svg; test_parser p;
mapnik::svg_storage_type path; REQUIRE(!p->parse_from_string(svg_str));
vertex_stl_adapter<svg_path_storage> stl_storage(path.source()); REQUIRE(join(p->error_messages()) == join(expected_errors));
svg_path_adapter svg_path(stl_storage);
svg_converter_type svg(svg_path, path.attributes());
svg_parser p(svg);
if (!p.parse_from_string(svg_str))
{
auto const& errors = p.error_messages();
REQUIRE(errors.size() == 2);
REQUIRE(errors[0] == "Failed to find gradient fill: MyGradient");
REQUIRE(errors[1] == "Failed to find gradient stroke: MyGradient");
}
} }
SECTION("SVG missing <gradient> inheritance") SECTION("SVG missing <gradient> inheritance")