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