commonmarker 0.17.13 → 0.23.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of commonmarker might be problematic. Click here for more details.

Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +94 -18
  3. data/Rakefile +24 -5
  4. data/bin/commonmarker +107 -47
  5. data/commonmarker.gemspec +18 -15
  6. data/ext/commonmarker/autolink.c +10 -6
  7. data/ext/commonmarker/blocks.c +102 -31
  8. data/ext/commonmarker/buffer.c +0 -1
  9. data/ext/commonmarker/chunk.h +0 -1
  10. data/ext/commonmarker/cmark-gfm-core-extensions.h +29 -0
  11. data/ext/commonmarker/cmark-gfm-extension_api.h +19 -2
  12. data/ext/commonmarker/cmark-gfm.h +19 -5
  13. data/ext/commonmarker/cmark-gfm_version.h +2 -2
  14. data/ext/commonmarker/commonmark.c +33 -12
  15. data/ext/commonmarker/commonmarker.c +209 -100
  16. data/ext/commonmarker/core-extensions.c +2 -0
  17. data/ext/commonmarker/ext_scanners.c +622 -684
  18. data/ext/commonmarker/ext_scanners.h +2 -0
  19. data/ext/commonmarker/extconf.rb +3 -1
  20. data/ext/commonmarker/footnotes.c +23 -0
  21. data/ext/commonmarker/footnotes.h +2 -0
  22. data/ext/commonmarker/houdini_href_e.c +1 -1
  23. data/ext/commonmarker/html.c +46 -25
  24. data/ext/commonmarker/inlines.c +127 -30
  25. data/ext/commonmarker/iterator.h +0 -1
  26. data/ext/commonmarker/map.h +0 -1
  27. data/ext/commonmarker/node.c +17 -3
  28. data/ext/commonmarker/node.h +9 -0
  29. data/ext/commonmarker/parser.h +2 -1
  30. data/ext/commonmarker/plaintext.c +22 -0
  31. data/ext/commonmarker/render.c +18 -15
  32. data/ext/commonmarker/render.h +0 -1
  33. data/ext/commonmarker/scanners.c +779 -953
  34. data/ext/commonmarker/scanners.h +0 -2
  35. data/ext/commonmarker/strikethrough.c +4 -1
  36. data/ext/commonmarker/syntax_extension.c +10 -0
  37. data/ext/commonmarker/syntax_extension.h +2 -0
  38. data/ext/commonmarker/table.c +178 -31
  39. data/ext/commonmarker/tasklist.c +156 -0
  40. data/ext/commonmarker/tasklist.h +8 -0
  41. data/ext/commonmarker/xml.c +9 -2
  42. data/lib/commonmarker/config.rb +41 -38
  43. data/lib/commonmarker/errors.rb +12 -0
  44. data/lib/commonmarker/node/inspect.rb +15 -17
  45. data/lib/commonmarker/node.rb +14 -2
  46. data/lib/commonmarker/renderer/html_renderer.rb +45 -36
  47. data/lib/commonmarker/renderer.rb +16 -10
  48. data/lib/commonmarker/version.rb +3 -1
  49. data/lib/commonmarker.rb +8 -7
  50. data/test/benchmark.rb +26 -21
  51. data/test/fixtures/strong.md +1 -0
  52. data/test/fixtures/table.md +10 -0
  53. data/test/test_attributes.rb +5 -3
  54. data/test/test_basics.rb +19 -0
  55. data/test/test_commands.rb +72 -0
  56. data/test/test_commonmark.rb +15 -13
  57. data/test/test_doc.rb +31 -29
  58. data/test/test_encoding.rb +9 -5
  59. data/test/test_extensions.rb +66 -73
  60. data/test/test_footnotes.rb +47 -12
  61. data/test/test_gc.rb +6 -2
  62. data/test/test_helper.rb +25 -15
  63. data/test/test_linebreaks.rb +2 -0
  64. data/test/test_maliciousness.rb +189 -190
  65. data/test/test_node.rb +12 -12
  66. data/test/test_options.rb +17 -15
  67. data/test/test_pathological_inputs.rb +14 -12
  68. data/test/test_plaintext.rb +23 -21
  69. data/test/test_renderer.rb +29 -10
  70. data/test/test_smartpunct.rb +7 -2
  71. data/test/test_spec.rb +7 -4
  72. data/test/test_tasklists.rb +43 -0
  73. data/test/test_xml.rb +107 -0
  74. metadata +74 -30
