diff --git a/data/replaced_tags.txt b/data/replaced_tags.txt index 4901f19947..0794d28e76 100644 --- a/data/replaced_tags.txt +++ b/data/replaced_tags.txt @@ -1,3 +1,11 @@ +# Format: +# ::= ' : ' +# ::= +# ::= [', ' ] +# ::= '=' +# ::= string without spaces, '=' and ',' symbols +# ::= string without spaces, '=' and ',' symbols + atm=yes : amenity=atm bench=yes : amenity=bench shelter=yes : amenity=shelter diff --git a/generator/generator_tests/tag_admixer_test.cpp b/generator/generator_tests/tag_admixer_test.cpp index 0e1e24f87b..4502e988f1 100644 --- a/generator/generator_tests/tag_admixer_test.cpp +++ b/generator/generator_tests/tag_admixer_test.cpp @@ -2,9 +2,42 @@ #include "generator/tag_admixer.hpp" +#include "platform/platform_tests_support/scoped_file.hpp" + +#include #include #include #include +#include + +using platform::tests_support::ScopedFile; + +namespace +{ +void TestReplacer(std::string const & source, + TagReplacer::Replacements const & expectedReplacements) +{ + auto const filename = "test.txt"; + ScopedFile sf(filename, source); + TagReplacer replacer(sf.GetFullPath()); + auto const & replacements = replacer.GetReplacementsForTesting(); + TEST_EQUAL(replacements.size(), expectedReplacements.size(), + (source, replacements, expectedReplacements)); + for (auto const & replacement : replacements) + { + auto const it = expectedReplacements.find(replacement.first); + TEST(it != expectedReplacements.end(), + ("Unexpected replacement for key", replacement.first, ":", replacement.second)); + TEST_EQUAL(replacement.second.size(), it->second.size(), + ("Different rules number for tag", replacement.first)); + for (auto const & tag : replacement.second) + { + auto const tagIt = std::find(it->second.begin(), it->second.end(), tag); + TEST(tagIt != it->second.end(), ("Unexpected rule for tag", replacement.first)); + } + } +} +} // namespace UNIT_TEST(WaysParserTests) { @@ -32,3 +65,43 @@ UNIT_TEST(CapitalsParserTests) TEST(capitals.find(448768937) != capitals.end(), ()); TEST(capitals.find(140247101) == capitals.end(), ()); } + +UNIT_TEST(TagsReplacer_Smoke) +{ + { + std::string const source = ""; + TagReplacer::Replacements replacements = {}; + TestReplacer(source, replacements); + } + { + std::string const source = "aerodrome:type=international : aerodrome=international"; + TagReplacer::Replacements replacements = { + {{"aerodrome:type", "international"}, {{"aerodrome", "international"}}}}; + TestReplacer(source, replacements); + } + { + std::string const source = + " aerodrome:type = international : aerodrome = international "; + TagReplacer::Replacements replacements = { + {{"aerodrome:type", "international"}, {{"aerodrome", "international"}}}}; + TestReplacer(source, replacements); + } + { + std::string const source = "natural=marsh : natural=wetland, wetland=marsh"; + TagReplacer::Replacements replacements = { + {{"natural", "marsh"}, {{"natural", "wetland"}, {"wetland", "marsh"}}}}; + TestReplacer(source, replacements); + } + { + std::string const source = + "natural = forest : natural = wood\n" + "# TODO\n" + "# natural = ridge + cliff=yes -> natural=cliff\n" + "\n" + "office=travel_agent : shop=travel_agency"; + TagReplacer::Replacements replacements = { + {{"natural", "forest"}, {{"natural", "wood"}}}, + {{"office", "travel_agent"}, {{"shop", "travel_agency"}}}}; + TestReplacer(source, replacements); + } +} diff --git a/generator/tag_admixer.hpp b/generator/tag_admixer.hpp index 82facb756a..68efab9fad 100644 --- a/generator/tag_admixer.hpp +++ b/generator/tag_admixer.hpp @@ -147,66 +147,88 @@ private: class TagReplacer { public: + using Replacements = std::map>; + TagReplacer() = default; explicit TagReplacer(std::string const & filePath) { std::ifstream stream(filePath); - OsmElement::Tag tag; - std::vector values; std::string line; + size_t lineNumber = 0; while (std::getline(stream, line)) { - if (line.empty()) + ++lineNumber; + strings::Trim(line); + if (line.empty() || line[0] == '#') continue; - strings::SimpleTokenizer iter(line, " \t=,:"); - if (!iter) - continue; - tag.m_key = *iter; - ++iter; - if (!iter) - continue; - tag.m_value = *iter; + auto keyPos = line.find("="); + CHECK(keyPos != std::string::npos, ("Cannot find source tag key in", line)); + auto key = line.substr(0, keyPos); + strings::Trim(key); - values.clear(); - while (++iter) - values.push_back(*iter); + // Skip '='. + ++keyPos; + auto valuePos = line.find(" : ", keyPos); + CHECK(valuePos != std::string::npos, ("Cannot find source tag value in", line)); + auto value = line.substr(keyPos, valuePos - keyPos); + strings::Trim(value); - if (values.size() >= 2 && values.size() % 2 == 0) - m_entries[tag].swap(values); + // Skip ' : '. + valuePos += 3; + + auto rawReplacements = line.substr(valuePos); + strings::Trim(rawReplacements); + CHECK(!rawReplacements.empty(), ("Empty replacement in", line)); + + auto const replacements = strings::Tokenize(rawReplacements, ","); + for (auto const & replacement : replacements) + { + auto kv = strings::Tokenize(replacement, "="); + CHECK_EQUAL(kv.size(), 2, + ("Cannot parse replacement tag:", replacement, "in line", lineNumber)); + strings::Trim(kv[0]); + strings::Trim(kv[1]); + m_replacements[{key, value}].emplace_back(kv[0], kv[1]); + } } } - TagReplacer(TagReplacer const & other) : m_entries(other.m_entries) {} + TagReplacer(TagReplacer const & other) : m_replacements(other.m_replacements) {} TagReplacer & operator=(TagReplacer const & other) { if (this != &other) - m_entries = other.m_entries; + m_replacements = other.m_replacements; return *this; } void Process(OsmElement & element) const { - for (auto & tag : element.m_tags) + std::vector add; + base::EraseIf(element.m_tags, [&](auto const & tag) { - auto const it = m_entries.find(tag); - if (it != m_entries.end()) + auto const it = m_replacements.find(tag); + if (it != m_replacements.end()) { - auto const & v = it->second; - tag.m_key = v[0]; - tag.m_value = v[1]; - for (size_t i = 2; i < v.size(); i += 2) - element.AddTag(v[i], v[i + 1]); + for (auto const & replacement : it->second) + add.push_back(replacement); + return true; } - } + return false; + }); + + for (auto const & tag : add) + element.AddTag(tag); } + Replacements const & GetReplacementsForTesting() const { return m_replacements; } + private: - std::map> m_entries; + Replacements m_replacements; }; class OsmTagMixer