From 7e00a86bb47dce69913bf27588b73d9a96dce254 Mon Sep 17 00:00:00 2001 From: Dennis Luxen Date: Mon, 2 Mar 2015 16:41:03 +0100 Subject: [PATCH] implement ISO 8601 durations parsing, cf. #1399 --- extractor/extraction_helper_functions.hpp | 99 +++++++++++++------- features/car/ferry.feature | 21 ++++- unit_tests/algorithms/duration_parsing.cpp | 64 +++++++++++++ util/iso_8601_duration_parser.hpp | 102 +++++++++++++++++++++ 4 files changed, 250 insertions(+), 36 deletions(-) create mode 100644 unit_tests/algorithms/duration_parsing.cpp create mode 100644 util/iso_8601_duration_parser.hpp diff --git a/extractor/extraction_helper_functions.hpp b/extractor/extraction_helper_functions.hpp index c56c3b7e5..d10200abb 100644 --- a/extractor/extraction_helper_functions.hpp +++ b/extractor/extraction_helper_functions.hpp @@ -29,6 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define EXTRACTION_HELPER_FUNCTIONS_HPP #include "../util/cast.hpp" +#include "../util/iso_8601_duration_parser.hpp" #include #include @@ -37,53 +38,81 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -namespace qi = boost::spirit::qi; - -// TODO: Move into LUA - -bool durationIsValid(const std::string &s) +bool simple_duration_is_valid(const std::string &s) { - boost::regex e( + boost::regex simple_format( "((\\d|\\d\\d):(\\d|\\d\\d):(\\d|\\d\\d))|((\\d|\\d\\d):(\\d|\\d\\d))|(\\d|\\d\\d)", boost::regex_constants::icase | boost::regex_constants::perl); - std::vector result; - boost::algorithm::split_regex(result, s, boost::regex(":")); - const bool matched = regex_match(s, e); - return matched; + const bool simple_matched = regex_match(s, simple_format); + + if (simple_matched) + { + return true; + } + return false; +} + +bool iso_8601_duration_is_valid(const std::string &s) +{ + iso_8601_grammar iso_parser; + const bool result = qi::parse(s.begin(), s.end(), iso_parser); + + // check if the was an error with the request + if (result && (0 != iso_parser.get_duration())) + { + return true; + } + return false; +} + +bool durationIsValid(const std::string &s) +{ + return simple_duration_is_valid(s) || iso_8601_duration_is_valid(s); } unsigned parseDuration(const std::string &s) { - unsigned hours = 0; - unsigned minutes = 0; - unsigned seconds = 0; - boost::regex e( - "((\\d|\\d\\d):(\\d|\\d\\d):(\\d|\\d\\d))|((\\d|\\d\\d):(\\d|\\d\\d))|(\\d|\\d\\d)", - boost::regex_constants::icase | boost::regex_constants::perl); - - std::vector result; - boost::algorithm::split_regex(result, s, boost::regex(":")); - const bool matched = regex_match(s, e); - if (matched) + if (simple_duration_is_valid(s)) { - if (1 == result.size()) + unsigned hours = 0; + unsigned minutes = 0; + unsigned seconds = 0; + boost::regex e( + "((\\d|\\d\\d):(\\d|\\d\\d):(\\d|\\d\\d))|((\\d|\\d\\d):(\\d|\\d\\d))|(\\d|\\d\\d)", + boost::regex_constants::icase | boost::regex_constants::perl); + + std::vector result; + boost::algorithm::split_regex(result, s, boost::regex(":")); + const bool matched = regex_match(s, e); + if (matched) { - minutes = cast::string_to_int(result[0]); + if (1 == result.size()) + { + minutes = cast::string_to_int(result[0]); + } + if (2 == result.size()) + { + minutes = cast::string_to_int(result[1]); + hours = cast::string_to_int(result[0]); + } + if (3 == result.size()) + { + seconds = cast::string_to_int(result[2]); + minutes = cast::string_to_int(result[1]); + hours = cast::string_to_int(result[0]); + } + return 10 * (3600 * hours + 60 * minutes + seconds); } - if (2 == result.size()) - { - minutes = cast::string_to_int(result[1]); - hours = cast::string_to_int(result[0]); - } - if (3 == result.size()) - { - seconds = cast::string_to_int(result[2]); - minutes = cast::string_to_int(result[1]); - hours = cast::string_to_int(result[0]); - } - return 10 * (3600 * hours + 60 * minutes + seconds); } + else if (iso_8601_duration_is_valid(s)) + { + iso_8601_grammar iso_parser; + qi::parse(s.begin(), s.end(), iso_parser); + + return iso_parser.get_duration(); + } + return std::numeric_limits::max(); } diff --git a/features/car/ferry.feature b/features/car/ferry.feature index abbe5ed49..eb245598a 100644 --- a/features/car/ferry.feature +++ b/features/car/ferry.feature @@ -27,7 +27,7 @@ Feature: Car - Handle ferry routes | c | f | cde,efg | 2,1 | | c | g | cde,efg | 2,1 | - Scenario: Car - Properly handle durations + Scenario: Car - Properly handle simple durations Given the node map | a | b | c | | | | | | d | | | @@ -45,3 +45,22 @@ Feature: Car - Handle ferry routes | b | f | abc,cde,efg | 1,2,1 | 20 km/h | | c | e | cde | 2 | 12 km/h | | e | c | cde | 2 | 12 km/h | + + Scenario: Car - Properly handle ISO 8601 durations + Given the node map + | a | b | c | | | + | | | d | | | + | | | e | f | g | + + And the ways + | nodes | highway | route | duration | + | abc | primary | | | + | cde | | ferry | PT1M | + | efg | primary | | | + + When I route I should get + | from | to | route | modes | speed | + | a | g | abc,cde,efg | 1,2,1 | 26 km/h | + | b | f | abc,cde,efg | 1,2,1 | 20 km/h | + | c | e | cde | 2 | 12 km/h | + | e | c | cde | 2 | 12 km/h | diff --git a/unit_tests/algorithms/duration_parsing.cpp b/unit_tests/algorithms/duration_parsing.cpp new file mode 100644 index 000000000..ec3b8191a --- /dev/null +++ b/unit_tests/algorithms/duration_parsing.cpp @@ -0,0 +1,64 @@ +/* + +Copyright (c) 2015, Project OSRM contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "../../extractor/extraction_helper_functions.hpp" + +#include +#include + +BOOST_AUTO_TEST_SUITE(durations_are_valid) + +BOOST_AUTO_TEST_CASE(all_necessary_test) +{ + BOOST_CHECK_EQUAL(durationIsValid("00:01"), true); + BOOST_CHECK_EQUAL(durationIsValid("00:01:01"), true); + BOOST_CHECK_EQUAL(durationIsValid("PT15M"), true); +} + +BOOST_AUTO_TEST_CASE(common_durations_get_translated) +{ + BOOST_CHECK_EQUAL(parseDuration("00:01"), 600); + BOOST_CHECK_EQUAL(parseDuration("00:01:01"), 610); + BOOST_CHECK_EQUAL(parseDuration("01:01"), 36600); + + // check all combinations of iso duration tokens + BOOST_CHECK_EQUAL(parseDuration("PT1M1S"), 610); + BOOST_CHECK_EQUAL(parseDuration("PT1H1S"), 36010); + BOOST_CHECK_EQUAL(parseDuration("PT15M"), 9000); + BOOST_CHECK_EQUAL(parseDuration("PT15S"), 150); + BOOST_CHECK_EQUAL(parseDuration("PT15H"), 540000); + BOOST_CHECK_EQUAL(parseDuration("PT1H15M"), 45000); + BOOST_CHECK_EQUAL(parseDuration("PT1H15M1S"), 45010); +} + +BOOST_AUTO_TEST_CASE(iso_8601_durations_case_insensitive) +{ + BOOST_CHECK_EQUAL(parseDuration("PT15m"), 9000); + BOOST_CHECK_EQUAL(parseDuration("PT1h15m"), 45000); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/util/iso_8601_duration_parser.hpp b/util/iso_8601_duration_parser.hpp new file mode 100644 index 000000000..1981c8982 --- /dev/null +++ b/util/iso_8601_duration_parser.hpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2015, Project OSRM contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef ISO_8601_DURATION_PARSER_HPP +#define ISO_8601_DURATION_PARSER_HPP + +#include +#include +#include + +namespace qi = boost::spirit::qi; + +template struct iso_8601_grammar : qi::grammar +{ + iso_8601_grammar() + : iso_8601_grammar::base_type(iso_period), temp(0), hours(0), minutes(0), seconds(0) + { + iso_period = qi::lit('P') >> qi::lit('T') >> + ((value >> hour >> value >> minute >> value >> second) | + (value >> hour >> value >> minute) | (value >> hour >> value >> second) | + (value >> hour) | (value >> minute >> value >> second) | (value >> minute) | + (value >> second)); + + value = qi::uint_[boost::bind(&iso_8601_grammar::set_temp, this, ::_1)]; + second = (qi::lit('s') | + qi::lit('S'))[boost::bind(&iso_8601_grammar::set_seconds, this)]; + minute = (qi::lit('m') | + qi::lit('M'))[boost::bind(&iso_8601_grammar::set_minutes, this)]; + hour = (qi::lit('h') | + qi::lit('H'))[boost::bind(&iso_8601_grammar::set_hours, this)]; + } + + qi::rule iso_period; + qi::rule value, hour, minute, second; + + unsigned temp; + unsigned hours; + unsigned minutes; + unsigned seconds; + + void set_temp(unsigned number) { temp = number; } + + void set_hours() + { + if (temp < 24) + { + hours = temp; + } + } + + void set_minutes() + { + if (temp < 60) + { + minutes = temp; + } + } + + void set_seconds() + { + if (temp < 60) + { + seconds = temp; + } + } + + unsigned get_duration() const + { + unsigned temp = 10 * (3600 * hours + 60 * minutes + seconds); + if (temp == 0) + { + temp = std::numeric_limits::max(); + } + return temp; + } +}; + +#endif // ISO_8601_DURATION_PARSER_HPP