Update extensions folder
parent
f192b24534
commit
718cb16675
|
@ -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
|
@ -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; }
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -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 there’s 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 =
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue