diff --git a/Makefile b/Makefile index b92c1c11..9e682c56 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,3 @@ ext/commonmarker/cmark-gfm_export.h: ext/commonmarker/cmark-upstream/build/src/c ext/commonmarker/cmark-gfm_version.h: ext/commonmarker/cmark-upstream/build/src/cmark-gfm_version.h cp $< $@ - -ext/commonmarker/cmark-gfm-extensions_export.h: ext/commonmarker/cmark-upstream/build/extensions/cmark-gfm-extensions_export.h - cp $< $@ diff --git a/ext/commonmarker/arena.c b/ext/commonmarker/arena.c index 6373c0d0..da1a70e9 100644 --- a/ext/commonmarker/arena.c +++ b/ext/commonmarker/arena.c @@ -99,6 +99,6 @@ static void arena_free(void *ptr) { cmark_mem CMARK_ARENA_MEM_ALLOCATOR = {arena_calloc, arena_realloc, arena_free}; -cmark_mem *cmark_get_arena_mem_allocator() { +cmark_mem *cmark_get_arena_mem_allocator(void) { return &CMARK_ARENA_MEM_ALLOCATOR; } diff --git a/ext/commonmarker/autolink.c b/ext/commonmarker/autolink.c index f3b75a7e..491d96c3 100644 --- a/ext/commonmarker/autolink.c +++ b/ext/commonmarker/autolink.c @@ -267,6 +267,11 @@ static cmark_node *url_match(cmark_parser *parser, cmark_node *parent, cmark_node *text = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); text->as.literal = url; 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 = max_rewind - rewind; + node->end_column = text->end_column = cmark_inline_parser_get_column(inline_parser) - 1; return node; } diff --git a/ext/commonmarker/blocks.c b/ext/commonmarker/blocks.c index 94045aba..03a58748 100644 --- a/ext/commonmarker/blocks.c +++ b/ext/commonmarker/blocks.c @@ -27,6 +27,14 @@ #define CODE_INDENT 4 #define TAB_STOP 4 +/** + * Very deeply nested lists can cause quadratic performance issues. + * This constant is used in open_new_blocks() to limit the nesting + * depth. It is unlikely that a non-contrived markdown document will + * be nested this deeply. + */ +#define MAX_LIST_DEPTH 100 + #ifndef MIN #define MIN(x, y) ((x < y) ? x : y) #endif @@ -1119,10 +1127,11 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container, bool has_content; int save_offset; int save_column; + size_t depth = 0; while (cont_type != CMARK_NODE_CODE_BLOCK && cont_type != CMARK_NODE_HTML_BLOCK) { - + depth++; S_find_first_nonspace(parser, input); indented = parser->indent >= CODE_INDENT; @@ -1224,6 +1233,7 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container, (*container)->internal_offset = matched; } else if ((!indented || cont_type == CMARK_NODE_LIST) && parser->indent < 4 && + depth < MAX_LIST_DEPTH && (matched = parse_list_marker( parser->mem, input, parser->first_nonspace, (*container)->type == CMARK_NODE_PARAGRAPH, &data))) { diff --git a/ext/commonmarker/cmark-gfm-core-extensions.h b/ext/commonmarker/cmark-gfm-core-extensions.h index 0645915f..bc29ffd4 100644 --- a/ext/commonmarker/cmark-gfm-core-extensions.h +++ b/ext/commonmarker/cmark-gfm-core-extensions.h @@ -6,45 +6,45 @@ extern "C" { #endif #include "cmark-gfm-extension_api.h" -#include "cmark-gfm-extensions_export.h" -#include "config.h" // for bool +#include "cmark-gfm_export.h" +#include #include -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT void cmark_gfm_core_extensions_ensure_registered(void); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT uint16_t cmark_gfm_extensions_get_table_columns(cmark_node *node); /** Sets the number of columns for the table, returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_table_columns(cmark_node *node, uint16_t n_columns); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT uint8_t *cmark_gfm_extensions_get_table_alignments(cmark_node *node); /** Sets the alignments for the table, returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_table_alignments(cmark_node *node, uint16_t ncols, uint8_t *alignments); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_get_table_row_is_header(cmark_node *node); /** Sets whether the node is a table header row, returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_table_row_is_header(cmark_node *node, int is_header); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node); /* For backwards compatibility */ #define cmark_gfm_extensions_tasklist_is_checked cmark_gfm_extensions_get_tasklist_item_checked /** Sets whether a tasklist item is "checked" (completed), returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_checked); #ifdef __cplusplus diff --git a/ext/commonmarker/cmark-gfm.h b/ext/commonmarker/cmark-gfm.h index 6fb28693..0544057a 100644 --- a/ext/commonmarker/cmark-gfm.h +++ b/ext/commonmarker/cmark-gfm.h @@ -111,13 +111,13 @@ typedef struct cmark_mem { * realloc and free. */ CMARK_GFM_EXPORT -cmark_mem *cmark_get_default_mem_allocator(); +cmark_mem *cmark_get_default_mem_allocator(void); /** An arena allocator; uses system calloc to allocate large * slabs of memory. Memory in these slabs is not reused at all. */ CMARK_GFM_EXPORT -cmark_mem *cmark_get_arena_mem_allocator(); +cmark_mem *cmark_get_arena_mem_allocator(void); /** Resets the arena allocator, quickly returning all used memory * to the operating system. @@ -225,6 +225,11 @@ CMARK_GFM_EXPORT cmark_node *cmark_node_first_child(cmark_node *node); */ CMARK_GFM_EXPORT cmark_node *cmark_node_last_child(cmark_node *node); +/** Returns the footnote reference of 'node', or NULL if 'node' doesn't have a + * footnote reference. + */ +CMARK_GFM_EXPORT cmark_node *cmark_node_parent_footnote_def(cmark_node *node); + /** * ## Iterator * @@ -408,6 +413,17 @@ CMARK_GFM_EXPORT int cmark_node_get_list_tight(cmark_node *node); */ CMARK_GFM_EXPORT int cmark_node_set_list_tight(cmark_node *node, int tight); +/** + * Returns item index of 'node'. This is only used when rendering output + * formats such as commonmark, which need to output the index. It is not + * required for formats such as html or latex. + */ +CMARK_GFM_EXPORT int cmark_node_get_item_index(cmark_node *node); + +/** Sets item index of 'node'. Returns 1 on success, 0 on failure. + */ +CMARK_GFM_EXPORT int cmark_node_set_item_index(cmark_node *node, int idx); + /** Returns the info string from a fenced code block. */ CMARK_GFM_EXPORT const char *cmark_node_get_fence_info(cmark_node *node); diff --git a/ext/commonmarker/cmark-gfm_version.h b/ext/commonmarker/cmark-gfm_version.h index e2bbfbbb..19e67d81 100644 --- a/ext/commonmarker/cmark-gfm_version.h +++ b/ext/commonmarker/cmark-gfm_version.h @@ -1,7 +1,7 @@ #ifndef CMARK_GFM_VERSION_H #define CMARK_GFM_VERSION_H -#define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 6) -#define CMARK_GFM_VERSION_STRING "0.29.0.gfm.6" +#define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 11) +#define CMARK_GFM_VERSION_STRING "0.29.0.gfm.11" #endif diff --git a/ext/commonmarker/cmark-upstream b/ext/commonmarker/cmark-upstream index 57d5e093..1e230827 160000 --- a/ext/commonmarker/cmark-upstream +++ b/ext/commonmarker/cmark-upstream @@ -1 +1 @@ -Subproject commit 57d5e093ef801f54bf4174c900f7a863599bb47d +Subproject commit 1e230827a584ebc9938c3eadc5059c55ef3c9abf diff --git a/ext/commonmarker/cmark.c b/ext/commonmarker/cmark.c index b3fad4b0..68c40c47 100644 --- a/ext/commonmarker/cmark.c +++ b/ext/commonmarker/cmark.c @@ -10,9 +10,9 @@ cmark_node_type CMARK_NODE_LAST_BLOCK = CMARK_NODE_FOOTNOTE_DEFINITION; cmark_node_type CMARK_NODE_LAST_INLINE = CMARK_NODE_FOOTNOTE_REFERENCE; -int cmark_version() { return CMARK_GFM_VERSION; } +int cmark_version(void) { return CMARK_GFM_VERSION; } -const char *cmark_version_string() { return CMARK_GFM_VERSION_STRING; } +const char *cmark_version_string(void) { return CMARK_GFM_VERSION_STRING; } static void *xcalloc(size_t nmem, size_t size) { void *ptr = calloc(nmem, size); @@ -38,7 +38,7 @@ static void xfree(void *ptr) { cmark_mem CMARK_DEFAULT_MEM_ALLOCATOR = {xcalloc, xrealloc, xfree}; -cmark_mem *cmark_get_default_mem_allocator() { +cmark_mem *cmark_get_default_mem_allocator(void) { return &CMARK_DEFAULT_MEM_ALLOCATOR; } diff --git a/ext/commonmarker/commonmark.c b/ext/commonmarker/commonmark.c index 2e071944..4815bfc3 100644 --- a/ext/commonmarker/commonmark.c +++ b/ext/commonmarker/commonmark.c @@ -153,23 +153,8 @@ static bool is_autolink(cmark_node *node) { link_text->as.literal.len) == 0); } -// if node is a block node, returns node. -// otherwise returns first block-level node that is an ancestor of node. -// if there is no block-level ancestor, returns NULL. -static cmark_node *get_containing_block(cmark_node *node) { - while (node) { - if (CMARK_NODE_BLOCK_P(node)) { - return node; - } else { - node = node->parent; - } - } - return NULL; -} - static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { - cmark_node *tmp; int list_number; cmark_delim_type list_delim; int numticks; @@ -189,14 +174,17 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, // Don't adjust tight list status til we've started the list. // Otherwise we loose the blank line between a paragraph and // a following list. - if (!(node->type == CMARK_NODE_ITEM && node->prev == NULL && entering)) { - tmp = get_containing_block(node); - renderer->in_tight_list_item = - tmp && // tmp might be NULL if there is no containing block - ((tmp->type == CMARK_NODE_ITEM && - cmark_node_get_list_tight(tmp->parent)) || - (tmp && tmp->parent && tmp->parent->type == CMARK_NODE_ITEM && - cmark_node_get_list_tight(tmp->parent->parent))); + if (entering) { + if (node->parent && node->parent->type == CMARK_NODE_ITEM) { + renderer->in_tight_list_item = node->parent->parent->as.list.tight; + } + } else { + if (node->type == CMARK_NODE_LIST) { + renderer->in_tight_list_item = + node->parent && + node->parent->type == CMARK_NODE_ITEM && + node->parent->parent->as.list.tight; + } } if (node->extension && node->extension->commonmark_render_func) { @@ -234,13 +222,8 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { marker_width = 4; } else { - list_number = cmark_node_get_list_start(node->parent); + list_number = cmark_node_get_item_index(node); list_delim = cmark_node_get_list_delim(node->parent); - tmp = node; - while (tmp->prev) { - tmp = tmp->prev; - list_number += 1; - } // we ensure a width of at least 4 so // we get nice transition from single digits // to double @@ -405,10 +388,12 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_STRONG: - if (entering) { - LIT("**"); - } else { - LIT("**"); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + LIT("**"); + } else { + LIT("**"); + } } break; diff --git a/ext/commonmarker/html.c b/ext/commonmarker/html.c index 12d3c3e9..22513c93 100644 --- a/ext/commonmarker/html.c +++ b/ext/commonmarker/html.c @@ -63,10 +63,16 @@ static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf * if (renderer->written_footnote_ix >= renderer->footnote_ix) return false; renderer->written_footnote_ix = renderer->footnote_ix; + char m[32]; + snprintf(m, sizeof(m), "%d", renderer->written_footnote_ix); cmark_strbuf_puts(html, "as.literal.data, node->as.literal.len); - cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩"); + cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\""); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "\" aria-label=\"Back to reference "); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "\">↩"); if (node->footnote.def_count > 1) { @@ -78,7 +84,15 @@ static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf * houdini_escape_href(html, node->as.literal.data, node->as.literal.len); cmark_strbuf_puts(html, "-"); cmark_strbuf_puts(html, n); - cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩"); + cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\""); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "-"); + cmark_strbuf_puts(html, n); + cmark_strbuf_puts(html, "\" aria-label=\"Back to reference "); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "-"); + cmark_strbuf_puts(html, n); + cmark_strbuf_puts(html, "\">↩"); cmark_strbuf_puts(html, n); cmark_strbuf_puts(html, ""); } @@ -350,10 +364,12 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node, break; case CMARK_NODE_STRONG: - if (entering) { - cmark_strbuf_puts(html, ""); - } else { - cmark_strbuf_puts(html, ""); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + cmark_strbuf_puts(html, ""); + } else { + cmark_strbuf_puts(html, ""); + } } break; diff --git a/ext/commonmarker/latex.c b/ext/commonmarker/latex.c index 8be15b0d..1a6367a4 100644 --- a/ext/commonmarker/latex.c +++ b/ext/commonmarker/latex.c @@ -385,10 +385,12 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_STRONG: - if (entering) { - LIT("\\textbf{"); - } else { - LIT("}"); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + LIT("\\textbf{"); + } else { + LIT("}"); + } } break; diff --git a/ext/commonmarker/man.c b/ext/commonmarker/man.c index 441a96e4..634fd9d0 100644 --- a/ext/commonmarker/man.c +++ b/ext/commonmarker/man.c @@ -74,7 +74,6 @@ static void S_outc(cmark_renderer *renderer, cmark_node *node, static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { - cmark_node *tmp; int list_number; bool entering = (ev_type == CMARK_EVENT_ENTER); bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options); @@ -123,12 +122,7 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { LIT("\\[bu] 2"); } else { - list_number = cmark_node_get_list_start(node->parent); - tmp = node; - while (tmp->prev) { - tmp = tmp->prev; - list_number += 1; - } + list_number = cmark_node_get_item_index(node); char list_number_s[LIST_NUMBER_SIZE]; snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number); LIT(list_number_s); @@ -225,10 +219,12 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_STRONG: - if (entering) { - LIT("\\f[B]"); - } else { - LIT("\\f[]"); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + LIT("\\f[B]"); + } else { + LIT("\\f[]"); + } } break; diff --git a/ext/commonmarker/node.c b/ext/commonmarker/node.c index 80c434e3..67f657d8 100644 --- a/ext/commonmarker/node.c +++ b/ext/commonmarker/node.c @@ -5,16 +5,22 @@ #include "node.h" #include "syntax_extension.h" +/** + * Expensive safety checks are off by default, but can be enabled + * by calling cmark_enable_safety_checks(). + */ +static bool enable_safety_checks = false; + +void cmark_enable_safety_checks(bool enable) { + enable_safety_checks = enable; +} + static void S_node_unlink(cmark_node *node); #define NODE_MEM(node) cmark_node_mem(node) -cmark_node__internal_flags CMARK_NODE__OPEN; -cmark_node__internal_flags CMARK_NODE__LAST_LINE_BLANK; -cmark_node__internal_flags CMARK_NODE__LAST_LINE_CHECKED; - -void cmark_register_node_flag(cmark_node__internal_flags *flags) { - static uint8_t shift = 0; +void cmark_register_node_flag(cmark_node_internal_flags *flags) { + static cmark_node_internal_flags nextflag = CMARK_NODE__REGISTER_FIRST; // flags should be a pointer to a global variable and this function // should only be called once to initialize its value. @@ -24,24 +30,16 @@ void cmark_register_node_flag(cmark_node__internal_flags *flags) { } // Check that we haven't run out of bits. - if (shift >= 8 * sizeof(cmark_node__internal_flags)) { + if (nextflag == 0) { fprintf(stderr, "too many flags in cmark_register_node_flag\n"); abort(); } - *flags = (cmark_node__internal_flags)1 << shift; - shift++; + *flags = nextflag; + nextflag <<= 1; } -void cmark_init_standard_node_flags() { - static int initialized = 0; - if (!initialized) { - initialized = 1; - cmark_register_node_flag(&CMARK_NODE__OPEN); - cmark_register_node_flag(&CMARK_NODE__LAST_LINE_BLANK); - cmark_register_node_flag(&CMARK_NODE__LAST_LINE_CHECKED); - } -} +void cmark_init_standard_node_flags(void) {} bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type) { if (child_type == CMARK_NODE_DOCUMENT) { @@ -82,8 +80,6 @@ bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type) { } static bool S_can_contain(cmark_node *node, cmark_node *child) { - cmark_node *cur; - if (node == NULL || child == NULL) { return false; } @@ -91,14 +87,16 @@ static bool S_can_contain(cmark_node *node, cmark_node *child) { return 0; } - // Verify that child is not an ancestor of node or equal to node. - cur = node; - do { - if (cur == child) { - return false; - } - cur = cur->parent; - } while (cur != NULL); + if (enable_safety_checks) { + // Verify that child is not an ancestor of node or equal to node. + cmark_node *cur = node; + do { + if (cur == child) { + return false; + } + cur = cur->parent; + } while (cur != NULL); + } return cmark_node_can_contain_type(node, (cmark_node_type) child->type); } @@ -335,6 +333,14 @@ cmark_node *cmark_node_last_child(cmark_node *node) { } } +cmark_node *cmark_node_parent_footnote_def(cmark_node *node) { + if (node == NULL) { + return NULL; + } else { + return node->parent_footnote_def; + } +} + void *cmark_node_get_user_data(cmark_node *node) { if (node == NULL) { return NULL; @@ -558,6 +564,31 @@ int cmark_node_set_list_tight(cmark_node *node, int tight) { } } +int cmark_node_get_item_index(cmark_node *node) { + if (node == NULL) { + return 0; + } + + if (node->type == CMARK_NODE_ITEM) { + return node->as.list.start; + } else { + return 0; + } +} + +int cmark_node_set_item_index(cmark_node *node, int idx) { + if (node == NULL || idx < 0) { + return 0; + } + + if (node->type == CMARK_NODE_ITEM) { + node->as.list.start = idx; + return 1; + } else { + return 0; + } +} + const char *cmark_node_get_fence_info(cmark_node *node) { if (node == NULL) { return NULL; diff --git a/ext/commonmarker/node.h b/ext/commonmarker/node.h index 281258f7..38ac4a6f 100644 --- a/ext/commonmarker/node.h +++ b/ext/commonmarker/node.h @@ -48,7 +48,17 @@ typedef struct { cmark_chunk on_exit; } cmark_custom; -typedef uint16_t cmark_node__internal_flags; +enum cmark_node__internal_flags { + CMARK_NODE__OPEN = (1 << 0), + CMARK_NODE__LAST_LINE_BLANK = (1 << 1), + CMARK_NODE__LAST_LINE_CHECKED = (1 << 2), + + // Extensions can register custom flags by calling `cmark_register_node_flag`. + // This is the starting value for the custom flags. + CMARK_NODE__REGISTER_FIRST = (1 << 3), +}; + +typedef uint16_t cmark_node_internal_flags; struct cmark_node { cmark_strbuf content; @@ -68,10 +78,18 @@ struct cmark_node { int end_column; int internal_offset; uint16_t type; - cmark_node__internal_flags flags; + cmark_node_internal_flags flags; cmark_syntax_extension *extension; + /** + * Used during cmark_render() to cache the most recent non-NULL + * extension, if you go up the parent chain like this: + * + * node->parent->...parent->extension + */ + cmark_syntax_extension *ancestor_extension; + union { int ref_ix; int def_count; @@ -98,22 +116,18 @@ struct cmark_node { * which will store the flag value. */ CMARK_GFM_EXPORT -void cmark_register_node_flag(cmark_node__internal_flags *flags); +void cmark_register_node_flag(cmark_node_internal_flags *flags); /** - * Standard node flags. (Initialized using `cmark_init_standard_node_flags`.) - */ -extern cmark_node__internal_flags CMARK_NODE__OPEN; -extern cmark_node__internal_flags CMARK_NODE__LAST_LINE_BLANK; -extern cmark_node__internal_flags CMARK_NODE__LAST_LINE_CHECKED; - -/** - * Uses `cmark_register_node_flag` to initialize the standard node flags. - * This function should be called at program startup time. Calling it - * multiple times has no additional effect. + * DEPRECATED. + * + * This function was added in cmark-gfm version 0.29.0.gfm.7, and was + * required to be called at program start time, which caused + * backwards-compatibility issues in applications that use cmark-gfm as a + * library. It is now a no-op. */ CMARK_GFM_EXPORT -void cmark_init_standard_node_flags(); +void cmark_init_standard_node_flags(void); static CMARK_INLINE cmark_mem *cmark_node_mem(cmark_node *node) { return node->content.mem; @@ -138,6 +152,13 @@ static CMARK_INLINE bool CMARK_NODE_INLINE_P(cmark_node *node) { CMARK_GFM_EXPORT bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type); +/** + * Enable (or disable) extra safety checks. These extra checks cause + * extra performance overhead (in some cases quadratic), so they are only + * intended to be used during testing. + */ +CMARK_GFM_EXPORT void cmark_enable_safety_checks(bool enable); + #ifdef __cplusplus } #endif diff --git a/ext/commonmarker/plaintext.c b/ext/commonmarker/plaintext.c index b25e4a39..0c7d257b 100644 --- a/ext/commonmarker/plaintext.c +++ b/ext/commonmarker/plaintext.c @@ -16,23 +16,8 @@ static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_node *node, cmark_render_code_point(renderer, c); } -// if node is a block node, returns node. -// otherwise returns first block-level node that is an ancestor of node. -// if there is no block-level ancestor, returns NULL. -static cmark_node *get_containing_block(cmark_node *node) { - while (node) { - if (CMARK_NODE_BLOCK_P(node)) { - return node; - } else { - node = node->parent; - } - } - return NULL; -} - static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { - cmark_node *tmp; int list_number; cmark_delim_type list_delim; int i; @@ -46,14 +31,17 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, // Don't adjust tight list status til we've started the list. // Otherwise we loose the blank line between a paragraph and // a following list. - if (!(node->type == CMARK_NODE_ITEM && node->prev == NULL && entering)) { - tmp = get_containing_block(node); - renderer->in_tight_list_item = - tmp && // tmp might be NULL if there is no containing block - ((tmp->type == CMARK_NODE_ITEM && - cmark_node_get_list_tight(tmp->parent)) || - (tmp && tmp->parent && tmp->parent->type == CMARK_NODE_ITEM && - cmark_node_get_list_tight(tmp->parent->parent))); + if (entering) { + if (node->parent && node->parent->type == CMARK_NODE_ITEM) { + renderer->in_tight_list_item = node->parent->parent->as.list.tight; + } + } else { + if (node->type == CMARK_NODE_LIST) { + renderer->in_tight_list_item = + node->parent && + node->parent->type == CMARK_NODE_ITEM && + node->parent->parent->as.list.tight; + } } if (node->extension && node->extension->plaintext_render_func) { @@ -79,13 +67,8 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { marker_width = 4; } else { - list_number = cmark_node_get_list_start(node->parent); + list_number = cmark_node_get_item_index(node); list_delim = cmark_node_get_list_delim(node->parent); - tmp = node; - while (tmp->prev) { - tmp = tmp->prev; - list_number += 1; - } // we ensure a width of at least 4 so // we get nice transition from single digits // to double diff --git a/ext/commonmarker/render.c b/ext/commonmarker/render.c index 02e9e838..1a0d2ae8 100644 --- a/ext/commonmarker/render.c +++ b/ext/commonmarker/render.c @@ -31,13 +31,7 @@ static void S_out(cmark_renderer *renderer, cmark_node *node, cmark_chunk remainder = cmark_chunk_literal(""); int k = renderer->buffer->size - 1; - cmark_syntax_extension *ext = NULL; - cmark_node *n = node; - while (n && !ext) { - ext = n->extension; - if (!ext) - n = n->parent; - } + cmark_syntax_extension *ext = node->ancestor_extension; if (ext && !ext->commonmark_escape_func) ext = NULL; @@ -182,6 +176,20 @@ char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width, while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { cur = cmark_iter_get_node(iter); + if (cur->extension) { + cur->ancestor_extension = cur->extension; + } else if (cur->parent) { + cur->ancestor_extension = cur->parent->ancestor_extension; + } + if (cur->type == CMARK_NODE_ITEM) { + // Calculate the list item's index, for the benefit of output formats + // like commonmark and plaintext. + if (cur->prev) { + cmark_node_set_item_index(cur, 1 + cmark_node_get_item_index(cur->prev)); + } else { + cmark_node_set_item_index(cur, cmark_node_get_list_start(cur->parent)); + } + } if (!render_node(&renderer, cur, ev_type, options)) { // a false value causes us to skip processing // the node's contents. this is used for diff --git a/ext/commonmarker/table.c b/ext/commonmarker/table.c index 7f281322..e53ea315 100644 --- a/ext/commonmarker/table.c +++ b/ext/commonmarker/table.c @@ -12,7 +12,7 @@ #include "cmark-gfm-core-extensions.h" // Custom node flag, initialized in `create_table_extension`. -static cmark_node__internal_flags CMARK_NODE__TABLE_VISITED; +static cmark_node_internal_flags CMARK_NODE__TABLE_VISITED; cmark_node_type CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW, CMARK_NODE_TABLE_CELL; diff --git a/ext/commonmarker/xml.c b/ext/commonmarker/xml.c index 2975bf96..5753e5ab 100644 --- a/ext/commonmarker/xml.c +++ b/ext/commonmarker/xml.c @@ -11,6 +11,7 @@ #include "syntax_extension.h" #define BUFFER_SIZE 100 +#define MAX_INDENT 40 // Functions to convert cmark_nodes to XML strings. @@ -26,7 +27,7 @@ struct render_state { static CMARK_INLINE void indent(struct render_state *state) { int i; - for (i = 0; i < state->indent; i++) { + for (i = 0; i < state->indent && i < MAX_INDENT; i++) { cmark_strbuf_putc(state->xml, ' '); } } diff --git a/lib/commonmarker/renderer/html_renderer.rb b/lib/commonmarker/renderer/html_renderer.rb index df10042d..2d8375ab 100644 --- a/lib/commonmarker/renderer/html_renderer.rb +++ b/lib/commonmarker/renderer/html_renderer.rb @@ -129,8 +129,12 @@ def emph(_) out("", :children, "") end - def strong(_) - out("", :children, "") + def strong(node) + if node.parent&.type == :strong + out(:children) + else + out("", :children, "") + end end def link(node) diff --git a/lib/commonmarker/version.rb b/lib/commonmarker/version.rb index 7d03a7ea..77032fd8 100644 --- a/lib/commonmarker/version.rb +++ b/lib/commonmarker/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module CommonMarker - VERSION = "0.23.7" + VERSION = "0.23.9" end diff --git a/test/test_footnotes.rb b/test/test_footnotes.rb index 7cdfd93a..0e00a913 100644 --- a/test/test_footnotes.rb +++ b/test/test_footnotes.rb @@ -13,7 +13,7 @@ def test_to_html
  1. -

    Hey!

    +

    Hey!

@@ -50,7 +50,7 @@ def test_render_html
  1. -

    This is a footnote

    +

    This is a footnote