Compiled with extensions lib

master
Melroy van den Berg 2020-11-13 03:43:20 +01:00
parent 120071b3cc
commit dc33ebcd2f
16 changed files with 2944 additions and 0 deletions

View File

@ -0,0 +1,22 @@
set (LIBRARY_SOURCES
core-extensions.c
table.c
strikethrough.c
autolink.c
tagfilter.c
ext_scanners.c
ext_scanners.re
ext_scanners.h
tasklist.c
)
include_directories (
${PROJECT_SOURCE_DIR}/lib/commonmarker/src
${PROJECT_BINARY_DIR}/lib/commonmarker/src
)
include (GenerateExportHeader)
include_directories (. ${CMAKE_CURRENT_BINARY_DIR})
add_library (CommonMarkerExtensions ${LIBRARY_SOURCES})
target_include_directories (CommonMarkerExtensions PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -0,0 +1,425 @@
#include "autolink.h"
#include <parser.h>
#include <string.h>
#include <utf8.h>
#if defined(_WIN32)
#define strncasecmp _strnicmp
#else
#include <strings.h>
#endif
static int is_valid_hostchar(const uint8_t *link, size_t link_len) {
int32_t ch;
int r = cmark_utf8proc_iterate(link, (bufsize_t)link_len, &ch);
if (r < 0)
return 0;
return !cmark_utf8proc_is_space(ch) && !cmark_utf8proc_is_punctuation(ch);
}
static int sd_autolink_issafe(const uint8_t *link, size_t link_len) {
static const size_t valid_uris_count = 3;
static const char *valid_uris[] = {"http://", "https://", "ftp://"};
size_t i;
for (i = 0; i < valid_uris_count; ++i) {
size_t len = strlen(valid_uris[i]);
if (link_len > len && strncasecmp((char *)link, valid_uris[i], len) == 0 &&
is_valid_hostchar(link + len, link_len - len))
return 1;
}
return 0;
}
static size_t autolink_delim(uint8_t *data, size_t link_end) {
uint8_t cclose, copen;
size_t i;
for (i = 0; i < link_end; ++i)
if (data[i] == '<') {
link_end = i;
break;
}
while (link_end > 0) {
cclose = data[link_end - 1];
switch (cclose) {
case ')':
copen = '(';
break;
default:
copen = 0;
}
if (strchr("?!.,:*_~'\"", data[link_end - 1]) != NULL)
link_end--;
else if (data[link_end - 1] == ';') {
size_t new_end = link_end - 2;
while (new_end > 0 && cmark_isalpha(data[new_end]))
new_end--;
if (new_end < link_end - 2 && data[new_end] == '&')
link_end = new_end;
else
link_end--;
} else if (copen != 0) {
size_t closing = 0;
size_t opening = 0;
i = 0;
/* Allow any number of matching brackets (as recognised in copen/cclose)
* at the end of the URL. If there is a greater number of closing
* brackets than opening ones, we remove one character from the end of
* the link.
*
* Examples (input text => output linked portion):
*
* http://www.pokemon.com/Pikachu_(Electric)
* => http://www.pokemon.com/Pikachu_(Electric)
*
* http://www.pokemon.com/Pikachu_((Electric)
* => http://www.pokemon.com/Pikachu_((Electric)
*
* http://www.pokemon.com/Pikachu_(Electric))
* => http://www.pokemon.com/Pikachu_(Electric)
*
* http://www.pokemon.com/Pikachu_((Electric))
* => http://www.pokemon.com/Pikachu_((Electric))
*/
while (i < link_end) {
if (data[i] == copen)
opening++;
else if (data[i] == cclose)
closing++;
i++;
}
if (closing <= opening)
break;
link_end--;
} else
break;
}
return link_end;
}
static size_t check_domain(uint8_t *data, size_t size, int allow_short) {
size_t i, np = 0, uscore1 = 0, uscore2 = 0;
for (i = 1; i < size - 1; i++) {
if (data[i] == '_')
uscore2++;
else if (data[i] == '.') {
uscore1 = uscore2;
uscore2 = 0;
np++;
} else if (!is_valid_hostchar(data + i, size - i) && data[i] != '-')
break;
}
if (uscore1 > 0 || uscore2 > 0)
return 0;
if (allow_short) {
/* We don't need a valid domain in the strict sense (with
* least one dot; so just make sure it's composed of valid
* domain characters and return the length of the the valid
* sequence. */
return i;
} else {
/* a valid domain needs to have at least a dot.
* that's as far as we get */
return np ? i : 0;
}
}
static cmark_node *www_match(cmark_parser *parser, cmark_node *parent,
cmark_inline_parser *inline_parser) {
cmark_chunk *chunk = cmark_inline_parser_get_chunk(inline_parser);
size_t max_rewind = cmark_inline_parser_get_offset(inline_parser);
uint8_t *data = chunk->data + max_rewind;
size_t size = chunk->len - max_rewind;
int start = cmark_inline_parser_get_column(inline_parser);
size_t link_end;
if (max_rewind > 0 && strchr("*_~(", data[-1]) == NULL &&
!cmark_isspace(data[-1]))
return 0;
if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0)
return 0;
link_end = check_domain(data, size, 0);
if (link_end == 0)
return NULL;
while (link_end < size && !cmark_isspace(data[link_end]))
link_end++;
link_end = autolink_delim(data, link_end);
if (link_end == 0)
return NULL;
cmark_inline_parser_set_offset(inline_parser, (int)(max_rewind + link_end));
cmark_node *node = cmark_node_new_with_mem(CMARK_NODE_LINK, parser->mem);
cmark_strbuf buf;
cmark_strbuf_init(parser->mem, &buf, 10);
cmark_strbuf_puts(&buf, "http://");
cmark_strbuf_put(&buf, data, (bufsize_t)link_end);
node->as.link.url = cmark_chunk_buf_detach(&buf);
cmark_node *text = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
text->as.literal =
cmark_chunk_dup(chunk, (bufsize_t)max_rewind, (bufsize_t)link_end);
cmark_node_append_child(node, text);
node->start_line = text->start_line =
node->end_line = text->end_line =
cmark_inline_parser_get_line(inline_parser);
node->start_column = text->start_column = start - 1;
node->end_column = text->end_column = cmark_inline_parser_get_column(inline_parser) - 1;
return node;
}
static cmark_node *url_match(cmark_parser *parser, cmark_node *parent,
cmark_inline_parser *inline_parser) {
size_t link_end, domain_len;
int rewind = 0;
cmark_chunk *chunk = cmark_inline_parser_get_chunk(inline_parser);
int max_rewind = cmark_inline_parser_get_offset(inline_parser);
uint8_t *data = chunk->data + max_rewind;
size_t size = chunk->len - max_rewind;
if (size < 4 || data[1] != '/' || data[2] != '/')
return 0;
while (rewind < max_rewind && cmark_isalpha(data[-rewind - 1]))
rewind++;
if (!sd_autolink_issafe(data - rewind, size + rewind))
return 0;
link_end = strlen("://");
domain_len = check_domain(data + link_end, size - link_end, 1);
if (domain_len == 0)
return 0;
link_end += domain_len;
while (link_end < size && !cmark_isspace(data[link_end]))
link_end++;
link_end = autolink_delim(data, link_end);
if (link_end == 0)
return NULL;
cmark_inline_parser_set_offset(inline_parser, (int)(max_rewind + link_end));
cmark_node_unput(parent, rewind);
cmark_node *node = cmark_node_new_with_mem(CMARK_NODE_LINK, parser->mem);
cmark_chunk url = cmark_chunk_dup(chunk, max_rewind - rewind,
(bufsize_t)(link_end + rewind));
node->as.link.url = url;
cmark_node *text = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
text->as.literal = url;
cmark_node_append_child(node, text);
return node;
}
static cmark_node *match(cmark_syntax_extension *ext, cmark_parser *parser,
cmark_node *parent, unsigned char c,
cmark_inline_parser *inline_parser) {
if (cmark_inline_parser_in_bracket(inline_parser, false) ||
cmark_inline_parser_in_bracket(inline_parser, true))
return NULL;
if (c == ':')
return url_match(parser, parent, inline_parser);
if (c == 'w')
return www_match(parser, parent, inline_parser);
return NULL;
// note that we could end up re-consuming something already a
// part of an inline, because we don't track when the last
// inline was finished in inlines.c.
}
static void postprocess_text(cmark_parser *parser, cmark_node *text, int offset, int depth) {
// postprocess_text can recurse very deeply if there is a very long line of
// '@' only. Stop at a reasonable depth to ensure it cannot crash.
if (depth > 1000) return;
size_t link_end;
uint8_t *data = text->as.literal.data,
*at;
size_t size = text->as.literal.len;
int rewind, max_rewind,
nb = 0, np = 0, ns = 0;
if (offset < 0 || (size_t)offset >= size)
return;
data += offset;
size -= offset;
at = (uint8_t *)memchr(data, '@', size);
if (!at)
return;
max_rewind = (int)(at - data);
data += max_rewind;
size -= max_rewind;
for (rewind = 0; rewind < max_rewind; ++rewind) {
uint8_t c = data[-rewind - 1];
if (cmark_isalnum(c))
continue;
if (strchr(".+-_", c) != NULL)
continue;
if (c == '/')
ns++;
break;
}
if (rewind == 0 || ns > 0) {
postprocess_text(parser, text, max_rewind + 1 + offset, depth + 1);
return;
}
for (link_end = 0; link_end < size; ++link_end) {
uint8_t c = data[link_end];
if (cmark_isalnum(c))
continue;
if (c == '@')
nb++;
else if (c == '.' && link_end < size - 1 && cmark_isalnum(data[link_end + 1]))
np++;
else if (c != '-' && c != '_')
break;
}
if (link_end < 2 || nb != 1 || np == 0 ||
(!cmark_isalpha(data[link_end - 1]) && data[link_end - 1] != '.')) {
postprocess_text(parser, text, max_rewind + 1 + offset, depth + 1);
return;
}
link_end = autolink_delim(data, link_end);
if (link_end == 0) {
postprocess_text(parser, text, max_rewind + 1 + offset, depth + 1);
return;
}
cmark_chunk_to_cstr(parser->mem, &text->as.literal);
cmark_node *link_node = cmark_node_new_with_mem(CMARK_NODE_LINK, parser->mem);
cmark_strbuf buf;
cmark_strbuf_init(parser->mem, &buf, 10);
cmark_strbuf_puts(&buf, "mailto:");
cmark_strbuf_put(&buf, data - rewind, (bufsize_t)(link_end + rewind));
link_node->as.link.url = cmark_chunk_buf_detach(&buf);
cmark_node *link_text = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
cmark_chunk email = cmark_chunk_dup(
&text->as.literal,
offset + max_rewind - rewind,
(bufsize_t)(link_end + rewind));
cmark_chunk_to_cstr(parser->mem, &email);
link_text->as.literal = email;
cmark_node_append_child(link_node, link_text);
cmark_node_insert_after(text, link_node);
cmark_node *post = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
post->as.literal = cmark_chunk_dup(&text->as.literal,
(bufsize_t)(offset + max_rewind + link_end),
(bufsize_t)(size - link_end));
cmark_chunk_to_cstr(parser->mem, &post->as.literal);
cmark_node_insert_after(link_node, post);
text->as.literal.len = offset + max_rewind - rewind;
text->as.literal.data[text->as.literal.len] = 0;
postprocess_text(parser, post, 0, depth + 1);
}
static cmark_node *postprocess(cmark_syntax_extension *ext, cmark_parser *parser, cmark_node *root) {
cmark_iter *iter;
cmark_event_type ev;
cmark_node *node;
bool in_link = false;
cmark_consolidate_text_nodes(root);
iter = cmark_iter_new(root);
while ((ev = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
node = cmark_iter_get_node(iter);
if (in_link) {
if (ev == CMARK_EVENT_EXIT && node->type == CMARK_NODE_LINK) {
in_link = false;
}
continue;
}
if (ev == CMARK_EVENT_ENTER && node->type == CMARK_NODE_LINK) {
in_link = true;
continue;
}
if (ev == CMARK_EVENT_ENTER && node->type == CMARK_NODE_TEXT) {
postprocess_text(parser, node, 0, /*depth*/0);
}
}
cmark_iter_free(iter);
return root;
}
cmark_syntax_extension *create_autolink_extension(void) {
cmark_syntax_extension *ext = cmark_syntax_extension_new("autolink");
cmark_llist *special_chars = NULL;
cmark_syntax_extension_set_match_inline_func(ext, match);
cmark_syntax_extension_set_postprocess_func(ext, postprocess);
cmark_mem *mem = cmark_get_default_mem_allocator();
special_chars = cmark_llist_append(mem, special_chars, (void *)':');
special_chars = cmark_llist_append(mem, special_chars, (void *)'w');
cmark_syntax_extension_set_special_inline_chars(ext, special_chars);
return ext;
}

View File

@ -0,0 +1,8 @@
#ifndef CMARK_GFM_AUTOLINK_H
#define CMARK_GFM_AUTOLINK_H
#include "cmark-gfm-core-extensions.h"
cmark_syntax_extension *create_autolink_extension(void);
#endif

View File

@ -0,0 +1,31 @@
#ifndef CMARK_GFM_CORE_EXTENSIONS_H
#define CMARK_GFM_CORE_EXTENSIONS_H
#ifdef __cplusplus
extern "C" {
#endif
#include "cmark-gfm-extension_api.h"
#include "cmark-gfm-extensions_export.h"
#include <stdint.h>
CMARK_GFM_EXTENSIONS_EXPORT
void cmark_gfm_core_extensions_ensure_registered(void);
CMARK_GFM_EXTENSIONS_EXPORT
uint16_t cmark_gfm_extensions_get_table_columns(cmark_node *node);
CMARK_GFM_EXTENSIONS_EXPORT
uint8_t *cmark_gfm_extensions_get_table_alignments(cmark_node *node);
CMARK_GFM_EXTENSIONS_EXPORT
int cmark_gfm_extensions_get_table_row_is_header(cmark_node *node);
CMARK_GFM_EXTENSIONS_EXPORT
char *cmark_gfm_extensions_get_tasklist_state(cmark_node *node);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,27 @@
#include "cmark-gfm-core-extensions.h"
#include "autolink.h"
#include "strikethrough.h"
#include "table.h"
#include "tagfilter.h"
#include "tasklist.h"
#include "registry.h"
#include "plugin.h"
static int core_extensions_registration(cmark_plugin *plugin) {
cmark_plugin_register_syntax_extension(plugin, create_table_extension());
cmark_plugin_register_syntax_extension(plugin,
create_strikethrough_extension());
cmark_plugin_register_syntax_extension(plugin, create_autolink_extension());
cmark_plugin_register_syntax_extension(plugin, create_tagfilter_extension());
cmark_plugin_register_syntax_extension(plugin, create_tasklist_extension());
return 1;
}
void cmark_gfm_core_extensions_ensure_registered(void) {
static int registered = 0;
if (!registered) {
cmark_register_plugin(core_extensions_registration);
registered = 1;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
#include "chunk.h"
#include "cmark-gfm.h"
#ifdef __cplusplus
extern "C" {
#endif
bufsize_t _ext_scan_at(bufsize_t (*scanner)(const unsigned char *),
unsigned char *ptr, int len, bufsize_t offset);
bufsize_t _scan_table_start(const unsigned char *p);
bufsize_t _scan_table_cell(const unsigned char *p);
bufsize_t _scan_table_cell_end(const unsigned char *p);
bufsize_t _scan_table_row_end(const unsigned char *p);
bufsize_t _scan_tasklist(const unsigned char *p);
#define scan_table_start(c, l, n) _ext_scan_at(&_scan_table_start, c, l, n)
#define scan_table_cell(c, l, n) _ext_scan_at(&_scan_table_cell, c, l, n)
#define scan_table_cell_end(c, l, n) _ext_scan_at(&_scan_table_cell_end, c, l, n)
#define scan_table_row_end(c, l, n) _ext_scan_at(&_scan_table_row_end, c, l, n)
#define scan_tasklist(c, l, n) _ext_scan_at(&_scan_tasklist, c, l, n)
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,85 @@
#include <stdlib.h>
#include "ext_scanners.h"
bufsize_t _ext_scan_at(bufsize_t (*scanner)(const unsigned char *), unsigned char *ptr, int len, bufsize_t offset)
{
bufsize_t res;
if (ptr == NULL || offset >= len) {
return 0;
} else {
unsigned char lim = ptr[len];
ptr[len] = '\0';
res = scanner(ptr + offset);
ptr[len] = lim;
}
return res;
}
/*!re2c
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];
newline = [\r]?[\n];
escaped_char = [\\][|!"#$%&'()*+,./:;<=>?@[\\\]^_`{}~-];
table_marker = (spacechar*[:]?[-]+[:]?spacechar*);
table_cell = (escaped_char|[^|\r\n])*;
tasklist = spacechar*("-"|"+"|"*"|[0-9]+.)spacechar+("[ ]"|"[x]")spacechar+;
*/
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; }
*/
}
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; }
*/
}
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; }
*/
}
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; }
*/
}
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; }
*/
}

View File

@ -0,0 +1,167 @@
#include "strikethrough.h"
#include <parser.h>
#include <render.h>
cmark_node_type CMARK_NODE_STRIKETHROUGH;
static cmark_node *match(cmark_syntax_extension *self, cmark_parser *parser,
cmark_node *parent, unsigned char character,
cmark_inline_parser *inline_parser) {
cmark_node *res = NULL;
int left_flanking, right_flanking, punct_before, punct_after, delims;
char buffer[101];
if (character != '~')
return NULL;
delims = cmark_inline_parser_scan_delimiters(
inline_parser, sizeof(buffer) - 1, '~',
&left_flanking,
&right_flanking, &punct_before, &punct_after);
memset(buffer, '~', delims);
buffer[delims] = 0;
res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
cmark_node_set_literal(res, buffer);
res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser);
res->start_column = cmark_inline_parser_get_column(inline_parser) - delims;
if ((left_flanking || right_flanking) &&
(delims == 2 || (!(parser->options & CMARK_OPT_STRIKETHROUGH_DOUBLE_TILDE) && delims == 1))) {
cmark_inline_parser_push_delimiter(inline_parser, character, left_flanking,
right_flanking, res);
}
return res;
}
static delimiter *insert(cmark_syntax_extension *self, cmark_parser *parser,
cmark_inline_parser *inline_parser, delimiter *opener,
delimiter *closer) {
cmark_node *strikethrough;
cmark_node *tmp, *next;
delimiter *delim, *tmp_delim;
delimiter *res = closer->next;
strikethrough = opener->inl_text;
if (opener->inl_text->as.literal.len != closer->inl_text->as.literal.len)
goto done;
if (!cmark_node_set_type(strikethrough, CMARK_NODE_STRIKETHROUGH))
goto done;
cmark_node_set_syntax_extension(strikethrough, self);
tmp = cmark_node_next(opener->inl_text);
while (tmp) {
if (tmp == closer->inl_text)
break;
next = cmark_node_next(tmp);
cmark_node_append_child(strikethrough, tmp);
tmp = next;
}
strikethrough->end_column = closer->inl_text->start_column + closer->inl_text->as.literal.len - 1;
cmark_node_free(closer->inl_text);
delim = closer;
while (delim != NULL && delim != opener) {
tmp_delim = delim->previous;
cmark_inline_parser_remove_delimiter(inline_parser, delim);
delim = tmp_delim;
}
cmark_inline_parser_remove_delimiter(inline_parser, opener);
done:
return res;
}
static const char *get_type_string(cmark_syntax_extension *extension,
cmark_node *node) {
return node->type == CMARK_NODE_STRIKETHROUGH ? "strikethrough" : "<unknown>";
}
static int can_contain(cmark_syntax_extension *extension, cmark_node *node,
cmark_node_type child_type) {
if (node->type != CMARK_NODE_STRIKETHROUGH)
return false;
return CMARK_NODE_TYPE_INLINE_P(child_type);
}
static void commonmark_render(cmark_syntax_extension *extension,
cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
renderer->out(renderer, node, "~~", false, LITERAL);
}
static void latex_render(cmark_syntax_extension *extension,
cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
// requires \usepackage{ulem}
bool entering = (ev_type == CMARK_EVENT_ENTER);
if (entering) {
renderer->out(renderer, node, "\\sout{", false, LITERAL);
} else {
renderer->out(renderer, node, "}", false, LITERAL);
}
}
static void man_render(cmark_syntax_extension *extension,
cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
bool entering = (ev_type == CMARK_EVENT_ENTER);
if (entering) {
renderer->cr(renderer);
renderer->out(renderer, node, ".ST \"", false, LITERAL);
} else {
renderer->out(renderer, node, "\"", false, LITERAL);
renderer->cr(renderer);
}
}
static void html_render(cmark_syntax_extension *extension,
cmark_html_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
bool entering = (ev_type == CMARK_EVENT_ENTER);
if (entering) {
cmark_strbuf_puts(renderer->html, "<del>");
} else {
cmark_strbuf_puts(renderer->html, "</del>");
}
}
static void plaintext_render(cmark_syntax_extension *extension,
cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
renderer->out(renderer, node, "~", false, LITERAL);
}
cmark_syntax_extension *create_strikethrough_extension(void) {
cmark_syntax_extension *ext = cmark_syntax_extension_new("strikethrough");
cmark_llist *special_chars = NULL;
cmark_syntax_extension_set_get_type_string_func(ext, get_type_string);
cmark_syntax_extension_set_can_contain_func(ext, can_contain);
cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render);
cmark_syntax_extension_set_latex_render_func(ext, latex_render);
cmark_syntax_extension_set_man_render_func(ext, man_render);
cmark_syntax_extension_set_html_render_func(ext, html_render);
cmark_syntax_extension_set_plaintext_render_func(ext, plaintext_render);
CMARK_NODE_STRIKETHROUGH = cmark_syntax_extension_add_node(1);
cmark_syntax_extension_set_match_inline_func(ext, match);
cmark_syntax_extension_set_inline_from_delim_func(ext, insert);
cmark_mem *mem = cmark_get_default_mem_allocator();
special_chars = cmark_llist_append(mem, special_chars, (void *)'~');
cmark_syntax_extension_set_special_inline_chars(ext, special_chars);
cmark_syntax_extension_set_emphasis(ext, 1);
return ext;
}

