[editor] New XMLFeature methods.

This commit is contained in:
Alex Zolotarev 2016-01-31 15:00:25 +03:00 committed by Sergey Yershov
parent daa4280f67
commit 0120cb4a24
3 changed files with 110 additions and 95 deletions

View file

@ -135,61 +135,7 @@ UNIT_TEST(XMLFeature_IsArea)
TEST(!XMLFeature(node).IsArea(), ());
}
// UNIT_TEST(XMLFeature_FromXml)
// {
// auto const srcString = R"(<?xml version="1.0"?>
// <node
// lat="55.7978998"
// lon="37.474528"
// timestamp="2015-11-27T21:13:32Z">
// <tag
// k="name"
// v="Gorki Park" />
// <tag
// k="name:en"
// v="Gorki Park" />
// <tag
// k="name:ru"
// v="Парк Горького" />
// <tag
// k="addr:housenumber"
// v="10" />
// <tag
// k="opening_hours"
// v="Mo-Fr 08:15-17:30" />
// <tag
// k="amenity"
// v="atm" />
// </node>
// )";
// 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"(<?xml version="1.0"?>
auto const kTestNode = R"(<?xml version="1.0"?>
<node lat="55.7978998" lon="37.474528" timestamp="2015-11-27T21:13:32Z">
<tag k="name" v="Gorki Park" />
<tag k="name:en" v="Gorki Park" />
@ -200,7 +146,35 @@ UNIT_TEST(XMLFeature_ForEachName)
</node>
)";
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<string, string> 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"(<?xml version="1.0"?>
<osm>
<node id="4" lat="55.7978998" lon="37.474528" timestamp="2015-11-27T21:13:32Z"/>
<node id="5" lat="55.7977777" lon="37.474528" timestamp="2015-11-27T21:13:33Z"/>
<way id="3" timestamp="2015-11-27T21:13:34Z">
<nd ref="4"/>
<nd ref="5"/>
<tag k="hi" v="test"/>
</way>
</osm>
)";
UNIT_TEST(XMLFeature_FromOSM)
{
TEST_ANY_THROW(XMLFeature::FromOSM(""), ());
TEST_ANY_THROW(XMLFeature::FromOSM("This is not XML"), ());
TEST_ANY_THROW(XMLFeature::FromOSM("<?xml version=\"1.0\"?>"), ());
TEST_NO_THROW(XMLFeature::FromOSM("<?xml version=\"1.0\"?><osm></osm>"), ());
TEST_ANY_THROW(XMLFeature::FromOSM("<?xml version=\"1.0\"?><osm><node lat=\"11.11\"/></osm>"), ());
vector<XMLFeature> features;
TEST_NO_THROW(features = XMLFeature::FromOSM(kTestNodeWay), ());
TEST_EQUAL(3, features.size(), ());
TEST_EQUAL(features[2].GetTagValue("hi"), "test", ());
}

View file

@ -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> 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<XMLFeature> 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

View file

@ -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<XMLFeature> 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;