diff options
-rw-r--r-- | src/description.rs | 17 | ||||
-rw-r--r-- | src/itemized_list.rs | 195 | ||||
-rw-r--r-- | src/lib.rs | 1 |
3 files changed, 212 insertions, 1 deletions
diff --git a/src/description.rs b/src/description.rs index dfa521b..f536b4f 100644 --- a/src/description.rs +++ b/src/description.rs @@ -1,4 +1,5 @@ //! Reference entry description. +use crate::itemized_list::{Error as ItemizedListError, ItemizedList}; use crate::variable_list::{Error as VariableListError, VariableList}; use crate::xml::element::{Elements, FromElements, Tagged}; @@ -164,6 +165,9 @@ pub enum ParagraphPart /// Variable list part. VariableList(VariableList), + + /// Itemized list part. + ItemizedList(ItemizedList), } impl FromElements for ParagraphPart @@ -195,7 +199,7 @@ impl ParagraphPart "function" => Self::Function, "parameter" => Self::Parameter, "citerefentry" => Self::Entry, - "variablelist" => |_| { + "variablelist" | "itemizedlist" => |_| { unreachable!(); }, _ => { @@ -226,6 +230,13 @@ impl ParagraphPart return Ok(Self::VariableList(variable_list)); } + if tagged_element.name() == "itemizedlist" { + let itemized_list = + ItemizedList::from_elements(tagged_element.child_elements())?; + + return Ok(Self::ItemizedList(itemized_list)); + } + let text_element = tagged_element .child_elements() .get_first_text_element() @@ -258,4 +269,8 @@ pub enum ParagraphPartError /// Invalid variable list. #[error("Invalid variable list")] InvalidVariableList(#[from] VariableListError), + + /// Invalid itemized list. + #[error("Invalid itemized list")] + InvalidItemizedList(#[from] ItemizedListError), } 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<Item>, +} + +impl ItemizedList +{ + /// Returns a new `ItemizedList`. + pub fn new(entries: impl IntoIterator<Item = Item>) -> 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<Self, Self::Error> + { + let items = elements + .get_all_tagged_elements_with_name("listitem") + .into_iter() + .map(|entry_elem| Item::from_elements(entry_elem.child_elements())) + .collect::<Result<Vec<_>, _>>()?; + + 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<Paragraph>, +} + +impl Item +{ + /// Returns a new `Item`. + pub fn new(paragraphs: impl IntoIterator<Item = Paragraph>) -> 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<Self, Self::Error> + { + 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::<Result<Vec<_>, _>>() + .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<ParagraphPartError>), + + /// Invalid item paragraph. + #[error("Invalid item paragraph")] + InvalidItemParagraph(#[source] Box<ParagraphError>), +} + +#[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())]), + ] + ); + } +} @@ -14,6 +14,7 @@ use crate::xml::element::{Attribute, Elements, FromElements}; use crate::xml::parser::{Error as ParserError, Parser}; pub mod description; +pub mod itemized_list; pub mod variable_list; mod xml; |