From a15a878b87a4891ec856178c151faeaaa1799ad3 Mon Sep 17 00:00:00 2001 From: HampusM Date: Sat, 4 Mar 2023 13:18:33 +0100 Subject: feat: add table support --- src/description.rs | 53 +++++-- src/informal_table.rs | 343 ------------------------------------------- src/lib.rs | 2 +- src/table.rs | 396 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 435 insertions(+), 359 deletions(-) delete mode 100644 src/informal_table.rs create mode 100644 src/table.rs (limited to 'src') diff --git a/src/description.rs b/src/description.rs index e6cd1cd..615b0c2 100644 --- a/src/description.rs +++ b/src/description.rs @@ -1,6 +1,6 @@ //! Reference entry description. -use crate::informal_table::{Error as InformalTableError, InformalTable}; use crate::itemized_list::{Error as ItemizedListError, ItemizedList}; +use crate::table::{Error as TableError, Informal, Table}; use crate::variable_list::{Error as VariableListError, VariableList}; use crate::xml::element::{Element, Elements, FromElements, Tagged}; @@ -97,7 +97,7 @@ impl FromElements for Description .unwrap_or_default(), ))), "informaltable" => Some( - InformalTable::from_elements(part_elem.child_elements()) + Informal::from_elements(part_elem.child_elements()) .map(Part::InformalTable) .map_err(Self::Error::InvalidInformalTable), ), @@ -106,6 +106,11 @@ impl FromElements for Description .map(Part::ItemizedList) .map_err(Self::Error::InvalidItemizedList), ), + "table" => Some( + Table::from_elements(part_elem.child_elements()) + .map(Part::Table) + .map_err(Self::Error::InvalidTable), + ), "title" => None, name => Some(Err(Self::Error::UnknownPartFound(name.to_string()))), }) @@ -136,11 +141,15 @@ pub enum Error /// Invalid informal table. #[error("Invalid informal table")] - InvalidInformalTable(#[source] InformalTableError), + InvalidInformalTable(#[source] TableError), /// Invalid itemized list. #[error("Invalid itemized list")] InvalidItemizedList(#[source] ItemizedListError), + + /// Invalid table. + #[error("Invalid table")] + InvalidTable(#[source] TableError), } /// Description part. @@ -157,10 +166,13 @@ pub enum Part ProgramListing(String), /// Informal table. - InformalTable(InformalTable), + InformalTable(Informal), /// Itemized list. ItemizedList(ItemizedList), + + /// Table. + Table(Table), } /// Reference entry description paragraph. @@ -258,13 +270,16 @@ pub enum ParagraphPart ItemizedList(ItemizedList), /// Informal table part. - InformalTable(InformalTable), + InformalTable(Informal), /// Paragraph part. Paragraph(Paragraph), /// Footnote part. Footnote(Paragraph), + + /// Table part. + Table(Table), } impl FromElements for ParagraphPart @@ -300,11 +315,10 @@ impl ParagraphPart "inlineequation" => Self::InlineEquation, "programlisting" => Self::ProgramListing, "citerefentry" => Self::Entry, - "variablelist" | "itemizedlist" | "informaltable" | "para" | "footnote" => { - |_| { - unreachable!(); - } - } + "variablelist" | "itemizedlist" | "informaltable" | "para" | "footnote" + | "table" => |_| { + unreachable!(); + }, _ => { return Err(::Error::UnknownPart( tagged_element.name().to_string(), @@ -341,10 +355,8 @@ impl ParagraphPart } if tagged_element.name() == "informaltable" { - let informal_table = InformalTable::from_elements( - tagged_element.child_elements(), - ) - .map_err(|err| ParagraphPartError::InvalidInformalTable(Box::new(err)))?; + let informal_table = Informal::from_elements(tagged_element.child_elements()) + .map_err(|err| ParagraphPartError::InvalidInformalTable(Box::new(err)))?; return Ok(Self::InformalTable(informal_table)); } @@ -373,6 +385,13 @@ impl ParagraphPart )); } + if tagged_element.name() == "table" { + let table = Table::from_elements(tagged_element.child_elements()) + .map_err(|err| ParagraphPartError::InvalidInformalTable(Box::new(err)))?; + + return Ok(Self::Table(table)); + } + let text_element = tagged_element .child_elements() .get_first_text_element() @@ -412,7 +431,7 @@ pub enum ParagraphPartError /// Invalid informal table. #[error("Invalid informal table")] - InvalidInformalTable(#[source] Box), + InvalidInformalTable(#[source] Box), /// Invalid paragraph. #[error("Invalid paragraph")] @@ -421,4 +440,8 @@ pub enum ParagraphPartError /// Invalid footnote. #[error("Invalid footnote")] InvalidFootnote(#[source] Box), + + /// Invalid table. + #[error("Invalid table")] + InvalidTable(#[source] Box), } diff --git a/src/informal_table.rs b/src/informal_table.rs deleted file mode 100644 index a940de4..0000000 --- a/src/informal_table.rs +++ /dev/null @@ -1,343 +0,0 @@ -//! 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: Option, - 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) -> Option<&str> - { - self.align.as_deref() - } - - /// 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 = match element.attributes().iter().find(|attr| attr.key == "align") { - Some(attr) => Some( - String::from_utf8(attr.value.clone()) - .map_err(|_| TableGroupError::AlignNotUTF8)?, - ), - None => None, - }; - - 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 mut 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::, _>>()?; - - let head = element - .child_elements() - .get_first_tagged_with_name("thead") - .map_or_else( - || Ok(vec![body.remove(0)]), - |head_element| { - head_element - .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, - - /// Invalid column specification. - #[error("Invalid column specification")] - InvalidColumnSpec(#[from] ColumnSpecError), - - /// 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 f1e15cb..c02716f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,8 +14,8 @@ 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 table; pub mod variable_list; mod xml; diff --git a/src/table.rs b/src/table.rs new file mode 100644 index 0000000..b549b95 --- /dev/null +++ b/src/table.rs @@ -0,0 +1,396 @@ +//! Tables. +use crate::description::{Paragraph, ParagraphError}; +use crate::xml::element::{Elements, FromElements, Tagged}; + +/// Informal table. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Informal +{ + groups: Vec, +} + +impl Informal +{ + /// Returns the table groups. + #[must_use] + pub fn groups(&self) -> &[Group] + { + &self.groups + } +} + +impl FromElements for Informal +{ + type Error = Error; + + fn from_elements(elements: &Elements) -> Result + { + let groups = elements + .get_all_tagged_elements_with_name("tgroup") + .into_iter() + .map(Group::from_tagged_element) + .collect::, _>>()?; + + Ok(Self { groups }) + } +} + +/// Table. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Table +{ + title: String, + groups: Vec, +} + +impl Table +{ + /// Returns the table title. + #[must_use] + pub fn title(&self) -> &str + { + &self.title + } + + /// Returns the table groups. + #[must_use] + pub fn groups(&self) -> &[Group] + { + &self.groups + } +} + +impl FromElements for Table +{ + type Error = Error; + + fn from_elements(elements: &Elements) -> Result + { + let title = elements + .get_first_tagged_with_name("title") + .ok_or(Self::Error::MissingTitle)? + .child_elements() + .get_first_text_element() + .cloned() + .unwrap_or_default(); + + let groups = elements + .get_all_tagged_elements_with_name("tgroup") + .into_iter() + .map(Group::from_tagged_element) + .collect::, _>>()?; + + Ok(Self { title, groups }) + } +} + +/// Table error. +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + /// Invalid group. + #[error("Invalid group")] + InvalidGroup(#[from] GroupError), + + /// Missing title. + #[error("Missing title")] + MissingTitle, +} + +/// Table group +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Group +{ + cols: u32, + align: Option, + column_specs: Vec, + head: Vec, + body: Vec, +} + +impl Group +{ + /// Returns the column count. + #[must_use] + pub fn cols(&self) -> u32 + { + self.cols + } + + /// Returns the column alignment. + #[must_use] + pub fn align(&self) -> Option<&str> + { + self.align.as_deref() + } + + /// Returns the column specifications. + #[must_use] + pub fn column_specs(&self) -> &[ColumnSpec] + { + &self.column_specs + } + + /// Returns the head. + #[must_use] + pub fn head(&self) -> &[Row] + { + &self.head + } + + /// Returns the body. + #[must_use] + pub fn body(&self) -> &[Row] + { + &self.body + } +} + +impl Group +{ + fn from_tagged_element(element: &Tagged) -> Result + { + let cols = String::from_utf8( + element + .attributes() + .iter() + .find(|attr| attr.key == "cols") + .ok_or(GroupError::MissingCols)? + .value + .clone(), + ) + .map_err(|_| GroupError::ColsNotUTF8)? + .parse::() + .map_err(|_| GroupError::ColsNotNumber)?; + + let align = match element.attributes().iter().find(|attr| attr.key == "align") { + Some(attr) => Some( + String::from_utf8(attr.value.clone()) + .map_err(|_| GroupError::AlignNotUTF8)?, + ), + None => None, + }; + + 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 mut body = element + .child_elements() + .get_first_tagged_with_name("tbody") + .ok_or(GroupError::MissingBody)? + .child_elements() + .get_all_tagged_elements_with_name("row") + .into_iter() + .map(|row_element| Row::from_elements(row_element.child_elements())) + .collect::, _>>()?; + + let head = element + .child_elements() + .get_first_tagged_with_name("thead") + .map_or_else( + || Ok(vec![body.remove(0)]), + |head_element| { + head_element + .child_elements() + .get_all_tagged_elements_with_name("row") + .into_iter() + .map(|row_element| { + Row::from_elements(row_element.child_elements()) + }) + .collect::, _>>() + }, + )?; + + Ok(Self { + cols, + align, + column_specs, + head, + body, + }) + } +} + +/// [`Group`] error. +#[derive(Debug, thiserror::Error)] +pub enum GroupError +{ + /// Missing cols element attribute. + #[error("Missing cols")] + MissingCols, + + /// Invalid column specification. + #[error("Invalid column specification")] + InvalidColumnSpec(#[from] ColumnSpecError), + + /// Missing body element. + #[error("Missing body")] + MissingBody, + + /// Invalid row. + #[error("Invalid row")] + InvalidRow(#[from] RowError), + + /// 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 Row +{ + entries: Vec, +} + +impl Row +{ + /// Returns the entries. + #[must_use] + pub fn entries(&self) -> &[Paragraph] + { + &self.entries + } +} + +impl FromElements for Row +{ + type Error = RowError; + + 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 }) + } +} + +/// [`Row`] error. +#[derive(Debug, thiserror::Error)] +pub enum RowError +{ + /// Invalid paragraph. + #[error("Invalid paragraph")] + InvalidParagraph(#[from] ParagraphError), +} -- cgit v1.2.3-18-g5258