1 #include <cmark-gfm-extension_api.h>
2 #include <html.h>
3 #include <inlines.h>
4 #include <parser.h>
5 #include <references.h>
6 #include <string.h>
7 #include <render.h>
8 
9 #include "ext_scanners.h"
10 #include "strikethrough.h"
11 #include "table.h"
12 #include "cmark-gfm-core-extensions.h"
13 
14 cmark_node_type CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW,
15     CMARK_NODE_TABLE_CELL;
16 
17 typedef struct {
18   uint16_t n_columns;
19   cmark_llist *cells;
20 } table_row;
21 
22 typedef struct {
23   uint16_t n_columns;
24   uint8_t *alignments;
25 } node_table;
26 
27 typedef struct {
28   bool is_header;
29 } node_table_row;
30 
31 typedef struct {
32   cmark_strbuf *buf;
33   int start_offset, end_offset, internal_offset;
34 } node_cell;
35 
free_table_cell(cmark_mem * mem,void * data)36 static void free_table_cell(cmark_mem *mem, void *data) {
37   node_cell *cell = (node_cell *)data;
38   cmark_strbuf_free((cmark_strbuf *)cell->buf);
39   mem->free(cell->buf);
40   mem->free(cell);
41 }
42 
free_table_row(cmark_mem * mem,table_row * row)43 static void free_table_row(cmark_mem *mem, table_row *row) {
44   if (!row)
45     return;
46 
47   cmark_llist_free_full(mem, row->cells, (cmark_free_func)free_table_cell);
48 
49   mem->free(row);
50 }
51 
free_node_table(cmark_mem * mem,void * ptr)52 static void free_node_table(cmark_mem *mem, void *ptr) {
53   node_table *t = (node_table *)ptr;
54   mem->free(t->alignments);
55   mem->free(t);
56 }
57 
free_node_table_row(cmark_mem * mem,void * ptr)58 static void free_node_table_row(cmark_mem *mem, void *ptr) {
59   mem->free(ptr);
60 }
61 
get_n_table_columns(cmark_node * node)62 static int get_n_table_columns(cmark_node *node) {
63   if (!node || node->type != CMARK_NODE_TABLE)
64     return -1;
65 
66   return (int)((node_table *)node->as.opaque)->n_columns;
67 }
68 
set_n_table_columns(cmark_node * node,uint16_t n_columns)69 static int set_n_table_columns(cmark_node *node, uint16_t n_columns) {
70   if (!node || node->type != CMARK_NODE_TABLE)
71     return 0;
72 
73   ((node_table *)node->as.opaque)->n_columns = n_columns;
74   return 1;
75 }
76 
get_table_alignments(cmark_node * node)77 static uint8_t *get_table_alignments(cmark_node *node) {
78   if (!node || node->type != CMARK_NODE_TABLE)
79     return 0;
80 
81   return ((node_table *)node->as.opaque)->alignments;
82 }
83 
set_table_alignments(cmark_node * node,uint8_t * alignments)84 static int set_table_alignments(cmark_node *node, uint8_t *alignments) {
85   if (!node || node->type != CMARK_NODE_TABLE)
86     return 0;
87 
88   ((node_table *)node->as.opaque)->alignments = alignments;
89   return 1;
90 }
91 
unescape_pipes(cmark_mem * mem,unsigned char * string,bufsize_t len)92 static cmark_strbuf *unescape_pipes(cmark_mem *mem, unsigned char *string, bufsize_t len)
93 {
94   cmark_strbuf *res = (cmark_strbuf *)mem->calloc(1, sizeof(cmark_strbuf));
95   bufsize_t r, w;
96 
97   cmark_strbuf_init(mem, res, len + 1);
98   cmark_strbuf_put(res, string, len);
99   cmark_strbuf_putc(res, '\0');
100 
101   for (r = 0, w = 0; r < len; ++r) {
102     if (res->ptr[r] == '\\' && res->ptr[r + 1] == '|')
103       r++;
104 
105     res->ptr[w++] = res->ptr[r];
106   }
107 
108   cmark_strbuf_truncate(res, w);
109 
110   return res;
111 }
112 
row_from_string(cmark_syntax_extension * self,cmark_parser * parser,unsigned char * string,int len)113 static table_row *row_from_string(cmark_syntax_extension *self,
114                                   cmark_parser *parser, unsigned char *string,
115                                   int len) {
116   table_row *row = NULL;
117   bufsize_t cell_matched = 1, pipe_matched = 1, offset;
118 
119   row = (table_row *)parser->mem->calloc(1, sizeof(table_row));
120   row->n_columns = 0;
121   row->cells = NULL;
122 
123   offset = scan_table_cell_end(string, len, 0);
124 
125   // Parse the cells of the row. Stop if we reach the end of the input, or if we
126   // cannot detect any more cells.
127   while (offset < len && (cell_matched || pipe_matched)) {
128     cell_matched = scan_table_cell(string, len, offset);
129     pipe_matched = scan_table_cell_end(string, len, offset + cell_matched);
130 
131     if (cell_matched || pipe_matched) {
132       cmark_strbuf *cell_buf = unescape_pipes(parser->mem, string + offset,
133           cell_matched);
134       cmark_strbuf_trim(cell_buf);
135 
136       node_cell *cell = (node_cell *)parser->mem->calloc(1, sizeof(*cell));
137       cell->buf = cell_buf;
138       cell->start_offset = offset;
139       cell->end_offset = offset + cell_matched - 1;
140       while (cell->start_offset > 0 && string[cell->start_offset - 1] != '|') {
141         --cell->start_offset;
142         ++cell->internal_offset;
143       }
144       row->n_columns += 1;
145       row->cells = cmark_llist_append(parser->mem, row->cells, cell);
146     }
147 
148     offset += cell_matched + pipe_matched;
149 
150     if (!pipe_matched) {
151       pipe_matched = scan_table_row_end(string, len, offset);
152       offset += pipe_matched;
153     }
154   }
155 
156   if (offset != len || !row->n_columns) {
157     free_table_row(parser->mem, row);
158     row = NULL;
159   }
160 
161   return row;
162 }
163 
try_opening_table_header(cmark_syntax_extension * self,cmark_parser * parser,cmark_node * parent_container,unsigned char * input,int len)164 static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
165                                             cmark_parser *parser,
166                                             cmark_node *parent_container,
167                                             unsigned char *input, int len) {
168   bufsize_t matched =
169       scan_table_start(input, len, cmark_parser_get_first_nonspace(parser));
170   cmark_node *table_header;
171   table_row *header_row = NULL;
172   table_row *marker_row = NULL;
173   node_table_row *ntr;
174   const char *parent_string;
175   uint16_t i;
176 
177   if (!matched)
178     return parent_container;
179 
180   parent_string = cmark_node_get_string_content(parent_container);
181 
182   cmark_arena_push();
183 
184   header_row = row_from_string(self, parser, (unsigned char *)parent_string,
185                                (int)strlen(parent_string));
186 
187   if (!header_row) {
188     free_table_row(parser->mem, header_row);
189     cmark_arena_pop();
190     return parent_container;
191   }
192 
193   marker_row = row_from_string(self, parser,
194                                input + cmark_parser_get_first_nonspace(parser),
195                                len - cmark_parser_get_first_nonspace(parser));
196 
197   assert(marker_row);
198 
199   if (header_row->n_columns != marker_row->n_columns) {
200     free_table_row(parser->mem, header_row);
201     free_table_row(parser->mem, marker_row);
202     cmark_arena_pop();
203     return parent_container;
204   }
205 
206   if (cmark_arena_pop()) {
207     header_row = row_from_string(self, parser, (unsigned char *)parent_string,
208                                  (int)strlen(parent_string));
209     marker_row = row_from_string(self, parser,
210                                  input + cmark_parser_get_first_nonspace(parser),
211                                  len - cmark_parser_get_first_nonspace(parser));
212   }
213 
214   if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) {
215     free_table_row(parser->mem, header_row);
216     free_table_row(parser->mem, marker_row);
217     return parent_container;
218   }
219 
220   cmark_node_set_syntax_extension(parent_container, self);
221 
222   parent_container->as.opaque = parser->mem->calloc(1, sizeof(node_table));
223 
224   set_n_table_columns(parent_container, header_row->n_columns);
225 
226   uint8_t *alignments =
227       (uint8_t *)parser->mem->calloc(header_row->n_columns, sizeof(uint8_t));
228   cmark_llist *it = marker_row->cells;
229   for (i = 0; it; it = it->next, ++i) {
230     node_cell *node = (node_cell *)it->data;
231     bool left = node->buf->ptr[0] == ':', right = node->buf->ptr[node->buf->size - 1] == ':';
232 
233     if (left && right)
234       alignments[i] = 'c';
235     else if (left)
236       alignments[i] = 'l';
237     else if (right)
238       alignments[i] = 'r';
239   }
240   set_table_alignments(parent_container, alignments);
241 
242   table_header =
243       cmark_parser_add_child(parser, parent_container, CMARK_NODE_TABLE_ROW,
244                              parent_container->start_column);
245   cmark_node_set_syntax_extension(table_header, self);
246   table_header->end_column = parent_container->start_column + (int)strlen(parent_string) - 2;
247   table_header->start_line = table_header->end_line = parent_container->start_line;
248 
249   table_header->as.opaque = ntr = (node_table_row *)parser->mem->calloc(1, sizeof(node_table_row));
250   ntr->is_header = true;
251 
252   {
253     cmark_llist *tmp;
254 
255     for (tmp = header_row->cells; tmp; tmp = tmp->next) {
256       node_cell *cell = (node_cell *) tmp->data;
257       cmark_node *header_cell = cmark_parser_add_child(parser, table_header,
258           CMARK_NODE_TABLE_CELL, parent_container->start_column + cell->start_offset);
259       header_cell->start_line = header_cell->end_line = parent_container->start_line;
260       header_cell->internal_offset = cell->internal_offset;
261       header_cell->end_column = parent_container->start_column + cell->end_offset;
262       cmark_node_set_string_content(header_cell, (char *) cell->buf->ptr);
263       cmark_node_set_syntax_extension(header_cell, self);
264     }
265   }
266 
267   cmark_parser_advance_offset(
268       parser, (char *)input,
269       (int)strlen((char *)input) - 1 - cmark_parser_get_offset(parser), false);
270 
271   free_table_row(parser->mem, header_row);
272   free_table_row(parser->mem, marker_row);
273   return parent_container;
274 }
275 
try_opening_table_row(cmark_syntax_extension * self,cmark_parser * parser,cmark_node * parent_container,unsigned char * input,int len)276 static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
277                                          cmark_parser *parser,
278                                          cmark_node *parent_container,
279                                          unsigned char *input, int len) {
280   cmark_node *table_row_block;
281   table_row *row;
282 
283   if (cmark_parser_is_blank(parser))
284     return NULL;
285 
286   table_row_block =
287       cmark_parser_add_child(parser, parent_container, CMARK_NODE_TABLE_ROW,
288                              parent_container->start_column);
289   cmark_node_set_syntax_extension(table_row_block, self);
290   table_row_block->end_column = parent_container->end_column;
291   table_row_block->as.opaque = parser->mem->calloc(1, sizeof(node_table_row));
292 
293   row = row_from_string(self, parser, input + cmark_parser_get_first_nonspace(parser),
294       len - cmark_parser_get_first_nonspace(parser));
295 
296   {
297     cmark_llist *tmp;
298     int i, table_columns = get_n_table_columns(parent_container);
299 
300     for (tmp = row->cells, i = 0; tmp && i < table_columns; tmp = tmp->next, ++i) {
301       node_cell *cell = (node_cell *) tmp->data;
302       cmark_node *node = cmark_parser_add_child(parser, table_row_block,
303           CMARK_NODE_TABLE_CELL, parent_container->start_column + cell->start_offset);
304       node->internal_offset = cell->internal_offset;
305       node->end_column = parent_container->start_column + cell->end_offset;
306       cmark_node_set_string_content(node, (char *) cell->buf->ptr);
307       cmark_node_set_syntax_extension(node, self);
308     }
309 
310     for (; i < table_columns; ++i) {
311       cmark_node *node = cmark_parser_add_child(
312           parser, table_row_block, CMARK_NODE_TABLE_CELL, 0);
313       cmark_node_set_syntax_extension(node, self);
314     }
315   }
316 
317   free_table_row(parser->mem, row);
318 
319   cmark_parser_advance_offset(parser, (char *)input,
320                               len - 1 - cmark_parser_get_offset(parser), false);
321 
322   return table_row_block;
323 }
324 
try_opening_table_block(cmark_syntax_extension * self,int indented,cmark_parser * parser,cmark_node * parent_container,unsigned char * input,int len)325 static cmark_node *try_opening_table_block(cmark_syntax_extension *self,
326                                            int indented, cmark_parser *parser,
327                                            cmark_node *parent_container,
328                                            unsigned char *input, int len) {
329   cmark_node_type parent_type = cmark_node_get_type(parent_container);
330 
331   if (!indented && parent_type == CMARK_NODE_PARAGRAPH) {
332     return try_opening_table_header(self, parser, parent_container, input, len);
333   } else if (!indented && parent_type == CMARK_NODE_TABLE) {
334     return try_opening_table_row(self, parser, parent_container, input, len);
335   }
336 
337   return NULL;
338 }
339 
matches(cmark_syntax_extension * self,cmark_parser * parser,unsigned char * input,int len,cmark_node * parent_container)340 static int matches(cmark_syntax_extension *self, cmark_parser *parser,
341                    unsigned char *input, int len,
342                    cmark_node *parent_container) {
343   int res = 0;
344 
345   if (cmark_node_get_type(parent_container) == CMARK_NODE_TABLE) {
346     cmark_arena_push();
347     table_row *new_row = row_from_string(
348         self, parser, input + cmark_parser_get_first_nonspace(parser),
349         len - cmark_parser_get_first_nonspace(parser));
350     if (new_row && new_row->n_columns)
351       res = 1;
352     free_table_row(parser->mem, new_row);
353     cmark_arena_pop();
354   }
355 
356   return res;
357 }
358 
get_type_string(cmark_syntax_extension * self,cmark_node * node)359 static const char *get_type_string(cmark_syntax_extension *self,
360                                    cmark_node *node) {
361   if (node->type == CMARK_NODE_TABLE) {
362     return "table";
363   } else if (node->type == CMARK_NODE_TABLE_ROW) {
364     if (((node_table_row *)node->as.opaque)->is_header)
365       return "table_header";
366     else
367       return "table_row";
368   } else if (node->type == CMARK_NODE_TABLE_CELL) {
369     return "table_cell";
370   }
371 
372   return "<unknown>";
373 }
374 
can_contain(cmark_syntax_extension * extension,cmark_node * node,cmark_node_type child_type)375 static int can_contain(cmark_syntax_extension *extension, cmark_node *node,
376                        cmark_node_type child_type) {
377   if (node->type == CMARK_NODE_TABLE) {
378     return child_type == CMARK_NODE_TABLE_ROW;
379   } else if (node->type == CMARK_NODE_TABLE_ROW) {
380     return child_type == CMARK_NODE_TABLE_CELL;
381   } else if (node->type == CMARK_NODE_TABLE_CELL) {
382     return child_type == CMARK_NODE_TEXT || child_type == CMARK_NODE_CODE ||
383            child_type == CMARK_NODE_EMPH || child_type == CMARK_NODE_STRONG ||
384            child_type == CMARK_NODE_LINK || child_type == CMARK_NODE_IMAGE ||
385            child_type == CMARK_NODE_STRIKETHROUGH ||
386            child_type == CMARK_NODE_HTML_INLINE ||
387            child_type == CMARK_NODE_FOOTNOTE_REFERENCE;
388   }
389   return false;
390 }
391 
contains_inlines(cmark_syntax_extension * extension,cmark_node * node)392 static int contains_inlines(cmark_syntax_extension *extension,
393                             cmark_node *node) {
394   return node->type == CMARK_NODE_TABLE_CELL;
395 }
396 
commonmark_render(cmark_syntax_extension * extension,cmark_renderer * renderer,cmark_node * node,cmark_event_type ev_type,int options)397 static void commonmark_render(cmark_syntax_extension *extension,
398                               cmark_renderer *renderer, cmark_node *node,
399                               cmark_event_type ev_type, int options) {
400   bool entering = (ev_type == CMARK_EVENT_ENTER);
401 
402   if (node->type == CMARK_NODE_TABLE) {
403     renderer->blankline(renderer);
404   } else if (node->type == CMARK_NODE_TABLE_ROW) {
405     if (entering) {
406       renderer->cr(renderer);
407       renderer->out(renderer, node, "|", false, LITERAL);
408     }
409   } else if (node->type == CMARK_NODE_TABLE_CELL) {
410     if (entering) {
411       renderer->out(renderer, node, " ", false, LITERAL);
412     } else {
413       renderer->out(renderer, node, " |", false, LITERAL);
414       if (((node_table_row *)node->parent->as.opaque)->is_header &&
415           !node->next) {
416         int i;
417         uint8_t *alignments = get_table_alignments(node->parent->parent);
418         uint16_t n_cols =
419             ((node_table *)node->parent->parent->as.opaque)->n_columns;
420         renderer->cr(renderer);
421         renderer->out(renderer, node, "|", false, LITERAL);
422         for (i = 0; i < n_cols; i++) {
423           switch (alignments[i]) {
424           case 0:   renderer->out(renderer, node, " --- |", false, LITERAL); break;
425           case 'l': renderer->out(renderer, node, " :-- |", false, LITERAL); break;
426           case 'c': renderer->out(renderer, node, " :-: |", false, LITERAL); break;
427           case 'r': renderer->out(renderer, node, " --: |", false, LITERAL); break;
428           }
429         }
430         renderer->cr(renderer);
431       }
432     }
433   } else {
434     assert(false);
435   }
436 }
437 
latex_render(cmark_syntax_extension * extension,cmark_renderer * renderer,cmark_node * node,cmark_event_type ev_type,int options)438 static void latex_render(cmark_syntax_extension *extension,
439                          cmark_renderer *renderer, cmark_node *node,
440                          cmark_event_type ev_type, int options) {
441   bool entering = (ev_type == CMARK_EVENT_ENTER);
442 
443   if (node->type == CMARK_NODE_TABLE) {
444     if (entering) {
445       int i;
446       uint16_t n_cols;
447       uint8_t *alignments = get_table_alignments(node);
448 
449       renderer->cr(renderer);
450       renderer->out(renderer, node, "\\begin{table}", false, LITERAL);
451       renderer->cr(renderer);
452       renderer->out(renderer, node, "\\begin{tabular}{", false, LITERAL);
453 
454       n_cols = ((node_table *)node->as.opaque)->n_columns;
455       for (i = 0; i < n_cols; i++) {
456         switch(alignments[i]) {
457         case 0:
458         case 'l':
459           renderer->out(renderer, node, "l", false, LITERAL);
460           break;
461         case 'c':
462           renderer->out(renderer, node, "c", false, LITERAL);
463           break;
464         case 'r':
465           renderer->out(renderer, node, "r", false, LITERAL);
466           break;
467         }
468       }
469       renderer->out(renderer, node, "}", false, LITERAL);
470       renderer->cr(renderer);
471     } else {
472       renderer->out(renderer, node, "\\end{tabular}", false, LITERAL);
473       renderer->cr(renderer);
474       renderer->out(renderer, node, "\\end{table}", false, LITERAL);
475       renderer->cr(renderer);
476     }
477   } else if (node->type == CMARK_NODE_TABLE_ROW) {
478     if (!entering) {
479       renderer->cr(renderer);
480     }
481   } else if (node->type == CMARK_NODE_TABLE_CELL) {
482     if (!entering) {
483       if (node->next) {
484         renderer->out(renderer, node, " & ", false, LITERAL);
485       } else {
486         renderer->out(renderer, node, " \\\\", false, LITERAL);
487       }
488     }
489   } else {
490     assert(false);
491   }
492 }
493 
xml_attr(cmark_syntax_extension * extension,cmark_node * node)494 static const char *xml_attr(cmark_syntax_extension *extension,
495                             cmark_node *node) {
496   if (node->type == CMARK_NODE_TABLE_CELL) {
497     if (cmark_gfm_extensions_get_table_row_is_header(node->parent)) {
498       uint8_t *alignments = get_table_alignments(node->parent->parent);
499       int i = 0;
500       cmark_node *n;
501       for (n = node->parent->first_child; n; n = n->next, ++i)
502         if (n == node)
503           break;
504       switch (alignments[i]) {
505       case 'l': return " align=\"left\"";
506       case 'c': return " align=\"center\"";
507       case 'r': return " align=\"right\"";
508       }
509     }
510   }
511 
512   return NULL;
513 }
514 
man_render(cmark_syntax_extension * extension,cmark_renderer * renderer,cmark_node * node,cmark_event_type ev_type,int options)515 static void man_render(cmark_syntax_extension *extension,
516                        cmark_renderer *renderer, cmark_node *node,
517                        cmark_event_type ev_type, int options) {
518   bool entering = (ev_type == CMARK_EVENT_ENTER);
519 
520   if (node->type == CMARK_NODE_TABLE) {
521     if (entering) {
522       int i;
523       uint16_t n_cols;
524       uint8_t *alignments = get_table_alignments(node);
525 
526       renderer->cr(renderer);
527       renderer->out(renderer, node, ".TS", false, LITERAL);
528       renderer->cr(renderer);
529       renderer->out(renderer, node, "tab(@);", false, LITERAL);
530       renderer->cr(renderer);
531 
532       n_cols = ((node_table *)node->as.opaque)->n_columns;
533 
534       for (i = 0; i < n_cols; i++) {
535         switch (alignments[i]) {
536         case 'l':
537           renderer->out(renderer, node, "l", false, LITERAL);
538           break;
539         case 0:
540         case 'c':
541           renderer->out(renderer, node, "c", false, LITERAL);
542           break;
543         case 'r':
544           renderer->out(renderer, node, "r", false, LITERAL);
545           break;
546         }
547       }
548 
549       if (n_cols) {
550         renderer->out(renderer, node, ".", false, LITERAL);
551         renderer->cr(renderer);
552       }
553     } else {
554       renderer->out(renderer, node, ".TE", false, LITERAL);
555       renderer->cr(renderer);
556     }
557   } else if (node->type == CMARK_NODE_TABLE_ROW) {
558     if (!entering) {
559       renderer->cr(renderer);
560     }
561   } else if (node->type == CMARK_NODE_TABLE_CELL) {
562     if (!entering && node->next) {
563       renderer->out(renderer, node, "@", false, LITERAL);
564     }
565   } else {
566     assert(false);
567   }
568 }
569 
html_table_add_align(cmark_strbuf * html,const char * align,int options)570 static void html_table_add_align(cmark_strbuf* html, const char* align, int options) {
571   if (options & CMARK_OPT_TABLE_PREFER_STYLE_ATTRIBUTES) {
572     cmark_strbuf_puts(html, " style=\"text-align: ");
573     cmark_strbuf_puts(html, align);
574     cmark_strbuf_puts(html, "\"");
575   } else {
576     cmark_strbuf_puts(html, " align=\"");
577     cmark_strbuf_puts(html, align);
578     cmark_strbuf_puts(html, "\"");
579   }
580 }
581 
582 struct html_table_state {
583   unsigned need_closing_table_body : 1;
584   unsigned in_table_header : 1;
585 };
586 
html_render(cmark_syntax_extension * extension,cmark_html_renderer * renderer,cmark_node * node,cmark_event_type ev_type,int options)587 static void html_render(cmark_syntax_extension *extension,
588                         cmark_html_renderer *renderer, cmark_node *node,
589                         cmark_event_type ev_type, int options) {
590   bool entering = (ev_type == CMARK_EVENT_ENTER);
591   cmark_strbuf *html = renderer->html;
592   cmark_node *n;
593 
594   // XXX: we just monopolise renderer->opaque.
595   struct html_table_state *table_state =
596       (struct html_table_state *)&renderer->opaque;
597 
598   if (node->type == CMARK_NODE_TABLE) {
599     if (entering) {
600       cmark_html_render_cr(html);
601       cmark_strbuf_puts(html, "<table");
602       cmark_html_render_sourcepos(node, html, options);
603       cmark_strbuf_putc(html, '>');
604       table_state->need_closing_table_body = false;
605     } else {
606       if (table_state->need_closing_table_body) {
607         cmark_html_render_cr(html);
608         cmark_strbuf_puts(html, "</tbody>");
609         cmark_html_render_cr(html);
610       }
611       table_state->need_closing_table_body = false;
612       cmark_html_render_cr(html);
613       cmark_strbuf_puts(html, "</table>");
614       cmark_html_render_cr(html);
615     }
616   } else if (node->type == CMARK_NODE_TABLE_ROW) {
617     if (entering) {
618       cmark_html_render_cr(html);
619       if (((node_table_row *)node->as.opaque)->is_header) {
620         table_state->in_table_header = 1;
621         cmark_strbuf_puts(html, "<thead>");
622         cmark_html_render_cr(html);
623       } else if (!table_state->need_closing_table_body) {
624         cmark_strbuf_puts(html, "<tbody>");
625         cmark_html_render_cr(html);
626         table_state->need_closing_table_body = 1;
627       }
628       cmark_strbuf_puts(html, "<tr");
629       cmark_html_render_sourcepos(node, html, options);
630       cmark_strbuf_putc(html, '>');
631     } else {
632       cmark_html_render_cr(html);
633       cmark_strbuf_puts(html, "</tr>");
634       if (((node_table_row *)node->as.opaque)->is_header) {
635         cmark_html_render_cr(html);
636         cmark_strbuf_puts(html, "</thead>");
637         table_state->in_table_header = false;
638       }
639     }
640   } else if (node->type == CMARK_NODE_TABLE_CELL) {
641     uint8_t *alignments = get_table_alignments(node->parent->parent);
642     if (entering) {
643       cmark_html_render_cr(html);
644       if (table_state->in_table_header) {
645         cmark_strbuf_puts(html, "<th");
646       } else {
647         cmark_strbuf_puts(html, "<td");
648       }
649 
650       int i = 0;
651       for (n = node->parent->first_child; n; n = n->next, ++i)
652         if (n == node)
653           break;
654 
655       switch (alignments[i]) {
656       case 'l': html_table_add_align(html, "left", options); break;
657       case 'c': html_table_add_align(html, "center", options); break;
658       case 'r': html_table_add_align(html, "right", options); break;
659       }
660 
661       cmark_html_render_sourcepos(node, html, options);
662       cmark_strbuf_putc(html, '>');
663     } else {
664       if (table_state->in_table_header) {
665         cmark_strbuf_puts(html, "</th>");
666       } else {
667         cmark_strbuf_puts(html, "</td>");
668       }
669     }
670   } else {
671     assert(false);
672   }
673 }
674 
opaque_alloc(cmark_syntax_extension * self,cmark_mem * mem,cmark_node * node)675 static void opaque_alloc(cmark_syntax_extension *self, cmark_mem *mem, cmark_node *node) {
676   if (node->type == CMARK_NODE_TABLE) {
677     node->as.opaque = mem->calloc(1, sizeof(node_table));
678   } else if (node->type == CMARK_NODE_TABLE_ROW) {
679     node->as.opaque = mem->calloc(1, sizeof(node_table_row));
680   } else if (node->type == CMARK_NODE_TABLE_CELL) {
681     node->as.opaque = mem->calloc(1, sizeof(node_cell));
682   }
683 }
684 
opaque_free(cmark_syntax_extension * self,cmark_mem * mem,cmark_node * node)685 static void opaque_free(cmark_syntax_extension *self, cmark_mem *mem, cmark_node *node) {
686   if (node->type == CMARK_NODE_TABLE) {
687     free_node_table(mem, node->as.opaque);
688   } else if (node->type == CMARK_NODE_TABLE_ROW) {
689     free_node_table_row(mem, node->as.opaque);
690   }
691 }
692 
escape(cmark_syntax_extension * self,cmark_node * node,int c)693 static int escape(cmark_syntax_extension *self, cmark_node *node, int c) {
694   return
695     node->type != CMARK_NODE_TABLE &&
696     node->type != CMARK_NODE_TABLE_ROW &&
697     node->type != CMARK_NODE_TABLE_CELL &&
698     c == '|';
699 }
700 
create_table_extension(void)701 cmark_syntax_extension *create_table_extension(void) {
702   cmark_syntax_extension *self = cmark_syntax_extension_new("table");
703 
704   cmark_syntax_extension_set_match_block_func(self, matches);
705   cmark_syntax_extension_set_open_block_func(self, try_opening_table_block);
706   cmark_syntax_extension_set_get_type_string_func(self, get_type_string);
707   cmark_syntax_extension_set_can_contain_func(self, can_contain);
708   cmark_syntax_extension_set_contains_inlines_func(self, contains_inlines);
709   cmark_syntax_extension_set_commonmark_render_func(self, commonmark_render);
710   cmark_syntax_extension_set_plaintext_render_func(self, commonmark_render);
711   cmark_syntax_extension_set_latex_render_func(self, latex_render);
712   cmark_syntax_extension_set_xml_attr_func(self, xml_attr);
713   cmark_syntax_extension_set_man_render_func(self, man_render);
714   cmark_syntax_extension_set_html_render_func(self, html_render);
715   cmark_syntax_extension_set_opaque_alloc_func(self, opaque_alloc);
716   cmark_syntax_extension_set_opaque_free_func(self, opaque_free);
717   cmark_syntax_extension_set_commonmark_escape_func(self, escape);
718   CMARK_NODE_TABLE = cmark_syntax_extension_add_node(0);
719   CMARK_NODE_TABLE_ROW = cmark_syntax_extension_add_node(0);
720   CMARK_NODE_TABLE_CELL = cmark_syntax_extension_add_node(0);
721 
722   return self;
723 }
724 
cmark_gfm_extensions_get_table_columns(cmark_node * node)725 uint16_t cmark_gfm_extensions_get_table_columns(cmark_node *node) {
726   if (node->type != CMARK_NODE_TABLE)
727     return 0;
728 
729   return ((node_table *)node->as.opaque)->n_columns;
730 }
731 
cmark_gfm_extensions_get_table_alignments(cmark_node * node)732 uint8_t *cmark_gfm_extensions_get_table_alignments(cmark_node *node) {
733   if (node->type != CMARK_NODE_TABLE)
734     return 0;
735 
736   return ((node_table *)node->as.opaque)->alignments;
737 }
738 
cmark_gfm_extensions_set_table_columns(cmark_node * node,uint16_t n_columns)739 int cmark_gfm_extensions_set_table_columns(cmark_node *node, uint16_t n_columns) {
740   return set_n_table_columns(node, n_columns);
741 }
742 
cmark_gfm_extensions_set_table_alignments(cmark_node * node,uint16_t ncols,uint8_t * alignments)743 int cmark_gfm_extensions_set_table_alignments(cmark_node *node, uint16_t ncols, uint8_t *alignments) {
744   uint8_t *a = (uint8_t *)cmark_node_mem(node)->calloc(1, ncols);
745   memcpy(a, alignments, ncols);
746   return set_table_alignments(node, a);
747 }
748 
cmark_gfm_extensions_get_table_row_is_header(cmark_node * node)749 int cmark_gfm_extensions_get_table_row_is_header(cmark_node *node)
750 {
751   if (!node || node->type != CMARK_NODE_TABLE_ROW)
752     return 0;
753 
754   return ((node_table_row *)node->as.opaque)->is_header;
755 }
756 
cmark_gfm_extensions_set_table_row_is_header(cmark_node * node,int is_header)757 int cmark_gfm_extensions_set_table_row_is_header(cmark_node *node, int is_header)
758 {
759   if (!node || node->type != CMARK_NODE_TABLE_ROW)
760     return 0;
761 
762   ((node_table_row *)node->as.opaque)->is_header = (is_header != 0);
763   return 1;
764 }
765