From 2d633644a53dfe278b681a911b2eaea1230a07cc Mon Sep 17 00:00:00 2001 From: HampusM Date: Sun, 26 Feb 2023 12:12:07 +0100 Subject: feat: add itemized list support --- src/itemized_list.rs | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 src/itemized_list.rs (limited to 'src/itemized_list.rs') diff --git a/src/itemized_list.rs b/src/itemized_list.rs new file mode 100644 index 0000000..efe43b5 --- /dev/null +++ b/src/itemized_list.rs @@ -0,0 +1,195 @@ +//! 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())]), + ] + ); + } +} -- cgit v1.2.3-18-g5258