View File

@ -0,0 +1,9 @@
#ifndef CMARK_GFM_STRIKETHROUGH_H
#define CMARK_GFM_STRIKETHROUGH_H
#include "cmark-gfm-core-extensions.h"
extern cmark_node_type CMARK_NODE_STRIKETHROUGH;
cmark_syntax_extension *create_strikethrough_extension(void);
#endif

View File

@ -0,0 +1,764 @@
#include <cmark-gfm-extension_api.h>
#include <html.h>
#include <inlines.h>
#include <parser.h>
#include <references.h>
#include <string.h>
#include <render.h>
#include "ext_scanners.h"
#include "strikethrough.h"
#include "table.h"
#include "cmark-gfm-core-extensions.h"
cmark_node_type CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW,
CMARK_NODE_TABLE_CELL;
typedef struct {
uint16_t n_columns;
cmark_llist *cells;
} table_row;
typedef struct {
uint16_t n_columns;
uint8_t *alignments;
} node_table;
typedef struct {
bool is_header;
} node_table_row;
typedef struct {
cmark_strbuf *buf;
int start_offset, end_offset, internal_offset;
} node_cell;
static void free_table_cell(cmark_mem *mem, void *data) {
node_cell *cell = (node_cell *)data;
cmark_strbuf_free((cmark_strbuf *)cell->buf);
mem->free(cell->buf);
mem->free(cell);
}
static void free_table_row(cmark_mem *mem, table_row *row) {
if (!row)
return;
cmark_llist_free_full(mem, row->cells, (cmark_free_func)free_table_cell);
mem->free(row);
}
static void free_node_table(cmark_mem *mem, void *ptr) {
node_table *t = (node_table *)ptr;
mem->free(t->alignments);
mem->free(t);
}
static void free_node_table_row(cmark_mem *mem, void *ptr) {
mem->free(ptr);
}
static int get_n_table_columns(cmark_node *node) {
if (!node || node->type != CMARK_NODE_TABLE)
return -1;
return (int)((node_table *)node->as.opaque)->n_columns;
}
static int set_n_table_columns(cmark_node *node, uint16_t n_columns) {
if (!node || node->type != CMARK_NODE_TABLE)
return 0;
((node_table *)node->as.opaque)->n_columns = n_columns;
return 1;
}
static uint8_t *get_table_alignments(cmark_node *node) {
if (!node || node->type != CMARK_NODE_TABLE)
return 0;
return ((node_table *)node->as.opaque)->alignments;
}
static int set_table_alignments(cmark_node *node, uint8_t *alignments) {
if (!node || node->type != CMARK_NODE_TABLE)
return 0;
((node_table *)node->as.opaque)->alignments = alignments;
return 1;
}
static cmark_strbuf *unescape_pipes(cmark_mem *mem, unsigned char *string, bufsize_t len)
{
cmark_strbuf *res = (cmark_strbuf *)mem->calloc(1, sizeof(cmark_strbuf));
bufsize_t r, w;
cmark_strbuf_init(mem, res, len + 1);
cmark_strbuf_put(res, string, len);
cmark_strbuf_putc(res, '\0');
for (r = 0, w = 0; r < len; ++r) {
if (res->ptr[r] == '\\' && res->ptr[r + 1] == '|')
r++;
res->ptr[w++] = res->ptr[r];
}
cmark_strbuf_truncate(res, w);
return res;
}
static table_row *row_from_string(cmark_syntax_extension *self,
cmark_parser *parser, unsigned char *string,
int len) {
table_row *row = NULL;
bufsize_t cell_matched = 1, pipe_matched = 1, offset;
row = (table_row *)parser->mem->calloc(1, sizeof(table_row));
row->n_columns = 0;
row->cells = NULL;
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)) {
cell_matched = scan_table_cell(string, len, offset);
pipe_matched = scan_table_cell_end(string, len, offset + cell_matched);
if (cell_matched || pipe_matched) {
cmark_strbuf *cell_buf = unescape_pipes(parser->mem, string + offset,
cell_matched);
cmark_strbuf_trim(cell_buf);
node_cell *cell = (node_cell *)parser->mem->calloc(1, sizeof(*cell));
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 (offset != len || !row->n_columns) {
free_table_row(parser->mem, row);
row = NULL;
}
return row;
}
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;
node_table_row *ntr;
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();
return parent_container;
}
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);
free_table_row(parser->mem, marker_row);
cmark_arena_pop();
return parent_container;
}
if (cmark_arena_pop()) {
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)) {
free_table_row(parser->mem, header_row);
free_table_row(parser->mem, marker_row);
return parent_container;
}
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 =
(uint8_t *)parser->mem->calloc(header_row->n_columns, sizeof(uint8_t));
cmark_llist *it = marker_row->cells;
for (i = 0; it; it = it->next, ++i) {
node_cell *node = (node_cell *)it->data;
bool left = node->buf->ptr[0] == ':', right = node->buf->ptr[node->buf->size - 1] == ':';
if (left && right)
alignments[i] = 'c';
else if (left)
alignments[i] = 'l';
else if (right)
alignments[i] = 'r';
}
set_table_alignments(parent_container, alignments);
table_header =
cmark_parser_add_child(parser, parent_container, CMARK_NODE_TABLE_ROW,
parent_container->start_column);
cmark_node_set_syntax_extension(table_header, self);
table_header->end_column = parent_container->start_column + (int)strlen(parent_string) - 2;
table_header->start_line = table_header->end_line = parent_container->start_line;
table_header->as.opaque = ntr = (node_table_row *)parser->mem->calloc(1, sizeof(node_table_row));
ntr->is_header = true;
{
cmark_llist *tmp;
for (tmp = header_row->cells; tmp; tmp = tmp->next) {
node_cell *cell = (node_cell *) tmp->data;
cmark_node *header_cell = cmark_parser_add_child(parser, table_header,
CMARK_NODE_TABLE_CELL, parent_container->start_column + cell->start_offset);
header_cell->start_line = header_cell->end_line = parent_container->start_line;
header_cell->internal_offset = cell->internal_offset;
header_cell->end_column = parent_container->start_column + cell->end_offset;
cmark_node_set_string_content(header_cell, (char *) cell->buf->ptr);
cmark_node_set_syntax_extension(header_cell, self);
}
}
cmark_parser_advance_offset(
parser, (char *)input,
(int)strlen((char *)input) - 1 - cmark_parser_get_offset(parser), false);
free_table_row(parser->mem, header_row);
free_table_row(parser->mem, marker_row);
return parent_container;
}
static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
cmark_parser *parser,
cmark_node *parent_container,
unsigned char *input, int len) {
cmark_node *table_row_block;
table_row *row;
if (cmark_parser_is_blank(parser))
return NULL;
table_row_block =
cmark_parser_add_child(parser, parent_container, CMARK_NODE_TABLE_ROW,
parent_container->start_column);
cmark_node_set_syntax_extension(table_row_block, self);
table_row_block->end_column = parent_container->end_column;
table_row_block->as.opaque = parser->mem->calloc(1, sizeof(node_table_row));
row = row_from_string(self, parser, input + cmark_parser_get_first_nonspace(parser),
len - cmark_parser_get_first_nonspace(parser));
{
cmark_llist *tmp;
int i, table_columns = get_n_table_columns(parent_container);
for (tmp = row->cells, i = 0; tmp && i < table_columns; tmp = tmp->next, ++i) {
node_cell *cell = (node_cell *) tmp->data;
cmark_node *node = cmark_parser_add_child(parser, table_row_block,
CMARK_NODE_TABLE_CELL, parent_container->start_column + cell->start_offset);
node->internal_offset = cell->internal_offset;
node->end_column = parent_container->start_column + cell->end_offset;
cmark_node_set_string_content(node, (char *) cell->buf->ptr);
cmark_node_set_syntax_extension(node, self);
}
for (; i < table_columns; ++i) {
cmark_node *node = cmark_parser_add_child(
parser, table_row_block, CMARK_NODE_TABLE_CELL, 0);
cmark_node_set_syntax_extension(node, self);
}
}
free_table_row(parser->mem, row);
cmark_parser_advance_offset(parser, (char *)input,
len - 1 - cmark_parser_get_offset(parser), false);
return table_row_block;
}
static cmark_node *try_opening_table_block(cmark_syntax_extension *self,
int indented, cmark_parser *parser,
cmark_node *parent_container,
unsigned char *input, int len) {
cmark_node_type parent_type = cmark_node_get_type(parent_container);
if (!indented && parent_type == CMARK_NODE_PARAGRAPH) {
return try_opening_table_header(self, parser, parent_container, input, len);
} else if (!indented && parent_type == CMARK_NODE_TABLE) {
return try_opening_table_row(self, parser, parent_container, input, len);
}
return NULL;
}
static int matches(cmark_syntax_extension *self, cmark_parser *parser,
unsigned char *input, int len,
cmark_node *parent_container) {
int res = 0;
if (cmark_node_get_type(parent_container) == CMARK_NODE_TABLE) {
cmark_arena_push();
table_row *new_row = row_from_string(
self, parser, input + cmark_parser_get_first_nonspace(parser),
len - cmark_parser_get_first_nonspace(parser));
if (new_row && new_row->n_columns)
res = 1;
free_table_row(parser->mem, new_row);
cmark_arena_pop();
}
return res;
}
static const char *get_type_string(cmark_syntax_extension *self,
cmark_node *node) {
if (node->type == CMARK_NODE_TABLE) {
return "table";
} else if (node->type == CMARK_NODE_TABLE_ROW) {
if (((node_table_row *)node->as.opaque)->is_header)
return "table_header";
else
return "table_row";
} else if (node->type == CMARK_NODE_TABLE_CELL) {
return "table_cell";
}
return "<unknown>";
}
static int can_contain(cmark_syntax_extension *extension, cmark_node *node,
cmark_node_type child_type) {
if (node->type == CMARK_NODE_TABLE) {
return child_type == CMARK_NODE_TABLE_ROW;
} else if (node->type == CMARK_NODE_TABLE_ROW) {
return child_type == CMARK_NODE_TABLE_CELL;
} else if (node->type == CMARK_NODE_TABLE_CELL) {
return child_type == CMARK_NODE_TEXT || child_type == CMARK_NODE_CODE ||
child_type == CMARK_NODE_EMPH || child_type == CMARK_NODE_STRONG ||
child_type == CMARK_NODE_LINK || child_type == CMARK_NODE_IMAGE ||
child_type == CMARK_NODE_STRIKETHROUGH ||
child_type == CMARK_NODE_HTML_INLINE ||
child_type == CMARK_NODE_FOOTNOTE_REFERENCE;
}
return false;
}
static int contains_inlines(cmark_syntax_extension *extension,
cmark_node *node) {
return node->type == CMARK_NODE_TABLE_CELL;
}
static void commonmark_render(cmark_syntax_extension *extension,
cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
bool entering = (ev_type == CMARK_EVENT_ENTER);
if (node->type == CMARK_NODE_TABLE) {
renderer->blankline(renderer);
} else if (node->type == CMARK_NODE_TABLE_ROW) {
if (entering) {
renderer->cr(renderer);
renderer->out(renderer, node, "|", false, LITERAL);
}
} else if (node->type == CMARK_NODE_TABLE_CELL) {
if (entering) {
renderer->out(renderer, node, " ", false, LITERAL);
} else {
renderer->out(renderer, node, " |", false, LITERAL);
if (((node_table_row *)node->parent->as.opaque)->is_header &&
!node->next) {
int i;
uint8_t *alignments = get_table_alignments(node->parent->parent);
uint16_t n_cols =
((node_table *)node->parent->parent->as.opaque)->n_columns;
renderer->cr(renderer);
renderer->out(renderer, node, "|", false, LITERAL);
for (i = 0; i < n_cols; i++) {
switch (alignments[i]) {
case 0: renderer->out(renderer, node, " --- |", false, LITERAL); break;
case 'l': renderer->out(renderer, node, " :-- |", false, LITERAL); break;
case 'c': renderer->out(renderer, node, " :-: |", false, LITERAL); break;
case 'r': renderer->out(renderer, node, " --: |", false, LITERAL); break;
}
}
renderer->cr(renderer);
}
}
} else {
assert(false);
}
}
static void latex_render(cmark_syntax_extension *extension,
cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
bool entering = (ev_type == CMARK_EVENT_ENTER);
if (node->type == CMARK_NODE_TABLE) {
if (entering) {
int i;
uint16_t n_cols;
uint8_t *alignments = get_table_alignments(node);
renderer->cr(renderer);
renderer->out(renderer, node, "\\begin{table}", false, LITERAL);
renderer->cr(renderer);
renderer->out(renderer, node, "\\begin{tabular}{", false, LITERAL);
n_cols = ((node_table *)node->as.opaque)->n_columns;
for (i = 0; i < n_cols; i++) {
switch(alignments[i]) {
case 0:
case 'l':
renderer->out(renderer, node, "l", false, LITERAL);
break;
case 'c':
renderer->out(renderer, node, "c", false, LITERAL);
break;
case 'r':
renderer->out(renderer, node, "r", false, LITERAL);
break;
}
}
renderer->out(renderer, node, "}", false, LITERAL);
renderer->cr(renderer);
} else {
renderer->out(renderer, node, "\\end{tabular}", false, LITERAL);
renderer->cr(renderer);
renderer->out(renderer, node, "\\end{table}", false, LITERAL);
renderer->cr(renderer);
}
} else if (node->type == CMARK_NODE_TABLE_ROW) {
if (!entering) {
renderer->cr(renderer);
}
} else if (node->type == CMARK_NODE_TABLE_CELL) {
if (!entering) {
if (node->next) {
renderer->out(renderer, node, " & ", false, LITERAL);
} else {
renderer->out(renderer, node, " \\\\", false, LITERAL);
}
}
} else {
assert(false);
}
}
static const char *xml_attr(cmark_syntax_extension *extension,
cmark_node *node) {
if (node->type == CMARK_NODE_TABLE_CELL) {
if (cmark_gfm_extensions_get_table_row_is_header(node->parent)) {
uint8_t *alignments = get_table_alignments(node->parent->parent);
int i = 0;
cmark_node *n;
for (n = node->parent->first_child; n; n = n->next, ++i)
if (n == node)
break;
switch (alignments[i]) {
case 'l': return " align=\"left\"";
case 'c': return " align=\"center\"";
case 'r': return " align=\"right\"";
}
}
}
return NULL;
}
static void man_render(cmark_syntax_extension *extension,
cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
bool entering = (ev_type == CMARK_EVENT_ENTER);
if (node->type == CMARK_NODE_TABLE) {
if (entering) {
int i;
uint16_t n_cols;
uint8_t *alignments = get_table_alignments(node);
renderer->cr(renderer);
renderer->out(renderer, node, ".TS", false, LITERAL);
renderer->cr(renderer);
renderer->out(renderer, node, "tab(@);", false, LITERAL);
renderer->cr(renderer);
n_cols = ((node_table *)node->as.opaque)->n_columns;
for (i = 0; i < n_cols; i++) {
switch (alignments[i]) {
case 'l':
renderer->out(renderer, node, "l", false, LITERAL);
break;
case 0:
case 'c':
renderer->out(renderer, node, "c", false, LITERAL);
break;
case 'r':
renderer->out(renderer, node, "r", false, LITERAL);
break;
}
}
if (n_cols) {
renderer->out(renderer, node, ".", false, LITERAL);
renderer->cr(renderer);
}
} else {
renderer->out(renderer, node, ".TE", false, LITERAL);
renderer->cr(renderer);
}
} else if (node->type == CMARK_NODE_TABLE_ROW) {
if (!entering) {
renderer->cr(renderer);
}
} else if (node->type == CMARK_NODE_TABLE_CELL) {
if (!entering && node->next) {
renderer->out(renderer, node, "@", false, LITERAL);
}
} else {
assert(false);
}
}
static void html_table_add_align(cmark_strbuf* html, const char* align, int options) {
if (options & CMARK_OPT_TABLE_PREFER_STYLE_ATTRIBUTES) {
cmark_strbuf_puts(html, " style=\"text-align: ");
cmark_strbuf_puts(html, align);
cmark_strbuf_puts(html, "\"");
} else {
cmark_strbuf_puts(html, " align=\"");
cmark_strbuf_puts(html, align);
cmark_strbuf_puts(html, "\"");
}
}
struct html_table_state {
unsigned need_closing_table_body : 1;
unsigned in_table_header : 1;
};
static void html_render(cmark_syntax_extension *extension,
cmark_html_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
bool entering = (ev_type == CMARK_EVENT_ENTER);
cmark_strbuf *html = renderer->html;
cmark_node *n;
// XXX: we just monopolise renderer->opaque.
struct html_table_state *table_state =
(struct html_table_state *)&renderer->opaque;
if (node->type == CMARK_NODE_TABLE) {
if (entering) {
cmark_html_render_cr(html);
cmark_strbuf_puts(html, "<table");
cmark_html_render_sourcepos(node, html, options);
cmark_strbuf_putc(html, '>');
table_state->need_closing_table_body = false;
} else {
if (table_state->need_closing_table_body) {
cmark_html_render_cr(html);
cmark_strbuf_puts(html, "</tbody>");
cmark_html_render_cr(html);
}
table_state->need_closing_table_body = false;
cmark_html_render_cr(html);
cmark_strbuf_puts(html, "</table>");
cmark_html_render_cr(html);
}
} else if (node->type == CMARK_NODE_TABLE_ROW) {
if (entering) {
cmark_html_render_cr(html);
if (((node_table_row *)node->as.opaque)->is_header) {
table_state->in_table_header = 1;
cmark_strbuf_puts(html, "<thead>");
cmark_html_render_cr(html);
} else if (!table_state->need_closing_table_body) {
cmark_strbuf_puts(html, "<tbody>");
cmark_html_render_cr(html);
table_state->need_closing_table_body = 1;
}
cmark_strbuf_puts(html, "<tr");
cmark_html_render_sourcepos(node, html, options);
cmark_strbuf_putc(html, '>');
} else {
cmark_html_render_cr(html);
cmark_strbuf_puts(html, "</tr>");
if (((node_table_row *)node->as.opaque)->is_header) {
cmark_html_render_cr(html);
cmark_strbuf_puts(html, "</thead>");
table_state->in_table_header = false;
}
}
} else if (node->type == CMARK_NODE_TABLE_CELL) {
uint8_t *alignments = get_table_alignments(node->parent->parent);
if (entering) {
cmark_html_render_cr(html);
if (table_state->in_table_header) {
cmark_strbuf_puts(html, "<th");
} else {
cmark_strbuf_puts(html, "<td");
}
int i = 0;
for (n = node->parent->first_child; n; n = n->next, ++i)
if (n == node)
break;
switch (alignments[i]) {
case 'l': html_table_add_align(html, "left", options); break;
case 'c': html_table_add_align(html, "center", options); break;
case 'r': html_table_add_align(html, "right", options); break;
}
cmark_html_render_sourcepos(node, html, options);
cmark_strbuf_putc(html, '>');
} else {
if (table_state->in_table_header) {
cmark_strbuf_puts(html, "</th>");
} else {
cmark_strbuf_puts(html, "</td>");
}
}
} else {
assert(false);
}
}
static void opaque_alloc(cmark_syntax_extension *self, cmark_mem *mem, cmark_node *node) {
if (node->type == CMARK_NODE_TABLE) {
node->as.opaque = mem->calloc(1, sizeof(node_table));
} else if (node->type == CMARK_NODE_TABLE_ROW) {
node->as.opaque = mem->calloc(1, sizeof(node_table_row));
} else if (node->type == CMARK_NODE_TABLE_CELL) {
node->as.opaque = mem->calloc(1, sizeof(node_cell));
}
}
static void opaque_free(cmark_syntax_extension *self, cmark_mem *mem, cmark_node *node) {
if (node->type == CMARK_NODE_TABLE) {
free_node_table(mem, node->as.opaque);
} else if (node->type == CMARK_NODE_TABLE_ROW) {
free_node_table_row(mem, node->as.opaque);
}
}
static int escape(cmark_syntax_extension *self, cmark_node *node, int c) {
return
node->type != CMARK_NODE_TABLE &&
node->type != CMARK_NODE_TABLE_ROW &&
node->type != CMARK_NODE_TABLE_CELL &&
c == '|';
}
cmark_syntax_extension *create_table_extension(void) {
cmark_syntax_extension *self = cmark_syntax_extension_new("table");
cmark_syntax_extension_set_match_block_func(self, matches);
cmark_syntax_extension_set_open_block_func(self, try_opening_table_block);
cmark_syntax_extension_set_get_type_string_func(self, get_type_string);
cmark_syntax_extension_set_can_contain_func(self, can_contain);
cmark_syntax_extension_set_contains_inlines_func(self, contains_inlines);
cmark_syntax_extension_set_commonmark_render_func(self, commonmark_render);
cmark_syntax_extension_set_plaintext_render_func(self, commonmark_render);
cmark_syntax_extension_set_latex_render_func(self, latex_render);
cmark_syntax_extension_set_xml_attr_func(self, xml_attr);
cmark_syntax_extension_set_man_render_func(self, man_render);
cmark_syntax_extension_set_html_render_func(self, html_render);
cmark_syntax_extension_set_opaque_alloc_func(self, opaque_alloc);
cmark_syntax_extension_set_opaque_free_func(self, opaque_free);
cmark_syntax_extension_set_commonmark_escape_func(self, escape);
CMARK_NODE_TABLE = cmark_syntax_extension_add_node(0);
CMARK_NODE_TABLE_ROW = cmark_syntax_extension_add_node(0);
CMARK_NODE_TABLE_CELL = cmark_syntax_extension_add_node(0);
return self;
}
uint16_t cmark_gfm_extensions_get_table_columns(cmark_node *node) {
if (node->type != CMARK_NODE_TABLE)
return 0;
return ((node_table *)node->as.opaque)->n_columns;
}
uint8_t *cmark_gfm_extensions_get_table_alignments(cmark_node *node) {
if (node->type != CMARK_NODE_TABLE)
return 0;
return ((node_table *)node->as.opaque)->alignments;
}
int cmark_gfm_extensions_set_table_columns(cmark_node *node, uint16_t n_columns) {
return set_n_table_columns(node, n_columns);
}
int cmark_gfm_extensions_set_table_alignments(cmark_node *node, uint16_t ncols, uint8_t *alignments) {
uint8_t *a = (uint8_t *)cmark_node_mem(node)->calloc(1, ncols);
memcpy(a, alignments, ncols);
return set_table_alignments(node, a);
}
int cmark_gfm_extensions_get_table_row_is_header(cmark_node *node)
{
if (!node || node->type != CMARK_NODE_TABLE_ROW)
return 0;
return ((node_table_row *)node->as.opaque)->is_header;
}
int cmark_gfm_extensions_set_table_row_is_header(cmark_node *node, int is_header)
{
if (!node || node->type != CMARK_NODE_TABLE_ROW)
return 0;
((node_table_row *)node->as.opaque)->is_header = (is_header != 0);
return 1;
}

