1use serde::Serialize;
4
5use super::{
6 AttributeName, AttributeValue, Block, BlockMetadata, DocumentAttributeAssignment,
7 location::Location,
8};
9
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum TableFrame {
14 #[default]
16 All,
17 Ends,
19 Sides,
21 None,
23}
24
25#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum TableGrid {
29 #[default]
31 All,
32 Rows,
34 Columns,
36 None,
38}
39
40#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
42#[non_exhaustive]
43pub enum TableStripes {
44 #[default]
46 None,
47 All,
49 Odd,
51 Even,
53 Hover,
55}
56
57#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
59#[non_exhaustive]
60pub struct TablePresentation {
61 frame: TableFrame,
62 grid: TableGrid,
63 stripes: TableStripes,
64}
65
66impl TablePresentation {
67 #[must_use]
69 pub fn from_attributes<'value>(
70 metadata: &'value BlockMetadata<'_>,
71 mut lookup: impl FnMut(&str) -> Option<&'value crate::DocumentAttributeValue<'value>>,
72 ) -> Self {
73 Self {
74 frame: resolve_table_attribute(metadata, &mut lookup, "frame", "table-frame").map_or(
75 TableFrame::All,
76 |value| match value {
77 "all" => TableFrame::All,
78 "ends" | "topbot" => TableFrame::Ends,
79 "sides" => TableFrame::Sides,
80 _ => TableFrame::None,
81 },
82 ),
83 grid: resolve_table_attribute(metadata, &mut lookup, "grid", "table-grid").map_or(
84 TableGrid::All,
85 |value| match value {
86 "all" => TableGrid::All,
87 "rows" => TableGrid::Rows,
88 "cols" => TableGrid::Columns,
89 _ => TableGrid::None,
90 },
91 ),
92 stripes: resolve_table_attribute(metadata, &mut lookup, "stripes", "table-stripes")
93 .map_or(TableStripes::None, |value| match value {
94 "all" => TableStripes::All,
95 "odd" => TableStripes::Odd,
96 "even" => TableStripes::Even,
97 "hover" => TableStripes::Hover,
98 _ => TableStripes::None,
99 }),
100 }
101 }
102
103 #[must_use]
105 pub const fn frame(self) -> TableFrame {
106 self.frame
107 }
108
109 #[must_use]
111 pub const fn grid(self) -> TableGrid {
112 self.grid
113 }
114
115 #[must_use]
117 pub const fn stripes(self) -> TableStripes {
118 self.stripes
119 }
120}
121
122fn resolve_table_attribute<'value>(
123 metadata: &'value BlockMetadata<'_>,
124 lookup: &mut impl FnMut(&str) -> Option<&'value crate::DocumentAttributeValue<'value>>,
125 local_name: &str,
126 document_name: &str,
127) -> Option<&'value str> {
128 if let Some(value) = metadata.attributes.get(local_name) {
129 return match value {
130 AttributeValue::String(value) => Some(value),
131 AttributeValue::Bool(true) => Some(""),
132 AttributeValue::Bool(false) | AttributeValue::None => None,
133 };
134 }
135 let value = lookup(document_name)?;
136 if value.is_presence() {
137 Some("")
138 } else {
139 value.text()
140 }
141}
142
143#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
145#[serde(rename_all = "lowercase")]
146pub enum HorizontalAlignment {
147 #[default]
148 Left,
149 Center,
150 Right,
151}
152
153#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
155#[serde(rename_all = "lowercase")]
156pub enum VerticalAlignment {
157 #[default]
158 Top,
159 Middle,
160 Bottom,
161}
162
163#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
165#[serde(rename_all = "lowercase")]
166#[non_exhaustive]
167pub enum ColumnWidth {
168 Proportional(u32),
170 Percentage(u32),
172 Auto,
174}
175
176impl Default for ColumnWidth {
177 fn default() -> Self {
178 ColumnWidth::Proportional(1)
179 }
180}
181
182#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
184#[serde(rename_all = "lowercase")]
185#[non_exhaustive]
186pub enum ColumnStyle {
187 #[serde(rename = "asciidoc")]
189 AsciiDoc,
190 #[default]
192 Default,
193 Emphasis,
195 Header,
197 Literal,
199 Monospace,
201 Strong,
203}
204
205#[derive(Clone, Debug, Default, PartialEq, Serialize)]
207#[non_exhaustive]
208pub struct ColumnFormat {
209 #[serde(default, skip_serializing_if = "is_default_halign")]
210 pub halign: HorizontalAlignment,
211 #[serde(default, skip_serializing_if = "is_default_valign")]
212 pub valign: VerticalAlignment,
213 #[serde(default, skip_serializing_if = "is_default_width")]
214 pub width: ColumnWidth,
215 #[serde(default, skip_serializing_if = "is_default_style")]
216 pub style: ColumnStyle,
217}
218
219impl ColumnFormat {
220 #[must_use]
222 pub fn new() -> Self {
223 Self::default()
224 }
225
226 #[must_use]
228 pub fn with_halign(mut self, halign: HorizontalAlignment) -> Self {
229 self.halign = halign;
230 self
231 }
232
233 #[must_use]
235 pub fn with_valign(mut self, valign: VerticalAlignment) -> Self {
236 self.valign = valign;
237 self
238 }
239
240 #[must_use]
242 pub fn with_width(mut self, width: ColumnWidth) -> Self {
243 self.width = width;
244 self
245 }
246
247 #[must_use]
249 pub fn with_style(mut self, style: ColumnStyle) -> Self {
250 self.style = style;
251 self
252 }
253}
254
255#[allow(clippy::trivially_copy_pass_by_ref)]
256fn is_default_halign(h: &HorizontalAlignment) -> bool {
257 *h == HorizontalAlignment::default()
258}
259
260#[allow(clippy::trivially_copy_pass_by_ref)]
261fn is_default_valign(v: &VerticalAlignment) -> bool {
262 *v == VerticalAlignment::default()
263}
264
265#[allow(clippy::trivially_copy_pass_by_ref)]
266fn is_default_width(w: &ColumnWidth) -> bool {
267 *w == ColumnWidth::default()
268}
269
270#[allow(clippy::trivially_copy_pass_by_ref)]
271fn is_default_style(s: &ColumnStyle) -> bool {
272 *s == ColumnStyle::default()
273}
274
275pub(crate) fn are_all_columns_default(specs: &[ColumnFormat]) -> bool {
276 specs.iter().all(|s| *s == ColumnFormat::default())
277}
278
279#[derive(Clone, Debug, PartialEq, Serialize)]
281#[non_exhaustive]
282pub struct Table<'a> {
283 pub header: Option<TableRow<'a>>,
284 pub footer: Option<TableRow<'a>>,
285 pub rows: Vec<TableRow<'a>>,
286 #[serde(default, skip_serializing_if = "are_all_columns_default")]
289 pub columns: Vec<ColumnFormat>,
290 #[serde(skip)]
291 presentation: Option<TablePresentation>,
292 pub location: Location,
293}
294
295impl<'a> Table<'a> {
296 #[must_use]
298 pub fn new(rows: Vec<TableRow<'a>>, location: Location) -> Self {
299 Self {
300 header: None,
301 footer: None,
302 rows,
303 columns: Vec::new(),
304 presentation: None,
305 location,
306 }
307 }
308
309 #[must_use]
311 pub fn with_header(mut self, header: Option<TableRow<'a>>) -> Self {
312 self.header = header;
313 self
314 }
315
316 #[must_use]
318 pub fn with_footer(mut self, footer: Option<TableRow<'a>>) -> Self {
319 self.footer = footer;
320 self
321 }
322
323 #[must_use]
325 pub fn with_columns(mut self, columns: Vec<ColumnFormat>) -> Self {
326 self.columns = columns;
327 self
328 }
329
330 #[must_use]
331 pub(crate) fn with_presentation(mut self, presentation: TablePresentation) -> Self {
332 self.presentation = Some(presentation);
333 self
334 }
335
336 #[must_use]
338 pub const fn presentation(&self) -> Option<TablePresentation> {
339 self.presentation
340 }
341}
342
343#[derive(Clone, Debug, PartialEq, Serialize)]
357#[non_exhaustive]
358pub struct TableRow<'a> {
359 pub columns: Vec<TableColumn<'a>>,
361}
362
363impl<'a> TableRow<'a> {
364 #[must_use]
366 pub fn new(columns: Vec<TableColumn<'a>>) -> Self {
367 Self { columns }
368 }
369}
370
371#[derive(Clone, Debug, PartialEq, Serialize)]
373#[non_exhaustive]
374pub struct TableColumn<'a> {
375 #[serde(skip)]
376 pub(crate) initial_attributes: Vec<(AttributeName<'a>, DocumentAttributeAssignment<'a>)>,
377 pub content: Vec<Block<'a>>,
378 #[serde(skip_serializing_if = "is_default_span")]
381 pub colspan: usize,
382 #[serde(skip_serializing_if = "is_default_span")]
385 pub rowspan: usize,
386 #[serde(default, skip_serializing_if = "Option::is_none")]
389 pub halign: Option<HorizontalAlignment>,
390 #[serde(default, skip_serializing_if = "Option::is_none")]
393 pub valign: Option<VerticalAlignment>,
394 #[serde(default, skip_serializing_if = "Option::is_none")]
397 pub style: Option<ColumnStyle>,
398}
399
400#[allow(clippy::trivially_copy_pass_by_ref)]
401const fn is_default_span(span: &usize) -> bool {
402 *span == 1
403}
404
405impl<'a> TableColumn<'a> {
406 pub fn initial_attributes(
410 &self,
411 ) -> impl Iterator<Item = (&str, &DocumentAttributeAssignment<'a>)> {
412 self.initial_attributes
413 .iter()
414 .map(|(name, assignment)| (name.as_ref(), assignment))
415 }
416
417 #[must_use]
419 pub(crate) fn with_format(
420 content: Vec<Block<'a>>,
421 colspan: usize,
422 rowspan: usize,
423 halign: Option<HorizontalAlignment>,
424 valign: Option<VerticalAlignment>,
425 style: Option<ColumnStyle>,
426 ) -> Self {
427 Self {
428 initial_attributes: Vec::new(),
429 content,
430 colspan,
431 rowspan,
432 halign,
433 valign,
434 style,
435 }
436 }
437}