From c49f62a8c369e58ee5cbffe853a436e724789d2f Mon Sep 17 00:00:00 2001 From: HampusM Date: Sat, 4 Mar 2023 15:01:38 +0100 Subject: feat: add gloss list support --- src/description.rs | 13 +++ src/gloss_list.rs | 267 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 3 files changed, 281 insertions(+) create mode 100644 src/gloss_list.rs (limited to 'src') diff --git a/src/description.rs b/src/description.rs index 92af9c1..4f3b08a 100644 --- a/src/description.rs +++ b/src/description.rs @@ -1,4 +1,5 @@ //! Reference entry description. +use crate::gloss_list::{Error as GlossListError, GlossList}; use crate::itemized_list::{Error as ItemizedListError, ItemizedList}; use crate::table::{Error as TableError, Informal, Table}; use crate::variable_list::{Error as VariableListError, VariableList}; @@ -111,6 +112,11 @@ impl FromElements for Description .map(Part::Table) .map_err(Self::Error::InvalidTable), ), + "glosslist" => Some( + GlossList::from_elements(part_elem.child_elements()) + .map(Part::GlossList) + .map_err(Self::Error::InvalidGlossList), + ), "title" => None, name => Some(Err(Self::Error::UnknownPartFound(name.to_string()))), }) @@ -150,6 +156,10 @@ pub enum Error /// Invalid table. #[error("Invalid table")] InvalidTable(#[source] TableError), + + /// Invalid gloss list. + #[error("Invalid gloss list")] + InvalidGlossList(#[source] GlossListError), } /// Description part. @@ -173,6 +183,9 @@ pub enum Part /// Table. Table(Table), + + /// Gloss list. + GlossList(GlossList), } /// Reference entry description paragraph. diff --git a/src/gloss_list.rs b/src/gloss_list.rs new file mode 100644 index 0000000..74cf6f1 --- /dev/null +++ b/src/gloss_list.rs @@ -0,0 +1,267 @@ +//! Gloss list. +use crate::description::{Paragraph, ParagraphError}; +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::description::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())]), + ] + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index c02716f..9dfc888 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ use crate::xml::element::{Elements, FromElements}; use crate::xml::parser::{Error as ParserError, Parser}; pub mod description; +pub mod gloss_list; pub mod itemized_list; pub mod table; pub mod variable_list; -- cgit v1.2.3-18-g5258