org_rust_parser/element/
table.rs

1use crate::constants::{HYPHEN, NEWLINE};
2use crate::node_pool::NodeID;
3use crate::object::TableCell;
4use crate::types::{Cursor, Expr, MarkupKind, ParseOpts, Parseable, Parser, Result};
5
6/// A table consisting of a collection of [`TableRow`]s
7///
8/// | one | two |
9/// | three | four |
10#[derive(Debug, Clone)]
11pub struct Table {
12    pub rows: usize,
13    pub cols: usize,
14    pub children: Vec<NodeID>,
15}
16
17/// A row of a [`Table`] consisting of [`TableCell`]s or a [`TableRow::Rule`].
18///
19/// A [`TableRow::Rule`] occurs when a row begins with a hyphen:
20///
21/// ```text
22/// |1|2|
23/// |---|
24/// |3|4|
25/// ```
26///
27/// This table's rows are:
28///
29/// ```text
30/// TableRow::Standard(TableCell, TableCell)
31/// TableRow::Rule
32/// TableRow::Standard(TableCell, TableCell)
33/// ```
34#[derive(Debug, Clone)]
35pub enum TableRow {
36    Rule, // hrule
37    Standard(Vec<NodeID>),
38}
39
40impl<'a> Parseable<'a> for Table {
41    fn parse(
42        parser: &mut Parser<'a>,
43        mut cursor: Cursor<'a>,
44        parent: Option<NodeID>,
45        mut parse_opts: ParseOpts,
46    ) -> Result<NodeID> {
47        let start = cursor.index;
48
49        // we are a table now
50        parse_opts.markup.insert(MarkupKind::Table);
51        let reserve_id = parser.pool.reserve_id();
52        let mut children: Vec<NodeID> = Vec::new();
53        let mut rows = 0;
54        let mut cols = 0;
55        while let Ok(row_id) = TableRow::parse(parser, cursor, Some(reserve_id), parse_opts) {
56            let obj = &parser.pool[row_id];
57
58            children.push(row_id);
59            rows += 1;
60            if let Expr::TableRow(TableRow::Standard(node_ids)) = &obj.obj {
61                cols = cols.max(node_ids.len());
62            }
63
64            cursor.index = parser.pool[row_id].end;
65        }
66
67        Ok(parser.alloc_with_id(
68            Self {
69                rows,
70                cols,
71                children,
72            },
73            start,
74            cursor.index,
75            parent,
76            reserve_id,
77        ))
78    }
79}
80
81impl<'a> Parseable<'a> for TableRow {
82    fn parse(
83        parser: &mut Parser<'a>,
84        mut cursor: Cursor<'a>,
85        parent: Option<NodeID>,
86        parse_opts: ParseOpts,
87    ) -> Result<NodeID> {
88        let start = cursor.index;
89
90        // TODO: doesn't play well with lists
91        // should break if the indentation is not even for the next element in the list
92        // but shouldn't break otherwise
93        cursor.curr_valid()?;
94        cursor.skip_ws();
95        cursor.word("|")?;
96
97        // we deliberately avoid erroring out of the function from here
98        // in the case of "|EOF" since we need to allocate a node to tell the parent
99        // that we've successfully read past the "|"
100        // otherwise, Table will allocate in the cache with zero progress, and cause an infinite loop
101
102        // implies horizontal rule
103        // |-
104        if let Ok(val) = cursor.try_curr() {
105            if val == HYPHEN {
106                // adv_till_byte handles eof
107                cursor.adv_till_byte(b'\n');
108                // cursor.index + 1 to start at the next | on the next line
109                return Ok(parser
110                    .pool
111                    .alloc(Self::Rule, start, cursor.index + 1, parent));
112            }
113        }
114
115        let mut children: Vec<NodeID> = Vec::new();
116        while let Ok(table_cell_id) = TableCell::parse(parser, cursor, parent, parse_opts) {
117            let node_item = &parser.pool[table_cell_id];
118            children.push(table_cell_id);
119
120            cursor.index = node_item.end;
121            if let Ok(val) = cursor.try_curr() {
122                if val == NEWLINE {
123                    cursor.next();
124                    break;
125                }
126            } else {
127                break;
128            }
129        }
130
131        Ok(parser
132            .pool
133            .alloc(Self::Standard(children), start, cursor.index, parent))
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use crate::{expr_in_pool, parse_org, Expr};
140
141    #[test]
142    fn basic_table() {
143        let input = r"
144|one|two|
145|three|four|
146";
147        let pool = parse_org(input);
148
149        pool.print_tree();
150    }
151
152    #[test]
153    fn table_eof_1() {
154        let input = r"
155|one|two|
156|three|four|
157";
158        let pool = parse_org(input);
159
160        pool.print_tree();
161    }
162
163    #[test]
164    fn table_eof_2() {
165        let input = r"
166|one|two|
167|three|four|";
168        let pool = parse_org(input);
169
170        pool.print_tree();
171    }
172
173    #[test]
174    fn table_no_nl() {
175        let input = r"
176|one|two
177|three|four
178
179";
180        let pool = parse_org(input);
181
182        pool.print_tree();
183    }
184
185    #[test]
186    fn table_with_hrule() {
187        let input = r"
188|one|two
189|--------|
190|three|four
191
192";
193        let pool = parse_org(input);
194
195        pool.print_tree();
196    }
197
198    #[test]
199    fn table_markup_1() {
200        let input = r"
201|one|tw *o*                                      |
202|three|four|
203";
204        let pool = parse_org(input);
205
206        pool.print_tree();
207    }
208
209    #[test]
210    fn table_empty_cells() {
211        let input = r"
212||a|
213|b||
214";
215        let pool = parse_org(input);
216
217        pool.print_tree();
218    }
219
220    /// test that alignment spaces are removed
221    #[test]
222    fn table_aligned_cells() {
223        let input = r"
224|one two |three|
225|s       |     |
226";
227
228        let pool = parse_org(input);
229
230        pool.print_tree();
231    }
232
233    #[test]
234    fn table_uneven_cols() {
235        let input = r"
236|one two |three|||||
237|s       |     |
238";
239
240        let pool = parse_org(input);
241
242        pool.print_tree();
243    }
244
245    #[test]
246    fn table_indented() {
247        let input = r"
248word
249        |one two |three|
250        |s       |     |
251        |four | five|
252word
253
254";
255
256        let pool = parse_org(input);
257        pool.print_tree();
258    }
259
260    #[test]
261    fn table_indented_list() {
262        let input = r"
263- one
264   - two
265        |one two |three|
266        |s       |     |
267        |four | five|
268- three
269";
270
271        let pool = parse_org(input);
272
273        pool.print_tree();
274    }
275
276    #[test]
277    fn table_no_start() {
278        let input = r"|";
279
280        let pool = parse_org(input);
281        let tab = expr_in_pool!(pool, Table).unwrap();
282        assert_eq!(tab.rows, 1);
283    }
284}