//! Gloss list. use crate::paragraph::{Error as ParagraphError, Paragraph}; use crate::xml::element::{Element, FromElements}; /// Gloss list. #[derive(Debug, Clone, PartialEq, Eq)] pub struct GlossList { entries: Vec, } impl GlossList { /// Returns a new `GlossList`. pub fn new(entries: impl IntoIterator) -> Self { Self { entries: entries.into_iter().collect(), } } /// Returns the gloss list entries. #[must_use] pub fn entries(&self) -> &[Entry] { &self.entries } } impl FromElements for GlossList { type Error = Error; fn from_elements( elements: &crate::xml::element::Elements, ) -> Result { let entries = elements .get_all_tagged_elements_with_name("glossentry") .into_iter() .map(|entry_elem| Entry::from_elements(entry_elem.child_elements())) .collect::, _>>()?; Ok(Self { entries }) } } /// [`GlossList`] error. #[derive(Debug, thiserror::Error)] pub enum Error { /// Invalid entry. #[error("Invalid entry")] InvalidEntry(#[from] EntryError), } /// Gloss list entry. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Entry { term: String, definition: Vec, } impl Entry { /// Returns a new `Entry`. pub fn new(term: String, definition: impl IntoIterator) -> Self { Self { term, definition: definition.into_iter().collect(), } } /// Returns the gloss list entry term. #[must_use] pub fn term(&self) -> &str { &self.term } /// Returns the gloss list entry definition. #[must_use] pub fn definition(&self) -> &[Paragraph] { &self.definition } } 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("glossterm") .ok_or(Self::Error::MissingTerm)?; let term = term_element .child_elements() .get_first_text_element() .cloned() .unwrap_or_default(); let def_element = elements .get_first_tagged_with_name("glossdef") .ok_or(Self::Error::MissingDefinition)?; let definition = def_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::InvalidDefinitionParagraph(Box::new(err)))?; Ok(Self { term, definition }) } } /// [`Entry`] error. #[derive(Debug, thiserror::Error)] pub enum EntryError { /// Missing term. #[error("Missing term")] MissingTerm, /// Missing definition. #[error("Missing definition")] MissingDefinition, /// Invalid definition paragraph. #[error("Invalid definition paragraph")] InvalidDefinitionParagraph(#[source] Box), } #[cfg(test)] mod tests { use pretty_assertions::assert_str_eq; use super::*; use crate::paragraph::Part as ParagraphPart; use crate::xml::element::{Element, Elements, Tagged}; #[test] fn gloss_list_from_elements_works() { let gloss_list = GlossList::from_elements(&Elements::from([ Element::Tagged(Tagged::new( &"glossentry", [ Element::Tagged(Tagged::new( &"glossterm", [Element::Text("foobar".to_string())], [], )), Element::Tagged(Tagged::new( &"glossdef", [Element::Tagged(Tagged::new( &"para", [Element::Text("Hello hello.".to_string())], [], ))], [], )), ], [], )), Element::Tagged(Tagged::new( &"glossentry", [ Element::Tagged(Tagged::new( &"glossterm", [Element::Text("Hello there".to_string())], [], )), Element::Tagged(Tagged::new( &"glossdef", [Element::Tagged(Tagged::new( &"para", [ Element::Text("Tosche station".to_string()), Element::Text("Power converters".to_string()), ], [], ))], [], )), ], [], )), Element::Tagged(Tagged::new( &"glossentry", [ Element::Tagged(Tagged::new( &"glossterm", [Element::Text("There is another".to_string())], [], )), Element::Tagged(Tagged::new( &"glossdef", [Element::Tagged(Tagged::new( &"para", [Element::Text("It's a trap".to_string())], [], ))], [], )), ], [], )), ])) .expect("Expected Ok"); assert_eq!(gloss_list.entries.len(), 3); } #[test] fn entry_from_elements_works() { let entry = Entry::from_elements(&Elements::from([ Element::Tagged(Tagged::new( &"glossterm", [Element::Text("Foo".to_string())], [], )), Element::Tagged(Tagged::new( &"glossdef", [ 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_str_eq!(entry.term, "Foo"); assert_eq!( entry.definition, vec![ Paragraph::new([ParagraphPart::Text("bar".to_string())]), Paragraph::new([ParagraphPart::Text("Hello there.".to_string())]), ] ); } }