module; #include module routemon:gpx$impl; import std; import :gpx; import :util; import :xml; using namespace std::literals::string_view_literals; namespace routemon::gpx { namespace v10 { constexpr auto xmlns = "http://www.topografix.com/GPX/1/0"sv; auto qname(std::string_view local) -> xml::qname_view { return {.ns_uri = xmlns, .local = local}; } auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs) -> xml::parser { auto parse_xml_double = [](std::string_view sv) -> std::optional { return util::parse_double(sv, std::chars_format::fixed); }; auto mlat = std::optional{}; auto mlon = std::optional{}; for (auto const& [name, value] : attrs) { if (name == qname("lat")) { mlat = parse_xml_double(value); } else if (name == qname("lon")) { mlon = parse_xml_double(value); } } if (!mlat || !mlon) throw std::runtime_error{"expected valid latitude and longitude for waypoint"}; co_await xml::ignore_contents(e); co_return geo::point{*mlon, *mlat}; } auto parse_trkseg(xml::executor_ref e, xml::attribute_view) -> xml::parser { auto s = track_segment{}; while (auto mwpt = co_await allow_element(e, qname("trkpt"), xml::hohalo())) bgeo::append(s.waypoints, *mwpt); co_return std::move(s); } auto parse_trk(xml::executor_ref e, xml::attribute_view) -> xml::parser { auto t = track{}; t.name = co_await allow_element(e, qname("name"), xml::hohalo()); co_await allow_element(e, qname("cmt"), xml::hohalo()); t.desc = co_await allow_element(e, qname("desc"), xml::hohalo()); co_await xml::ignore_contents(e, /* until */ qname("trkseg")); while (auto mseg = co_await allow_element(e, qname("trkseg"), xml::hohalo())) t.segments.push_back(std::move(*mseg)); co_return std::move(t); } auto parse_gpx(xml::executor_ref e, xml::attribute_view attrs) -> xml::parser { auto f = file{}; if (attrs.lookup(qname("version")) != "1.0"sv) throw std::runtime_error{"expected GPX version to be 1.0"}; if (auto mcreator = attrs.lookup(qname("creator"))) f.creator = *mcreator; else throw std::runtime_error{"expected GPX file to have creator"}; f.meta.name = co_await allow_element(e, qname("name"), xml::hohalo()); f.meta.desc = co_await allow_element(e, qname("desc"), xml::hohalo()); co_await xml::ignore_contents(e, /* until */ qname("trk")); while (auto mtrk = co_await allow_element(e, qname("trk"), xml::hohalo())) f.tracks.push_back(std::move(*mtrk)); co_await xml::ignore_contents(e); co_return std::move(f); } } // namespace v10 namespace v11 { constexpr auto xmlns = "http://www.topografix.com/GPX/1/1"sv; auto qname(std::string_view local) -> xml::qname_view { return {.ns_uri = xmlns, .local = local}; } auto parse_metadata(xml::executor_ref e, xml::attribute_view) -> xml::parser { auto meta = metadata{}; meta.name = co_await allow_element(e, qname("name"), xml::hohalo()); meta.desc = co_await allow_element(e, qname("desc"), xml::hohalo()); co_await xml::ignore_contents(e); co_return meta; } auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs) -> xml::parser { auto parse_xml_double = [](std::string_view sv) -> std::optional { return util::parse_double(sv, std::chars_format::fixed); }; auto mlat = std::optional{}; auto mlon = std::optional{}; for (auto const& [name, value] : attrs) { if (name == qname("lat")) { mlat = parse_xml_double(value); } else if (name == qname("lon")) { mlon = parse_xml_double(value); } } if (!mlat || !mlon) throw std::runtime_error{"expected valid latitude and longitude for waypoint"}; co_await xml::ignore_contents(e); co_return geo::point{*mlon, *mlat}; } auto parse_trkseg(xml::executor_ref e, xml::attribute_view) -> xml::parser { auto s = track_segment{}; while (auto mwpt = co_await allow_element(e, qname("trkpt"), xml::hohalo())) bgeo::append(s.waypoints, *mwpt); co_await allow_element(e, qname("extensions"), xml::hohalo()); co_return std::move(s); } auto parse_trk(xml::executor_ref e, xml::attribute_view) -> xml::parser { auto t = track{}; t.name = co_await allow_element(e, qname("name"), xml::hohalo()); co_await allow_element(e, qname("cmt"), xml::hohalo()); t.desc = co_await allow_element(e, qname("desc"), xml::hohalo()); co_await xml::ignore_contents(e, /* until */ qname("trkseg")); while (auto mseg = co_await allow_element(e, qname("trkseg"), xml::hohalo())) t.segments.push_back(std::move(*mseg)); co_return std::move(t); } auto parse_gpx(xml::executor_ref e, xml::attribute_view attrs) -> xml::parser { auto f = file{}; if (attrs.lookup(qname("version")) != "1.1"sv) throw std::runtime_error{"expected GPX version to be 1.1"}; if (auto mcreator = attrs.lookup(qname("creator"))) f.creator = *mcreator; else throw std::runtime_error{"expected GPX file to have creator"}; if (auto mmeta = co_await allow_element(e, qname("metadata"), xml::hohalo())) f.meta = *mmeta; co_await xml::ignore_contents(e, /* until */ qname("trk")); while (auto mtrk = co_await allow_element(e, qname("trk"), xml::hohalo())) f.tracks.push_back(std::move(*mtrk)); co_await allow_element(e, qname("extensions"), xml::hohalo()); co_return std::move(f); } } // namespace v11 auto parse_file(xml::executor_ref e) -> xml::parser { auto decl = co_await expect_event(e); if (decl.version != "1.0"sv) throw std::runtime_error{std::format("unsupported XML version, got {}", std::string_view{decl.version})}; if (decl.encoding != "UTF-8"sv) throw std::runtime_error{"unsupported encoding"}; if (auto mf = co_await allow_element(e, v10::qname("gpx"), xml::hohalo())) co_return std::move(*mf); if (auto mf = co_await allow_element(e, v11::qname("gpx"), xml::hohalo())) co_return std::move(*mf); throw std::runtime_error{"no supported GPX document found"}; } reader::reader() : p_{parse_file(util::not_null{&e_})} { e_.set_continuation(p_.promise().base_handle()); } auto reader::init() -> void { e_.start(); } auto reader::put(std::string_view buf) -> void { e_.read(buf, false); } auto reader::finish() -> gpx::file { e_.read(std::string_view{}, true); e_.end(); // Promise is still alive since the last coroutine performs a // symmetric transfer to std::noop_coroutine() in final_suspend(). return std::move(p_.promise().returned_value()); } } // namespace routemon::gpx