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.
- checksums.yaml +5 -5
- data/README.md +94 -18
- data/Rakefile +24 -5
- data/bin/commonmarker +107 -47
- data/commonmarker.gemspec +18 -15
- data/ext/commonmarker/autolink.c +10 -6
- data/ext/commonmarker/blocks.c +102 -31
- data/ext/commonmarker/buffer.c +0 -1
- data/ext/commonmarker/chunk.h +0 -1
- data/ext/commonmarker/cmark-gfm-core-extensions.h +29 -0
- data/ext/commonmarker/cmark-gfm-extension_api.h +19 -2
- data/ext/commonmarker/cmark-gfm.h +19 -5
- data/ext/commonmarker/cmark-gfm_version.h +2 -2
- data/ext/commonmarker/commonmark.c +33 -12
- data/ext/commonmarker/commonmarker.c +209 -100
- data/ext/commonmarker/core-extensions.c +2 -0
- data/ext/commonmarker/ext_scanners.c +622 -684
- data/ext/commonmarker/ext_scanners.h +2 -0
- data/ext/commonmarker/extconf.rb +3 -1
- data/ext/commonmarker/footnotes.c +23 -0
- data/ext/commonmarker/footnotes.h +2 -0
- data/ext/commonmarker/houdini_href_e.c +1 -1
- data/ext/commonmarker/html.c +46 -25
- data/ext/commonmarker/inlines.c +127 -30
- data/ext/commonmarker/iterator.h +0 -1
- data/ext/commonmarker/map.h +0 -1
- data/ext/commonmarker/node.c +17 -3
- data/ext/commonmarker/node.h +9 -0
- data/ext/commonmarker/parser.h +2 -1
- data/ext/commonmarker/plaintext.c +22 -0
- data/ext/commonmarker/render.c +18 -15
- data/ext/commonmarker/render.h +0 -1
- data/ext/commonmarker/scanners.c +779 -953
- data/ext/commonmarker/scanners.h +0 -2
- data/ext/commonmarker/strikethrough.c +4 -1
- data/ext/commonmarker/syntax_extension.c +10 -0
- data/ext/commonmarker/syntax_extension.h +2 -0
- data/ext/commonmarker/table.c +178 -31
- data/ext/commonmarker/tasklist.c +156 -0
- data/ext/commonmarker/tasklist.h +8 -0
- data/ext/commonmarker/xml.c +9 -2
- data/lib/commonmarker/config.rb +41 -38
- data/lib/commonmarker/errors.rb +12 -0
- data/lib/commonmarker/node/inspect.rb +15 -17
- data/lib/commonmarker/node.rb +14 -2
- data/lib/commonmarker/renderer/html_renderer.rb +45 -36
- data/lib/commonmarker/renderer.rb +16 -10
- data/lib/commonmarker/version.rb +3 -1
- data/lib/commonmarker.rb +8 -7
- data/test/benchmark.rb +26 -21
- data/test/fixtures/strong.md +1 -0
- data/test/fixtures/table.md +10 -0
- data/test/test_attributes.rb +5 -3
- data/test/test_basics.rb +19 -0
- data/test/test_commands.rb +72 -0
- data/test/test_commonmark.rb +15 -13
- data/test/test_doc.rb +31 -29
- data/test/test_encoding.rb +9 -5
- data/test/test_extensions.rb +66 -73
- data/test/test_footnotes.rb +47 -12
- data/test/test_gc.rb +6 -2
- data/test/test_helper.rb +25 -15
- data/test/test_linebreaks.rb +2 -0
- data/test/test_maliciousness.rb +189 -190
- data/test/test_node.rb +12 -12
- data/test/test_options.rb +17 -15
- data/test/test_pathological_inputs.rb +14 -12
- data/test/test_plaintext.rb +23 -21
- data/test/test_renderer.rb +29 -10
- data/test/test_smartpunct.rb +7 -2
- data/test/test_spec.rb +7 -4
- data/test/test_tasklists.rb +43 -0
- data/test/test_xml.rb +107 -0
- metadata +74 -30
data/ext/commonmarker/scanners.h
CHANGED
@@ -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)
|
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
|
};
|
data/ext/commonmarker/table.c
CHANGED
@@ -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
|
-
|
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 (
|
148
|
-
|
149
|
-
|
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
|
-
}
|
202
|
+
}
|
152
203
|
|
153
|
-
if (offset != len ||
|
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 (!
|
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
|
-
|
197
|
-
|
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
|
-
|
207
|
-
|
208
|
-
|
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
|
-
|
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(
|
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
|
+
}
|
data/ext/commonmarker/xml.c
CHANGED
@@ -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));
|