From e225d7344cef05f03676f6579415999478328ead Mon Sep 17 00:00:00 2001 From: HampusM Date: Sat, 25 Feb 2023 20:36:06 +0100 Subject: feat: add variable list support --- src/variable_list.rs | 264 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 src/variable_list.rs (limited to 'src/variable_list.rs') diff --git a/src/variable_list.rs b/src/variable_list.rs new file mode 100644 index 0000000..2c839f0 --- /dev/null +++ b/src/variable_list.rs @@ -0,0 +1,264 @@ +//! Variable list. + +use crate::description::{Paragraph, ParagraphError, ParagraphPart, ParagraphPartError}; +use crate::xml::element::{Element, FromElements}; + +/// Variable list. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VariableList +{ + entries: Vec, +} + +impl VariableList +{ + /// Returns a new `VariableList`. + pub fn new(entries: impl IntoIterator) -> Self + { + Self { + entries: entries.into_iter().collect(), + } + } + + /// Returns the variable list entries. + #[must_use] + pub fn entries(&self) -> &[Entry] + { + &self.entries + } +} + +impl FromElements for VariableList +{ + type Error = Error; + + fn from_elements( + elements: &crate::xml::element::Elements, + ) -> Result + { + let entries = elements + .get_all_tagged_elements_with_name("varlistentry") + .into_iter() + .map(|entry_elem| Entry::from_elements(entry_elem.child_elements())) + .collect::, _>>()?; + + Ok(Self { entries }) + } +} + +/// [`VariableList`] error. +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + /// Invalid entry. + #[error("Invalid entry")] + InvalidEntry(#[from] EntryError), +} + +/// Variable list entry. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Entry +{ + term: ParagraphPart, + item: Vec, +} + +impl Entry +{ + /// Returns a new `Entry`. + pub fn new(term: ParagraphPart, item: impl IntoIterator) -> Self + { + Self { + term, + item: item.into_iter().collect(), + } + } + + /// Returns the variable list entry term. + #[must_use] + pub fn term(&self) -> &ParagraphPart + { + &self.term + } + + /// Returns the variable list entry item. + #[must_use] + pub fn item(&self) -> &[Paragraph] + { + &self.item + } +} + +impl FromElements for Entry +{ + type Error = EntryError; + + fn from_elements( + elements: &crate::xml::element::Elements, + ) -> Result + { + let term_element = elements + .get_first_tagged_with_name("term") + .ok_or(Self::Error::MissingTerm)?; + + let term = ParagraphPart::from_elements(term_element.child_elements()) + .map_err(|err| Self::Error::InvalidTerm(Box::new(err)))?; + + let item_element = elements + .get_first_tagged_with_name("listitem") + .ok_or(Self::Error::MissingListItem)?; + + let item = item_element + .child_elements() + .into_iter() + .filter_map(|elem| match elem { + Element::Tagged(tagged_elem) if tagged_elem.name() == "para" => { + Some(tagged_elem) + } + _ => None, + }) + .map(|para_elem| Paragraph::from_elements(para_elem.child_elements())) + .collect::, _>>() + .map_err(|err| Self::Error::InvalidItemParagraph(Box::new(err)))?; + + Ok(Self { term, item }) + } +} + +/// [`Entry`] error. +#[derive(Debug, thiserror::Error)] +pub enum EntryError +{ + /// Missing tagged element with name 'term'. + #[error("Missing tagged element with name 'term'")] + MissingTerm, + + /// Missing tagged element with name 'listitem'. + #[error("Missing tagged element with name 'listitem'")] + MissingListItem, + + /// Invalid term. + #[error("Invalid term")] + InvalidTerm(#[source] Box), + + /// Invalid item paragraph. + #[error("Invalid item paragraph")] + InvalidItemParagraph(#[source] Box), +} + +#[cfg(test)] +mod tests +{ + use super::*; + use crate::xml::element::{Element, Elements, Tagged}; + + #[test] + fn variable_list_from_elements_works() + { + let variable_list = VariableList::from_elements(&Elements::from([ + Element::Tagged(Tagged::new( + &"varlistentry", + [ + Element::Tagged(Tagged::new( + &"term", + [Element::Text("foobar".to_string())], + [], + )), + Element::Tagged(Tagged::new( + &"listitem", + [Element::Tagged(Tagged::new( + &"para", + [Element::Text("Hello hello.".to_string())], + [], + ))], + [], + )), + ], + [], + )), + Element::Tagged(Tagged::new( + &"varlistentry", + [ + Element::Tagged(Tagged::new( + &"term", + [Element::Text("Hello there".to_string())], + [], + )), + Element::Tagged(Tagged::new( + &"listitem", + [Element::Tagged(Tagged::new( + &"para", + [ + Element::Text("Tosche station".to_string()), + Element::Text("Power converters".to_string()), + ], + [], + ))], + [], + )), + ], + [], + )), + Element::Tagged(Tagged::new( + &"varlistentry", + [ + Element::Tagged(Tagged::new( + &"term", + [Element::Text("There is another".to_string())], + [], + )), + Element::Tagged(Tagged::new( + &"listitem", + [Element::Tagged(Tagged::new( + &"para", + [Element::Text("It's a trap".to_string())], + [], + ))], + [], + )), + ], + [], + )), + ])) + .expect("Expected Ok"); + + assert_eq!(variable_list.entries.len(), 3); + } + + #[test] + fn entry_from_elements_works() + { + let entry = Entry::from_elements(&Elements::from([ + Element::Tagged(Tagged::new(&"term", [Element::Text("Foo".to_string())], [])), + Element::Tagged(Tagged::new( + &"listitem", + [ + Element::Tagged(Tagged::new( + &"para", + [Element::Text("bar".to_string())], + [], + )), + Element::Tagged(Tagged::new( + &"para", + [Element::Text("Hello there.".to_string())], + [], + )), + ], + [], + )), + ])) + .expect("Expected Ok"); + + assert!( + matches!(entry.term, ParagraphPart::Text(para_part) if para_part == "Foo") + ); + + assert_eq!( + entry.item, + vec![ + Paragraph::new([ParagraphPart::Text("bar".to_string())]), + Paragraph::new([ParagraphPart::Text("Hello there.".to_string())]), + ] + ); + } +} -- cgit v1.2.3-18-g5258