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;