@@ -26,7 +26,6 @@ bufsize_t _scan_link_title(const unsigned char *p);
26
26
  bufsize_t _scan_spacechars(const unsigned char *p);
27
27
  bufsize_t _scan_atx_heading_start(const unsigned char *p);
28
28
  bufsize_t _scan_setext_heading_line(const unsigned char *p);
29
- bufsize_t _scan_thematic_break(const unsigned char *p);
30
29
  bufsize_t _scan_open_code_fence(const unsigned char *p);
31
30
  bufsize_t _scan_close_code_fence(const unsigned char *p);
32
31
  bufsize_t _scan_entity(const unsigned char *p);
@@ -50,7 +49,6 @@ bufsize_t _scan_footnote_definition(const unsigned char *p);
50
49
  #define scan_atx_heading_start(c, n) _scan_at(&_scan_atx_heading_start, c, n)
51
50
  #define scan_setext_heading_line(c, n) \
52
51
  _scan_at(&_scan_setext_heading_line, c, n)
53
- #define scan_thematic_break(c, n) _scan_at(&_scan_thematic_break, c, n)
54
52
  #define scan_open_code_fence(c, n) _scan_at(&_scan_open_code_fence, c, n)
55
53
  #define scan_close_code_fence(c, n) _scan_at(&_scan_close_code_fence, c, n)
56
54
  #define scan_entity(c, n) _scan_at(&_scan_entity, c, n)
