Compiled with extensions lib
parent
120071b3cc
commit
dc33ebcd2f
|
@ -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})
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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
|
|
@ -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; }
|
||||
*/
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef TASKLIST_H
|
||||
#define TASKLIST_H
|
||||
|
||||
#include "cmark-gfm-core-extensions.h"
|
||||
|
||||
cmark_syntax_extension *create_tasklist_extension(void);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue