diff --git a/editor/editor_tests/xml_feature_test.cpp b/editor/editor_tests/xml_feature_test.cpp index 450ea99117..f8b5d86f01 100644 --- a/editor/editor_tests/xml_feature_test.cpp +++ b/editor/editor_tests/xml_feature_test.cpp @@ -135,61 +135,7 @@ UNIT_TEST(XMLFeature_IsArea) TEST(!XMLFeature(node).IsArea(), ()); } -// UNIT_TEST(XMLFeature_FromXml) -// { -// auto const srcString = R"( -// -// -// -// -// -// -// -// -// )"; - -// XMLFeature feature(srcString); - -// stringstream sstr; -// feature.Save(sstr); -// TEST_EQUAL(srcString, sstr.str(), ()); - -// TEST(feature.HasKey("opening_hours"), ()); -// TEST(feature.HasKey("lat"), ()); -// TEST(feature.HasKey("lon"), ()); -// TEST(!feature.HasKey("FooBarBaz"), ()); - -// TEST_EQUAL(feature.GetHouse(), "10", ()); -// TEST_EQUAL(feature.GetCenter(), MercatorBounds::FromLatLon(55.7978998, 37.4745280), ()); -// TEST_EQUAL(feature.GetName(), "Gorki Park", ()); -// TEST_EQUAL(feature.GetName("default"), "Gorki Park", ()); -// TEST_EQUAL(feature.GetName("en"), "Gorki Park", ()); -// TEST_EQUAL(feature.GetName("ru"), "Парк Горького", ()); -// TEST_EQUAL(feature.GetName("No such language"), "", ()); - -// TEST_EQUAL(feature.GetTagValue("opening_hours"), "Mo-Fr 08:15-17:30", ()); -// TEST_EQUAL(feature.GetTagValue("amenity"), "atm", ()); -// TEST_EQUAL(my::TimestampToString(feature.GetModificationTime()), "2015-11-27T21:13:32Z", ()); -// } - -UNIT_TEST(XMLFeature_ForEachName) -{ - auto const srcString = R"( +auto const kTestNode = R"( @@ -200,7 +146,35 @@ UNIT_TEST(XMLFeature_ForEachName) )"; - XMLFeature feature(srcString); +UNIT_TEST(XMLFeature_FromXml) +{ + XMLFeature feature(kTestNode); + + stringstream sstr; + feature.Save(sstr); + TEST_EQUAL(kTestNode, sstr.str(), ()); + + TEST(feature.HasKey("opening_hours"), ()); + TEST(feature.HasKey("lat"), ()); + TEST(feature.HasKey("lon"), ()); + TEST(!feature.HasKey("FooBarBaz"), ()); + + TEST_EQUAL(feature.GetHouse(), "10", ()); + TEST_EQUAL(feature.GetCenter(), ms::LatLon(55.7978998, 37.4745280), ()); + TEST_EQUAL(feature.GetName(), "Gorki Park", ()); + TEST_EQUAL(feature.GetName("default"), "Gorki Park", ()); + TEST_EQUAL(feature.GetName("en"), "Gorki Park", ()); + TEST_EQUAL(feature.GetName("ru"), "Парк Горького", ()); + TEST_EQUAL(feature.GetName("No such language"), "", ()); + + TEST_EQUAL(feature.GetTagValue("opening_hours"), "Mo-Fr 08:15-17:30", ()); + TEST_EQUAL(feature.GetTagValue("amenity"), "atm", ()); + TEST_EQUAL(my::TimestampToString(feature.GetModificationTime()), "2015-11-27T21:13:32Z", ()); +} + +UNIT_TEST(XMLFeature_ForEachName) +{ + XMLFeature feature(kTestNode); map names; feature.ForEachName([&names](string const & lang, string const & name) @@ -212,3 +186,29 @@ UNIT_TEST(XMLFeature_ForEachName) {"default", "Gorki Park"}, {"en", "Gorki Park"}, {"ru", "Парк Горького"}}), ()); } + +auto const kTestNodeWay = R"( + + + + + + + + + +)"; + + +UNIT_TEST(XMLFeature_FromOSM) +{ + TEST_ANY_THROW(XMLFeature::FromOSM(""), ()); + TEST_ANY_THROW(XMLFeature::FromOSM("This is not XML"), ()); + TEST_ANY_THROW(XMLFeature::FromOSM(""), ()); + TEST_NO_THROW(XMLFeature::FromOSM(""), ()); + TEST_ANY_THROW(XMLFeature::FromOSM(""), ()); + vector features; + TEST_NO_THROW(features = XMLFeature::FromOSM(kTestNodeWay), ()); + TEST_EQUAL(3, features.size(), ()); + TEST_EQUAL(features[2].GetTagValue("hi"), "test", ()); +} diff --git a/editor/xml_feature.cpp b/editor/xml_feature.cpp index 8fd7a283af..b3b46a210e 100644 --- a/editor/xml_feature.cpp +++ b/editor/xml_feature.cpp @@ -1,6 +1,5 @@ #include "editor/xml_feature.hpp" -#include "base/assert.hpp" #include "base/macros.hpp" #include "base/string_utils.hpp" #include "base/timer.hpp" @@ -31,37 +30,25 @@ ms::LatLon PointFromLatLon(pugi::xml_node const & node) { ms::LatLon ll; if (!strings::to_double(node.attribute("lat").value(), ll.lat)) - { - MYTHROW(editor::XMLFeatureNoLatLonError, - ("Can't parse lat attribute: " + string(node.attribute("lat").value()))); - } + MYTHROW(editor::NoLatLon, ("Can't parse lat attribute: " + string(node.attribute("lat").value()))); if (!strings::to_double(node.attribute("lon").value(), ll.lon)) - { - MYTHROW(editor::XMLFeatureNoLatLonError, - ("Can't parse lon attribute: " + string(node.attribute("lon").value()))); - } + MYTHROW(editor::NoLatLon, ("Can't parse lon attribute: " + string(node.attribute("lon").value()))); return ll; } -void ValidateNode(pugi::xml_node const & node) +void ValidateElement(pugi::xml_node const & nodeOrWay) { - if (!node) - MYTHROW(editor::XMLFeatureNoNodeError, ("Document has no node")); + if (!nodeOrWay) + MYTHROW(editor::InvalidXML, ("Document has no valid root element.")); - // Check if point can be parsed. Throws if it's can't. - UNUSED_VALUE(PointFromLatLon(node)); + string const type = nodeOrWay.name(); + if (type == kNodeType) + UNUSED_VALUE(PointFromLatLon(nodeOrWay)); + else if (type != kWayType) + MYTHROW(editor::InvalidXML, ("XMLFeature does not support root tag", type)); - if (!node.attribute(kTimestamp)) - MYTHROW(editor::XMLFeatureNoTimestampError, ("Node has no timestamp attribute")); -} - -void ValidateWay(pugi::xml_node const & way) -{ - if (!way) - MYTHROW(editor::XMLFeatureNoNodeError, ("Document has no node")); - - if (!way.attribute(kTimestamp)) - MYTHROW(editor::XMLFeatureNoTimestampError, ("Way has no timestamp attribute")); + if (!nodeOrWay.attribute(kTimestamp)) + MYTHROW(editor::NoTimestamp, ("Node has no timestamp attribute")); } } // namespace @@ -80,23 +67,20 @@ XMLFeature::XMLFeature(Type const type) XMLFeature::XMLFeature(string const & xml) { m_document.load(xml.data()); - auto const r = GetRootNode(); - r.name() == kNodeType ? ValidateNode(r) : ValidateWay(r); + ValidateElement(GetRootNode()); } XMLFeature::XMLFeature(pugi::xml_document const & xml) { m_document.reset(xml); - auto const r = GetRootNode(); - r.name() == kNodeType ? ValidateNode(r) : ValidateWay(r); + ValidateElement(GetRootNode()); } XMLFeature::XMLFeature(pugi::xml_node const & xml) { m_document.reset(); m_document.append_copy(xml); - auto const r = GetRootNode(); - r.name() == kNodeType ? ValidateNode(r) : ValidateWay(r); + ValidateElement(GetRootNode()); } bool XMLFeature::operator==(XMLFeature const & other) const @@ -104,11 +88,33 @@ bool XMLFeature::operator==(XMLFeature const & other) const return ToOSMString() == other.ToOSMString(); } +vector XMLFeature::FromOSM(string const & osmXml) +{ + pugi::xml_document doc; + if (doc.load_string(osmXml.c_str()).status != pugi::status_ok) + MYTHROW(editor::InvalidXML, ("Not valid XML:", osmXml)); + + vector features; + for (auto const n : doc.child("osm").children()) + { + string const name(n.name()); + // TODO(AlexZ): Add relation support. + if (name == kNodeType || name == kWayType) + features.push_back(XMLFeature(n)); // TODO(AlexZ): Use emplace_back when pugi supports it. + } + return features; +} + XMLFeature::Type XMLFeature::GetType() const { return strcmp(GetRootNode().name(), "node") == 0 ? Type::Node : Type::Way; } +string XMLFeature::GetTypeString() const +{ + return GetRootNode().name(); +} + bool XMLFeature::IsArea() const { if (strcmp(GetRootNode().name(), kWayType) != 0) @@ -154,12 +160,16 @@ ms::LatLon XMLFeature::GetCenter() const return PointFromLatLon(GetRootNode()); } +void XMLFeature::SetCenter(ms::LatLon const & ll) +{ + ASSERT_EQUAL(GetRootNode().name(), string(kNodeType), ()); + SetAttribute("lat", strings::to_string_dac(ll.lat, kLatLonTolerance)); + SetAttribute("lon", strings::to_string_dac(ll.lon, kLatLonTolerance)); +} + void XMLFeature::SetCenter(m2::PointD const & mercatorCenter) { - SetAttribute("lat", strings::to_string_dac(MercatorBounds::YToLat(mercatorCenter.y), - kLatLonTolerance)); - SetAttribute("lon", strings::to_string_dac(MercatorBounds::XToLon(mercatorCenter.x), - kLatLonTolerance)); + SetCenter(MercatorBounds::ToLatLon(mercatorCenter)); } string XMLFeature::GetName(string const & lang) const diff --git a/editor/xml_feature.hpp b/editor/xml_feature.hpp index f2d59784e6..0c368f3226 100644 --- a/editor/xml_feature.hpp +++ b/editor/xml_feature.hpp @@ -9,16 +9,17 @@ #include "std/ctime.hpp" #include "std/iostream.hpp" +#include "std/vector.hpp" #include "3party/pugixml/src/pugixml.hpp" namespace editor { DECLARE_EXCEPTION(XMLFeatureError, RootException); -DECLARE_EXCEPTION(XMLFeatureNoNodeError, XMLFeatureError); -DECLARE_EXCEPTION(XMLFeatureNoLatLonError, XMLFeatureError); -DECLARE_EXCEPTION(XMLFeatureNoTimestampError, XMLFeatureError); -DECLARE_EXCEPTION(XMLFeatureNoHeaderError, XMLFeatureError); +DECLARE_EXCEPTION(InvalidXML, XMLFeatureError); +DECLARE_EXCEPTION(NoLatLon, XMLFeatureError); +DECLARE_EXCEPTION(NoTimestamp, XMLFeatureError); +DECLARE_EXCEPTION(NoHeader, XMLFeatureError); class XMLFeature { @@ -40,6 +41,8 @@ public: XMLFeature(pugi::xml_node const & xml); XMLFeature(XMLFeature const & feature) : XMLFeature(feature.m_document) {} bool operator==(XMLFeature const & other) const; + /// @returns nodes and ways from osmXml. Vector can be empty. + static vector FromOSM(string const & osmXml); void Save(ostream & ost) const; string ToOSMString() const; @@ -48,11 +51,13 @@ public: void ApplyPatch(XMLFeature const & featureWithChanges); Type GetType() const; + string GetTypeString() const; /// @returns true only if it is a way and it is closed (area). bool IsArea() const; ms::LatLon GetCenter() const; + void SetCenter(ms::LatLon const & ll); void SetCenter(m2::PointD const & mercatorCenter); string GetName(string const & lang) const;