use serde::Serialize;
use super::{
AttributeName, AttributeValue, Block, BlockMetadata, DocumentAttributeAssignment,
location::Location,
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum TableFrame {
#[default]
All,
Ends,
Sides,
None,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum TableGrid {
#[default]
All,
Rows,
Columns,
None,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum TableStripes {
#[default]
None,
All,
Odd,
Even,
Hover,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct TablePresentation {
frame: TableFrame,
grid: TableGrid,
stripes: TableStripes,
}
impl TablePresentation {
#[must_use]
pub fn from_attributes<'value>(
metadata: &'value BlockMetadata<'_>,
mut lookup: impl FnMut(&str) -> Option<&'value crate::DocumentAttributeValue<'value>>,
) -> Self {
Self {
frame: resolve_table_attribute(metadata, &mut lookup, "frame", "table-frame").map_or(
TableFrame::All,
|value| match value {
"all" => TableFrame::All,
"ends" | "topbot" => TableFrame::Ends,
"sides" => TableFrame::Sides,
_ => TableFrame::None,
},
),
grid: resolve_table_attribute(metadata, &mut lookup, "grid", "table-grid").map_or(
TableGrid::All,
|value| match value {
"all" => TableGrid::All,
"rows" => TableGrid::Rows,
"cols" => TableGrid::Columns,
_ => TableGrid::None,
},
),
stripes: resolve_table_attribute(metadata, &mut lookup, "stripes", "table-stripes")
.map_or(TableStripes::None, |value| match value {
"all" => TableStripes::All,
"odd" => TableStripes::Odd,
"even" => TableStripes::Even,
"hover" => TableStripes::Hover,
_ => TableStripes::None,
}),
}
}
#[must_use]
pub const fn frame(self) -> TableFrame {
self.frame
}
#[must_use]
pub const fn grid(self) -> TableGrid {
self.grid
}
#[must_use]
pub const fn stripes(self) -> TableStripes {
self.stripes
}
}
fn resolve_table_attribute<'value>(
metadata: &'value BlockMetadata<'_>,
lookup: &mut impl FnMut(&str) -> Option<&'value crate::DocumentAttributeValue<'value>>,
local_name: &str,
document_name: &str,
) -> Option<&'value str> {
if let Some(value) = metadata.attributes.get(local_name) {
return match value {
AttributeValue::String(value) => Some(value),
AttributeValue::Bool(true) => Some(""),
AttributeValue::Bool(false) | AttributeValue::None => None,
};
}
let value = lookup(document_name)?;
if value.is_presence() {
Some("")
} else {
value.text()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum HorizontalAlignment {
#[default]
Left,
Center,
Right,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum VerticalAlignment {
#[default]
Top,
Middle,
Bottom,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum ColumnWidth {
Proportional(u32),
Percentage(u32),
Auto,
}
impl Default for ColumnWidth {
fn default() -> Self {
ColumnWidth::Proportional(1)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum ColumnStyle {
#[serde(rename = "asciidoc")]
AsciiDoc,
#[default]
Default,
Emphasis,
Header,
Literal,
Monospace,
Strong,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize)]
#[non_exhaustive]
pub struct ColumnFormat {
#[serde(default, skip_serializing_if = "is_default_halign")]
pub halign: HorizontalAlignment,
#[serde(default, skip_serializing_if = "is_default_valign")]
pub valign: VerticalAlignment,
#[serde(default, skip_serializing_if = "is_default_width")]
pub width: ColumnWidth,
#[serde(default, skip_serializing_if = "is_default_style")]
pub style: ColumnStyle,
}
impl ColumnFormat {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_halign(mut self, halign: HorizontalAlignment) -> Self {
self.halign = halign;
self
}
#[must_use]
pub fn with_valign(mut self, valign: VerticalAlignment) -> Self {
self.valign = valign;
self
}
#[must_use]
pub fn with_width(mut self, width: ColumnWidth) -> Self {
self.width = width;
self
}
#[must_use]
pub fn with_style(mut self, style: ColumnStyle) -> Self {
self.style = style;
self
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_default_halign(h: &HorizontalAlignment) -> bool {
*h == HorizontalAlignment::default()
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_default_valign(v: &VerticalAlignment) -> bool {
*v == VerticalAlignment::default()
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_default_width(w: &ColumnWidth) -> bool {
*w == ColumnWidth::default()
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_default_style(s: &ColumnStyle) -> bool {
*s == ColumnStyle::default()
}
pub(crate) fn are_all_columns_default(specs: &[ColumnFormat]) -> bool {
specs.iter().all(|s| *s == ColumnFormat::default())
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[non_exhaustive]
pub struct Table<'a> {
pub header: Option<TableRow<'a>>,
pub footer: Option<TableRow<'a>>,
pub rows: Vec<TableRow<'a>>,
#[serde(default, skip_serializing_if = "are_all_columns_default")]
pub columns: Vec<ColumnFormat>,
#[serde(skip)]
presentation: Option<TablePresentation>,
pub location: Location,
}
impl<'a> Table<'a> {
#[must_use]
pub fn new(rows: Vec<TableRow<'a>>, location: Location) -> Self {
Self {
header: None,
footer: None,
rows,
columns: Vec::new(),
presentation: None,
location,
}
}
#[must_use]
pub fn with_header(mut self, header: Option<TableRow<'a>>) -> Self {
self.header = header;
self
}
#[must_use]
pub fn with_footer(mut self, footer: Option<TableRow<'a>>) -> Self {
self.footer = footer;
self
}
#[must_use]
pub fn with_columns(mut self, columns: Vec<ColumnFormat>) -> Self {
self.columns = columns;
self
}
#[must_use]
pub(crate) fn with_presentation(mut self, presentation: TablePresentation) -> Self {
self.presentation = Some(presentation);
self
}
#[must_use]
pub const fn presentation(&self) -> Option<TablePresentation> {
self.presentation
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[non_exhaustive]
pub struct TableRow<'a> {
pub columns: Vec<TableColumn<'a>>,
}
impl<'a> TableRow<'a> {
#[must_use]
pub fn new(columns: Vec<TableColumn<'a>>) -> Self {
Self { columns }
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[non_exhaustive]
pub struct TableColumn<'a> {
#[serde(skip)]
pub(crate) initial_attributes: Vec<(AttributeName<'a>, DocumentAttributeAssignment<'a>)>,
pub content: Vec<Block<'a>>,
#[serde(skip_serializing_if = "is_default_span")]
pub colspan: usize,
#[serde(skip_serializing_if = "is_default_span")]
pub rowspan: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub halign: Option<HorizontalAlignment>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub valign: Option<VerticalAlignment>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub style: Option<ColumnStyle>,
}
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn is_default_span(span: &usize) -> bool {
*span == 1
}
impl<'a> TableColumn<'a> {
pub fn initial_attributes(
&self,
) -> impl Iterator<Item = (&str, &DocumentAttributeAssignment<'a>)> {
self.initial_attributes
.iter()
.map(|(name, assignment)| (name.as_ref(), assignment))
}
#[must_use]
pub(crate) fn with_format(
content: Vec<Block<'a>>,
colspan: usize,
rowspan: usize,
halign: Option<HorizontalAlignment>,
valign: Option<VerticalAlignment>,
style: Option<ColumnStyle>,
) -> Self {
Self {
initial_attributes: Vec::new(),
content,
colspan,
rowspan,
halign,
valign,
style,
}
}
}