//! 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())]), ] ); } }