summaryrefslogtreecommitdiff
path: root/src/informal_table.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/informal_table.rs')
-rw-r--r--src/informal_table.rs347
1 files changed, 347 insertions, 0 deletions
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<TableGroup>,
+}
+
+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<Self, Self::Error>
+ {
+ let groups = elements
+ .get_all_tagged_elements_with_name("tgroup")
+ .into_iter()
+ .map(TableGroup::from_tagged_element)
+ .collect::<Result<Vec<_>, _>>()?;
+
+ 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<ColumnSpec>,
+ head: Vec<TableRow>,
+ body: Vec<TableRow>,
+}
+
+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<Self, TableGroupError>
+ {
+ let cols = String::from_utf8(
+ element
+ .attributes()
+ .iter()
+ .find(|attr| attr.key == "cols")
+ .ok_or(TableGroupError::MissingCols)?
+ .value
+ .clone(),
+ )
+ .map_err(|_| TableGroupError::ColsNotUTF8)?
+ .parse::<u32>()
+ .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::<Result<Vec<_>, _>>()?;
+
+ 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::<Result<Vec<_>, _>>()?;
+
+ 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::<Result<Vec<_>, _>>()?;
+
+ 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<u32>,
+ name: Option<String>,
+}
+
+impl ColumnSpec
+{
+ /// Returns the width.
+ #[must_use]
+ pub fn width(&self) -> &str
+ {
+ &self.width
+ }
+
+ /// Returns the number.
+ #[must_use]
+ pub fn num(&self) -> &Option<u32>
+ {
+ &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<Self, ColumnSpecError>
+ {
+ 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::<u32>()
+ .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<Paragraph>,
+}
+
+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<Self, Self::Error>
+ {
+ let entries = elements
+ .get_all_tagged_elements_with_name("entry")
+ .into_iter()
+ .map(|entry_element| Paragraph::from_elements(entry_element.child_elements()))
+ .collect::<Result<Vec<_>, _>>()?;
+
+ Ok(Self { entries })
+ }
+}
+
+/// [`TableRow`] error.
+#[derive(Debug, thiserror::Error)]
+pub enum TableRowError
+{
+ /// Invalid paragraph.
+ #[error("Invalid paragraph")]
+ InvalidParagraph(#[from] ParagraphError),
+}