diff --git a/kml/kml_tests/serdes_tests.cpp b/kml/kml_tests/serdes_tests.cpp index b265cc98ed..2047b242cb 100644 --- a/kml/kml_tests/serdes_tests.cpp +++ b/kml/kml_tests/serdes_tests.cpp @@ -124,40 +124,40 @@ char const * kTextKml = std::vector const kBinKml = { 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x28, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x00, + 0x00, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x24, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x00, 0x01, 0x01, 0x00, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE2, - 0xAB, 0xEB, 0x96, 0xE2, 0xFE, 0x83, 0xD4, 0x11, 0x00, 0x02, 0x01, 0x00, 0x03, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xF3, 0xC2, 0xFB, 0xF9, 0x01, 0x9E, 0xD0, 0xC7, 0x95, 0x9B, 0x9D, 0xBD, 0xAB, 0x12, 0x00, - 0x02, 0x01, 0x00, 0x04, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xBC, 0xED, 0xA7, 0x03, 0xEA, 0xA4, 0xD0, - 0x9C, 0xD9, 0xA7, 0xBE, 0x9A, 0x19, 0x00, 0x02, 0x01, 0x00, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xE4, 0xAF, 0xA7, 0xC1, 0xEB, 0xA6, 0xBE, 0x9A, 0x19, 0x00, 0x02, 0x01, 0x00, 0x07, 0x01, 0x00, - 0x08, 0x00, 0x54, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0E, 0x08, 0x1B, 0x1A, - 0x41, 0x0C, 0x11, 0x25, 0x3F, 0x00, 0x02, 0x00, 0x01, 0x08, 0x03, 0x02, 0x06, 0x01, 0x03, 0x06, - 0x40, 0x05, 0x07, 0x8D, 0x01, 0x06, 0x05, 0x02, 0x07, 0x07, 0x71, 0x09, 0x05, 0x05, 0x0A, 0x07, - 0x12, 0x0B, 0x06, 0x5E, 0x0D, 0x06, 0xBA, 0x01, 0x0E, 0x05, 0x04, 0x0F, 0x05, 0x75, 0x11, 0x05, - 0x0D, 0x12, 0x06, 0x09, 0x13, 0x07, 0x4E, 0x15, 0x06, 0x91, 0x01, 0x16, 0x06, 0x0B, 0x17, 0x06, - 0x73, 0x19, 0x05, 0x06, 0x1A, 0x08, 0x1C, 0x1B, 0x07, 0x63, 0x1D, 0x06, 0xC0, 0x01, 0x1E, 0x05, - 0x07, 0x1F, 0x06, 0x77, 0x21, 0x06, 0x0A, 0x22, 0x07, 0x08, 0x23, 0x07, 0x49, 0x25, 0x07, 0x90, - 0x01, 0x27, 0x06, 0x72, 0x2A, 0x08, 0x14, 0x2B, 0x06, 0x62, 0x2D, 0x06, 0xBD, 0x01, 0x32, 0x06, - 0x0C, 0x33, 0x06, 0x57, 0x35, 0x07, 0xA1, 0x01, 0x36, 0x06, 0x0F, 0x37, 0x07, 0x76, 0x3A, 0x08, - 0x31, 0x3B, 0x06, 0x68, 0x3D, 0x06, 0xD1, 0x01, 0x3F, 0x06, 0x79, 0x41, 0x07, 0xB8, 0x01, 0x45, - 0x07, 0x8F, 0x01, 0x47, 0x07, 0x74, 0x4A, 0x07, 0x13, 0x53, 0x07, 0x59, 0x5A, 0x07, 0x29, 0x5B, - 0x07, 0x6D, 0x62, 0x08, 0x10, 0x63, 0x07, 0x4B, 0x65, 0x07, 0x96, 0x01, 0x6A, 0x08, 0x18, 0x75, - 0x07, 0xB7, 0x01, 0x77, 0x07, 0x8A, 0x01, 0x7A, 0x08, 0x3F, 0x81, 0x01, 0x08, 0x0E, 0x9A, 0x01, - 0x08, 0x25, 0xAA, 0x01, 0x08, 0x17, 0xBA, 0x01, 0x08, 0x3E, 0xE2, 0x01, 0x08, 0x11, 0xEA, 0x01, - 0x08, 0x19, 0xFA, 0x01, 0x08, 0x41, 0xB0, 0x01, 0xD7, 0x64, 0xD6, 0x6E, 0x37, 0x46, 0x07, 0x3D, - 0xA1, 0xA8, 0x65, 0x2F, 0x34, 0x01, 0x83, 0xBE, 0x2E, 0x87, 0x3C, 0x64, 0x71, 0x6E, 0xA3, 0xE7, - 0xCC, 0xB7, 0x8E, 0x6D, 0xA7, 0xE2, 0xFD, 0x13, 0xBC, 0x9A, 0x51, 0x7D, 0xF2, 0xE9, 0x17, 0x8F, - 0x33, 0x91, 0x28, 0xDB, 0xA1, 0x22, 0x5F, 0xB3, 0x14, 0x65, 0xE9, 0x57, 0x92, 0xAC, 0x33, 0xC5, - 0xF8, 0x3E, 0x37, 0x35, 0x00, 0x88, 0x1C, 0x96, 0x62, 0x97, 0x17, 0x09, 0xA5, 0x3F, 0x42, 0x18, - 0x98, 0xD5, 0x45, 0x96, 0x1D, 0x0A, 0x46, 0x2C, 0xB9, 0x94, 0xFE, 0x35, 0xF7, 0x00, 0x00, 0xF4, - 0x00, 0x00, 0x00, 0x00, 0x1A, 0xD0, 0xA2, 0xD9, 0x95, 0x6B, 0xDC, 0x69, 0xEA, 0x2D, 0x52, 0xB0, - 0x43, 0xA8, 0x7C, 0xA9, 0x32, 0x0E, 0x01, 0x00, 0x09, + 0xAB, 0xEB, 0x96, 0xE2, 0xFE, 0x83, 0xD4, 0x11, 0x00, 0x02, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, + 0xC2, 0xFB, 0xF9, 0x01, 0x9E, 0xD0, 0xC7, 0x95, 0x9B, 0x9D, 0xBD, 0xAB, 0x12, 0x00, 0x02, 0x01, + 0x00, 0x04, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xBC, 0xED, 0xA7, 0x03, 0xEA, 0xA4, 0xD0, 0x9C, 0xD9, + 0xA7, 0xBE, 0x9A, 0x19, 0x00, 0x02, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE4, 0xAF, 0xA7, 0xC1, + 0xEB, 0xA6, 0xBE, 0x9A, 0x19, 0x00, 0x02, 0x01, 0x00, 0x07, 0x01, 0x00, 0x08, 0x00, 0x54, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0E, 0x08, 0x1B, 0x1A, 0x41, 0x0C, 0x11, 0x25, + 0x3F, 0x00, 0x02, 0x00, 0x01, 0x08, 0x03, 0x02, 0x06, 0x01, 0x03, 0x06, 0x40, 0x05, 0x07, 0x8D, + 0x01, 0x06, 0x05, 0x02, 0x07, 0x07, 0x71, 0x09, 0x05, 0x05, 0x0A, 0x07, 0x12, 0x0B, 0x06, 0x5E, + 0x0D, 0x06, 0xBA, 0x01, 0x0E, 0x05, 0x04, 0x0F, 0x05, 0x75, 0x11, 0x05, 0x0D, 0x12, 0x06, 0x09, + 0x13, 0x07, 0x4E, 0x15, 0x06, 0x91, 0x01, 0x16, 0x06, 0x0B, 0x17, 0x06, 0x73, 0x19, 0x05, 0x06, + 0x1A, 0x08, 0x1C, 0x1B, 0x07, 0x63, 0x1D, 0x06, 0xC0, 0x01, 0x1E, 0x05, 0x07, 0x1F, 0x06, 0x77, + 0x21, 0x06, 0x0A, 0x22, 0x07, 0x08, 0x23, 0x07, 0x49, 0x25, 0x07, 0x90, 0x01, 0x27, 0x06, 0x72, + 0x2A, 0x08, 0x14, 0x2B, 0x06, 0x62, 0x2D, 0x06, 0xBD, 0x01, 0x32, 0x06, 0x0C, 0x33, 0x06, 0x57, + 0x35, 0x07, 0xA1, 0x01, 0x36, 0x06, 0x0F, 0x37, 0x07, 0x76, 0x3A, 0x08, 0x31, 0x3B, 0x06, 0x68, + 0x3D, 0x06, 0xD1, 0x01, 0x3F, 0x06, 0x79, 0x41, 0x07, 0xB8, 0x01, 0x45, 0x07, 0x8F, 0x01, 0x47, + 0x07, 0x74, 0x4A, 0x07, 0x13, 0x53, 0x07, 0x59, 0x5A, 0x07, 0x29, 0x5B, 0x07, 0x6D, 0x62, 0x08, + 0x10, 0x63, 0x07, 0x4B, 0x65, 0x07, 0x96, 0x01, 0x6A, 0x08, 0x18, 0x75, 0x07, 0xB7, 0x01, 0x77, + 0x07, 0x8A, 0x01, 0x7A, 0x08, 0x3F, 0x81, 0x01, 0x08, 0x0E, 0x9A, 0x01, 0x08, 0x25, 0xAA, 0x01, + 0x08, 0x17, 0xBA, 0x01, 0x08, 0x3E, 0xE2, 0x01, 0x08, 0x11, 0xEA, 0x01, 0x08, 0x19, 0xFA, 0x01, + 0x08, 0x41, 0xB0, 0x01, 0xD7, 0x64, 0xD6, 0x6E, 0x37, 0x46, 0x07, 0x3D, 0xA1, 0xA8, 0x65, 0x2F, + 0x34, 0x01, 0x83, 0xBE, 0x2E, 0x87, 0x3C, 0x64, 0x71, 0x6E, 0xA3, 0xE7, 0xCC, 0xB7, 0x8E, 0x6D, + 0xA7, 0xE2, 0xFD, 0x13, 0xBC, 0x9A, 0x51, 0x7D, 0xF2, 0xE9, 0x17, 0x8F, 0x33, 0x91, 0x28, 0xDB, + 0xA1, 0x22, 0x5F, 0xB3, 0x14, 0x65, 0xE9, 0x57, 0x92, 0xAC, 0x33, 0xC5, 0xF8, 0x3E, 0x37, 0x35, + 0x00, 0x88, 0x1C, 0x96, 0x62, 0x97, 0x17, 0x09, 0xA5, 0x3F, 0x42, 0x18, 0x98, 0xD5, 0x45, 0x96, + 0x1D, 0x0A, 0x46, 0x2C, 0xB9, 0x94, 0xFE, 0x35, 0xF7, 0x00, 0x00, 0xF4, 0x00, 0x00, 0x00, 0x00, + 0x1A, 0xD0, 0xA2, 0xD9, 0x95, 0x6B, 0xDC, 0x69, 0xEA, 0x2D, 0x52, 0xB0, 0x43, 0xA8, 0x7C, 0xA9, + 0x32, 0x0E, 0x01, 0x00, 0x09, }; // This function can be used to generate textual representation of vector like you see above. @@ -174,12 +174,15 @@ std::string FormatBytesFromBuffer(std::vector const & buffer) } auto const kDefaultLang = StringUtf8Multilang::kDefaultCode; +auto const kRuLang = static_cast(8); kml::CategoryData GenerateCategoryData() { kml::CategoryData categoryData; categoryData.m_name[kDefaultLang] = "Test category"; + categoryData.m_name[kRuLang] = "Тестовая категория"; categoryData.m_description[kDefaultLang] = "Test description"; + categoryData.m_description[kRuLang] = "Тестовое описание"; categoryData.m_visible = true; categoryData.m_authorName = "Maps.Me"; categoryData.m_authorId = "12345"; @@ -187,12 +190,14 @@ kml::CategoryData GenerateCategoryData() categoryData.m_accessRules = kml::AccessRules::Public; categoryData.m_tags = {"mountains", "ski", "snowboard"}; categoryData.m_toponyms = {"Georgia", "Gudauri"}; - categoryData.m_languageCodes = {0, 2, 5}; + categoryData.m_languageCodes = {1, 2, 8}; kml::BookmarkData bookmarkData; bookmarkData.m_id = 10; bookmarkData.m_name[kDefaultLang] = "Test bookmark"; + bookmarkData.m_name[kRuLang] = "Тестовая метка"; bookmarkData.m_description[kDefaultLang] = "Test bookmark description"; + bookmarkData.m_description[kRuLang] = "Тестовое описание метки"; bookmarkData.m_types = {8, 13, 34, 565}; bookmarkData.m_color = {kml::PredefinedColor::Blue, 0}; bookmarkData.m_icon = kml::BookmarkIcon::None; @@ -206,7 +211,9 @@ kml::CategoryData GenerateCategoryData() trackData.m_id = 121; trackData.m_localId = 0; trackData.m_name[kDefaultLang] = "Test track"; + trackData.m_name[kRuLang] = "Тестовый трек"; trackData.m_description[kDefaultLang] = "Test track description"; + trackData.m_description[kRuLang] = "Тестовое описание трека"; trackData.m_layers = {{6.0, {kml::PredefinedColor::None, 0xff0000ff}}, {7.0, {kml::PredefinedColor::None, 0x00ff00ff}}}; trackData.m_timestamp = std::chrono::system_clock::from_time_t(900); @@ -280,14 +287,54 @@ char const * kGeneratedKml = " Test category\n" " Test description\n" " 1\n" + " \n" + " \n" + " Тестовая категория\n" + " \n" + " \n" + " Тестовое описание\n" + " \n" + " Maps.Me\n" + " 1970-01-01T00:16:40Z\n" + " Public\n" + " \n" + " mountains\n" + " ski\n" + " snowboard\n" + " \n" + " \n" + " Georgia\n" + " Gudauri\n" + " \n" + " \n" + " en\n" + " ja\n" + " ru\n" + " \n" + " \n" " \n" " Test bookmark\n" " Test bookmark description\n" " 1970-01-01T00:13:20Z\n" " #placemark-blue\n" " 45.9242,49.326859\n" - " \n" + " \n" + " \n" + " Тестовая метка\n" + " \n" + " \n" + " Тестовое описание метки\n" + " \n" + " \n" + " 8\n" + " 13\n" + " 34\n" + " 565\n" + " \n" " 15\n" + " \n" + " 0\n" + " \n" " \n" " \n" " \n" @@ -297,7 +344,23 @@ char const * kGeneratedKml = " FF0000FF\n" " 6\n" " \n" + " 1970-01-01T00:15:00Z\n" " 45.9242,49.326859 45.2244,48.941288 45.1964,49.401948\n" + " \n" + " \n" + " Тестовый трек\n" + " \n" + " \n" + " Тестовое описание трека\n" + " \n" + " 0\n" + " \n" + " \n" + " FF00FF00\n" + " 7\n" + " \n" + " \n" + " \n" " \n" "\n" ""; @@ -320,6 +383,15 @@ UNIT_TEST(Kml_Deserialization_Text_Bin_Memory) TEST(false, ("Exception raised", exc.what())); } +// TODO: uncomment to output bytes to the log. +// std::vector buffer; +// { +// kml::binary::SerializerKml ser(dataFromText); +// MemWriter sink(buffer); +// ser.Serialize(sink); +// } +// LOG(LINFO, (FormatBytesFromBuffer(buffer))); + kml::CategoryData dataFromBin; try { diff --git a/kml/serdes.cpp b/kml/serdes.cpp index 03e00fca9e..498568366e 100644 --- a/kml/serdes.cpp +++ b/kml/serdes.cpp @@ -24,6 +24,7 @@ std::string const kDocument = "Document"; std::string const kStyleMap = "StyleMap"; std::string const kStyleUrl = "styleUrl"; std::string const kPair = "Pair"; +std::string const kExtendedData = "ExtendedData"; std::string const kKmlHeader = "\n" @@ -34,8 +35,31 @@ std::string const kKmlFooter = "\n" "\n"; +std::string const kExtendedDataHeader = + "\n"; + +std::string const kExtendedDataFooter = + "\n"; + auto const kDefaultLang = StringUtf8Multilang::kDefaultCode; +auto const kDefaultTrackWidth = 5.0; + +std::string Indent(size_t count) +{ + static std::string const kIndent = " "; + std::ostringstream indent; + for (size_t i = 0; i < count; ++i) + indent << kIndent; + return indent.str(); +} + +static std::string const kIndent2 = Indent(2); +static std::string const kIndent4 = Indent(4); +static std::string const kIndent6 = Indent(6); +static std::string const kIndent8 = Indent(8); +static std::string const kIndent10 = Indent(10); + std::string PointToString(m2::PointD const & org) { double const lon = MercatorBounds::XToLon(org.x); @@ -118,13 +142,13 @@ void SaveStyle(KmlWriter::WriterWrapper & writer, std::string const & style) if (style.empty()) return; - writer << " \n"; + writer << kIndent2 << "\n"; } void SaveColorToABGR(KmlWriter::WriterWrapper & writer, uint32_t rgba) @@ -135,75 +159,211 @@ void SaveColorToABGR(KmlWriter::WriterWrapper & writer, uint32_t rgba) << NumToHex(static_cast((rgba >> 24) & 0xFF)); } +std::string TimestampToString(Timestamp const & timestamp) +{ + auto const ts = std::chrono::system_clock::to_time_t(timestamp); + std::string const strTimeStamp = my::TimestampToString(ts); + if (strTimeStamp.size() != 20) + MYTHROW(KmlWriter::WriteKmlException, ("We always generate fixed length UTC-format timestamp.")); + return strTimeStamp; +} + +void SaveLocalizableString(KmlWriter::WriterWrapper & writer, LocalizableString const & str, + std::string const & tagName, std::string const & offsetStr) +{ + if (str.size() < 2) + return; + + writer << offsetStr << "\n"; + for (auto const & s : str) + { + if (s.first == kDefaultLang) + continue; + + writer << offsetStr << kIndent2 << ""; + SaveStringWithCDATA(writer, s.second); + writer << "\n"; + } + writer << offsetStr << "\n"; +} + +void SaveStringsArray(KmlWriter::WriterWrapper & writer, + std::vector const & stringsArray, + std::string const & tagName, std::string const & offsetStr) +{ + if (stringsArray.empty()) + return; + + writer << offsetStr << "\n"; + for (auto const & s : stringsArray) + { + writer << offsetStr << kIndent2 << ""; + SaveStringWithCDATA(writer, s); + writer << "\n"; + } + writer << offsetStr << "\n"; +} + +void SaveCategoryExtendedData(KmlWriter::WriterWrapper & writer, CategoryData const & categoryData) +{ + writer << kIndent2 << kExtendedDataHeader; + SaveLocalizableString(writer, categoryData.m_name, "name", kIndent4); + SaveLocalizableString(writer, categoryData.m_description, "description", kIndent4); + if (!categoryData.m_authorId.empty() || !categoryData.m_authorName.empty()) + { + writer << kIndent4 << ""; + SaveStringWithCDATA(writer, categoryData.m_authorName); + writer << "\n"; + } + if (categoryData.m_lastModified != Timestamp()) + { + writer << kIndent4 << "" << TimestampToString(categoryData.m_lastModified) + << "\n"; + } + writer << kIndent4 << "" << DebugPrint(categoryData.m_accessRules) + << "\n"; + SaveStringsArray(writer, categoryData.m_tags, "tags", kIndent4); + SaveStringsArray(writer, categoryData.m_toponyms, "toponyms", kIndent4); + std::vector languageCodes; + languageCodes.reserve(categoryData.m_languageCodes.size()); + for (auto const & lang : categoryData.m_languageCodes) + { + std::string str = StringUtf8Multilang::GetLangByCode(lang); + if (!str.empty()) + languageCodes.push_back(std::move(str)); + } + SaveStringsArray(writer, languageCodes, "languageCodes", kIndent4); + writer << kIndent2 << kExtendedDataFooter; +} + void SaveCategoryData(KmlWriter::WriterWrapper & writer, CategoryData const & categoryData) { for (uint8_t i = 0; i < my::Key(PredefinedColor::Count); ++i) SaveStyle(writer, GetStyleForPredefinedColor(static_cast(i))); // Use CDATA if we have special symbols in the name. - writer << " "; + writer << kIndent2 << ""; SaveStringWithCDATA(writer, GetLocalizableString(categoryData.m_name, kDefaultLang)); writer << "\n"; if (!categoryData.m_description.empty()) { - writer << " "; + writer << kIndent2 << ""; SaveStringWithCDATA(writer, GetLocalizableString(categoryData.m_description, kDefaultLang)); writer << "\n"; } - writer << " " << (categoryData.m_visible ? "1" : "0") <<"\n"; + writer << kIndent2 << "" << (categoryData.m_visible ? "1" : "0") <<"\n"; + + SaveCategoryExtendedData(writer, categoryData); } -void SaveBoormarkData(KmlWriter::WriterWrapper & writer, BookmarkData const & bookmarkData) +void SaveBookmarkExtendedData(KmlWriter::WriterWrapper & writer, BookmarkData const & bookmarkData) { - writer << " \n"; - writer << " "; + if (bookmarkData.m_name.size() < 2 && bookmarkData.m_description.size() < 2 && + bookmarkData.m_viewportScale == 0 && bookmarkData.m_icon == BookmarkIcon::None && + bookmarkData.m_types.empty() && bookmarkData.m_boundTracks.empty()) + { + return; + } + + writer << kIndent4 << kExtendedDataHeader; + SaveLocalizableString(writer, bookmarkData.m_name, "name", kIndent6); + SaveLocalizableString(writer, bookmarkData.m_description, "description", kIndent6); + + std::vector types; + types.reserve(bookmarkData.m_types.size()); + for (auto const & t : bookmarkData.m_types) + types.push_back(strings::to_string(t)); + SaveStringsArray(writer, types, "types", kIndent6); + + if (bookmarkData.m_viewportScale != 0) + { + auto const scale = strings::to_string(static_cast(bookmarkData.m_viewportScale)); + writer << kIndent6 << "" << scale << "\n"; + } + if (bookmarkData.m_icon != BookmarkIcon::None) + writer << kIndent6 << "" << DebugPrint(bookmarkData.m_icon) << "\n"; + + std::vector boundTracks; + boundTracks.reserve(bookmarkData.m_boundTracks.size()); + for (auto const & t : bookmarkData.m_boundTracks) + boundTracks.push_back(strings::to_string(static_cast(t))); + SaveStringsArray(writer, boundTracks, "boundTracks", kIndent6); + + writer << kIndent4 << kExtendedDataFooter; +} + +void SaveBookmarkData(KmlWriter::WriterWrapper & writer, BookmarkData const & bookmarkData) +{ + writer << kIndent2 << "\n"; + writer << kIndent4 << ""; SaveStringWithCDATA(writer, GetLocalizableString(bookmarkData.m_name, kDefaultLang)); writer << "\n"; if (!bookmarkData.m_description.empty()) { - writer << " "; + writer << kIndent4 << ""; SaveStringWithCDATA(writer, GetLocalizableString(bookmarkData.m_description, kDefaultLang)); writer << "\n"; } if (bookmarkData.m_timestamp != Timestamp()) { - auto const ts = std::chrono::system_clock::to_time_t(bookmarkData.m_timestamp); - std::string const strTimeStamp = my::TimestampToString(ts); - if (strTimeStamp.size() != 20) - MYTHROW(KmlWriter::WriteKmlException, ("We always generate fixed length UTC-format timestamp.")); - writer << " " << strTimeStamp << "\n"; + writer << kIndent4 << "" << TimestampToString(bookmarkData.m_timestamp) + << "\n"; } auto const style = GetStyleForPredefinedColor(bookmarkData.m_color.m_predefinedColor); - writer << " #" << style << "\n" - << " " << PointToString(bookmarkData.m_point) + writer << kIndent4 << "#" << style << "\n" + << kIndent4 << "" << PointToString(bookmarkData.m_point) << "\n"; - if (bookmarkData.m_viewportScale != 0) - { - auto const scale = strings::to_string(static_cast(bookmarkData.m_viewportScale)); - /// @todo Factor out to separate function to use for other custom params. - writer << " \n" - << " " << scale << "\n" - << " \n"; - } + SaveBookmarkExtendedData(writer, bookmarkData); - writer << " \n"; + writer << kIndent2 << "\n"; +} + +void SaveTrackLayer(KmlWriter::WriterWrapper & writer, TrackLayer const & layer, + std::string const & offsetStr) +{ + writer << offsetStr << ""; + SaveColorToABGR(writer, layer.m_color.m_rgba); + writer << "\n"; + writer << offsetStr << "" << strings::to_string(layer.m_lineWidth) << "\n"; +} + +void SaveTrackExtendedData(KmlWriter::WriterWrapper & writer, TrackData const & trackData) +{ + writer << kIndent4 << kExtendedDataHeader; + SaveLocalizableString(writer, trackData.m_name, "name", kIndent6); + SaveLocalizableString(writer, trackData.m_description, "description", kIndent6); + + auto const localId = strings::to_string(static_cast(trackData.m_localId)); + writer << kIndent6 << "" << localId << "\n"; + + writer << kIndent6 << "\n"; + for (size_t i = 1 /* since second layer */; i < trackData.m_layers.size(); ++i) + { + writer << kIndent8 << "\n"; + SaveTrackLayer(writer, trackData.m_layers[i], kIndent10); + writer << kIndent8 << "\n"; + } + writer << kIndent6 << "\n"; + + writer << kIndent4 << kExtendedDataFooter; } void SaveTrackData(KmlWriter::WriterWrapper & writer, TrackData const & trackData) { - writer << " \n"; - writer << " "; + writer << kIndent2 << "\n"; + writer << kIndent4 << ""; SaveStringWithCDATA(writer, GetLocalizableString(trackData.m_name, kDefaultLang)); writer << "\n"; if (!trackData.m_description.empty()) { - writer << " "; + writer << kIndent4 << ""; SaveStringWithCDATA(writer, GetLocalizableString(trackData.m_description, kDefaultLang)); writer << "\n"; } @@ -212,24 +372,28 @@ void SaveTrackData(KmlWriter::WriterWrapper & writer, TrackData const & trackDat MYTHROW(KmlWriter::WriteKmlException, ("Layers list is empty.")); auto const & layer = trackData.m_layers.front(); - writer << " \n"; + writer << kIndent4 << "\n"; - writer << " "; + if (trackData.m_timestamp != Timestamp()) + { + writer << kIndent4 << "" << TimestampToString(trackData.m_timestamp) + << "\n"; + } + writer << kIndent4 << ""; for (size_t i = 0; i < trackData.m_points.size(); ++i) { writer << PointToString(trackData.m_points[i]); if (i + 1 != trackData.m_points.size()) writer << " "; } - writer << "\n"; - writer << " \n"; + + SaveTrackExtendedData(writer, trackData); + + writer << kIndent2 << "\n"; } } // namespace @@ -248,7 +412,7 @@ void KmlWriter::Write(CategoryData const & categoryData) // Save bookmarks. for (auto const & bookmarkData : categoryData.m_bookmarksData) - SaveBoormarkData(m_writer, bookmarkData); + SaveBookmarkData(m_writer, bookmarkData); // Saving tracks. for (auto const & trackData : categoryData.m_tracksData) @@ -257,9 +421,14 @@ void KmlWriter::Write(CategoryData const & categoryData) m_writer << kKmlFooter; } -KmlParser::KmlParser(CategoryData & data) : m_data(data) { Reset(); } +KmlParser::KmlParser(CategoryData & data) + : m_data(data) + , m_attrCode(StringUtf8Multilang::kUnsupportedLanguageCode) +{ + ResetPoint(); +} -void KmlParser::Reset() +void KmlParser::ResetPoint() { m_name.clear(); m_description.clear(); @@ -273,6 +442,13 @@ void KmlParser::Reset() m_mapStyleId.clear(); m_styleUrlKey.clear(); + m_types.clear(); + m_boundTracks.clear(); + m_localId = 0; + m_trackLayers.clear(); + m_trackWidth = kDefaultTrackWidth; + m_icon = BookmarkIcon::None; + m_points.clear(); m_geometryType = GEOMETRY_TYPE_UNKNOWN; } @@ -281,20 +457,19 @@ bool KmlParser::ParsePoint(std::string const & s, char const * delim, m2::PointD { // Order in string is: lon, lat, z. strings::SimpleTokenizer iter(s, delim); - if (iter) + if (!iter) + return false; + + double lon; + if (strings::to_double(*iter, lon) && MercatorBounds::ValidLon(lon) && ++iter) { - double lon; - if (strings::to_double(*iter, lon) && MercatorBounds::ValidLon(lon) && ++iter) + double lat; + if (strings::to_double(*iter, lat) && MercatorBounds::ValidLat(lat)) { - double lat; - if (strings::to_double(*iter, lat) && MercatorBounds::ValidLat(lat)) - { - pt = MercatorBounds::FromLatLon(lat, lon); - return true; - } + pt = MercatorBounds::FromLatLon(lat, lon); + return true; } } - return false; } @@ -318,7 +493,7 @@ void KmlParser::ParseLineCoordinates(std::string const & s, char const * blockSe m2::PointD pt; if (ParsePoint(*tupleIter, coordSeparator, pt)) { - if (m_points.size() == 0 || !(pt - m_points.back()).IsAlmostZero()) + if (m_points.empty() || !(pt - m_points.back()).IsAlmostZero()) m_points.push_back(std::move(pt)); } ++tupleIter; @@ -333,7 +508,7 @@ bool KmlParser::MakeValid() { // Set default name. if (m_name.empty()) - m_name = PointToString(m_org); + m_name[kDefaultLang] = PointToString(m_org); // Set default pin. if (m_predefinedColor == PredefinedColor::None) @@ -391,6 +566,11 @@ void KmlParser::AddAttr(std::string const & attr, std::string const & value) m_styleId = value; else if (IsValidAttribute(kStyleMap, value, attrInLowerCase)) m_mapStyleId = value; + + if (attrInLowerCase == "code") + m_attrCode = StringUtf8Multilang::GetLangIndex(value); + else if (attrInLowerCase == "id") + m_attrId = value; } bool KmlParser::IsValidAttribute(std::string const & type, std::string const & value, @@ -416,30 +596,31 @@ void KmlParser::Pop(std::string const & tag) if (GEOMETRY_TYPE_POINT == m_geometryType) { BookmarkData data; - data.m_name[kDefaultLang] = m_name; - data.m_description[kDefaultLang] = m_description; + data.m_name = m_name; + data.m_description = m_description; data.m_color.m_predefinedColor = m_predefinedColor; data.m_color.m_rgba = m_color; + data.m_icon = m_icon; data.m_viewportScale = m_viewportScale; data.m_timestamp = m_timestamp; data.m_point = m_org; - m_data.m_bookmarksData.emplace_back(std::move(data)); + data.m_types = std::move(m_types); + data.m_boundTracks = std::move(m_boundTracks); + m_data.m_bookmarksData.push_back(std::move(data)); } else if (GEOMETRY_TYPE_LINE == m_geometryType) { TrackData data; - data.m_name[kDefaultLang] = m_name; - data.m_description[kDefaultLang] = m_description; - TrackLayer layer; - layer.m_color.m_predefinedColor = PredefinedColor::None; - layer.m_color.m_rgba = m_color; - data.m_layers.emplace_back(std::move(layer)); + data.m_localId = m_localId; + data.m_name = m_name; + data.m_description = m_description; + data.m_layers = std::move(m_trackLayers); data.m_timestamp = m_timestamp; data.m_points = m_points; - m_data.m_tracksData.emplace_back(std::move(data)); + m_data.m_tracksData.push_back(std::move(data)); } } - Reset(); + ResetPoint(); } else if (tag == kStyle) { @@ -452,6 +633,17 @@ void KmlParser::Pop(std::string const & tag) } } } + else if (tag == "mwm:additionalLineStyle" || tag == "LineStyle") + { + TrackLayer layer; + layer.m_lineWidth = m_trackWidth; + layer.m_color.m_predefinedColor = PredefinedColor::None; + layer.m_color.m_rgba = m_color; + m_trackLayers.push_back(std::move(layer)); + + m_trackWidth = kDefaultTrackWidth; + m_color = 0; + } m_tags.pop_back(); } @@ -466,6 +658,7 @@ void KmlParser::CharData(std::string value) std::string const & currTag = m_tags[count - 1]; std::string const & prevTag = m_tags[count - 2]; std::string const ppTag = count > 2 ? m_tags[count - 3] : std::string(); + std::string const pppTag = count > 3 ? m_tags[count - 4] : std::string(); if (prevTag == kDocument) { @@ -476,11 +669,56 @@ void KmlParser::CharData(std::string value) else if (currTag == "visibility") m_data.m_visible = value != "0"; } + else if (prevTag == kExtendedData && ppTag == kDocument) + { + if (currTag == "mwm:author") + { + m_data.m_authorName = value; + m_data.m_authorId = m_attrId; + m_attrId.clear(); + } + else if (currTag == "mwm:lastModified") + { + auto const ts = my::StringToTimestamp(value); + if (ts != my::INVALID_TIME_STAMP) + m_data.m_lastModified = std::chrono::system_clock::from_time_t(ts); + } + else if (currTag == "mwm:accessRules") + { + if (value == "Public") + m_data.m_accessRules = AccessRules::Public; + } + } + else if (pppTag == kDocument && ppTag == kExtendedData && currTag == "mwm:lang") + { + if (prevTag == "mwm:name" && m_attrCode > 0) + m_data.m_name[m_attrCode] = value; + else if (prevTag == "mwm:description" && m_attrCode > 0) + m_data.m_description[m_attrCode] = value; + m_attrCode = StringUtf8Multilang::kUnsupportedLanguageCode; + } + else if (pppTag == kDocument && ppTag == kExtendedData && currTag == "mwm:value") + { + if (prevTag == "mwm:tags") + { + m_data.m_tags.push_back(value); + } + else if (prevTag == "mwm:toponyms") + { + m_data.m_toponyms.push_back(value); + } + else if (prevTag == "mwm:languageCodes") + { + auto const lang = StringUtf8Multilang::GetLangIndex(value); + if (lang != StringUtf8Multilang::kUnsupportedLanguageCode) + m_data.m_languageCodes.push_back(lang); + } + } else if (prevTag == kPlacemark) { if (currTag == "name") { - m_name = value; + m_name[kDefaultLang] = value; } else if (currTag == "styleUrl") { @@ -498,12 +736,21 @@ void KmlParser::CharData(std::string value) } else if (currTag == "description") { - m_description = value; + m_description[kDefaultLang] = value; } } - else if (prevTag == "LineStyle" && currTag == "color") + else if (prevTag == "LineStyle" || prevTag == "mwm:additionalLineStyle") { - ParseColor(value); + if (currTag == "color") + { + ParseColor(value); + } + else if (currTag == "width") + { + double val; + if (strings::to_double(value, val)) + m_trackWidth = val; + } } else if (ppTag == kStyleMap && prevTag == kPair && currTag == kStyleUrl && m_styleUrlKey == "normal") @@ -532,7 +779,7 @@ void KmlParser::CharData(std::string value) if (currTag == "gx:coord") ParseLineCoordinates(value, "\n\r\t", " "); } - else if (prevTag == "ExtendedData") + else if (prevTag == kExtendedData) { if (currTag == "mwm:scale") { @@ -541,6 +788,18 @@ void KmlParser::CharData(std::string value) scale = 0.0; m_viewportScale = static_cast(scale); } + else if (currTag == "mwm:localId") + { + uint32_t localId; + if (!strings::to_uint(value, localId)) + localId = 0; + m_localId = static_cast(localId); + } + else if (currTag == "mwm:icon") + { + //TODO: add new icon types. + //m_icon = ; + } } else if (prevTag == "TimeStamp") { @@ -582,6 +841,25 @@ void KmlParser::CharData(std::string value) ParseLineCoordinates(value, "\n\r\t", " "); } } + else if (pppTag == kPlacemark && ppTag == kExtendedData) + { + if (currTag == "mwm:lang") + { + if (prevTag == "mwm:name" && m_attrCode > 0) + m_name[m_attrCode] = value; + else if (prevTag == "mwm:description" && m_attrCode > 0) + m_description[m_attrCode] = value; + m_attrCode = StringUtf8Multilang::kUnsupportedLanguageCode; + } + else if (currTag == "mwm:value") + { + uint32_t i; + if (prevTag == "mwm:types" && strings::to_uint(value, i)) + m_types.push_back(i); + else if (prevTag == "mwm:boundTracks" && strings::to_uint(value, i)) + m_boundTracks.push_back(static_cast(i)); + } + } } } diff --git a/kml/serdes.hpp b/kml/serdes.hpp index 4a68117998..ccb209e839 100644 --- a/kml/serdes.hpp +++ b/kml/serdes.hpp @@ -78,7 +78,7 @@ private: GEOMETRY_TYPE_LINE }; - void Reset(); + void ResetPoint(); bool ParsePoint(std::string const & s, char const * delim, m2::PointD & pt); void SetOrigin(std::string const & s); void ParseLineCoordinates(std::string const & s, char const * blockSeparator, @@ -100,12 +100,21 @@ private: std::map m_styleUrl2Color; std::map m_mapStyle2Style; - std::string m_name; - std::string m_description; + int8_t m_attrCode; + std::string m_attrId; + + LocalizableString m_name; + LocalizableString m_description; PredefinedColor m_predefinedColor; Timestamp m_timestamp; m2::PointD m_org; uint8_t m_viewportScale; + std::vector m_types; + std::vector m_boundTracks; + LocalId m_localId; + BookmarkIcon m_icon; + std::vector m_trackLayers; + double m_trackWidth; }; class DeserializerKml