1use super::*;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5#[non_exhaustive]
6pub enum RdColumnAlign {
7 Left,
8 Center,
9 Right,
10}
11
12#[derive(Debug, Clone, PartialEq)]
21pub struct RdTabular<'a> {
22 path: RdPath,
23 columns: Vec<RdColumnAlign>,
24 rows: Vec<RdTableRow<'a>>,
25 diagnostics: Vec<RdShapeError>,
26}
27
28impl<'a> RdTabular<'a> {
29 pub fn path(&self) -> &RdPath {
30 &self.path
31 }
32 pub fn columns(&self) -> &[RdColumnAlign] {
33 &self.columns
34 }
35 pub fn rows(&self) -> &[RdTableRow<'a>] {
36 &self.rows
37 }
38 pub fn diagnostics(&self) -> &[RdShapeError] {
39 &self.diagnostics
40 }
41}
42
43#[derive(Debug, Clone, PartialEq)]
45pub struct RdTableRow<'a> {
46 path: RdPath,
47 cells: Vec<RdTableCell<'a>>,
48}
49
50impl<'a> RdTableRow<'a> {
51 pub fn path(&self) -> &RdPath {
52 &self.path
53 }
54 pub fn cells(&self) -> &[RdTableCell<'a>] {
55 &self.cells
56 }
57}
58
59#[derive(Debug, Clone, PartialEq)]
61pub struct RdTableCell<'a> {
62 path: RdPath,
63 nodes: &'a [RdNode],
64}
65
66impl<'a> RdTableCell<'a> {
67 pub fn path(&self) -> &RdPath {
68 &self.path
69 }
70 pub fn nodes(&self) -> &'a [RdNode] {
71 self.nodes
72 }
73}
74
75impl RdTagged {
76 pub fn inspect_tabular<'a>(
82 &'a self,
83 base_path: &RdPath,
84 ) -> Result<RdTabular<'a>, RdShapeError> {
85 if self.tag() != &RdTag::Tabular {
86 return Err(shape(
87 base_path.clone(),
88 Some(self.tag().clone()),
89 RdShapeErrorKind::UnexpectedNode {
90 expected: RdExpectedNode::Tabular,
91 actual: RdNodeKind::Tagged,
92 },
93 ));
94 }
95 if self.option().is_some() {
96 return Err(shape(
97 base_path.clone(),
98 Some(RdTag::Tabular),
99 RdShapeErrorKind::UnexpectedOption,
100 ));
101 }
102 if self.children().len() != 2 {
103 return Err(shape(
104 base_path.clone(),
105 Some(RdTag::Tabular),
106 RdShapeErrorKind::WrongArity {
107 expected: RdArity::Exactly(2),
108 actual: self.children().len(),
109 },
110 ));
111 }
112 let [colspec_node, body_node] = self.children() else {
113 unreachable!()
114 };
115 let colspec_path = base_path.with_child(0);
116 let colspec_group = colspec_node.as_group().ok_or_else(|| {
117 shape(
118 colspec_path.clone(),
119 Some(RdTag::Tabular),
120 RdShapeErrorKind::UnexpectedNode {
121 expected: RdExpectedNode::Group,
122 actual: RdNodeKind::of(colspec_node),
123 },
124 )
125 })?;
126 let body_path = base_path.with_child(1);
127 let body_group = body_node.as_group().ok_or_else(|| {
128 shape(
129 body_path.clone(),
130 Some(RdTag::Tabular),
131 RdShapeErrorKind::UnexpectedNode {
132 expected: RdExpectedNode::Group,
133 actual: RdNodeKind::of(body_node),
134 },
135 )
136 })?;
137
138 let colspec_children = colspec_group.children();
139 if colspec_children.len() != 1 {
140 return Err(shape(
141 colspec_path,
142 Some(RdTag::Tabular),
143 RdShapeErrorKind::WrongArity {
144 expected: RdArity::Exactly(1),
145 actual: colspec_children.len(),
146 },
147 ));
148 }
149 let RdNode::Text(spec) = &colspec_children[0] else {
150 return Err(shape(
151 base_path.with_child(0).with_child(0),
152 Some(RdTag::Tabular),
153 RdShapeErrorKind::UnexpectedContent {
154 actual: RdNodeKind::of(&colspec_children[0]),
155 },
156 ));
157 };
158
159 let mut columns = Vec::new();
160 let mut diagnostics = Vec::new();
161 let spec_path = base_path.with_child(0).with_child(0);
162 for (index, character) in spec.chars().enumerate() {
163 let alignment = match character {
164 'l' => Some(RdColumnAlign::Left),
165 'c' => Some(RdColumnAlign::Center),
166 'r' => Some(RdColumnAlign::Right),
167 _ => {
168 diagnostics.push(shape(
169 spec_path.clone().with_character(index),
170 Some(RdTag::Tabular),
171 RdShapeErrorKind::InvalidValue {
172 construct: RdConstruct::ColumnSpec,
173 value: character.to_string(),
174 },
175 ));
176 None
177 }
178 };
179 if let Some(alignment) = alignment {
180 columns.push(alignment);
181 }
182 }
183
184 let body = body_group.children();
185 let mut rows = Vec::new();
186 let mut current_cells = Vec::new();
187 let mut cell_start = 0;
188 let mut row_anchor = body_path.clone();
189 let mut row_has_content = false;
190
191 for (index, node) in body.iter().enumerate() {
192 let separator = node.as_tagged().and_then(|tagged| {
193 if tagged.tag() == &RdTag::Tab {
194 Some(RdTag::Tab)
195 } else if tagged.tag() == &RdTag::Cr {
196 Some(RdTag::Cr)
197 } else {
198 None
199 }
200 });
201 let Some(separator) = separator else {
202 if !row_has_content && current_cells.is_empty() {
203 row_anchor = body_path.with_child(index);
204 }
205 row_has_content = true;
206 continue;
207 };
208 let tagged = node.as_tagged().unwrap();
209 let separator_path = body_path.with_child(index);
210 if !row_has_content && current_cells.is_empty() {
211 row_anchor = separator_path.clone();
212 }
213 if tagged.option().is_some() {
214 diagnostics.push(shape(
215 separator_path.clone(),
216 Some(separator.clone()),
217 RdShapeErrorKind::UnexpectedOption,
218 ));
219 }
220 if !tagged.children().is_empty() {
221 diagnostics.push(shape(
222 separator_path.clone(),
223 Some(separator.clone()),
224 RdShapeErrorKind::WrongArity {
225 expected: RdArity::Exactly(0),
226 actual: tagged.children().len(),
227 },
228 ));
229 }
230 let cell_path = if cell_start < index {
231 body_path.with_child(cell_start)
232 } else {
233 separator_path.clone()
234 };
235 current_cells.push(RdTableCell {
236 path: cell_path,
237 nodes: &body[cell_start..index],
238 });
239 if separator == RdTag::Cr {
240 finish_table_row(
241 &mut rows,
242 &mut current_cells,
243 &mut diagnostics,
244 row_anchor.clone(),
245 columns.len(),
246 );
247 cell_start = index + 1;
248 row_anchor = body_path.with_child(index);
249 row_has_content = false;
250 } else {
251 cell_start = index + 1;
252 }
253 }
254 if cell_start < body.len() {
255 let cell_path = body_path.with_child(cell_start);
256 current_cells.push(RdTableCell {
257 path: cell_path.clone(),
258 nodes: &body[cell_start..],
259 });
260 if !row_has_content {
261 row_anchor = cell_path.clone();
262 }
263 finish_table_row(
264 &mut rows,
265 &mut current_cells,
266 &mut diagnostics,
267 row_anchor,
268 columns.len(),
269 );
270 } else if !current_cells.is_empty() {
271 finish_table_row(
272 &mut rows,
273 &mut current_cells,
274 &mut diagnostics,
275 row_anchor,
276 columns.len(),
277 );
278 }
279
280 Ok(RdTabular {
281 path: base_path.clone(),
282 columns,
283 rows,
284 diagnostics,
285 })
286 }
287}
288fn finish_table_row<'a>(
289 rows: &mut Vec<RdTableRow<'a>>,
290 cells: &mut Vec<RdTableCell<'a>>,
291 diagnostics: &mut Vec<RdShapeError>,
292 path: RdPath,
293 expected_columns: usize,
294) {
295 if cells.len() != expected_columns {
296 diagnostics.push(shape(
297 path.clone(),
298 Some(RdTag::Cr),
299 RdShapeErrorKind::CountMismatch {
300 construct: RdConstruct::TableRow,
301 expected: expected_columns,
302 actual: cells.len(),
303 },
304 ));
305 }
306 rows.push(RdTableRow {
307 path,
308 cells: std::mem::take(cells),
309 });
310}