//! Itemized list. use crate::description::{Paragraph, ParagraphError, ParagraphPartError}; use crate::xml::element::{Element, FromElements}; /// Itemized list. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ItemizedList { items: Vec, } impl ItemizedList { /// Returns a new `ItemizedList`. pub fn new(entries: impl IntoIterator) -> Self { Self { items: entries.into_iter().collect(), } } /// Returns the itemized list's items. #[must_use] pub fn items(&self) -> &[Item] { &self.items } } impl FromElements for ItemizedList { type Error = Error; fn from_elements( elements: &crate::xml::element::Elements, ) -> Result { let items = elements .get_all_tagged_elements_with_name("listitem") .into_iter() .map(|entry_elem| Item::from_elements(entry_elem.child_elements())) .collect::, _>>()?; Ok(Self { items }) } } /// [`ItemizedList`] error. #[derive(Debug, thiserror::Error)] pub enum Error { /// Invalid item. #[error("Invalid item")] InvalidItem(#[from] ItemError), } /// Itemized list item. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Item { paragraphs: Vec, } impl Item { /// Returns a new `Item`. pub fn new(paragraphs: impl IntoIterator) -> Self { Self { paragraphs: paragraphs.into_iter().collect(), } } /// Returns the itemized list item paragraphs. #[must_use] pub fn paragraphs(&self) -> &[Paragraph] { &self.paragraphs } } impl FromElements for Item { type Error = ItemError; fn from_elements( elements: &crate::xml::element::Elements, ) -> Result { let item = 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 { paragraphs: item }) } } /// [`Item`] error. #[derive(Debug, thiserror::Error)] pub enum ItemError { /// 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::description::ParagraphPart; use crate::xml::element::{Element, Elements, Tagged}; #[test] fn itemized_list_from_elements_works() { let itemized_list = ItemizedList::from_elements(&Elements::from([ Element::Tagged(Tagged::new( &"listitem", [Element::Tagged(Tagged::new( &"para", [Element::Text("Hello hello.".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( &"listitem", [Element::Tagged(Tagged::new( &"para", [Element::Text("It's a trap".to_string())], [], ))], [], )), ])) .expect("Expected Ok"); assert_eq!(itemized_list.items.len(), 3); } #[test] fn item_from_elements_works() { let item = Item::from_elements(&Elements::from([ 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_eq!( item.paragraphs, vec![ Paragraph::new([ParagraphPart::Text("bar".to_string())]), Paragraph::new([ParagraphPart::Text("Hello there.".to_string())]), ] ); } }