View File

@ -0,0 +1,12 @@
#ifndef CMARK_GFM_TABLE_H
#define CMARK_GFM_TABLE_H
#include "cmark-gfm-core-extensions.h"
extern cmark_node_type CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW,
CMARK_NODE_TABLE_CELL;
cmark_syntax_extension *create_table_extension(void);
#endif

View File

@ -0,0 +1,60 @@
#include "tagfilter.h"
#include <parser.h>
#include <ctype.h>
static const char *blacklist[] = {
"title", "textarea", "style", "xmp", "iframe",
"noembed", "noframes", "script", "plaintext", NULL,
};
static int is_tag(const unsigned char *tag_data, size_t tag_size,
const char *tagname) {
size_t i;
if (tag_size < 3 || tag_data[0] != '<')
return 0;
i = 1;
if (tag_data[i] == '/') {
i++;
}
for (; i < tag_size; ++i, ++tagname) {
if (*tagname == 0)
break;
if (tolower(tag_data[i]) != *tagname)
return 0;
}
if (i == tag_size)
return 0;
if (cmark_isspace(tag_data[i]) || tag_data[i] == '>')
return 1;
if (tag_data[i] == '/' && tag_size >= i + 2 && tag_data[i + 1] == '>')
return 1;
return 0;
}
static int filter(cmark_syntax_extension *ext, const unsigned char *tag,
size_t tag_len) {
const char **it;
for (it = blacklist; *it; ++it) {
if (is_tag(tag, tag_len, *it)) {
return 0;
}
}
return 1;
}
cmark_syntax_extension *create_tagfilter_extension(void) {
cmark_syntax_extension *ext = cmark_syntax_extension_new("tagfilter");
cmark_syntax_extension_set_html_filter_func(ext, filter);
return ext;
}

