summaryrefslogtreecommitdiff
path: root/src/gloss_list.rs
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2023-03-04 15:01:38 +0100
committerHampusM <hampus@hampusmat.com>2023-03-04 15:01:38 +0100
commitc49f62a8c369e58ee5cbffe853a436e724789d2f (patch)
treea219e8377a3cac00990cebaa9b19507a48b64b2e /src/gloss_list.rs
parentdcf34eff5651294d7c579431143b8638fbe338df (diff)
feat: add gloss list support
Diffstat (limited to 'src/gloss_list.rs')
-rw-r--r--src/gloss_list.rs267
1 files changed, 267 insertions, 0 deletions
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<Entry>,
+}
+
+impl GlossList
+{
+ /// Returns a new `GlossList`.
+ pub fn new(entries: impl IntoIterator<Item = Entry>) -> 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<Self, Self::Error>
+ {
+ let entries = elements
+ .get_all_tagged_elements_with_name("glossentry")
+ .into_iter()
+ .map(|entry_elem| Entry::from_elements(entry_elem.child_elements()))
+ .collect::<Result<Vec<_>, _>>()?;
+
+ 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<Paragraph>,
+}
+
+impl Entry
+{
+ /// Returns a new `Entry`.
+ pub fn new(term: String, definition: impl IntoIterator<Item = Paragraph>) -> 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<Self, Self::Error>
+ {
+ 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::<Result<Vec<_>, _>>()
+ .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<ParagraphError>),
+}
+
+#[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())]),
+ ]
+ );
+ }
+}