diff --git a/src/basic.rs b/src/basic.rs index 808254c..884924c 100644 --- a/src/basic.rs +++ b/src/basic.rs @@ -241,3 +241,22 @@ impl std::fmt::Display for Number { } } } + +/// Duration. +#[derive(Debug, PartialEq, Eq)] +pub enum Duration { + Duration(chrono::Duration), + Other(Other), +} + +impl Duration { + pub fn parse_from_int_string(s: &str) -> Self { + match s.parse::() { + Ok(x) => Self::Duration(chrono::Duration::seconds(x as i64)), + Err(_) => Self::Other(( + s.to_string(), + "should be a non-negative integer".to_string(), + )), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index b9a724a..3fae488 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ pub mod itunes; /// Implementation details adapted from . pub mod podcast; -pub use crate::basic::{Bool, Float, Integer, Number}; +pub use crate::basic::{Bool, Duration, Float, Integer, Number}; pub use crate::language::{ Language, LanguageChinese, LanguageDutch, LanguageEnglish, LanguageFrench, LanguageGerman, LanguageItalian, LanguagePortugese, LanguageRomanian, LanguageRussian, LanguageSpanish, diff --git a/src/parse.rs b/src/parse.rs index 52409b7..33df607 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -745,6 +745,12 @@ fn parse_podcast_value(value: roxmltree::Node) -> podcast::Value { .value_recipient .push(parse_podcast_value_recipient(child)); } + + if let (Some(NS_PODCAST_1 | NS_PODCAST_2), "valueTimeSplit") = (namespace, tag_name) { + new_value + .value_time_split + .push(parse_podcast_value_time_split(child)); + } } new_value @@ -781,6 +787,53 @@ fn parse_podcast_value_recipient(value_recipient: roxmltree::Node) -> podcast::V new_value_recipient } +fn parse_podcast_value_time_split(value_time_split: roxmltree::Node) -> podcast::ValueTimeSplit { + let mut new_value_time_split = podcast::ValueTimeSplit { + ..Default::default() + }; + + for attribute in value_time_split.attributes() { + match attribute.name() { + "startTime" => { + new_value_time_split.start_time = + Some(basic::Duration::parse_from_int_string(attribute.value())) + } + "duration" => { + new_value_time_split.duration = + Some(basic::Duration::parse_from_int_string(attribute.value())) + } + "remoteStartTime" => { + new_value_time_split.remote_start_time = + Some(basic::Duration::parse_from_int_string(attribute.value())) + } + "remotePercentage" => { + new_value_time_split.remote_percentage = + Some(Integer::parse(attribute.value(), NumberConstraint::None)) + } + _ => {} + } + } + + for child in value_time_split.children() { + let namespace = child.tag_name().namespace(); + let tag_name = child.tag_name().name(); + + if let (Some(NS_PODCAST_1 | NS_PODCAST_2), "valueRecipient") = (namespace, tag_name) { + new_value_time_split + .value_recipient + .push(parse_podcast_value_recipient(child)); + } + + if let (Some(NS_PODCAST_1 | NS_PODCAST_2), "remoteItem") = (namespace, tag_name) { + new_value_time_split + .remote_item + .push(parse_podcast_remote_item(child)); + } + } + + new_value_time_split +} + pub fn parse_podcast_images(images: roxmltree::Node) -> podcast::Images { let mut new_images = podcast::Images { ..Default::default() diff --git a/src/podcast.rs b/src/podcast.rs index 95189fa..e629c79 100644 --- a/src/podcast.rs +++ b/src/podcast.rs @@ -174,6 +174,7 @@ pub struct Value { pub method: Option, pub suggested: Option, pub value_recipient: Vec, + pub value_time_split: Vec, } /// Destination for payments to be sent to during consumption of enclosed media. @@ -269,3 +270,14 @@ pub struct RemoteItem { pub item_guid: Option, pub medium: Option, } + +/// This element allows different value splits for a certain period of time. +#[derive(Debug, PartialEq, Eq, Default)] +pub struct ValueTimeSplit { + pub start_time: Option, + pub duration: Option, + pub remote_start_time: Option, + pub remote_percentage: Option, + pub remote_item: Vec, + pub value_recipient: Vec, +} diff --git a/tests/data/podcast_namespace_example.xml b/tests/data/podcast_namespace_example.xml index 7028ddb..e834ffa 100644 --- a/tests/data/podcast_namespace_example.xml +++ b/tests/data/podcast_namespace_example.xml @@ -54,6 +54,10 @@ + + + + Coming April 1st, 2021 diff --git a/tests/tests.rs b/tests/tests.rs index 49d6598..90bb93f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -178,6 +178,7 @@ fn deserialize() { ..Default::default() }, }, + value_time_split: vec![], }], podcast_medium: vec![podcast::Medium::Music], podcast_images: vec![podcast::Images { @@ -312,6 +313,7 @@ fn deserialize() { method: Some(podcast::ValueMethod::Keysend), suggested: Some(Float::Ok(0.00000015)), value_recipient: vec!{}, + value_time_split: vec![], }], podcast_social_interact: vec! { podcast::SocialInteract{ @@ -462,6 +464,21 @@ fn deserialize() { ..Default::default() }, }, + value_time_split: vec![ + podcast::ValueTimeSplit{ + start_time: Some(badpod::Duration::Duration(chrono::Duration::minutes(1))), + duration: Some(badpod::Duration::Duration(chrono::Duration::minutes(3) + chrono::Duration::seconds(57))), + remote_start_time: None, + remote_percentage: Some(Integer::Ok(95)), + value_recipient: vec![], + remote_item: vec![podcast::RemoteItem{ + feed_guid: Some(podcast::Guid::Ok("a94f5cc9-8c58-55fc-91fe-a324087a655b".into())), + feed_url: None, + item_guid: Some(GuidValue::Url(url::Url::parse("https://podcastindex.org/podcast/4148683#1").unwrap())), + medium: Some(podcast::Medium::Music), + }], + } + ], }], podcast_trailer: vec!{ podcast::Trailer{ @@ -736,6 +753,7 @@ fn deserialize() { ..Default::default() }, }, + value_time_split: vec![], }], podcast_social_interact: vec!{ podcast::SocialInteract{ @@ -930,6 +948,7 @@ fn deserialize() { ..Default::default() }, }, + value_time_split: vec![], }], ..Default::default() },