View File

@ -0,0 +1,8 @@
#ifndef CMARK_GFM_TAGFILTER_H
#define CMARK_GFM_TAGFILTER_H
#include "cmark-gfm-core-extensions.h"
cmark_syntax_extension *create_tagfilter_extension(void);
#endif

View File

@ -0,0 +1,135 @@
#include "tasklist.h"
#include <parser.h>
#include <render.h>
#include <html.h>
#include "ext_scanners.h"
typedef enum {
CMARK_TASKLIST_NOCHECKED,
CMARK_TASKLIST_CHECKED,
} cmark_tasklist_type;
static const char *get_type_string(cmark_syntax_extension *extension, cmark_node *node) {
return "tasklist";
}
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 0;
if ((int)node->as.opaque != CMARK_TASKLIST_CHECKED) {
return "checked";
}
else {
return "unchecked";
}
}
static bool parse_node_item_prefix(cmark_parser *parser, const char *input,
cmark_node *container) {
bool res = false;
if (parser->indent >=
container->as.list.marker_offset + container->as.list.padding) {
cmark_parser_advance_offset(parser, input, container->as.list.marker_offset +
container->as.list.padding,
true);
res = true;
} else if (parser->blank && container->first_child != NULL) {
// if container->first_child is NULL, then the opening line
// of the list item was blank after the list marker; in this
// case, we are done with the list item.
cmark_parser_advance_offset(parser, input, parser->first_nonspace - parser->offset,
false);
res = true;
}
return res;
}
static int matches(cmark_syntax_extension *self, cmark_parser *parser,
unsigned char *input, int len,
cmark_node *parent_container) {
return parse_node_item_prefix(parser, (const char*)input, parent_container);
}
static int can_contain(cmark_syntax_extension *extension, cmark_node *node,
cmark_node_type child_type) {
return (node->type == CMARK_NODE_ITEM) ? 1 : 0;
}
static cmark_node *open_tasklist_item(cmark_syntax_extension *self,
int indented, cmark_parser *parser,
cmark_node *parent_container,
unsigned char *input, int len) {
cmark_node_type node_type = cmark_node_get_type(parent_container);
if (node_type != CMARK_NODE_ITEM) {
return NULL;
}
bufsize_t matched = scan_tasklist(input, len, 0);
if (!matched) {
return NULL;
}
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;
}
return NULL;
}
static void commonmark_render(cmark_syntax_extension *extension,
cmark_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
bool entering = (ev_type == CMARK_EVENT_ENTER);
if (entering) {
renderer->cr(renderer);
if ((int)node->as.opaque == CMARK_TASKLIST_CHECKED) {
renderer->out(renderer, node, "- [x] ", false, LITERAL);
} else {
renderer->out(renderer, node, "- [ ] ", false, LITERAL);
}
cmark_strbuf_puts(renderer->prefix, " ");
} else {
cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 2);
renderer->cr(renderer);
}
}
static void html_render(cmark_syntax_extension *extension,
cmark_html_renderer *renderer, cmark_node *node,
cmark_event_type ev_type, int options) {
bool entering = (ev_type == CMARK_EVENT_ENTER);
if (entering) {
cmark_html_render_cr(renderer->html);
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) {
cmark_strbuf_puts(renderer->html, "<input type=\"checkbox\" checked=\"\" disabled=\"\" /> ");
} else {
cmark_strbuf_puts(renderer->html, "<input type=\"checkbox\" disabled=\"\" /> ");
}
} else {
cmark_strbuf_puts(renderer->html, "</li>\n");
}
}
cmark_syntax_extension *create_tasklist_extension(void) {
cmark_syntax_extension *ext = cmark_syntax_extension_new("tasklist");
cmark_syntax_extension_set_match_block_func(ext, matches);
cmark_syntax_extension_set_get_type_string_func(ext, get_type_string);
cmark_syntax_extension_set_open_block_func(ext, open_tasklist_item);
cmark_syntax_extension_set_can_contain_func(ext, can_contain);
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);
return ext;
}

View File

@ -0,0 +1,8 @@
#ifndef TASKLIST_H
#define TASKLIST_H
#include "cmark-gfm-core-extensions.h"
cmark_syntax_extension *create_tasklist_extension(void);
#endif