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