Update extensions folder

master
Melroy van den Berg 2021-02-25 22:01:15 +01:00
parent f192b24534
commit 718cb16675
7 changed files with 548 additions and 717 deletions

View File

@ -7,6 +7,7 @@ extern "C" {
#include "cmark-gfm-extension_api.h"
#include "cmark-gfm-extensions_export.h"
#include "config.h" // for bool
#include <stdint.h>
CMARK_GFM_EXTENSIONS_EXPORT
@ -15,14 +16,36 @@ void cmark_gfm_core_extensions_ensure_registered(void);
CMARK_GFM_EXTENSIONS_EXPORT
uint16_t cmark_gfm_extensions_get_table_columns(cmark_node *node);
/** Sets the number of columns for the table, returning 1 on success and 0 on error.
*/
CMARK_GFM_EXTENSIONS_EXPORT
int cmark_gfm_extensions_set_table_columns(cmark_node *node, uint16_t n_columns);
CMARK_GFM_EXTENSIONS_EXPORT
uint8_t *cmark_gfm_extensions_get_table_alignments(cmark_node *node);
/** Sets the alignments for the table, returning 1 on success and 0 on error.
*/
CMARK_GFM_EXTENSIONS_EXPORT
int cmark_gfm_extensions_set_table_alignments(cmark_node *node, uint16_t ncols, uint8_t *alignments);
CMARK_GFM_EXTENSIONS_EXPORT
int cmark_gfm_extensions_get_table_row_is_header(cmark_node *node);
/** Sets whether the node is a table header row, returning 1 on success and 0 on error.
*/
CMARK_GFM_EXTENSIONS_EXPORT
char *cmark_gfm_extensions_get_tasklist_state(cmark_node *node);
int cmark_gfm_extensions_set_table_row_is_header(cmark_node *node, int is_header);
CMARK_GFM_EXTENSIONS_EXPORT
bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node);
/* For backwards compatibility */
#define cmark_gfm_extensions_tasklist_is_checked cmark_gfm_extensions_get_tasklist_item_checked
/** Sets whether a tasklist item is "checked" (completed), returning 1 on success and 0 on error.
*/
CMARK_GFM_EXTENSIONS_EXPORT
int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_checked);
#ifdef __cplusplus
}

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,6 @@
/*!re2c re2c:flags:no-debug-info = 1; */
/*!re2c re2c:indent:string = ' '; */
#include <stdlib.h>
#include "ext_scanners.h"
@ -22,7 +25,6 @@ bufsize_t _ext_scan_at(bufsize_t (*scanner)(const unsigned char *), unsigned cha
re2c:define:YYCTYPE = "unsigned char";
re2c:define:YYCURSOR = p;
re2c:define:YYMARKER = marker;
re2c:define:YYCTXMARKER = marker;
re2c:yyfill:enable = 0;
spacechar = [ \t\v\f];
@ -30,7 +32,7 @@ bufsize_t _ext_scan_at(bufsize_t (*scanner)(const unsigned char *), unsigned cha
escaped_char = [\\][|!"#$%&'()*+,./:;<=>?@[\\\]^_`{}~-];
table_marker = (spacechar*[:]?[-]+[:]?spacechar*);
table_cell = (escaped_char|[^|\r\n])*;
table_cell = (escaped_char|[^|\r\n])+;
tasklist = spacechar*("-"|"+"|"*"|[0-9]+.)spacechar+("[ ]"|"[x]")spacechar+;
*/
@ -39,47 +41,52 @@ bufsize_t _scan_table_start(const unsigned char *p)
{
const unsigned char *marker = NULL;
const unsigned char *start = p;
/*!re2c
[|]? table_marker ([|] table_marker)* [|]? spacechar* newline { return (bufsize_t)(p - start); }
.? { return 0; }
*/
/*!re2c
[|]? table_marker ([|] table_marker)* [|]? spacechar* newline {
return (bufsize_t)(p - start);
}
* { return 0; }
*/
}
bufsize_t _scan_table_cell(const unsigned char *p)
{
const unsigned char *marker = NULL;
const unsigned char *start = p;
/*!re2c
table_cell { return (bufsize_t)(p - start); }
.? { return 0; }
*/
/*!re2c
// In fact, `table_cell` matches non-empty table cells only. The empty
// string is also a valid table cell, but is handled by the default rule.
// This approach prevents re2c's match-empty-string warning.
table_cell { return (bufsize_t)(p - start); }
* { return 0; }
*/
}
bufsize_t _scan_table_cell_end(const unsigned char *p)
{
const unsigned char *marker = NULL;
const unsigned char *start = p;
/*!re2c
[|] spacechar* newline? { return (bufsize_t)(p - start); }
.? { return 0; }
*/
/*!re2c
[|] spacechar* { return (bufsize_t)(p - start); }
* { return 0; }
*/
}
bufsize_t _scan_table_row_end(const unsigned char *p)
{
const unsigned char *marker = NULL;
const unsigned char *start = p;
/*!re2c
spacechar* newline { return (bufsize_t)(p - start); }
.? { return 0; }
*/
/*!re2c
spacechar* newline { return (bufsize_t)(p - start); }
* { return 0; }
*/
}
bufsize_t _scan_tasklist(const unsigned char *p)
{
const unsigned char *marker = NULL;
const unsigned char *start = p;
/*!re2c
tasklist { return (bufsize_t)(p - start); }
.? { return 0; }
*/
/*!re2c
tasklist { return (bufsize_t)(p - start); }
* { return 0; }
*/
}

View File

@ -16,6 +16,7 @@ cmark_node_type CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW,
typedef struct {
uint16_t n_columns;
int paragraph_offset;
cmark_llist *cells;
} table_row;
@ -113,22 +114,39 @@ static cmark_strbuf *unescape_pipes(cmark_mem *mem, unsigned char *string, bufsi
static table_row *row_from_string(cmark_syntax_extension *self,
cmark_parser *parser, unsigned char *string,
int len) {
// Parses a single table row. It has the following form:
// `delim? table_cell (delim table_cell)* delim? newline`
// Note that cells are allowed to be empty.
//
// From the GitHub-flavored Markdown specification:
//
// > Each row consists of cells containing arbitrary text, in which inlines
// > are parsed, separated by pipes (|). A leading and trailing pipe is also
// > recommended for clarity of reading, and if theres otherwise parsing
// > ambiguity.
table_row *row = NULL;
bufsize_t cell_matched = 1, pipe_matched = 1, offset;
int expect_more_cells = 1;
int row_end_offset = 0;
row = (table_row *)parser->mem->calloc(1, sizeof(table_row));
row->n_columns = 0;
row->cells = NULL;
// Scan past the (optional) leading pipe.
offset = scan_table_cell_end(string, len, 0);
// Parse the cells of the row. Stop if we reach the end of the input, or if we
// cannot detect any more cells.
while (offset < len && (cell_matched || pipe_matched)) {
while (offset < len && expect_more_cells) {
cell_matched = scan_table_cell(string, len, offset);
pipe_matched = scan_table_cell_end(string, len, offset + cell_matched);
if (cell_matched || pipe_matched) {
// We are guaranteed to have a cell, since (1) either we found some
// content and cell_matched, or (2) we found an empty cell followed by a
// pipe.
cmark_strbuf *cell_buf = unescape_pipes(parser->mem, string + offset,
cell_matched);
cmark_strbuf_trim(cell_buf);
@ -137,23 +155,46 @@ static table_row *row_from_string(cmark_syntax_extension *self,
cell->buf = cell_buf;
cell->start_offset = offset;
cell->end_offset = offset + cell_matched - 1;
while (cell->start_offset > 0 && string[cell->start_offset - 1] != '|') {
--cell->start_offset;
++cell->internal_offset;
}
row->n_columns += 1;
row->cells = cmark_llist_append(parser->mem, row->cells, cell);
}
offset += cell_matched + pipe_matched;
if (!pipe_matched) {
pipe_matched = scan_table_row_end(string, len, offset);
offset += pipe_matched;
if (pipe_matched) {
expect_more_cells = 1;
} else {
// We've scanned the last cell. Check if we have reached the end of the row
row_end_offset = scan_table_row_end(string, len, offset);
offset += row_end_offset;
// If the end of the row is not the end of the input,
// the row is not a real row but potentially part of the paragraph
// preceding the table.
if (row_end_offset && offset != len) {
row->paragraph_offset = offset;
cmark_llist_free_full(parser->mem, row->cells, (cmark_free_func)free_table_cell);
row->cells = NULL;
row->n_columns = 0;
// Scan past the (optional) leading pipe.
offset += scan_table_cell_end(string, len, offset);
expect_more_cells = 1;
} else {
expect_more_cells = 0;
}
}
}
if (offset != len || !row->n_columns) {
if (offset != len || row->n_columns == 0) {
free_table_row(parser->mem, row);
row = NULL;
}
@ -161,12 +202,30 @@ static table_row *row_from_string(cmark_syntax_extension *self,
return row;
}
static void try_inserting_table_header_paragraph(cmark_parser *parser,
cmark_node *parent_container,
unsigned char *parent_string,
int paragraph_offset) {
cmark_node *paragraph;
cmark_strbuf *paragraph_content;
paragraph = cmark_node_new_with_mem(CMARK_NODE_PARAGRAPH, parser->mem);
paragraph_content = unescape_pipes(parser->mem, parent_string, paragraph_offset);
cmark_strbuf_trim(paragraph_content);
cmark_node_set_string_content(paragraph, (char *) paragraph_content->ptr);
cmark_strbuf_free(paragraph_content);
parser->mem->free(paragraph_content);
if (!cmark_node_insert_before(parent_container, paragraph)) {
parser->mem->free(paragraph);
}
}
static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
cmark_parser *parser,
cmark_node *parent_container,
unsigned char *input, int len) {
bufsize_t matched =
scan_table_start(input, len, cmark_parser_get_first_nonspace(parser));
cmark_node *table_header;
table_row *header_row = NULL;
table_row *marker_row = NULL;
@ -174,41 +233,37 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
const char *parent_string;
uint16_t i;
if (!matched)
return parent_container;
parent_string = cmark_node_get_string_content(parent_container);
cmark_arena_push();
header_row = row_from_string(self, parser, (unsigned char *)parent_string,
(int)strlen(parent_string));
if (!header_row) {
free_table_row(parser->mem, header_row);
cmark_arena_pop();
if (!scan_table_start(input, len, cmark_parser_get_first_nonspace(parser))) {
return parent_container;
}
// Since scan_table_start was successful, we must have a marker row.
marker_row = row_from_string(self, parser,
input + cmark_parser_get_first_nonspace(parser),
len - cmark_parser_get_first_nonspace(parser));
assert(marker_row);
if (header_row->n_columns != marker_row->n_columns) {
free_table_row(parser->mem, header_row);
cmark_arena_push();
// Check for a matching header row. We call `row_from_string` with the entire
// (potentially long) parent container as input, but this should be safe since
// `row_from_string` bails out early if it does not find a row.
parent_string = cmark_node_get_string_content(parent_container);
header_row = row_from_string(self, parser, (unsigned char *)parent_string,
(int)strlen(parent_string));
if (!header_row || header_row->n_columns != marker_row->n_columns) {
free_table_row(parser->mem, marker_row);
free_table_row(parser->mem, header_row);
cmark_arena_pop();
return parent_container;
}
if (cmark_arena_pop()) {
marker_row = row_from_string(
self, parser, input + cmark_parser_get_first_nonspace(parser),
len - cmark_parser_get_first_nonspace(parser));
header_row = row_from_string(self, parser, (unsigned char *)parent_string,
(int)strlen(parent_string));
marker_row = row_from_string(self, parser,
input + cmark_parser_get_first_nonspace(parser),
len - cmark_parser_get_first_nonspace(parser));
}
if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) {
@ -217,10 +272,13 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
return parent_container;
}
if (header_row->paragraph_offset) {
try_inserting_table_header_paragraph(parser, parent_container, (unsigned char *)parent_string,
header_row->paragraph_offset);
}
cmark_node_set_syntax_extension(parent_container, self);
parent_container->as.opaque = parser->mem->calloc(1, sizeof(node_table));
set_n_table_columns(parent_container, header_row->n_columns);
uint8_t *alignments =

View File

@ -9,19 +9,33 @@ typedef enum {
CMARK_TASKLIST_CHECKED,
} cmark_tasklist_type;
// Local constants
static const char *TYPE_STRING = "tasklist";
static const char *get_type_string(cmark_syntax_extension *extension, cmark_node *node) {
return "tasklist";
return TYPE_STRING;
}
char *cmark_gfm_extensions_get_tasklist_state(cmark_node *node) {
if (!node || ((int)node->as.opaque != CMARK_TASKLIST_CHECKED && (int)node->as.opaque != CMARK_TASKLIST_NOCHECKED))
// Return 1 if state was set, 0 otherwise
int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_checked) {
// The node has to exist, and be an extension, and actually be the right type in order to get the value.
if (!node || !node->extension || strcmp(cmark_node_get_type_string(node), TYPE_STRING))
return 0;
if ((int)node->as.opaque != CMARK_TASKLIST_CHECKED) {
return "checked";
node->as.list.checked = is_checked;
return 1;
}
bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node) {
if (!node || !node->extension || strcmp(cmark_node_get_type_string(node), TYPE_STRING))
return false;
if (node->as.list.checked) {
return true;
}
else {
return "unchecked";
return false;
}
}
@ -74,11 +88,8 @@ static cmark_node *open_tasklist_item(cmark_syntax_extension *self,
cmark_node_set_syntax_extension(parent_container, self);
cmark_parser_advance_offset(parser, (char *)input, 3, false);
if (strstr((char*)input, "[x]")) {
parent_container->as.opaque = (void *)CMARK_TASKLIST_CHECKED;
} else {
parent_container->as.opaque = (void *)CMARK_TASKLIST_NOCHECKED;
}
// Either an upper or lower case X means the task is completed.
parent_container->as.list.checked = (strstr((char*)input, "[x]") || strstr((char*)input, "[X]"));
return NULL;
}
@ -89,7 +100,7 @@ static void commonmark_render(cmark_syntax_extension *extension,
bool entering = (ev_type == CMARK_EVENT_ENTER);
if (entering) {
renderer->cr(renderer);
if ((int)node->as.opaque == CMARK_TASKLIST_CHECKED) {
if (node->as.list.checked) {
renderer->out(renderer, node, "- [x] ", false, LITERAL);
} else {
renderer->out(renderer, node, "- [ ] ", false, LITERAL);
@ -110,7 +121,7 @@ static void html_render(cmark_syntax_extension *extension,
cmark_strbuf_puts(renderer->html, "<li");
cmark_html_render_sourcepos(node, renderer->html, options);
cmark_strbuf_putc(renderer->html, '>');
if ((int)node->as.opaque == CMARK_TASKLIST_CHECKED) {
if (node->as.list.checked) {
cmark_strbuf_puts(renderer->html, "<input type=\"checkbox\" checked=\"\" disabled=\"\" /> ");
} else {
cmark_strbuf_puts(renderer->html, "<input type=\"checkbox\" disabled=\"\" /> ");
@ -120,6 +131,15 @@ static void html_render(cmark_syntax_extension *extension,
}
}
static const char *xml_attr(cmark_syntax_extension *extension,
cmark_node *node) {
if (node->as.list.checked) {
return " completed=\"true\"";
} else {
return " completed=\"false\"";
}
}
cmark_syntax_extension *create_tasklist_extension(void) {
cmark_syntax_extension *ext = cmark_syntax_extension_new("tasklist");
@ -130,6 +150,7 @@ cmark_syntax_extension *create_tasklist_extension(void) {
cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render);
cmark_syntax_extension_set_plaintext_render_func(ext, commonmark_render);
cmark_syntax_extension_set_html_render_func(ext, html_render);
cmark_syntax_extension_set_xml_attr_func(ext, xml_attr);
return ext;
}

View File

@ -225,6 +225,7 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
// this ensures that a following indented code block or list will be
// inteprereted correctly.
CR();
// MBGJ: Do not output html in parser
//LIT("<!-- end list -->");
BLANKLINE();
}

View File

@ -21,6 +21,7 @@ typedef struct {
cmark_delim_type delimiter;
unsigned char bullet_char;
bool tight;
bool checked; // For task list extension
} cmark_list;
typedef struct {