From 656ca92bc5958752d6a12e2a659fe17809fe5e86 Mon Sep 17 00:00:00 2001 From: HampusM Date: Wed, 1 Mar 2023 21:50:13 +0100 Subject: feat: add informal table support --- src/description.rs | 31 ++++- src/informal_table.rs | 347 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/xml/element.rs | 5 + 4 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 src/informal_table.rs diff --git a/src/description.rs b/src/description.rs index f6e0f70..5d0cb1f 100644 --- a/src/description.rs +++ b/src/description.rs @@ -1,4 +1,5 @@ //! Reference entry description. +use crate::informal_table::{Error as InformalTableError, InformalTable}; use crate::itemized_list::{Error as ItemizedListError, ItemizedList}; use crate::variable_list::{Error as VariableListError, VariableList}; use crate::xml::element::{Element, Elements, FromElements, Tagged}; @@ -95,6 +96,11 @@ impl FromElements for Description .cloned() .unwrap_or_default(), ))), + "informaltable" => Some( + InformalTable::from_elements(part_elem.child_elements()) + .map(Part::InformalTable) + .map_err(Self::Error::InvalidInformalTable), + ), "title" => None, name => Some(Err(Self::Error::UnknownPartFound(name.to_string()))), }) @@ -122,6 +128,10 @@ pub enum Error /// Invalid variable list. #[error("Invalid variable list")] InvalidVariableList(#[source] VariableListError), + + /// Invalid informal table. + #[error("Invalid informal table")] + InvalidInformalTable(#[source] InformalTableError), } /// Description part. @@ -136,6 +146,9 @@ pub enum Part /// Program listing. ProgramListing(String), + + /// Informal table. + InformalTable(InformalTable), } /// Reference entry description paragraph. @@ -231,6 +244,9 @@ pub enum ParagraphPart /// Itemized list part. ItemizedList(ItemizedList), + + /// Informal table part. + InformalTable(InformalTable), } impl FromElements for ParagraphPart @@ -266,7 +282,7 @@ impl ParagraphPart "inlineequation" => Self::InlineEquation, "programlisting" => Self::ProgramListing, "citerefentry" => Self::Entry, - "variablelist" | "itemizedlist" => |_| { + "variablelist" | "itemizedlist" | "informaltable" => |_| { unreachable!(); }, _ => { @@ -304,6 +320,15 @@ impl ParagraphPart return Ok(Self::ItemizedList(itemized_list)); } + if tagged_element.name() == "informaltable" { + let informal_table = InformalTable::from_elements( + tagged_element.child_elements(), + ) + .map_err(|err| ParagraphPartError::InvalidInformalTable(Box::new(err)))?; + + return Ok(Self::InformalTable(informal_table)); + } + if tagged_element.name() == "inlineequation" { return Ok(Self::InlineEquation( tagged_element @@ -350,4 +375,8 @@ pub enum ParagraphPartError /// Invalid itemized list. #[error("Invalid itemized list")] InvalidItemizedList(#[from] ItemizedListError), + + /// Invalid informal table. + #[error("Invalid informal table")] + InvalidInformalTable(#[source] Box), } diff --git a/src/informal_table.rs b/src/informal_table.rs new file mode 100644 index 0000000..bd633fc --- /dev/null +++ b/src/informal_table.rs @@ -0,0 +1,347 @@ +//! Informal table. +use crate::description::{Paragraph, ParagraphError}; +use crate::xml::element::{Elements, FromElements, Tagged}; + +/// Informal table. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InformalTable +{ + groups: Vec, +} + +impl InformalTable +{ + /// Returns the table groups. + #[must_use] + pub fn groups(&self) -> &[TableGroup] + { + &self.groups + } +} + +impl FromElements for InformalTable +{ + type Error = Error; + + fn from_elements(elements: &Elements) -> Result + { + let groups = elements + .get_all_tagged_elements_with_name("tgroup") + .into_iter() + .map(TableGroup::from_tagged_element) + .collect::, _>>()?; + + Ok(Self { groups }) + } +} + +/// [`InformalTable`] error. +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + /// Invalid group. + #[error("Invalid group")] + InvalidGroup(#[from] TableGroupError), +} + +/// Table group +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TableGroup +{ + cols: u32, + align: String, + column_specs: Vec, + head: Vec, + body: Vec, +} + +impl TableGroup +{ + /// Returns the column count. + #[must_use] + pub fn cols(&self) -> u32 + { + self.cols + } + + /// Returns the column alignment. + #[must_use] + pub fn align(&self) -> &str + { + &self.align + } + + /// Returns the column specifications. + #[must_use] + pub fn column_specs(&self) -> &[ColumnSpec] + { + &self.column_specs + } + + /// Returns the head. + #[must_use] + pub fn head(&self) -> &[TableRow] + { + &self.head + } + + /// Returns the body. + #[must_use] + pub fn body(&self) -> &[TableRow] + { + &self.body + } +} + +impl TableGroup +{ + fn from_tagged_element(element: &Tagged) -> Result + { + let cols = String::from_utf8( + element + .attributes() + .iter() + .find(|attr| attr.key == "cols") + .ok_or(TableGroupError::MissingCols)? + .value + .clone(), + ) + .map_err(|_| TableGroupError::ColsNotUTF8)? + .parse::() + .map_err(|_| TableGroupError::ColsNotNumber)?; + + let align = String::from_utf8( + element + .attributes() + .iter() + .find(|attr| attr.key == "align") + .ok_or(TableGroupError::MissingAlign)? + .value + .clone(), + ) + .map_err(|_| TableGroupError::AlignNotUTF8)?; + + let column_specs = element + .child_elements() + .get_all_tagged_elements_with_name("colspec") + .into_iter() + .map(|column_spec_element| { + ColumnSpec::from_tagged_element(column_spec_element) + }) + .collect::, _>>()?; + + let head = element + .child_elements() + .get_first_tagged_with_name("thead") + .ok_or(TableGroupError::MissingHead)? + .child_elements() + .get_all_tagged_elements_with_name("row") + .into_iter() + .map(|row_element| TableRow::from_elements(row_element.child_elements())) + .collect::, _>>()?; + + let body = element + .child_elements() + .get_first_tagged_with_name("tbody") + .ok_or(TableGroupError::MissingBody)? + .child_elements() + .get_all_tagged_elements_with_name("row") + .into_iter() + .map(|row_element| TableRow::from_elements(row_element.child_elements())) + .collect::, _>>()?; + + Ok(Self { + cols, + align, + column_specs, + head, + body, + }) + } +} + +/// [`TableGroup`] error. +#[derive(Debug, thiserror::Error)] +pub enum TableGroupError +{ + /// Missing cols element attribute. + #[error("Missing cols")] + MissingCols, + + /// Missing align element attribute. + #[error("Missing align")] + MissingAlign, + + /// Invalid column specification. + #[error("Invalid column specification")] + InvalidColumnSpec(#[from] ColumnSpecError), + + /// Missing head element. + #[error("Missing head")] + MissingHead, + + /// Missing body element. + #[error("Missing body")] + MissingBody, + + /// Invalid row. + #[error("Invalid row")] + InvalidRow(#[from] TableRowError), + + /// Cols is not valid UTF-8. + #[error("Cols is not valid UTF-8")] + ColsNotUTF8, + + /// Cols is not a number + #[error("Cols is not a number")] + ColsNotNumber, + + /// Align is not valid UTF-8. + #[error("Align is not valid UTF-8")] + AlignNotUTF8, +} + +/// Column specification. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ColumnSpec +{ + width: String, + num: Option, + name: Option, +} + +impl ColumnSpec +{ + /// Returns the width. + #[must_use] + pub fn width(&self) -> &str + { + &self.width + } + + /// Returns the number. + #[must_use] + pub fn num(&self) -> &Option + { + &self.num + } + + /// Returns the name. + #[must_use] + pub fn name(&self) -> Option<&str> + { + self.name.as_deref() + } +} + +impl ColumnSpec +{ + fn from_tagged_element(element: &Tagged) -> Result + { + let width = String::from_utf8( + element + .attributes() + .iter() + .find(|attr| attr.key == "colwidth") + .ok_or(ColumnSpecError::MissingWidth)? + .value + .clone(), + ) + .map_err(|_| ColumnSpecError::WidthNotUTF8)?; + + let num_result = element + .attributes() + .iter() + .find(|attr| attr.key == "colnum") + .map(|attr| String::from_utf8(attr.value.clone())); + + let num = match num_result { + Some(num) => Some( + num.map_err(|_| ColumnSpecError::NumNotUTF8)? + .parse::() + .map_err(|_| ColumnSpecError::NumNotNumber)?, + ), + None => None, + }; + + let name_result = element + .attributes() + .iter() + .find(|attr| attr.key == "colname") + .map(|attr| String::from_utf8(attr.value.clone())); + + let name = match name_result { + Some(name) => Some(name.map_err(|_| ColumnSpecError::NameNotUTF8)?), + None => None, + }; + + Ok(Self { width, num, name }) + } +} + +/// [`ColumnSpec`] error. +#[derive(Debug, thiserror::Error)] +pub enum ColumnSpecError +{ + /// Missing width element. + #[error("Missing width")] + MissingWidth, + + /// Width is not valid UTF-8. + #[error("Width is not valid UTF-8")] + WidthNotUTF8, + + /// Num is not valid UTF-8. + #[error("Num is not valid UTF-8")] + NumNotUTF8, + + /// Name is not valid UTF-8. + #[error("Name is not valid UTF-8")] + NameNotUTF8, + + /// Num is not a number. + #[error("Num is not a number")] + NumNotNumber, +} + +/// Table row. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TableRow +{ + entries: Vec, +} + +impl TableRow +{ + /// Returns the entries. + #[must_use] + pub fn entries(&self) -> &[Paragraph] + { + &self.entries + } +} + +impl FromElements for TableRow +{ + type Error = TableRowError; + + fn from_elements(elements: &Elements) -> Result + { + let entries = elements + .get_all_tagged_elements_with_name("entry") + .into_iter() + .map(|entry_element| Paragraph::from_elements(entry_element.child_elements())) + .collect::, _>>()?; + + Ok(Self { entries }) + } +} + +/// [`TableRow`] error. +#[derive(Debug, thiserror::Error)] +pub enum TableRowError +{ + /// Invalid paragraph. + #[error("Invalid paragraph")] + InvalidParagraph(#[from] ParagraphError), +} diff --git a/src/lib.rs b/src/lib.rs index 7a715f1..fddc1fb 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 informal_table; pub mod itemized_list; pub mod variable_list; diff --git a/src/xml/element.rs b/src/xml/element.rs index 0f44182..db2878b 100644 --- a/src/xml/element.rs +++ b/src/xml/element.rs @@ -216,6 +216,11 @@ impl Tagged &self.name } + pub fn attributes(&self) -> &[Attribute] + { + &self.attributes + } + pub fn child_elements(&self) -> &Elements { &self.child_elements -- cgit v1.2.3-18-g5258