@@ -28,7 +28,7 @@ static cmark_node *match(cmark_syntax_extension *self, cmark_parser *parser,
28
28
  res->start_column = cmark_inline_parser_get_column(inline_parser) - delims;
29
29
 
30
30
  if ((left_flanking || right_flanking) &&
31
- (!(parser->options & CMARK_OPT_STRIKETHROUGH_DOUBLE_TILDE) || delims == 2)) {
31
+ (delims == 2 || (!(parser->options & CMARK_OPT_STRIKETHROUGH_DOUBLE_TILDE) && delims == 1))) {
32
32
  cmark_inline_parser_push_delimiter(inline_parser, character, left_flanking,
33
33
  right_flanking, res);
34
34
  }
@@ -46,6 +46,9 @@ static delimiter *insert(cmark_syntax_extension *self, cmark_parser *parser,
46
46
 
47
47
  strikethrough = opener->inl_text;
48
48
 
49
+ if (opener->inl_text->as.literal.len != closer->inl_text->as.literal.len)
50
+ goto done;
51
+
49
52
  if (!cmark_node_set_type(strikethrough, CMARK_NODE_STRIKETHROUGH))
50
53
  goto done;
51
54
 
@@ -97,6 +97,11 @@ void cmark_syntax_extension_set_latex_render_func(cmark_syntax_extension *extens
97
97
  extension->latex_render_func = func;
98
98
  }
99
99
 
100
+ void cmark_syntax_extension_set_xml_attr_func(cmark_syntax_extension *extension,
101
+ cmark_xml_attr_func func) {
102
+ extension->xml_attr_func = func;
103
+ }
104
+
100
105
  void cmark_syntax_extension_set_man_render_func(cmark_syntax_extension *extension,
101
106
  cmark_common_render_func func) {
102
107
  extension->man_render_func = func;
@@ -128,6 +133,11 @@ void *cmark_syntax_extension_get_private(cmark_syntax_extension *extension) {
128
133
  return extension->priv;
129
134
  }
130
135
 
136
+ void cmark_syntax_extension_set_opaque_alloc_func(cmark_syntax_extension *extension,
137
+ cmark_opaque_alloc_func func) {
138
+ extension->opaque_alloc_func = func;
139
+ }
140
+
131
141
  void cmark_syntax_extension_set_opaque_free_func(cmark_syntax_extension *extension,
132
142
  cmark_opaque_free_func func) {
133
143
  extension->opaque_free_func = func;
@@ -21,10 +21,12 @@ struct cmark_syntax_extension {
21
21
  cmark_common_render_func commonmark_render_func;
22
22
  cmark_common_render_func plaintext_render_func;
23
23
  cmark_common_render_func latex_render_func;
24
+ cmark_xml_attr_func xml_attr_func;
24
25
  cmark_common_render_func man_render_func;
25
26
  cmark_html_render_func html_render_func;
26
27
  cmark_html_filter_func html_filter_func;
27
28
  cmark_postprocess_func postprocess_func;
29
+ cmark_opaque_alloc_func opaque_alloc_func;
28
30
  cmark_opaque_free_func opaque_free_func;
29
31
  cmark_commonmark_escape_func commonmark_escape_func;
30
32
  };
@@ -9,12 +9,14 @@
9
9
  #include "ext_scanners.h"
10
10
  #include "strikethrough.h"
11
11
  #include "table.h"
12
+ #include "cmark-gfm-core-extensions.h"
12
13
 
13
14
  cmark_node_type CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW,
14
15
  CMARK_NODE_TABLE_CELL;
15
16
 
16
17
  typedef struct {
17
18
  uint16_t n_columns;
19
+ int paragraph_offset;
18
20
  cmark_llist *cells;
19
21
  } table_row;
20
22
 
@@ -112,20 +114,40 @@ static cmark_strbuf *unescape_pipes(cmark_mem *mem, unsigned char *string, bufsi
112
114
  static table_row *row_from_string(cmark_syntax_extension *self,
113
115
  cmark_parser *parser, unsigned char *string,
114
116
  int len) {
117
+ // Parses a single table row. It has the following form:
118
+ // `delim? table_cell (delim table_cell)* delim? newline`
119
+ // Note that cells are allowed to be empty.
120
+ //
121
+ // From the GitHub-flavored Markdown specification:
122
+ //
123
+ // > Each row consists of cells containing arbitrary text, in which inlines
124
+ // > are parsed, separated by pipes (|). A leading and trailing pipe is also
125
+ // > recommended for clarity of reading, and if there’s otherwise parsing
126
+ // > ambiguity.
127
+
115
128
  table_row *row = NULL;
116
- bufsize_t cell_matched, pipe_matched, offset;
129
+ bufsize_t cell_matched = 1, pipe_matched = 1, offset;
130
+ int expect_more_cells = 1;
131
+ int row_end_offset = 0;
132
+ int int_overflow_abort = 0;
117
133
 
118
134
  row = (table_row *)parser->mem->calloc(1, sizeof(table_row));
119
135
  row->n_columns = 0;
120
136
  row->cells = NULL;
121
137
 
138
+ // Scan past the (optional) leading pipe.
122
139
  offset = scan_table_cell_end(string, len, 0);
123
140
 
124
- do {
141
+ // Parse the cells of the row. Stop if we reach the end of the input, or if we
142
+ // cannot detect any more cells.
143
+ while (offset < len && expect_more_cells) {
125
144
  cell_matched = scan_table_cell(string, len, offset);
126
145
  pipe_matched = scan_table_cell_end(string, len, offset + cell_matched);
127
146
 
128
147
  if (cell_matched || pipe_matched) {
148
+ // We are guaranteed to have a cell, since (1) either we found some
149
+ // content and cell_matched, or (2) we found an empty cell followed by a
150
+ // pipe.
129
151
  cmark_strbuf *cell_buf = unescape_pipes(parser->mem, string + offset,
130
152
  cell_matched);
131
153
  cmark_strbuf_trim(cell_buf);
@@ -134,23 +156,52 @@ static table_row *row_from_string(cmark_syntax_extension *self,
134
156
  cell->buf = cell_buf;
135
157
  cell->start_offset = offset;
136
158
  cell->end_offset = offset + cell_matched - 1;
159
+
137
160
  while (cell->start_offset > 0 && string[cell->start_offset - 1] != '|') {
138
161
  --cell->start_offset;
139
162
  ++cell->internal_offset;
140
163
  }
164
+
165
+ // make sure we never wrap row->n_columns
166
+ // offset will != len and our exit will clean up as intended
167
+ if (row->n_columns == UINT16_MAX) {
168
+ int_overflow_abort = 1;
169
+ break;
170
+ }
141
171
  row->n_columns += 1;
142
172
  row->cells = cmark_llist_append(parser->mem, row->cells, cell);
143
173
  }
144
174
 
145
175
  offset += cell_matched + pipe_matched;
146
176
 
147
- if (!pipe_matched) {
148
- pipe_matched = scan_table_row_end(string, len, offset);
149
- offset += pipe_matched;
177
+ if (pipe_matched) {
178
+ expect_more_cells = 1;
179
+ } else {
180
+ // We've scanned the last cell. Check if we have reached the end of the row
181
+ row_end_offset = scan_table_row_end(string, len, offset);
182
+ offset += row_end_offset;
183
+
184
+ // If the end of the row is not the end of the input,
185
+ // the row is not a real row but potentially part of the paragraph
186
+ // preceding the table.
187
+ if (row_end_offset && offset != len) {
188
+ row->paragraph_offset = offset;
189
+
190
+ cmark_llist_free_full(parser->mem, row->cells, (cmark_free_func)free_table_cell);
191
+ row->cells = NULL;
192
+ row->n_columns = 0;
193
+
194
+ // Scan past the (optional) leading pipe.
195
+ offset += scan_table_cell_end(string, len, offset);
196
+
197
+ expect_more_cells = 1;
198
+ } else {
199
+ expect_more_cells = 0;
200
+ }
150
201
  }
151
- } while ((cell_matched || pipe_matched) && offset < len);
202
+ }
152
203
 
153
- if (offset != len || !row->n_columns) {
204
+ if (offset != len || row->n_columns == 0 || int_overflow_abort) {
154
205
  free_table_row(parser->mem, row);
155
206
  row = NULL;
156
207
  }
@@ -158,12 +209,30 @@ static table_row *row_from_string(cmark_syntax_extension *self,
158
209
  return row;
159
210
  }
160
211
 
212
+ static void try_inserting_table_header_paragraph(cmark_parser *parser,
213
+ cmark_node *parent_container,
214
+ unsigned char *parent_string,
215
+ int paragraph_offset) {
216
+ cmark_node *paragraph;
217
+ cmark_strbuf *paragraph_content;
218
+
219
+ paragraph = cmark_node_new_with_mem(CMARK_NODE_PARAGRAPH, parser->mem);
220
+
221
+ paragraph_content = unescape_pipes(parser->mem, parent_string, paragraph_offset);
222
+ cmark_strbuf_trim(paragraph_content);
223
+ cmark_node_set_string_content(paragraph, (char *) paragraph_content->ptr);
224
+ cmark_strbuf_free(paragraph_content);
225
+ parser->mem->free(paragraph_content);
226
+
227
+ if (!cmark_node_insert_before(parent_container, paragraph)) {
228
+ parser->mem->free(paragraph);
229
+ }
230
+ }
231
+
161
232
  static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
162
233
  cmark_parser *parser,
163
234
  cmark_node *parent_container,
164
235
  unsigned char *input, int len) {
165
- bufsize_t matched =
166
- scan_table_start(input, len, cmark_parser_get_first_nonspace(parser));
167
236
  cmark_node *table_header;
168
237
  table_row *header_row = NULL;
169
238
  table_row *marker_row = NULL;
@@ -171,41 +240,48 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
171
240
  const char *parent_string;
172
241
  uint16_t i;
173
242
 
174
- if (!matched)
175
- return parent_container;
176
-
177
- parent_string = cmark_node_get_string_content(parent_container);
178
-
179
- cmark_arena_push();
180
-
181
- header_row = row_from_string(self, parser, (unsigned char *)parent_string,
182
- (int)strlen(parent_string));
183
-
184
- if (!header_row) {
185
- free_table_row(parser->mem, header_row);
186
- cmark_arena_pop();
243
+ if (!scan_table_start(input, len, cmark_parser_get_first_nonspace(parser))) {
187
244
  return parent_container;
188
245
  }
189
246
 
247
+ // Since scan_table_start was successful, we must have a marker row.
190
248
  marker_row = row_from_string(self, parser,
191
249
  input + cmark_parser_get_first_nonspace(parser),
192
250
  len - cmark_parser_get_first_nonspace(parser));
193
-
251
+ // assert may be optimized out, don't rely on it for security boundaries
252
+ if (!marker_row) {
253
+ return parent_container;
254
+ }
255
+
194
256
  assert(marker_row);
195
257
 
196
- if (header_row->n_columns != marker_row->n_columns) {
197
- free_table_row(parser->mem, header_row);
258
+ cmark_arena_push();
259
+
260
+ // Check for a matching header row. We call `row_from_string` with the entire
261
+ // (potentially long) parent container as input, but this should be safe since
262
+ // `row_from_string` bails out early if it does not find a row.
263
+ parent_string = cmark_node_get_string_content(parent_container);
264
+ header_row = row_from_string(self, parser, (unsigned char *)parent_string,
265
+ (int)strlen(parent_string));
266
+ if (!header_row || header_row->n_columns != marker_row->n_columns) {
198
267
  free_table_row(parser->mem, marker_row);
268
+ free_table_row(parser->mem, header_row);
199
269
  cmark_arena_pop();
200
270
  return parent_container;
201
271
  }
202
272
 
203
273
  if (cmark_arena_pop()) {
274
+ marker_row = row_from_string(
275
+ self, parser, input + cmark_parser_get_first_nonspace(parser),
276
+ len - cmark_parser_get_first_nonspace(parser));
204
277
  header_row = row_from_string(self, parser, (unsigned char *)parent_string,
205
278
  (int)strlen(parent_string));
206
- marker_row = row_from_string(self, parser,
207
- input + cmark_parser_get_first_nonspace(parser),
208
- len - cmark_parser_get_first_nonspace(parser));
279
+ // row_from_string can return NULL, add additional check to ensure n_columns match
280
+ if (!marker_row || !header_row || header_row->n_columns != marker_row->n_columns) {
281
+ free_table_row(parser->mem, marker_row);
282
+ free_table_row(parser->mem, header_row);
283
+ return parent_container;
284
+ }
209
285
  }
210
286
 
211
287
  if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) {
@@ -214,14 +290,19 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
214
290
  return parent_container;
215
291
  }
216
292
 
217
- cmark_node_set_syntax_extension(parent_container, self);
293
+ if (header_row->paragraph_offset) {
294
+ try_inserting_table_header_paragraph(parser, parent_container, (unsigned char *)parent_string,
295
+ header_row->paragraph_offset);
296
+ }
218
297
 
298
+ cmark_node_set_syntax_extension(parent_container, self);
219
299
  parent_container->as.opaque = parser->mem->calloc(1, sizeof(node_table));
220
-
221
300
  set_n_table_columns(parent_container, header_row->n_columns);
222
301
 
302
+ // allocate alignments based on marker_row->n_columns
303
+ // since we populate the alignments array based on marker_row->cells
223
304
  uint8_t *alignments =
224
- (uint8_t *)parser->mem->calloc(header_row->n_columns, sizeof(uint8_t));
305
+ (uint8_t *)parser->mem->calloc(marker_row->n_columns, sizeof(uint8_t));
225
306
  cmark_llist *it = marker_row->cells;
226
307
  for (i = 0; it; it = it->next, ++i) {
227
308
  node_cell *node = (node_cell *)it->data;
@@ -290,6 +371,12 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
290
371
  row = row_from_string(self, parser, input + cmark_parser_get_first_nonspace(parser),
291
372
  len - cmark_parser_get_first_nonspace(parser));
292
373
 
374
+ if (!row) {
375
+ // clean up the dangling node
376
+ cmark_node_free(table_row_block);
377
+ return NULL;
378
+ }
379
+
293
380
  {
294
381
  cmark_llist *tmp;
295
382
  int i, table_columns = get_n_table_columns(parent_container);
@@ -488,6 +575,27 @@ static void latex_render(cmark_syntax_extension *extension,
488
575
  }
489
576
  }
490
577
 
578
+ static const char *xml_attr(cmark_syntax_extension *extension,
579
+ cmark_node *node) {
580
+ if (node->type == CMARK_NODE_TABLE_CELL) {
581
+ if (cmark_gfm_extensions_get_table_row_is_header(node->parent)) {
582
+ uint8_t *alignments = get_table_alignments(node->parent->parent);
583
+ int i = 0;
584
+ cmark_node *n;
585
+ for (n = node->parent->first_child; n; n = n->next, ++i)
586
+ if (n == node)
587
+ break;
588
+ switch (alignments[i]) {
589
+ case 'l': return " align=\"left\"";
590
+ case 'c': return " align=\"center\"";
591
+ case 'r': return " align=\"right\"";
592
+ }
593
+ }
594
+ }
595
+
596
+ return NULL;
597
+ }
598
+
491
599
  static void man_render(cmark_syntax_extension *extension,
492
600
  cmark_renderer *renderer, cmark_node *node,
493
601
  cmark_event_type ev_type, int options) {
@@ -648,6 +756,16 @@ static void html_render(cmark_syntax_extension *extension,
648
756
  }
649
757
  }
650
758
 
759
+ static void opaque_alloc(cmark_syntax_extension *self, cmark_mem *mem, cmark_node *node) {
760
+ if (node->type == CMARK_NODE_TABLE) {
761
+ node->as.opaque = mem->calloc(1, sizeof(node_table));
762
+ } else if (node->type == CMARK_NODE_TABLE_ROW) {
763
+ node->as.opaque = mem->calloc(1, sizeof(node_table_row));
764
+ } else if (node->type == CMARK_NODE_TABLE_CELL) {
765
+ node->as.opaque = mem->calloc(1, sizeof(node_cell));
766
+ }
767
+ }
768
+
651
769
  static void opaque_free(cmark_syntax_extension *self, cmark_mem *mem, cmark_node *node) {
652
770
  if (node->type == CMARK_NODE_TABLE) {
653
771
  free_node_table(mem, node->as.opaque);
@@ -675,8 +793,10 @@ cmark_syntax_extension *create_table_extension(void) {
675
793
  cmark_syntax_extension_set_commonmark_render_func(self, commonmark_render);
676
794
  cmark_syntax_extension_set_plaintext_render_func(self, commonmark_render);
677
795
  cmark_syntax_extension_set_latex_render_func(self, latex_render);
796
+ cmark_syntax_extension_set_xml_attr_func(self, xml_attr);
678
797
  cmark_syntax_extension_set_man_render_func(self, man_render);
679
798
  cmark_syntax_extension_set_html_render_func(self, html_render);
799
+ cmark_syntax_extension_set_opaque_alloc_func(self, opaque_alloc);
680
800
  cmark_syntax_extension_set_opaque_free_func(self, opaque_free);
681
801
  cmark_syntax_extension_set_commonmark_escape_func(self, escape);
682
802
  CMARK_NODE_TABLE = cmark_syntax_extension_add_node(0);
@@ -699,3 +819,30 @@ uint8_t *cmark_gfm_extensions_get_table_alignments(cmark_node *node) {
699
819
 
700
820
  return ((node_table *)node->as.opaque)->alignments;
701
821
  }
822
+
823
+ int cmark_gfm_extensions_set_table_columns(cmark_node *node, uint16_t n_columns) {
824
+ return set_n_table_columns(node, n_columns);
825
+ }
826
+
827
+ int cmark_gfm_extensions_set_table_alignments(cmark_node *node, uint16_t ncols, uint8_t *alignments) {
828
+ uint8_t *a = (uint8_t *)cmark_node_mem(node)->calloc(1, ncols);
829
+ memcpy(a, alignments, ncols);
830
+ return set_table_alignments(node, a);
831
+ }
832
+
833
+ int cmark_gfm_extensions_get_table_row_is_header(cmark_node *node)
834
+ {
835
+ if (!node || node->type != CMARK_NODE_TABLE_ROW)
836
+ return 0;
837
+
838
+ return ((node_table_row *)node->as.opaque)->is_header;
839
+ }
840
+
841
+ int cmark_gfm_extensions_set_table_row_is_header(cmark_node *node, int is_header)
842
+ {
843
+ if (!node || node->type != CMARK_NODE_TABLE_ROW)
844
+ return 0;
845
+
846
+ ((node_table_row *)node->as.opaque)->is_header = (is_header != 0);
847
+ return 1;
848
+ }
@@ -0,0 +1,156 @@
1
+ #include "tasklist.h"
2
+ #include <parser.h>
3
+ #include <render.h>
4
+ #include <html.h>
5
+ #include "ext_scanners.h"
6
+
7
+ typedef enum {
8
+ CMARK_TASKLIST_NOCHECKED,
9
+ CMARK_TASKLIST_CHECKED,
10
+ } cmark_tasklist_type;
11
+
12
+ // Local constants
13
+ static const char *TYPE_STRING = "tasklist";
14
+
15
+ static const char *get_type_string(cmark_syntax_extension *extension, cmark_node *node) {
16
+ return TYPE_STRING;
17
+ }
18
+
19
+
20
+ // Return 1 if state was set, 0 otherwise
21
+ int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_checked) {
22
+ // The node has to exist, and be an extension, and actually be the right type in order to get the value.
23
+ if (!node || !node->extension || strcmp(cmark_node_get_type_string(node), TYPE_STRING))
24
+ return 0;
25
+
26
+ node->as.list.checked = is_checked;
27
+ return 1;
28
+ }
29
+
30
+ bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node) {
31
+ if (!node || !node->extension || strcmp(cmark_node_get_type_string(node), TYPE_STRING))
32
+ return false;
33
+
34
+ if (node->as.list.checked) {
35
+ return true;
36
+ }
37
+ else {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ static bool parse_node_item_prefix(cmark_parser *parser, const char *input,
43
+ cmark_node *container) {
44
+ bool res = false;
45
+
46
+ if (parser->indent >=
47
+ container->as.list.marker_offset + container->as.list.padding) {
48
+ cmark_parser_advance_offset(parser, input, container->as.list.marker_offset +
49
+ container->as.list.padding,
50
+ true);
51
+ res = true;
52
+ } else if (parser->blank && container->first_child != NULL) {
53
+ // if container->first_child is NULL, then the opening line
54
+ // of the list item was blank after the list marker; in this
55
+ // case, we are done with the list item.
56
+ cmark_parser_advance_offset(parser, input, parser->first_nonspace - parser->offset,
57
+ false);
58
+ res = true;
59
+ }
60
+ return res;
61
+ }
62
+
63
+ static int matches(cmark_syntax_extension *self, cmark_parser *parser,
64
+ unsigned char *input, int len,
65
+ cmark_node *parent_container) {
66
+ return parse_node_item_prefix(parser, (const char*)input, parent_container);
67
+ }
68
+
69
+ static int can_contain(cmark_syntax_extension *extension, cmark_node *node,
70
+ cmark_node_type child_type) {
71
+ return (node->type == CMARK_NODE_ITEM) ? 1 : 0;
72
+ }
73
+
74
+ static cmark_node *open_tasklist_item(cmark_syntax_extension *self,
75
+ int indented, cmark_parser *parser,
76
+ cmark_node *parent_container,
77
+ unsigned char *input, int len) {
78
+ cmark_node_type node_type = cmark_node_get_type(parent_container);
79
+ if (node_type != CMARK_NODE_ITEM) {
80
+ return NULL;
81
+ }
82
+
83
+ bufsize_t matched = scan_tasklist(input, len, 0);
84
+ if (!matched) {
85
+ return NULL;
86
+ }
87
+
88
+ cmark_node_set_syntax_extension(parent_container, self);
89
+ cmark_parser_advance_offset(parser, (char *)input, 3, false);
90
+
91
+ // Either an upper or lower case X means the task is completed.
92
+ parent_container->as.list.checked = (strstr((char*)input, "[x]") || strstr((char*)input, "[X]"));
93
+
94
+ return NULL;
95
+ }
96
+
97
+ static void commonmark_render(cmark_syntax_extension *extension,
98
+ cmark_renderer *renderer, cmark_node *node,
99
+ cmark_event_type ev_type, int options) {
100
+ bool entering = (ev_type == CMARK_EVENT_ENTER);
101
+ if (entering) {
102
+ renderer->cr(renderer);
103
+ if (node->as.list.checked) {
104
+ renderer->out(renderer, node, "- [x] ", false, LITERAL);
105
+ } else {
106
+ renderer->out(renderer, node, "- [ ] ", false, LITERAL);
107
+ }
108
+ cmark_strbuf_puts(renderer->prefix, " ");
109
+ } else {
110
+ cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 2);
111
+ renderer->cr(renderer);
112
+ }
113
+ }
114
+
115
+ static void html_render(cmark_syntax_extension *extension,
116
+ cmark_html_renderer *renderer, cmark_node *node,
117
+ cmark_event_type ev_type, int options) {
118
+ bool entering = (ev_type == CMARK_EVENT_ENTER);
119
+ if (entering) {
120
+ cmark_html_render_cr(renderer->html);
121
+ cmark_strbuf_puts(renderer->html, "<li");
122
+ cmark_html_render_sourcepos(node, renderer->html, options);
123
+ cmark_strbuf_putc(renderer->html, '>');
124
+ if (node->as.list.checked) {
125
+ cmark_strbuf_puts(renderer->html, "<input type=\"checkbox\" checked=\"\" disabled=\"\" /> ");
126
+ } else {
127
+ cmark_strbuf_puts(renderer->html, "<input type=\"checkbox\" disabled=\"\" /> ");
128
+ }
129
+ } else {
130
+ cmark_strbuf_puts(renderer->html, "</li>\n");
131
+ }
132
+ }
133
+
134
+ static const char *xml_attr(cmark_syntax_extension *extension,
135
+ cmark_node *node) {
136
+ if (node->as.list.checked) {
137
+ return " completed=\"true\"";
138
+ } else {
139
+ return " completed=\"false\"";
140
+ }
141
+ }
142
+
143
+ cmark_syntax_extension *create_tasklist_extension(void) {
144
+ cmark_syntax_extension *ext = cmark_syntax_extension_new("tasklist");
145
+
146
+ cmark_syntax_extension_set_match_block_func(ext, matches);
147
+ cmark_syntax_extension_set_get_type_string_func(ext, get_type_string);
148
+ cmark_syntax_extension_set_open_block_func(ext, open_tasklist_item);
149
+ cmark_syntax_extension_set_can_contain_func(ext, can_contain);
150
+ cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render);
151
+ cmark_syntax_extension_set_plaintext_render_func(ext, commonmark_render);
152
+ cmark_syntax_extension_set_html_render_func(ext, html_render);
153
+ cmark_syntax_extension_set_xml_attr_func(ext, xml_attr);
154
+
155
+ return ext;
156
+ }
@@ -0,0 +1,8 @@
1
+ #ifndef TASKLIST_H
2
+ #define TASKLIST_H
3
+
4
+ #include "cmark-gfm-core-extensions.h"
5
+
6
+ cmark_syntax_extension *create_tasklist_extension(void);
7
+
8
+ #endif
@@ -8,6 +8,7 @@
8
8
  #include "node.h"
9
9
  #include "buffer.h"
10
10
  #include "houdini.h"
11
+ #include "syntax_extension.h"
11
12
 
12
13
  #define BUFFER_SIZE 100
13
14
 
@@ -50,6 +51,12 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
50
51
  cmark_strbuf_puts(xml, buffer);
51
52
  }
52
53
 
54
+ if (node->extension && node->extension->xml_attr_func) {
55
+ const char* r = node->extension->xml_attr_func(node->extension, node);
56
+ if (r != NULL)
57
+ cmark_strbuf_puts(xml, r);
58
+ }
59
+
53
60
  literal = false;
54
61
 
55
62
  switch (node->type) {
@@ -60,7 +67,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
60
67
  case CMARK_NODE_CODE:
61
68
  case CMARK_NODE_HTML_BLOCK:
62
69
  case CMARK_NODE_HTML_INLINE:
63
- cmark_strbuf_puts(xml, ">");
70
+ cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
64
71
  escape_xml(xml, node->as.literal.data, node->as.literal.len);
65
72
  cmark_strbuf_puts(xml, "</");
66
73
  cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
@@ -100,7 +107,7 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type,
100
107
  escape_xml(xml, node->as.code.info.data, node->as.code.info.len);
101
108
  cmark_strbuf_putc(xml, '"');
102
109
  }
103
- cmark_strbuf_puts(xml, ">");
110
+ cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
104
111
  escape_xml(xml, node->as.code.literal.data, node->as.code.literal.len);
105
112
  cmark_strbuf_puts(xml, "</");
106
113
  cmark_strbuf_puts(xml, cmark_node_get_type_string(node));