From 371945e14a1fe77e05a0c39fc5ebfc148397e032 Mon Sep 17 00:00:00 2001 From: Kevin Macksamie Date: Mon, 18 Jul 2022 22:27:02 -0700 Subject: [PATCH 1/6] Add initial support for Link and Icon --- src/types/kml.rs | 5 ++- src/types/link.rs | 96 +++++++++++++++++++++++++++++++++++++++++++++++ src/types/mod.rs | 4 ++ src/writer.rs | 68 ++++++++++++++++++++++++++++++++- 4 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 src/types/link.rs diff --git a/src/types/kml.rs b/src/types/kml.rs index 7d4597c..edf366e 100644 --- a/src/types/kml.rs +++ b/src/types/kml.rs @@ -4,8 +4,8 @@ use std::str::FromStr; use crate::errors::Error; use crate::types::{ BalloonStyle, CoordType, Element, Icon, IconStyle, LabelStyle, LineString, LineStyle, - LinearRing, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, PolyStyle, - Polygon, Scale, Style, StyleMap, + LinearRing, Link, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, + PolyStyle, Polygon, Scale, Style, StyleMap, }; /// Enum for representing the KML version being parsed @@ -82,5 +82,6 @@ pub enum Kml { LineStyle(LineStyle), PolyStyle(PolyStyle), ListStyle(ListStyle), + Link(Link), Element(Element), } diff --git a/src/types/link.rs b/src/types/link.rs new file mode 100644 index 0000000..40676cd --- /dev/null +++ b/src/types/link.rs @@ -0,0 +1,96 @@ +use std::{ + collections::HashMap, + fmt::{Display, Formatter, Result}, +}; + +/// `kml:Link`, [13.1](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#974) in the KML specification. +#[derive(Clone, Debug, PartialEq)] +pub struct Link { + pub is_icon: bool, + pub href: String, + pub refresh_mode: Option, + pub refresh_interval: f64, + pub view_refresh_mode: Option, + pub view_refresh_time: f64, + pub view_bound_scale: f64, + pub view_format: String, + pub http_query: String, + pub attrs: HashMap, +} + +impl Default for Link { + fn default() -> Link { + Link { + is_icon: false, + href: "".to_string(), + refresh_mode: None, + refresh_interval: 4.0, + view_refresh_mode: None, + view_refresh_time: 4.0, + view_bound_scale: 1.0, + view_format: "".to_string(), + http_query: "".to_string(), + attrs: HashMap::new(), + } + } +} + +impl Link { + pub fn new(is_icon: bool, href: String) -> Self { + Link { + is_icon, + href, + ..Default::default() + } + } +} + +/// `kml:refreshModeEnumType`, [16.21](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#1239) in the KML specification. +#[derive(Clone, Debug, PartialEq)] +pub enum RefreshMode { + OnChange, + OnInterval, + OnExpire, +} + +impl Default for RefreshMode { + fn default() -> RefreshMode { + RefreshMode::OnChange + } +} + +impl Display for RefreshMode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + RefreshMode::OnChange => write!(f, "onChange"), + RefreshMode::OnInterval => write!(f, "onInterval"), + RefreshMode::OnExpire => write!(f, "onExpire"), + } + } +} + +/// `kml:viewRefreshModeEnumType`, [16.27](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#1270) in the KML specification. +#[derive(Clone, Debug, PartialEq)] +pub enum ViewRefreshMode { + Never, + OnRequest, + OnStop, + OnRegion, +} + +impl Default for ViewRefreshMode { + fn default() -> ViewRefreshMode { + ViewRefreshMode::Never + } +} + +impl Display for ViewRefreshMode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + ViewRefreshMode::Never => write!(f, "never"), + ViewRefreshMode::OnRequest => write!(f, "onRequest"), + ViewRefreshMode::OnStop => write!(f, "onStop"), + ViewRefreshMode::OnRegion => write!(f, "onRegion"), + } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index 73b543c..f899f44 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -36,6 +36,10 @@ mod geometry; pub use geometry::Geometry; +mod link; + +pub use link::{Link, RefreshMode, ViewRefreshMode}; + mod style; pub use style::{ diff --git a/src/writer.rs b/src/writer.rs index 87014f3..24910d8 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -12,7 +12,7 @@ use crate::errors::Error; use crate::types::geom_props::GeomProps; use crate::types::{ BalloonStyle, Coord, CoordType, Element, Geometry, Icon, IconStyle, Kml, LabelStyle, - LineString, LineStyle, LinearRing, ListStyle, Location, MultiGeometry, Orientation, Pair, + LineString, LineStyle, LinearRing, Link, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, PolyStyle, Polygon, Scale, Style, StyleMap, }; @@ -93,6 +93,7 @@ where Kml::LineStyle(l) => self.write_line_style(l)?, Kml::PolyStyle(p) => self.write_poly_style(p)?, Kml::ListStyle(l) => self.write_list_style(l)?, + Kml::Link(l) => self.write_link(l)?, Kml::Document { attrs, elements } => { self.write_container(b"Document", attrs, elements)? } @@ -427,6 +428,27 @@ where .write_event(Event::End(BytesEnd::borrowed(b"ListStyle")))?) } + fn write_link(&mut self, link: &Link) -> Result<(), Error> { + let tag_name = if link.is_icon { b"Icon" } else { b"Link" }; + self.writer + .write_event(Event::Start(BytesStart::owned_name(tag_name.to_vec())))?; + self.write_text_element(b"href", &link.href)?; + if let Some(refresh_mode) = &link.refresh_mode { + self.write_text_element(b"refreshMode", &refresh_mode.to_string())?; + } + self.write_text_element(b"refreshInterval", &link.refresh_interval.to_string())?; + if let Some(view_refresh_mode) = &link.view_refresh_mode { + self.write_text_element(b"viewRefreshMode", &view_refresh_mode.to_string())?; + } + self.write_text_element(b"viewRefreshTime", &link.view_refresh_time.to_string())?; + self.write_text_element(b"viewBoundScale", &link.view_bound_scale.to_string())?; + self.write_text_element(b"viewFormat", &link.view_format.to_string())?; + self.write_text_element(b"httpQuery", &link.http_query.to_string())?; + Ok(self + .writer + .write_event(Event::End(BytesEnd::borrowed(tag_name)))?) + } + fn write_geometry(&mut self, geometry: &Geometry) -> Result<(), Error> { match geometry { Geometry::Point(p) => self.write_point(p), @@ -540,6 +562,50 @@ mod tests { assert_eq!(expected_string, kml.to_string()); } + #[test] + fn test_write_link() { + let kml: Kml = Kml::Link(Link { + is_icon: false, + href: "/path/to/local/resource".to_string(), + refresh_mode: Some(types::RefreshMode::OnChange), + view_refresh_mode: Some(types::ViewRefreshMode::OnStop), + ..Default::default() + }); + let expected_string = "\ + /path/to/local/resource\ + onChange\ + 4\ + onStop\ + 4\ + 1\ + \ + \ + "; + assert_eq!(expected_string, kml.to_string()); + } + + #[test] + fn test_write_link_icon() { + let kml: Kml = Kml::Link(Link { + is_icon: true, + href: "/path/to/local/resource".to_string(), + refresh_mode: Some(types::RefreshMode::OnChange), + view_refresh_mode: Some(types::ViewRefreshMode::OnStop), + ..Default::default() + }); + let expected_string = "\ + /path/to/local/resource\ + onChange\ + 4\ + onStop\ + 4\ + 1\ + \ + \ + "; + assert_eq!(expected_string, kml.to_string()); + } + #[test] fn test_write_scale() { let kml = Kml::Scale(Scale { From 6e83cffea3c64a3a19f689722feadf580460cf6c Mon Sep 17 00:00:00 2001 From: Kevin Macksamie Date: Mon, 18 Jul 2022 23:15:45 -0700 Subject: [PATCH 2/6] Refactor kml:Link & kml:Icon to be enum variants --- src/types/kml.rs | 4 ++-- src/types/link.rs | 25 ++++++++++--------------- src/types/mod.rs | 2 +- src/writer.rs | 44 ++++++++++++++++++++++++-------------------- 4 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/types/kml.rs b/src/types/kml.rs index edf366e..0bd6822 100644 --- a/src/types/kml.rs +++ b/src/types/kml.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use crate::errors::Error; use crate::types::{ BalloonStyle, CoordType, Element, Icon, IconStyle, LabelStyle, LineString, LineStyle, - LinearRing, Link, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, + LinearRing, LinkType, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, PolyStyle, Polygon, Scale, Style, StyleMap, }; @@ -82,6 +82,6 @@ pub enum Kml { LineStyle(LineStyle), PolyStyle(PolyStyle), ListStyle(ListStyle), - Link(Link), + LinkType(LinkType), Element(Element), } diff --git a/src/types/link.rs b/src/types/link.rs index 40676cd..78ba2db 100644 --- a/src/types/link.rs +++ b/src/types/link.rs @@ -3,10 +3,9 @@ use std::{ fmt::{Display, Formatter, Result}, }; -/// `kml:Link`, [13.1](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#974) in the KML specification. +/// Common model for `kml:LinkType`, [13.1](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#974) in the KML specification. #[derive(Clone, Debug, PartialEq)] -pub struct Link { - pub is_icon: bool, +pub struct LinkModel { pub href: String, pub refresh_mode: Option, pub refresh_interval: f64, @@ -18,10 +17,9 @@ pub struct Link { pub attrs: HashMap, } -impl Default for Link { - fn default() -> Link { - Link { - is_icon: false, +impl Default for LinkModel { + fn default() -> LinkModel { + LinkModel { href: "".to_string(), refresh_mode: None, refresh_interval: 4.0, @@ -35,14 +33,11 @@ impl Default for Link { } } -impl Link { - pub fn new(is_icon: bool, href: String) -> Self { - Link { - is_icon, - href, - ..Default::default() - } - } +/// `kml:Link` and `kml:Icon`, [13.1](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#974) in the KML specification. +#[derive(Clone, Debug, PartialEq)] +pub enum LinkType { + Icon(LinkModel), + Link(LinkModel), } /// `kml:refreshModeEnumType`, [16.21](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#1239) in the KML specification. diff --git a/src/types/mod.rs b/src/types/mod.rs index f899f44..30bece5 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -38,7 +38,7 @@ pub use geometry::Geometry; mod link; -pub use link::{Link, RefreshMode, ViewRefreshMode}; +pub use link::{LinkModel, LinkType, RefreshMode, ViewRefreshMode}; mod style; diff --git a/src/writer.rs b/src/writer.rs index 24910d8..dd115ff 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -12,8 +12,8 @@ use crate::errors::Error; use crate::types::geom_props::GeomProps; use crate::types::{ BalloonStyle, Coord, CoordType, Element, Geometry, Icon, IconStyle, Kml, LabelStyle, - LineString, LineStyle, LinearRing, Link, ListStyle, Location, MultiGeometry, Orientation, Pair, - Placemark, Point, PolyStyle, Polygon, Scale, Style, StyleMap, + LineString, LineStyle, LinearRing, LinkType, ListStyle, Location, + MultiGeometry, Orientation, Pair, Placemark, Point, PolyStyle, Polygon, Scale, Style, StyleMap, }; /// Struct for managing writing KML @@ -93,7 +93,7 @@ where Kml::LineStyle(l) => self.write_line_style(l)?, Kml::PolyStyle(p) => self.write_poly_style(p)?, Kml::ListStyle(l) => self.write_list_style(l)?, - Kml::Link(l) => self.write_link(l)?, + Kml::LinkType(l) => self.write_link_type(l)?, Kml::Document { attrs, elements } => { self.write_container(b"Document", attrs, elements)? } @@ -428,22 +428,28 @@ where .write_event(Event::End(BytesEnd::borrowed(b"ListStyle")))?) } - fn write_link(&mut self, link: &Link) -> Result<(), Error> { - let tag_name = if link.is_icon { b"Icon" } else { b"Link" }; + fn write_link_type(&mut self, link: &LinkType) -> Result<(), Error> { + let (tag_name, link_model) = match link { + LinkType::Icon(model) => (b"Icon", &*model), + LinkType::Link(model) => (b"Link", &*model), + }; self.writer .write_event(Event::Start(BytesStart::owned_name(tag_name.to_vec())))?; - self.write_text_element(b"href", &link.href)?; - if let Some(refresh_mode) = &link.refresh_mode { + self.write_text_element(b"href", &link_model.href)?; + if let Some(refresh_mode) = &link_model.refresh_mode { self.write_text_element(b"refreshMode", &refresh_mode.to_string())?; } - self.write_text_element(b"refreshInterval", &link.refresh_interval.to_string())?; - if let Some(view_refresh_mode) = &link.view_refresh_mode { + self.write_text_element(b"refreshInterval", &link_model.refresh_interval.to_string())?; + if let Some(view_refresh_mode) = &link_model.view_refresh_mode { self.write_text_element(b"viewRefreshMode", &view_refresh_mode.to_string())?; } - self.write_text_element(b"viewRefreshTime", &link.view_refresh_time.to_string())?; - self.write_text_element(b"viewBoundScale", &link.view_bound_scale.to_string())?; - self.write_text_element(b"viewFormat", &link.view_format.to_string())?; - self.write_text_element(b"httpQuery", &link.http_query.to_string())?; + self.write_text_element( + b"viewRefreshTime", + &link_model.view_refresh_time.to_string(), + )?; + self.write_text_element(b"viewBoundScale", &link_model.view_bound_scale.to_string())?; + self.write_text_element(b"viewFormat", &link_model.view_format.to_string())?; + self.write_text_element(b"httpQuery", &link_model.http_query.to_string())?; Ok(self .writer .write_event(Event::End(BytesEnd::borrowed(tag_name)))?) @@ -530,7 +536,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::types; + use crate::types::{self, LinkModel}; #[test] fn test_write_point() { @@ -564,13 +570,12 @@ mod tests { #[test] fn test_write_link() { - let kml: Kml = Kml::Link(Link { - is_icon: false, + let kml: Kml = Kml::LinkType(LinkType::Link(LinkModel { href: "/path/to/local/resource".to_string(), refresh_mode: Some(types::RefreshMode::OnChange), view_refresh_mode: Some(types::ViewRefreshMode::OnStop), ..Default::default() - }); + })); let expected_string = "\ /path/to/local/resource\ onChange\ @@ -586,13 +591,12 @@ mod tests { #[test] fn test_write_link_icon() { - let kml: Kml = Kml::Link(Link { - is_icon: true, + let kml: Kml = Kml::LinkType(LinkType::Icon(LinkModel { href: "/path/to/local/resource".to_string(), refresh_mode: Some(types::RefreshMode::OnChange), view_refresh_mode: Some(types::ViewRefreshMode::OnStop), ..Default::default() - }); + })); let expected_string = "\ /path/to/local/resource\ onChange\ From 4ec2c4e2d92faf3e499054000c978be19ff2cf27 Mon Sep 17 00:00:00 2001 From: Kevin Macksamie Date: Wed, 27 Jul 2022 21:47:05 -0700 Subject: [PATCH 3/6] Add read support for Link and Icon --- src/reader.rs | 103 +++++++++++++++++++++++++++++++++++++++++++++++--- src/writer.rs | 4 +- 2 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/reader.rs b/src/reader.rs index 10618fc..39d944f 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -17,8 +17,8 @@ use crate::types::geom_props::GeomProps; use crate::types::{ self, coords_from_str, BalloonStyle, ColorMode, Coord, CoordType, Element, Geometry, Icon, IconStyle, Kml, KmlDocument, KmlVersion, LabelStyle, LineString, LineStyle, LinearRing, - ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, PolyStyle, Polygon, - Scale, Style, StyleMap, Units, Vec2, + LinkModel, LinkType, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, + PolyStyle, Polygon, RefreshMode, Scale, Style, StyleMap, Units, Vec2, ViewRefreshMode, }; /// Main struct for reading KML documents @@ -158,7 +158,10 @@ where elements.push(Kml::BalloonStyle(self.read_balloon_style(attrs)?)) } b"IconStyle" => elements.push(Kml::IconStyle(self.read_icon_style(attrs)?)), - b"Icon" => elements.push(Kml::Icon(self.read_icon()?)), + b"Link" => elements + .push(Kml::LinkType(LinkType::Link(self.read_link_model("Link")?))), + b"Icon" => elements + .push(Kml::LinkType(LinkType::Icon(self.read_link_model("Icon")?))), b"LabelStyle" => { elements.push(Kml::LabelStyle(self.read_label_style(attrs)?)) } @@ -560,7 +563,7 @@ where }); } } - b"Icon" => icon_style.icon = self.read_icon()?, + b"Icon" => icon_style.icon = self.read_basic_link_type_icon()?, b"color" => icon_style.color = self.read_str()?, b"colorMode" => { icon_style.color_mode = self.read_str()?.parse::()? @@ -578,7 +581,7 @@ where Ok(icon_style) } - fn read_icon(&mut self) -> Result { + fn read_basic_link_type_icon(&mut self) -> Result { let mut href = String::new(); loop { let mut e = self.reader.read_event(&mut self.buf)?; @@ -599,6 +602,48 @@ where Ok(Icon { href }) } + fn read_link_model(&mut self, tag: &str) -> Result { + let mut link_model = LinkModel::default(); + loop { + let mut e = self.reader.read_event(&mut self.buf)?; + match e { + Event::Start(ref mut e) => match e.local_name() { + b"href" => link_model.href = self.read_str()?, + b"refreshMode" => { + link_model.refresh_mode = match self.read_str()?.as_str() { + "onChange" => Some(RefreshMode::OnChange), + "onInterval" => Some(RefreshMode::OnInterval), + "onExpire" => Some(RefreshMode::OnExpire), + _ => None, + } + } + b"refreshInterval" => link_model.refresh_interval = self.read_float()?, + b"viewRefreshMode" => { + link_model.view_refresh_mode = match self.read_str()?.as_str() { + "never" => Some(ViewRefreshMode::Never), + "onRequest" => Some(ViewRefreshMode::OnRequest), + "onStop" => Some(ViewRefreshMode::OnStop), + "onRegion" => Some(ViewRefreshMode::OnRegion), + _ => None, + } + } + b"viewRefreshTime" => link_model.view_refresh_time = self.read_float()?, + b"viewBoundScale" => link_model.view_bound_scale = self.read_float()?, + b"viewFormat" => link_model.view_format = self.read_str()?, + b"httpQuery" => link_model.http_query = self.read_str()?, + _ => {} + }, + Event::End(ref mut e) => { + if e.local_name() == tag.as_bytes() { + break; + } + } + _ => break, + } + } + Ok(link_model) + } + fn read_balloon_style( &mut self, attrs: HashMap, @@ -930,6 +975,54 @@ mod tests { ); } + #[test] + fn test_read_link() { + let kml_str = r#" + /path/to/local/resource + onChange + 4 + onStop + 4 + 1 + + + "#; + let l: Kml = kml_str.parse().unwrap(); + assert_eq!( + l, + Kml::LinkType(LinkType::Link(LinkModel { + href: "/path/to/local/resource".to_string(), + refresh_mode: Some(types::RefreshMode::OnChange), + view_refresh_mode: Some(types::ViewRefreshMode::OnStop), + ..Default::default() + })) + ); + } + + #[test] + fn test_read_link_icon() { + let kml_str = r#" + /path/to/local/resource + onChange + 4 + onStop + 4 + 1 + + + "#; + let l: Kml = kml_str.parse().unwrap(); + assert_eq!( + l, + Kml::LinkType(LinkType::Icon(LinkModel { + href: "/path/to/local/resource".to_string(), + refresh_mode: Some(types::RefreshMode::OnChange), + view_refresh_mode: Some(types::ViewRefreshMode::OnStop), + ..Default::default() + })) + ); + } + #[test] fn test_parse_scale() { let kml_str = r#" diff --git a/src/writer.rs b/src/writer.rs index dd115ff..884fc77 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -12,8 +12,8 @@ use crate::errors::Error; use crate::types::geom_props::GeomProps; use crate::types::{ BalloonStyle, Coord, CoordType, Element, Geometry, Icon, IconStyle, Kml, LabelStyle, - LineString, LineStyle, LinearRing, LinkType, ListStyle, Location, - MultiGeometry, Orientation, Pair, Placemark, Point, PolyStyle, Polygon, Scale, Style, StyleMap, + LineString, LineStyle, LinearRing, LinkType, ListStyle, Location, MultiGeometry, Orientation, + Pair, Placemark, Point, PolyStyle, Polygon, Scale, Style, StyleMap, }; /// Struct for managing writing KML From 0b86a26b3539e486d0086aec5209310827e4deae Mon Sep 17 00:00:00 2001 From: Kevin Macksamie Date: Thu, 28 Jul 2022 22:46:10 -0700 Subject: [PATCH 4/6] Implement Link and Icon as separate structs Instead of using enum variants to represent `kml:Link` and `kml:Icon`, which makes it awkward to use them since enum variants are not first-class types in Rust, they are implemented using two separate structs. Since the library already exports the public type `Kml::Icon` from the `crate::types::style` module, `Kml:LinkTypeLink` and `Kml:LinkTypeIcon` are exported. These are aliases for the internal types `kml::types::link::{Link,Icon}`. --- src/reader.rs | 126 +++++++++++++++++++++++++++++++++++----------- src/types/kml.rs | 7 +-- src/types/link.rs | 53 +++++++++++++------ src/types/mod.rs | 2 +- src/writer.rs | 106 +++++++++++++++++++++++++------------- 5 files changed, 209 insertions(+), 85 deletions(-) diff --git a/src/reader.rs b/src/reader.rs index 39d944f..7423716 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -17,8 +17,8 @@ use crate::types::geom_props::GeomProps; use crate::types::{ self, coords_from_str, BalloonStyle, ColorMode, Coord, CoordType, Element, Geometry, Icon, IconStyle, Kml, KmlDocument, KmlVersion, LabelStyle, LineString, LineStyle, LinearRing, - LinkModel, LinkType, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, - PolyStyle, Polygon, RefreshMode, Scale, Style, StyleMap, Units, Vec2, ViewRefreshMode, + LinkTypeIcon, LinkTypeLink, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, + Point, PolyStyle, Polygon, RefreshMode, Scale, Style, StyleMap, Units, Vec2, ViewRefreshMode, }; /// Main struct for reading KML documents @@ -158,10 +158,12 @@ where elements.push(Kml::BalloonStyle(self.read_balloon_style(attrs)?)) } b"IconStyle" => elements.push(Kml::IconStyle(self.read_icon_style(attrs)?)), - b"Link" => elements - .push(Kml::LinkType(LinkType::Link(self.read_link_model("Link")?))), - b"Icon" => elements - .push(Kml::LinkType(LinkType::Icon(self.read_link_model("Icon")?))), + b"Link" => { + elements.push(Kml::LinkTypeLink(self.read_link_type_link(attrs)?)) + } + b"Icon" => { + elements.push(Kml::LinkTypeIcon(self.read_link_type_icon(attrs)?)) + } b"LabelStyle" => { elements.push(Kml::LabelStyle(self.read_label_style(attrs)?)) } @@ -602,24 +604,30 @@ where Ok(Icon { href }) } - fn read_link_model(&mut self, tag: &str) -> Result { - let mut link_model = LinkModel::default(); + fn read_link_type_icon( + &mut self, + attrs: HashMap, + ) -> Result { + let mut icon = LinkTypeIcon { + attrs, + ..Default::default() + }; loop { let mut e = self.reader.read_event(&mut self.buf)?; match e { Event::Start(ref mut e) => match e.local_name() { - b"href" => link_model.href = self.read_str()?, + b"href" => icon.href = Some(self.read_str()?), b"refreshMode" => { - link_model.refresh_mode = match self.read_str()?.as_str() { + icon.refresh_mode = match self.read_str()?.as_str() { "onChange" => Some(RefreshMode::OnChange), "onInterval" => Some(RefreshMode::OnInterval), "onExpire" => Some(RefreshMode::OnExpire), _ => None, } } - b"refreshInterval" => link_model.refresh_interval = self.read_float()?, + b"refreshInterval" => icon.refresh_interval = self.read_float()?, b"viewRefreshMode" => { - link_model.view_refresh_mode = match self.read_str()?.as_str() { + icon.view_refresh_mode = match self.read_str()?.as_str() { "never" => Some(ViewRefreshMode::Never), "onRequest" => Some(ViewRefreshMode::OnRequest), "onStop" => Some(ViewRefreshMode::OnStop), @@ -627,21 +635,69 @@ where _ => None, } } - b"viewRefreshTime" => link_model.view_refresh_time = self.read_float()?, - b"viewBoundScale" => link_model.view_bound_scale = self.read_float()?, - b"viewFormat" => link_model.view_format = self.read_str()?, - b"httpQuery" => link_model.http_query = self.read_str()?, + b"viewRefreshTime" => icon.view_refresh_time = self.read_float()?, + b"viewBoundScale" => icon.view_bound_scale = self.read_float()?, + b"viewFormat" => icon.view_format = Some(self.read_str()?), + b"httpQuery" => icon.http_query = Some(self.read_str()?), _ => {} }, Event::End(ref mut e) => { - if e.local_name() == tag.as_bytes() { + if e.local_name() == b"Icon" { break; } } _ => break, } } - Ok(link_model) + Ok(icon) + } + + fn read_link_type_link( + &mut self, + attrs: HashMap, + ) -> Result { + let mut link = LinkTypeLink { + attrs, + ..Default::default() + }; + loop { + let mut e = self.reader.read_event(&mut self.buf)?; + match e { + Event::Start(ref mut e) => match e.local_name() { + b"href" => link.href = Some(self.read_str()?), + b"refreshMode" => { + link.refresh_mode = match self.read_str()?.as_str() { + "onChange" => Some(RefreshMode::OnChange), + "onInterval" => Some(RefreshMode::OnInterval), + "onExpire" => Some(RefreshMode::OnExpire), + _ => None, + } + } + b"refreshInterval" => link.refresh_interval = self.read_float()?, + b"viewRefreshMode" => { + link.view_refresh_mode = match self.read_str()?.as_str() { + "never" => Some(ViewRefreshMode::Never), + "onRequest" => Some(ViewRefreshMode::OnRequest), + "onStop" => Some(ViewRefreshMode::OnStop), + "onRegion" => Some(ViewRefreshMode::OnRegion), + _ => None, + } + } + b"viewRefreshTime" => link.view_refresh_time = self.read_float()?, + b"viewBoundScale" => link.view_bound_scale = self.read_float()?, + b"viewFormat" => link.view_format = Some(self.read_str()?), + b"httpQuery" => link.http_query = Some(self.read_str()?), + _ => {} + }, + Event::End(ref mut e) => { + if e.local_name() == b"Link" { + break; + } + } + _ => break, + } + } + Ok(link) } fn read_balloon_style( @@ -976,8 +1032,8 @@ mod tests { } #[test] - fn test_read_link() { - let kml_str = r#" + fn test_read_link_type_link() { + let kml_str = r#" /path/to/local/resource onChange 4 @@ -985,23 +1041,28 @@ mod tests { 4 1 - "#; + + let mut attrs = HashMap::new(); + attrs.insert("id".to_string(), "Some ID".to_string()); + let l: Kml = kml_str.parse().unwrap(); assert_eq!( l, - Kml::LinkType(LinkType::Link(LinkModel { - href: "/path/to/local/resource".to_string(), + Kml::LinkTypeLink(LinkTypeLink { + href: Some("/path/to/local/resource".to_string()), refresh_mode: Some(types::RefreshMode::OnChange), view_refresh_mode: Some(types::ViewRefreshMode::OnStop), + view_format: Some(String::new()), + attrs, ..Default::default() - })) + }) ); } #[test] - fn test_read_link_icon() { - let kml_str = r#" + fn test_read_link_type_icon() { + let kml_str = r#" /path/to/local/resource onChange 4 @@ -1009,17 +1070,22 @@ mod tests { 4 1 - "#; + + let mut attrs = HashMap::new(); + attrs.insert("id".to_string(), "Some ID".to_string()); + let l: Kml = kml_str.parse().unwrap(); assert_eq!( l, - Kml::LinkType(LinkType::Icon(LinkModel { - href: "/path/to/local/resource".to_string(), + Kml::LinkTypeIcon(LinkTypeIcon { + href: Some("/path/to/local/resource".to_string()), refresh_mode: Some(types::RefreshMode::OnChange), view_refresh_mode: Some(types::ViewRefreshMode::OnStop), + view_format: Some(String::new()), + attrs, ..Default::default() - })) + }) ); } diff --git a/src/types/kml.rs b/src/types/kml.rs index 0bd6822..d0f4eaa 100644 --- a/src/types/kml.rs +++ b/src/types/kml.rs @@ -4,8 +4,8 @@ use std::str::FromStr; use crate::errors::Error; use crate::types::{ BalloonStyle, CoordType, Element, Icon, IconStyle, LabelStyle, LineString, LineStyle, - LinearRing, LinkType, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, - PolyStyle, Polygon, Scale, Style, StyleMap, + LinearRing, LinkTypeIcon, LinkTypeLink, ListStyle, Location, MultiGeometry, Orientation, Pair, + Placemark, Point, PolyStyle, Polygon, Scale, Style, StyleMap, }; /// Enum for representing the KML version being parsed @@ -82,6 +82,7 @@ pub enum Kml { LineStyle(LineStyle), PolyStyle(PolyStyle), ListStyle(ListStyle), - LinkType(LinkType), + LinkTypeIcon(LinkTypeIcon), + LinkTypeLink(LinkTypeLink), Element(Element), } diff --git a/src/types/link.rs b/src/types/link.rs index 78ba2db..33a604f 100644 --- a/src/types/link.rs +++ b/src/types/link.rs @@ -3,41 +3,64 @@ use std::{ fmt::{Display, Formatter, Result}, }; -/// Common model for `kml:LinkType`, [13.1](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#974) in the KML specification. +/// `kml:Link`, [13.1](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#974) in the KML specification. #[derive(Clone, Debug, PartialEq)] -pub struct LinkModel { - pub href: String, +pub struct Link { + pub href: Option, pub refresh_mode: Option, pub refresh_interval: f64, pub view_refresh_mode: Option, pub view_refresh_time: f64, pub view_bound_scale: f64, - pub view_format: String, - pub http_query: String, + pub view_format: Option, + pub http_query: Option, pub attrs: HashMap, } -impl Default for LinkModel { - fn default() -> LinkModel { - LinkModel { - href: "".to_string(), +impl Default for Link { + fn default() -> Self { + Self { + href: None, refresh_mode: None, refresh_interval: 4.0, view_refresh_mode: None, view_refresh_time: 4.0, view_bound_scale: 1.0, - view_format: "".to_string(), - http_query: "".to_string(), + view_format: None, + http_query: None, attrs: HashMap::new(), } } } -/// `kml:Link` and `kml:Icon`, [13.1](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#974) in the KML specification. +/// `kml:Icon`, [13.1](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#974) in the KML specification. #[derive(Clone, Debug, PartialEq)] -pub enum LinkType { - Icon(LinkModel), - Link(LinkModel), +pub struct Icon { + pub href: Option, + pub refresh_mode: Option, + pub refresh_interval: f64, + pub view_refresh_mode: Option, + pub view_refresh_time: f64, + pub view_bound_scale: f64, + pub view_format: Option, + pub http_query: Option, + pub attrs: HashMap, +} + +impl Default for Icon { + fn default() -> Self { + Self { + href: None, + refresh_mode: None, + refresh_interval: 4.0, + view_refresh_mode: None, + view_refresh_time: 4.0, + view_bound_scale: 1.0, + view_format: None, + http_query: None, + attrs: HashMap::new(), + } + } } /// `kml:refreshModeEnumType`, [16.21](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#1239) in the KML specification. diff --git a/src/types/mod.rs b/src/types/mod.rs index 30bece5..bc3db09 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -38,7 +38,7 @@ pub use geometry::Geometry; mod link; -pub use link::{LinkModel, LinkType, RefreshMode, ViewRefreshMode}; +pub use link::{Icon as LinkTypeIcon, Link as LinkTypeLink, RefreshMode, ViewRefreshMode}; mod style; diff --git a/src/writer.rs b/src/writer.rs index 884fc77..97aa82f 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -12,8 +12,8 @@ use crate::errors::Error; use crate::types::geom_props::GeomProps; use crate::types::{ BalloonStyle, Coord, CoordType, Element, Geometry, Icon, IconStyle, Kml, LabelStyle, - LineString, LineStyle, LinearRing, LinkType, ListStyle, Location, MultiGeometry, Orientation, - Pair, Placemark, Point, PolyStyle, Polygon, Scale, Style, StyleMap, + LineString, LineStyle, LinearRing, LinkTypeIcon, LinkTypeLink, ListStyle, Location, + MultiGeometry, Orientation, Pair, Placemark, Point, PolyStyle, Polygon, Scale, Style, StyleMap, }; /// Struct for managing writing KML @@ -93,7 +93,8 @@ where Kml::LineStyle(l) => self.write_line_style(l)?, Kml::PolyStyle(p) => self.write_poly_style(p)?, Kml::ListStyle(l) => self.write_list_style(l)?, - Kml::LinkType(l) => self.write_link_type(l)?, + Kml::LinkTypeIcon(i) => self.write_link_type_icon(i)?, + Kml::LinkTypeLink(l) => self.write_link_type_link(l)?, Kml::Document { attrs, elements } => { self.write_container(b"Document", attrs, elements)? } @@ -428,31 +429,60 @@ where .write_event(Event::End(BytesEnd::borrowed(b"ListStyle")))?) } - fn write_link_type(&mut self, link: &LinkType) -> Result<(), Error> { - let (tag_name, link_model) = match link { - LinkType::Icon(model) => (b"Icon", &*model), - LinkType::Link(model) => (b"Link", &*model), - }; - self.writer - .write_event(Event::Start(BytesStart::owned_name(tag_name.to_vec())))?; - self.write_text_element(b"href", &link_model.href)?; - if let Some(refresh_mode) = &link_model.refresh_mode { + fn write_link_type_icon(&mut self, icon: &LinkTypeIcon) -> Result<(), Error> { + self.writer.write_event(Event::Start( + BytesStart::owned_name(b"Icon".to_vec()) + .with_attributes(self.hash_map_as_attrs(&icon.attrs)), + ))?; + if let Some(href) = &icon.href { + self.write_text_element(b"href", href)?; + } + if let Some(refresh_mode) = &icon.refresh_mode { self.write_text_element(b"refreshMode", &refresh_mode.to_string())?; } - self.write_text_element(b"refreshInterval", &link_model.refresh_interval.to_string())?; - if let Some(view_refresh_mode) = &link_model.view_refresh_mode { + self.write_text_element(b"refreshInterval", &icon.refresh_interval.to_string())?; + if let Some(view_refresh_mode) = &icon.view_refresh_mode { self.write_text_element(b"viewRefreshMode", &view_refresh_mode.to_string())?; } - self.write_text_element( - b"viewRefreshTime", - &link_model.view_refresh_time.to_string(), - )?; - self.write_text_element(b"viewBoundScale", &link_model.view_bound_scale.to_string())?; - self.write_text_element(b"viewFormat", &link_model.view_format.to_string())?; - self.write_text_element(b"httpQuery", &link_model.http_query.to_string())?; + self.write_text_element(b"viewRefreshTime", &icon.view_refresh_time.to_string())?; + self.write_text_element(b"viewBoundScale", &icon.view_bound_scale.to_string())?; + if let Some(view_format) = &icon.view_format { + self.write_text_element(b"viewFormat", view_format)?; + } + if let Some(http_query) = &icon.http_query { + self.write_text_element(b"httpQuery", http_query)?; + } + Ok(self + .writer + .write_event(Event::End(BytesEnd::borrowed(b"Icon")))?) + } + + fn write_link_type_link(&mut self, link: &LinkTypeLink) -> Result<(), Error> { + self.writer.write_event(Event::Start( + BytesStart::owned_name(b"Link".to_vec()) + .with_attributes(self.hash_map_as_attrs(&link.attrs)), + ))?; + if let Some(href) = &link.href { + self.write_text_element(b"href", href)?; + } + if let Some(refresh_mode) = &link.refresh_mode { + self.write_text_element(b"refreshMode", &refresh_mode.to_string())?; + } + self.write_text_element(b"refreshInterval", &link.refresh_interval.to_string())?; + if let Some(view_refresh_mode) = &link.view_refresh_mode { + self.write_text_element(b"viewRefreshMode", &view_refresh_mode.to_string())?; + } + self.write_text_element(b"viewRefreshTime", &link.view_refresh_time.to_string())?; + self.write_text_element(b"viewBoundScale", &link.view_bound_scale.to_string())?; + if let Some(view_format) = &link.view_format { + self.write_text_element(b"viewFormat", view_format)?; + } + if let Some(http_query) = &link.http_query { + self.write_text_element(b"httpQuery", http_query)?; + } Ok(self .writer - .write_event(Event::End(BytesEnd::borrowed(tag_name)))?) + .write_event(Event::End(BytesEnd::borrowed(b"Link")))?) } fn write_geometry(&mut self, geometry: &Geometry) -> Result<(), Error> { @@ -536,7 +566,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::types::{self, LinkModel}; + use crate::types; #[test] fn test_write_point() { @@ -569,43 +599,47 @@ mod tests { } #[test] - fn test_write_link() { - let kml: Kml = Kml::LinkType(LinkType::Link(LinkModel { - href: "/path/to/local/resource".to_string(), + fn test_write_link_type_link() { + let mut attrs = HashMap::new(); + attrs.insert("id".to_string(), "Some ID".to_string()); + + let kml: Kml = Kml::LinkTypeLink(LinkTypeLink { + href: Some("/path/to/local/resource".to_string()), refresh_mode: Some(types::RefreshMode::OnChange), view_refresh_mode: Some(types::ViewRefreshMode::OnStop), + attrs, ..Default::default() - })); - let expected_string = "\ + }); + let expected_string = "\ /path/to/local/resource\ onChange\ 4\ onStop\ 4\ 1\ - \ - \ "; assert_eq!(expected_string, kml.to_string()); } #[test] fn test_write_link_icon() { - let kml: Kml = Kml::LinkType(LinkType::Icon(LinkModel { - href: "/path/to/local/resource".to_string(), + let mut attrs = HashMap::new(); + attrs.insert("id".to_string(), "Some ID".to_string()); + + let kml: Kml = Kml::LinkTypeIcon(LinkTypeIcon { + href: Some("/path/to/local/resource".to_string()), refresh_mode: Some(types::RefreshMode::OnChange), view_refresh_mode: Some(types::ViewRefreshMode::OnStop), + attrs, ..Default::default() - })); - let expected_string = "\ + }); + let expected_string = "\ /path/to/local/resource\ onChange\ 4\ onStop\ 4\ 1\ - \ - \ "; assert_eq!(expected_string, kml.to_string()); } From 8b0a6ad3c2ae4108cecb35d95c7091ab21eb794e Mon Sep 17 00:00:00 2001 From: Kevin Macksamie Date: Sat, 30 Jul 2022 08:06:18 -0700 Subject: [PATCH 5/6] Remove alias for kml::types::Link --- src/reader.rs | 21 ++++++++------------- src/types/kml.rs | 4 ++-- src/types/mod.rs | 2 +- src/writer.rs | 12 ++++++------ 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/reader.rs b/src/reader.rs index 7423716..faa0eb8 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -16,9 +16,9 @@ use crate::errors::Error; use crate::types::geom_props::GeomProps; use crate::types::{ self, coords_from_str, BalloonStyle, ColorMode, Coord, CoordType, Element, Geometry, Icon, - IconStyle, Kml, KmlDocument, KmlVersion, LabelStyle, LineString, LineStyle, LinearRing, - LinkTypeIcon, LinkTypeLink, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, - Point, PolyStyle, Polygon, RefreshMode, Scale, Style, StyleMap, Units, Vec2, ViewRefreshMode, + IconStyle, Kml, KmlDocument, KmlVersion, LabelStyle, LineString, LineStyle, LinearRing, Link, + LinkTypeIcon, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, + PolyStyle, Polygon, RefreshMode, Scale, Style, StyleMap, Units, Vec2, ViewRefreshMode, }; /// Main struct for reading KML documents @@ -158,9 +158,7 @@ where elements.push(Kml::BalloonStyle(self.read_balloon_style(attrs)?)) } b"IconStyle" => elements.push(Kml::IconStyle(self.read_icon_style(attrs)?)), - b"Link" => { - elements.push(Kml::LinkTypeLink(self.read_link_type_link(attrs)?)) - } + b"Link" => elements.push(Kml::Link(self.read_link(attrs)?)), b"Icon" => { elements.push(Kml::LinkTypeIcon(self.read_link_type_icon(attrs)?)) } @@ -652,11 +650,8 @@ where Ok(icon) } - fn read_link_type_link( - &mut self, - attrs: HashMap, - ) -> Result { - let mut link = LinkTypeLink { + fn read_link(&mut self, attrs: HashMap) -> Result { + let mut link = Link { attrs, ..Default::default() }; @@ -1032,7 +1027,7 @@ mod tests { } #[test] - fn test_read_link_type_link() { + fn test_read_link() { let kml_str = r#" /path/to/local/resource onChange @@ -1049,7 +1044,7 @@ mod tests { let l: Kml = kml_str.parse().unwrap(); assert_eq!( l, - Kml::LinkTypeLink(LinkTypeLink { + Kml::Link(Link { href: Some("/path/to/local/resource".to_string()), refresh_mode: Some(types::RefreshMode::OnChange), view_refresh_mode: Some(types::ViewRefreshMode::OnStop), diff --git a/src/types/kml.rs b/src/types/kml.rs index d0f4eaa..ae7f96b 100644 --- a/src/types/kml.rs +++ b/src/types/kml.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use crate::errors::Error; use crate::types::{ BalloonStyle, CoordType, Element, Icon, IconStyle, LabelStyle, LineString, LineStyle, - LinearRing, LinkTypeIcon, LinkTypeLink, ListStyle, Location, MultiGeometry, Orientation, Pair, + LinearRing, Link, LinkTypeIcon, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, PolyStyle, Polygon, Scale, Style, StyleMap, }; @@ -83,6 +83,6 @@ pub enum Kml { PolyStyle(PolyStyle), ListStyle(ListStyle), LinkTypeIcon(LinkTypeIcon), - LinkTypeLink(LinkTypeLink), + Link(Link), Element(Element), } diff --git a/src/types/mod.rs b/src/types/mod.rs index bc3db09..59c30c6 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -38,7 +38,7 @@ pub use geometry::Geometry; mod link; -pub use link::{Icon as LinkTypeIcon, Link as LinkTypeLink, RefreshMode, ViewRefreshMode}; +pub use link::{Icon as LinkTypeIcon, Link, RefreshMode, ViewRefreshMode}; mod style; diff --git a/src/writer.rs b/src/writer.rs index 97aa82f..ddec3a1 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -12,8 +12,8 @@ use crate::errors::Error; use crate::types::geom_props::GeomProps; use crate::types::{ BalloonStyle, Coord, CoordType, Element, Geometry, Icon, IconStyle, Kml, LabelStyle, - LineString, LineStyle, LinearRing, LinkTypeIcon, LinkTypeLink, ListStyle, Location, - MultiGeometry, Orientation, Pair, Placemark, Point, PolyStyle, Polygon, Scale, Style, StyleMap, + LineString, LineStyle, LinearRing, Link, LinkTypeIcon, ListStyle, Location, MultiGeometry, + Orientation, Pair, Placemark, Point, PolyStyle, Polygon, Scale, Style, StyleMap, }; /// Struct for managing writing KML @@ -94,7 +94,7 @@ where Kml::PolyStyle(p) => self.write_poly_style(p)?, Kml::ListStyle(l) => self.write_list_style(l)?, Kml::LinkTypeIcon(i) => self.write_link_type_icon(i)?, - Kml::LinkTypeLink(l) => self.write_link_type_link(l)?, + Kml::Link(l) => self.write_link(l)?, Kml::Document { attrs, elements } => { self.write_container(b"Document", attrs, elements)? } @@ -457,7 +457,7 @@ where .write_event(Event::End(BytesEnd::borrowed(b"Icon")))?) } - fn write_link_type_link(&mut self, link: &LinkTypeLink) -> Result<(), Error> { + fn write_link(&mut self, link: &Link) -> Result<(), Error> { self.writer.write_event(Event::Start( BytesStart::owned_name(b"Link".to_vec()) .with_attributes(self.hash_map_as_attrs(&link.attrs)), @@ -599,11 +599,11 @@ mod tests { } #[test] - fn test_write_link_type_link() { + fn test_write_link() { let mut attrs = HashMap::new(); attrs.insert("id".to_string(), "Some ID".to_string()); - let kml: Kml = Kml::LinkTypeLink(LinkTypeLink { + let kml: Kml = Kml::Link(Link { href: Some("/path/to/local/resource".to_string()), refresh_mode: Some(types::RefreshMode::OnChange), view_refresh_mode: Some(types::ViewRefreshMode::OnStop), From 5f70a9659bf60140977fecac62cfdeb4f148cf73 Mon Sep 17 00:00:00 2001 From: Kevin Macksamie Date: Sun, 31 Jul 2022 14:18:35 -0700 Subject: [PATCH 6/6] Impl FromStr for RefreshMode and ViewRefreshMode Enable and simplify reading these types using str's `parse` method. --- src/errors.rs | 4 +++ src/reader.rs | 30 +++-------------- src/types/link.rs | 85 ++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 85 insertions(+), 34 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 6f228e9..ad2271d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -28,6 +28,10 @@ pub enum Error { InvalidColorMode(String), #[error("Invalid list item type: {0}")] InvalidListItemType(String), + #[error("Invalid refresh mode: {0}")] + InvalidRefreshMode(String), + #[error("Invalid view refresh mode: {0}")] + InvalidViewRefreshMode(String), #[error("IO error: {0}")] IoError(#[from] std::io::Error), #[cfg(feature = "zip")] diff --git a/src/reader.rs b/src/reader.rs index faa0eb8..839f6b5 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -616,22 +616,11 @@ where Event::Start(ref mut e) => match e.local_name() { b"href" => icon.href = Some(self.read_str()?), b"refreshMode" => { - icon.refresh_mode = match self.read_str()?.as_str() { - "onChange" => Some(RefreshMode::OnChange), - "onInterval" => Some(RefreshMode::OnInterval), - "onExpire" => Some(RefreshMode::OnExpire), - _ => None, - } + icon.refresh_mode = Some(RefreshMode::from_str(&self.read_str()?)?); } b"refreshInterval" => icon.refresh_interval = self.read_float()?, b"viewRefreshMode" => { - icon.view_refresh_mode = match self.read_str()?.as_str() { - "never" => Some(ViewRefreshMode::Never), - "onRequest" => Some(ViewRefreshMode::OnRequest), - "onStop" => Some(ViewRefreshMode::OnStop), - "onRegion" => Some(ViewRefreshMode::OnRegion), - _ => None, - } + icon.view_refresh_mode = Some(ViewRefreshMode::from_str(&self.read_str()?)?) } b"viewRefreshTime" => icon.view_refresh_time = self.read_float()?, b"viewBoundScale" => icon.view_bound_scale = self.read_float()?, @@ -661,22 +650,11 @@ where Event::Start(ref mut e) => match e.local_name() { b"href" => link.href = Some(self.read_str()?), b"refreshMode" => { - link.refresh_mode = match self.read_str()?.as_str() { - "onChange" => Some(RefreshMode::OnChange), - "onInterval" => Some(RefreshMode::OnInterval), - "onExpire" => Some(RefreshMode::OnExpire), - _ => None, - } + link.refresh_mode = Some(RefreshMode::from_str(&self.read_str()?)?); } b"refreshInterval" => link.refresh_interval = self.read_float()?, b"viewRefreshMode" => { - link.view_refresh_mode = match self.read_str()?.as_str() { - "never" => Some(ViewRefreshMode::Never), - "onRequest" => Some(ViewRefreshMode::OnRequest), - "onStop" => Some(ViewRefreshMode::OnStop), - "onRegion" => Some(ViewRefreshMode::OnRegion), - _ => None, - } + link.view_refresh_mode = Some(ViewRefreshMode::from_str(&self.read_str()?)?) } b"viewRefreshTime" => link.view_refresh_time = self.read_float()?, b"viewBoundScale" => link.view_bound_scale = self.read_float()?, diff --git a/src/types/link.rs b/src/types/link.rs index 33a604f..9a00493 100644 --- a/src/types/link.rs +++ b/src/types/link.rs @@ -1,7 +1,8 @@ -use std::{ - collections::HashMap, - fmt::{Display, Formatter, Result}, -}; +use std::collections::HashMap; +use std::fmt; +use std::str::FromStr; + +use crate::Error; /// `kml:Link`, [13.1](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#974) in the KML specification. #[derive(Clone, Debug, PartialEq)] @@ -77,8 +78,21 @@ impl Default for RefreshMode { } } -impl Display for RefreshMode { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { +impl FromStr for RefreshMode { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "onChange" => Ok(Self::OnChange), + "onInterval" => Ok(Self::OnInterval), + "onExpire" => Ok(Self::OnExpire), + v => Err(Error::InvalidRefreshMode(v.to_string())), + } + } +} + +impl fmt::Display for RefreshMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { RefreshMode::OnChange => write!(f, "onChange"), RefreshMode::OnInterval => write!(f, "onInterval"), @@ -102,8 +116,22 @@ impl Default for ViewRefreshMode { } } -impl Display for ViewRefreshMode { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { +impl FromStr for ViewRefreshMode { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "never" => Ok(Self::Never), + "onRequest" => Ok(Self::OnRequest), + "onStop" => Ok(Self::OnStop), + "onRegion" => Ok(Self::OnRegion), + v => Err(Error::InvalidViewRefreshMode(v.to_string())), + } + } +} + +impl fmt::Display for ViewRefreshMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ViewRefreshMode::Never => write!(f, "never"), ViewRefreshMode::OnRequest => write!(f, "onRequest"), @@ -112,3 +140,44 @@ impl Display for ViewRefreshMode { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_refresh_mode_from_str() { + assert_eq!( + RefreshMode::OnChange, + RefreshMode::from_str("onChange").unwrap() + ); + assert_eq!( + RefreshMode::OnExpire, + RefreshMode::from_str("onExpire").unwrap() + ); + assert_eq!( + RefreshMode::OnInterval, + RefreshMode::from_str("onInterval").unwrap() + ); + } + + #[test] + fn test_view_refresh_mode_from_str() { + assert_eq!( + ViewRefreshMode::Never, + ViewRefreshMode::from_str("never").unwrap() + ); + assert_eq!( + ViewRefreshMode::OnRegion, + ViewRefreshMode::from_str("onRegion").unwrap() + ); + assert_eq!( + ViewRefreshMode::OnRequest, + ViewRefreshMode::from_str("onRequest").unwrap() + ); + assert_eq!( + ViewRefreshMode::OnStop, + ViewRefreshMode::from_str("onStop").unwrap() + ); + } +}