summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2023-02-25 20:36:06 +0100
committerHampusM <hampus@hampusmat.com>2023-02-25 20:36:06 +0100
commite225d7344cef05f03676f6579415999478328ead (patch)
tree1ba58a8920c86d157fc9faa0e2c2739dc8d2b605 /src
parent5a00534bd4c0a44d063c592a471b8b546f1b5925 (diff)
feat: add variable list support
Diffstat (limited to 'src')
-rw-r--r--src/description.rs89
-rw-r--r--src/lib.rs1
-rw-r--r--src/variable_list.rs264
-rw-r--r--src/xml/element.rs11
4 files changed, 348 insertions, 17 deletions
diff --git a/src/description.rs b/src/description.rs
index adc5324..dfa521b 100644
--- a/src/description.rs
+++ b/src/description.rs
@@ -1,11 +1,12 @@
//! Reference entry description.
+use crate::variable_list::{Error as VariableListError, VariableList};
use crate::xml::element::{Elements, FromElements, Tagged};
/// Reference entry description.
-#[derive(Debug)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Description
{
- paragraphs: Vec<Paragraph>,
+ parts: Vec<Part>,
}
impl Description
@@ -14,16 +15,14 @@ impl Description
#[must_use]
pub fn new() -> Self
{
- Self {
- paragraphs: Vec::new(),
- }
+ Self { parts: Vec::new() }
}
- /// Returns the reference description's paragraphs.
+ /// Returns the description's parts.
#[must_use]
- pub fn paragraphs(&self) -> &[Paragraph]
+ pub fn parts(&self) -> &[Part]
{
- &self.paragraphs
+ &self.parts
}
}
@@ -41,15 +40,27 @@ impl FromElements for Description
fn from_elements(elements: &Elements) -> Result<Self, Self::Error>
{
- let paragraphs = elements
- .get_all_tagged_elements_with_name("para")
+ let parts = elements
+ .get_all_tagged_elements()
.into_iter()
- .map(|paragraph_element| {
- Paragraph::from_elements(paragraph_element.child_elements())
+ .filter_map(|part_elem| match part_elem.name() {
+ "para" => Some(
+ Paragraph::from_elements(part_elem.child_elements())
+ .map(Part::Paragraph)
+ .map_err(Self::Error::InvalidParagraph),
+ ),
+
+ "variablelist" => Some(
+ VariableList::from_elements(part_elem.child_elements())
+ .map(Part::VariableList)
+ .map_err(Self::Error::InvalidVariableList),
+ ),
+ "title" => None,
+ name => Some(Err(Self::Error::UnknownPartFound(name.to_string()))),
})
- .collect::<Result<Vec<_>, _>>()?;
+ .collect::<Result<Vec<_>, Self::Error>>()?;
- Ok(Description { paragraphs })
+ Ok(Description { parts })
}
}
@@ -57,13 +68,32 @@ impl FromElements for Description
#[derive(Debug, thiserror::Error)]
pub enum Error
{
+ /// Unknown part element found.
+ #[error("Unknown part element with name '{0}' found")]
+ UnknownPartFound(String),
+
/// Invalid paragraph.
#[error("Invalid paragraph")]
- InvalidParagraph(#[from] ParagraphError),
+ InvalidParagraph(#[source] ParagraphError),
+
+ /// Invalid variable list.
+ #[error("Invalid variable list")]
+ InvalidVariableList(#[source] VariableListError),
+}
+
+/// Description part.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Part
+{
+ /// Paragraph.
+ Paragraph(Paragraph),
+
+ /// Variable list.
+ VariableList(VariableList),
}
/// Reference entry description paragraph.
-#[derive(Debug)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Paragraph
{
parts: Vec<ParagraphPart>,
@@ -71,6 +101,14 @@ pub struct Paragraph
impl Paragraph
{
+ /// Returns a new `Paragraph`.
+ pub fn new(parts: impl IntoIterator<Item = ParagraphPart>) -> Self
+ {
+ Self {
+ parts: parts.into_iter().collect(),
+ }
+ }
+
/// Returns the parts of the paragraph.
#[must_use]
pub fn parts(&self) -> &[ParagraphPart]
@@ -106,7 +144,7 @@ pub enum ParagraphError
}
/// Reference entry description paragraph part.
-#[derive(Debug)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParagraphPart
{
/// Text part.
@@ -123,6 +161,9 @@ pub enum ParagraphPart
/// Reference entry citation part.
Entry(String),
+
+ /// Variable list part.
+ VariableList(VariableList),
}
impl FromElements for ParagraphPart
@@ -154,6 +195,9 @@ impl ParagraphPart
"function" => Self::Function,
"parameter" => Self::Parameter,
"citerefentry" => Self::Entry,
+ "variablelist" => |_| {
+ unreachable!();
+ },
_ => {
return Err(<Self as FromElements>::Error::UnknownPart(
tagged_element.name().to_string(),
@@ -175,6 +219,13 @@ impl ParagraphPart
return Ok(Self::Entry(title.clone()));
}
+ if tagged_element.name() == "variablelist" {
+ let variable_list =
+ VariableList::from_elements(tagged_element.child_elements())?;
+
+ return Ok(Self::VariableList(variable_list));
+ }
+
let text_element = tagged_element
.child_elements()
.get_first_text_element()
@@ -203,4 +254,8 @@ pub enum ParagraphPartError
/// No entry title found.
#[error("No entry title found")]
NoEntryTitleFound,
+
+ /// Invalid variable list.
+ #[error("Invalid variable list")]
+ InvalidVariableList(#[from] VariableListError),
}
diff --git a/src/lib.rs b/src/lib.rs
index 11d376c..b6343de 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,6 +14,7 @@ use crate::xml::element::{Attribute, Elements, FromElements};
use crate::xml::parser::{Error as ParserError, Parser};
pub mod description;
+pub mod variable_list;
mod xml;
diff --git a/src/variable_list.rs b/src/variable_list.rs
new file mode 100644
index 0000000..2c839f0
--- /dev/null
+++ b/src/variable_list.rs
@@ -0,0 +1,264 @@
+//! Variable list.
+
+use crate::description::{Paragraph, ParagraphError, ParagraphPart, ParagraphPartError};
+use crate::xml::element::{Element, FromElements};
+
+/// Variable list.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct VariableList
+{
+ entries: Vec<Entry>,
+}
+
+impl VariableList
+{
+ /// Returns a new `VariableList`.
+ pub fn new(entries: impl IntoIterator<Item = Entry>) -> Self
+ {
+ Self {
+ entries: entries.into_iter().collect(),
+ }
+ }
+
+ /// Returns the variable list entries.
+ #[must_use]
+ pub fn entries(&self) -> &[Entry]
+ {
+ &self.entries
+ }
+}
+
+impl FromElements for VariableList
+{
+ type Error = Error;
+
+ fn from_elements(
+ elements: &crate::xml::element::Elements,
+ ) -> Result<Self, Self::Error>
+ {
+ let entries = elements
+ .get_all_tagged_elements_with_name("varlistentry")
+ .into_iter()
+ .map(|entry_elem| Entry::from_elements(entry_elem.child_elements()))
+ .collect::<Result<Vec<_>, _>>()?;
+
+ Ok(Self { entries })
+ }
+}
+
+/// [`VariableList`] error.
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ /// Invalid entry.
+ #[error("Invalid entry")]
+ InvalidEntry(#[from] EntryError),
+}
+
+/// Variable list entry.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Entry
+{
+ term: ParagraphPart,
+ item: Vec<Paragraph>,
+}
+
+impl Entry
+{
+ /// Returns a new `Entry`.
+ pub fn new(term: ParagraphPart, item: impl IntoIterator<Item = Paragraph>) -> Self
+ {
+ Self {
+ term,
+ item: item.into_iter().collect(),
+ }
+ }
+
+ /// Returns the variable list entry term.
+ #[must_use]
+ pub fn term(&self) -> &ParagraphPart
+ {
+ &self.term
+ }
+
+ /// Returns the variable list entry item.
+ #[must_use]
+ pub fn item(&self) -> &[Paragraph]
+ {
+ &self.item
+ }
+}
+
+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("term")
+ .ok_or(Self::Error::MissingTerm)?;
+
+ let term = ParagraphPart::from_elements(term_element.child_elements())
+ .map_err(|err| Self::Error::InvalidTerm(Box::new(err)))?;
+
+ let item_element = elements
+ .get_first_tagged_with_name("listitem")
+ .ok_or(Self::Error::MissingListItem)?;
+
+ let item = item_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::InvalidItemParagraph(Box::new(err)))?;
+
+ Ok(Self { term, item })
+ }
+}
+
+/// [`Entry`] error.
+#[derive(Debug, thiserror::Error)]
+pub enum EntryError
+{
+ /// 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::xml::element::{Element, Elements, Tagged};
+
+ #[test]
+ fn variable_list_from_elements_works()
+ {
+ let variable_list = VariableList::from_elements(&Elements::from([
+ Element::Tagged(Tagged::new(
+ &"varlistentry",
+ [
+ Element::Tagged(Tagged::new(
+ &"term",
+ [Element::Text("foobar".to_string())],
+ [],
+ )),
+ Element::Tagged(Tagged::new(
+ &"listitem",
+ [Element::Tagged(Tagged::new(
+ &"para",
+ [Element::Text("Hello hello.".to_string())],
+ [],
+ ))],
+ [],
+ )),
+ ],
+ [],
+ )),
+ Element::Tagged(Tagged::new(
+ &"varlistentry",
+ [
+ Element::Tagged(Tagged::new(
+ &"term",
+ [Element::Text("Hello there".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(
+ &"varlistentry",
+ [
+ Element::Tagged(Tagged::new(
+ &"term",
+ [Element::Text("There is another".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!(variable_list.entries.len(), 3);
+ }
+
+ #[test]
+ fn entry_from_elements_works()
+ {
+ let entry = Entry::from_elements(&Elements::from([
+ Element::Tagged(Tagged::new(&"term", [Element::Text("Foo".to_string())], [])),
+ Element::Tagged(Tagged::new(
+ &"listitem",
+ [
+ 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!(
+ matches!(entry.term, ParagraphPart::Text(para_part) if para_part == "Foo")
+ );
+
+ assert_eq!(
+ entry.item,
+ vec![
+ Paragraph::new([ParagraphPart::Text("bar".to_string())]),
+ Paragraph::new([ParagraphPart::Text("Hello there.".to_string())]),
+ ]
+ );
+ }
+}
diff --git a/src/xml/element.rs b/src/xml/element.rs
index 647fe90..b778dac 100644
--- a/src/xml/element.rs
+++ b/src/xml/element.rs
@@ -44,6 +44,17 @@ impl Elements
})
}
+ pub fn get_all_tagged_elements(&self) -> Vec<&Tagged>
+ {
+ self.elements
+ .iter()
+ .filter_map(|element| match element {
+ Element::Tagged(tagged_element) => Some(tagged_element),
+ _ => None,
+ })
+ .collect()
+ }
+
pub fn get_all_tagged_elements_with_name(&self, tag_name: &str) -> Vec<&Tagged>
{
self.elements