static link_type get_link_type(cmark_node *node) { size_t title_len, url_len; cmark_node *link_text; char *realurl; int realurllen; bool isemail = false; if (node->type != CMARK_NODE_LINK) { return NO_LINK; } const char *url = cmark_node_get_url(node); cmark_chunk url_chunk = cmark_chunk_literal(url); if (url && *url == '#') { return INTERNAL_LINK; } url_len = strlen(url); if (url_len == 0 || scan_scheme(&url_chunk, 0) == 0) { return NO_LINK; } const char *title = cmark_node_get_title(node); title_len = strlen(title); // if it has a title, we can't treat it as an autolink: if (title_len == 0) { link_text = node->first_child; cmark_consolidate_text_nodes(link_text); if (!link_text) return NO_LINK; realurl = (char *)url; realurllen = (int)url_len; if (strncmp(realurl, "mailto:", 7) == 0) { realurl += 7; realurllen -= 7; isemail = true; } if (realurllen == link_text->as.literal.len && strncmp(realurl, (char *)link_text->as.literal.data, link_text->as.literal.len) == 0) { if (isemail) { return EMAIL_AUTOLINK; } else { return URL_AUTOLINK; } } } return NORMAL_LINK; }
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; bool extra_spaces; int i; bool entering = (ev_type == CMARK_EVENT_ENTER); const char *info, *code, *title; char fencechar[2] = {'\0', '\0'}; size_t info_len, code_len; char listmarker[LISTMARKER_SIZE]; char *emph_delim; bool first_in_list_item; bufsize_t marker_width; bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options) && !(CMARK_OPT_HARDBREAKS & options); // 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))); } switch (node->type) { case CMARK_NODE_DOCUMENT: break; case CMARK_NODE_BLOCK_QUOTE: if (entering) { LIT("> "); renderer->begin_content = true; cmark_strbuf_puts(renderer->prefix, "> "); } else { cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 2); BLANKLINE(); } break; case CMARK_NODE_LIST: if (!entering && node->next && (node->next->type == CMARK_NODE_CODE_BLOCK || node->next->type == CMARK_NODE_LIST)) { // this ensures that a following indented code block or list will be // inteprereted correctly. CR(); LIT("<!-- end list -->"); BLANKLINE(); } break; case CMARK_NODE_ITEM: 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_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 snprintf(listmarker, LISTMARKER_SIZE, "%d%s%s", list_number, list_delim == CMARK_PAREN_DELIM ? ")" : ".", list_number < 10 ? " " : " "); marker_width = strlen(listmarker); } if (entering) { if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { LIT(" - "); renderer->begin_content = true; } else { LIT(listmarker); renderer->begin_content = true; } for (i = marker_width; i--;) { cmark_strbuf_putc(renderer->prefix, ' '); } } else { cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - marker_width); CR(); } break; case CMARK_NODE_HEADING: if (entering) { for (i = cmark_node_get_heading_level(node); i > 0; i--) { LIT("#"); } LIT(" "); renderer->begin_content = true; renderer->no_linebreaks = true; } else { renderer->no_linebreaks = false; BLANKLINE(); } break; case CMARK_NODE_CODE_BLOCK: first_in_list_item = node->prev == NULL && node->parent && node->parent->type == CMARK_NODE_ITEM; if (!first_in_list_item) { BLANKLINE(); } info = cmark_node_get_fence_info(node); info_len = strlen(info); fencechar[0] = strchr(info, '`') == NULL ? '`' : '~'; code = cmark_node_get_literal(node); code_len = strlen(code); // use indented form if no info, and code doesn't // begin or end with a blank line, and code isn't // first thing in a list item if (info_len == 0 && (code_len > 2 && !cmark_isspace(code[0]) && !(cmark_isspace(code[code_len - 1]) && cmark_isspace(code[code_len - 2]))) && !first_in_list_item) { LIT(" "); cmark_strbuf_puts(renderer->prefix, " "); OUT(cmark_node_get_literal(node), false, LITERAL); cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 4); } else { numticks = longest_backtick_sequence(code) + 1; if (numticks < 3) { numticks = 3; } for (i = 0; i < numticks; i++) { LIT(fencechar); } LIT(" "); OUT(info, false, LITERAL); CR(); OUT(cmark_node_get_literal(node), false, LITERAL); CR(); for (i = 0; i < numticks; i++) { LIT(fencechar); } } BLANKLINE(); break; case CMARK_NODE_HTML_BLOCK: BLANKLINE(); OUT(cmark_node_get_literal(node), false, LITERAL); BLANKLINE(); break; case CMARK_NODE_CUSTOM_BLOCK: BLANKLINE(); OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), false, LITERAL); BLANKLINE(); break; case CMARK_NODE_THEMATIC_BREAK: BLANKLINE(); LIT("-----"); BLANKLINE(); break; case CMARK_NODE_PARAGRAPH: if (!entering) { BLANKLINE(); } break; case CMARK_NODE_TEXT: OUT(cmark_node_get_literal(node), allow_wrap, NORMAL); break; case CMARK_NODE_LINEBREAK: if (!(CMARK_OPT_HARDBREAKS & options)) { LIT(" "); } CR(); break; case CMARK_NODE_SOFTBREAK: if (CMARK_OPT_HARDBREAKS & options) { LIT(" "); CR(); } else if (!renderer->no_linebreaks && renderer->width == 0 && !(CMARK_OPT_HARDBREAKS & options) && !(CMARK_OPT_NOBREAKS & options)) { CR(); } else { OUT(" ", allow_wrap, LITERAL); } break; case CMARK_NODE_CODE: code = cmark_node_get_literal(node); code_len = strlen(code); numticks = shortest_unused_backtick_sequence(code); extra_spaces = code_len == 0 || code[0] == '`' || code[code_len - 1] == '`' || code[0] == ' ' || code[code_len - 1] == ' '; for (i = 0; i < numticks; i++) { LIT("`"); } if (extra_spaces) { LIT(" "); } OUT(cmark_node_get_literal(node), allow_wrap, LITERAL); if (extra_spaces) { LIT(" "); } for (i = 0; i < numticks; i++) { LIT("`"); } break; case CMARK_NODE_HTML_INLINE: OUT(cmark_node_get_literal(node), false, LITERAL); break; case CMARK_NODE_CUSTOM_INLINE: OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), false, LITERAL); break; case CMARK_NODE_STRONG: if (entering) { LIT("**"); } else { LIT("**"); } break; case CMARK_NODE_EMPH: // If we have EMPH(EMPH(x)), we need to use *_x_* // because **x** is STRONG(x): if (node->parent && node->parent->type == CMARK_NODE_EMPH && node->next == NULL && node->prev == NULL) { emph_delim = "_"; } else { emph_delim = "*"; } if (entering) { LIT(emph_delim); } else { LIT(emph_delim); } break; case CMARK_NODE_LINK: if (is_autolink(node)) { if (entering) { LIT("<"); if (strncmp(cmark_node_get_url(node), "mailto:", 7) == 0) { LIT((const char *)cmark_node_get_url(node) + 7); } else { LIT((const char *)cmark_node_get_url(node)); } LIT(">"); // return signal to skip contents of node... return 0; } } else { if (entering) { LIT("["); } else { LIT("]("); OUT(cmark_node_get_url(node), false, URL); title = cmark_node_get_title(node); if (strlen(title) > 0) { LIT(" \""); OUT(title, false, TITLE); LIT("\""); } LIT(")"); } } break; case CMARK_NODE_IMAGE: if (entering) { LIT("!["); } else { LIT("]("); OUT(cmark_node_get_url(node), false, URL); title = cmark_node_get_title(node); if (strlen(title) > 0) { OUT(" \"", allow_wrap, LITERAL); OUT(title, false, TITLE); LIT("\""); } LIT(")"); } break; default: assert(false); break; } return 1; }
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; int i; bool entering = (ev_type == CMARK_EVENT_ENTER); const char *info, *code, *title; size_t info_len, code_len; cmark_strbuf listmarker = GH_BUF_INIT; char *emph_delim; bufsize_t marker_width; // 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->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)); } switch (node->type) { case CMARK_NODE_DOCUMENT: break; case CMARK_NODE_BLOCK_QUOTE: if (entering) { LIT("> "); cmark_strbuf_puts(renderer->prefix, "> "); } else { cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 2); BLANKLINE(); } break; case CMARK_NODE_LIST: if (!entering && node->next && (node->next->type == CMARK_NODE_CODE_BLOCK || node->next->type == CMARK_NODE_LIST)) { // this ensures 2 blank lines after list, // if before code block or list: LIT("\n"); } break; case CMARK_NODE_ITEM: if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { marker_width = 2; } else { list_number = cmark_node_get_list_start(node->parent); 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 cmark_strbuf_printf(&listmarker, "%d%s%s", list_number, list_delim == CMARK_PAREN_DELIM ? ")" : ".", list_number < 10 ? " " : " "); marker_width = listmarker.size; } if (entering) { if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { LIT("* "); cmark_strbuf_puts(renderer->prefix, " "); } else { LIT((char *)listmarker.ptr); for (i = marker_width; i--;) { cmark_strbuf_putc(renderer->prefix, ' '); } } } else { cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - marker_width); CR(); } cmark_strbuf_free(&listmarker); break; case CMARK_NODE_HEADER: if (entering) { for (int i = cmark_node_get_header_level(node); i > 0; i--) { LIT("#"); } LIT(" "); renderer->no_wrap = true; } else { renderer->no_wrap = false; BLANKLINE(); } break; case CMARK_NODE_CODE_BLOCK: BLANKLINE(); info = cmark_node_get_fence_info(node); info_len = safe_strlen(info); code = cmark_node_get_literal(node); code_len = safe_strlen(code); // use indented form if no info, and code doesn't // begin or end with a blank line, and code isn't // first thing in a list item if (info_len == 0 && (code_len > 2 && !isspace(code[0]) && !(isspace(code[code_len - 1]) && isspace(code[code_len - 2]))) && !(node->prev == NULL && node->parent && node->parent->type == CMARK_NODE_ITEM)) { LIT(" "); cmark_strbuf_puts(renderer->prefix, " "); OUT(cmark_node_get_literal(node), false, LITERAL); cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 4); } else { numticks = longest_backtick_sequence(code) + 1; if (numticks < 3) { numticks = 3; } for (i = 0; i < numticks; i++) { LIT("`"); } LIT(" "); OUT(info, false, LITERAL); CR(); OUT(cmark_node_get_literal(node), false, LITERAL); CR(); for (i = 0; i < numticks; i++) { LIT("`"); } } BLANKLINE(); break; case CMARK_NODE_HTML: BLANKLINE(); OUT(cmark_node_get_literal(node), false, LITERAL); BLANKLINE(); break; case CMARK_NODE_HRULE: BLANKLINE(); LIT("-----"); BLANKLINE(); break; case CMARK_NODE_PARAGRAPH: if (!entering) { BLANKLINE(); } break; case CMARK_NODE_TEXT: OUT(cmark_node_get_literal(node), true, NORMAL); break; case CMARK_NODE_LINEBREAK: if (!(CMARK_OPT_HARDBREAKS & options)) { LIT("\\"); } CR(); break; case CMARK_NODE_SOFTBREAK: if (renderer->width == 0 && !(CMARK_OPT_HARDBREAKS & options)) { CR(); } else { OUT(" ", true, LITERAL); } break; case CMARK_NODE_CODE: code = cmark_node_get_literal(node); code_len = safe_strlen(code); numticks = shortest_unused_backtick_sequence(code); for (i = 0; i < numticks; i++) { LIT("`"); } if (code_len == 0 || code[0] == '`') { LIT(" "); } OUT(cmark_node_get_literal(node), true, LITERAL); if (code_len == 0 || code[code_len - 1] == '`') { LIT(" "); } for (i = 0; i < numticks; i++) { LIT("`"); } break; case CMARK_NODE_INLINE_HTML: OUT(cmark_node_get_literal(node), false, LITERAL); break; case CMARK_NODE_STRONG: if (entering) { LIT("**"); } else { LIT("**"); } break; case CMARK_NODE_EMPH: // If we have EMPH(EMPH(x)), we need to use *_x_* // because **x** is STRONG(x): if (node->parent && node->parent->type == CMARK_NODE_EMPH && node->next == NULL && node->prev == NULL) { emph_delim = "_"; } else { emph_delim = "*"; } if (entering) { LIT(emph_delim); } else { LIT(emph_delim); } break; case CMARK_NODE_LINK: if (is_autolink(node)) { if (entering) { LIT("<"); if (strncmp(cmark_node_get_url(node), "mailto:", 7) == 0) { LIT((char *)cmark_node_get_url(node) + 7); } else { LIT((char *)cmark_node_get_url(node)); } LIT(">"); // return signal to skip contents of node... return 0; } } else { if (entering) { LIT("["); } else { LIT("]("); OUT(cmark_node_get_url(node), false, URL); title = cmark_node_get_title(node); if (safe_strlen(title) > 0) { LIT(" \""); OUT(title, false, TITLE); LIT("\""); } LIT(")"); } } break; case CMARK_NODE_IMAGE: if (entering) { LIT("!["); } else { LIT("]("); OUT(cmark_node_get_url(node), false, URL); title = cmark_node_get_title(node); if (safe_strlen(title) > 0) { OUT(" \"", true, LITERAL); OUT(title, false, TITLE); LIT("\""); } LIT(")"); } break; default: assert(false); break; } return 1; }
static void accessors(test_batch_runner *runner) { static const char markdown[] = "## Header\n" "\n" "* Item 1\n" "* Item 2\n" "\n" "2. Item 1\n" "\n" "3. Item 2\n" "\n" "\n" " code\n" "\n" "``` lang\n" "fenced\n" "```\n" "\n" "<div>html</div>\n" "\n" "[link](url 'title')\n"; cmark_node *doc = cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); // Getters cmark_node *header = cmark_node_first_child(doc); INT_EQ(runner, cmark_node_get_header_level(header), 2, "get_header_level"); cmark_node *bullet_list = cmark_node_next(header); INT_EQ(runner, cmark_node_get_list_type(bullet_list), CMARK_BULLET_LIST, "get_list_type bullet"); INT_EQ(runner, cmark_node_get_list_tight(bullet_list), 1, "get_list_tight tight"); cmark_node *ordered_list = cmark_node_next(bullet_list); INT_EQ(runner, cmark_node_get_list_type(ordered_list), CMARK_ORDERED_LIST, "get_list_type ordered"); INT_EQ(runner, cmark_node_get_list_delim(ordered_list), CMARK_PERIOD_DELIM, "get_list_delim ordered"); INT_EQ(runner, cmark_node_get_list_start(ordered_list), 2, "get_list_start"); INT_EQ(runner, cmark_node_get_list_tight(ordered_list), 0, "get_list_tight loose"); cmark_node *code = cmark_node_next(ordered_list); STR_EQ(runner, cmark_node_get_literal(code), "code\n", "get_literal indented code"); cmark_node *fenced = cmark_node_next(code); STR_EQ(runner, cmark_node_get_literal(fenced), "fenced\n", "get_literal fenced code"); STR_EQ(runner, cmark_node_get_fence_info(fenced), "lang", "get_fence_info"); cmark_node *html = cmark_node_next(fenced); STR_EQ(runner, cmark_node_get_literal(html), "<div>html</div>\n", "get_literal html"); cmark_node *paragraph = cmark_node_next(html); INT_EQ(runner, cmark_node_get_start_line(paragraph), 19, "get_start_line"); INT_EQ(runner, cmark_node_get_start_column(paragraph), 1, "get_start_column"); INT_EQ(runner, cmark_node_get_end_line(paragraph), 19, "get_end_line"); cmark_node *link = cmark_node_first_child(paragraph); STR_EQ(runner, cmark_node_get_url(link), "url", "get_url"); STR_EQ(runner, cmark_node_get_title(link), "title", "get_title"); cmark_node *string = cmark_node_first_child(link); STR_EQ(runner, cmark_node_get_literal(string), "link", "get_literal string"); // Setters OK(runner, cmark_node_set_header_level(header, 3), "set_header_level"); OK(runner, cmark_node_set_list_type(bullet_list, CMARK_ORDERED_LIST), "set_list_type ordered"); OK(runner, cmark_node_set_list_delim(bullet_list, CMARK_PAREN_DELIM), "set_list_delim paren"); OK(runner, cmark_node_set_list_start(bullet_list, 3), "set_list_start"); OK(runner, cmark_node_set_list_tight(bullet_list, 0), "set_list_tight loose"); OK(runner, cmark_node_set_list_type(ordered_list, CMARK_BULLET_LIST), "set_list_type bullet"); OK(runner, cmark_node_set_list_tight(ordered_list, 1), "set_list_tight tight"); OK(runner, cmark_node_set_literal(code, "CODE\n"), "set_literal indented code"); OK(runner, cmark_node_set_literal(fenced, "FENCED\n"), "set_literal fenced code"); OK(runner, cmark_node_set_fence_info(fenced, "LANG"), "set_fence_info"); OK(runner, cmark_node_set_literal(html, "<div>HTML</div>\n"), "set_literal html"); OK(runner, cmark_node_set_url(link, "URL"), "set_url"); OK(runner, cmark_node_set_title(link, "TITLE"), "set_title"); OK(runner, cmark_node_set_literal(string, "LINK"), "set_literal string"); char *rendered_html = cmark_render_html(doc, CMARK_OPT_DEFAULT); static const char expected_html[] = "<h3>Header</h3>\n" "<ol start=\"3\">\n" "<li>\n" "<p>Item 1</p>\n" "</li>\n" "<li>\n" "<p>Item 2</p>\n" "</li>\n" "</ol>\n" "<ul>\n" "<li>Item 1</li>\n" "<li>Item 2</li>\n" "</ul>\n" "<pre><code>CODE\n" "</code></pre>\n" "<pre><code class=\"language-LANG\">FENCED\n" "</code></pre>\n" "<div>HTML</div>\n" "<p><a href=\"URL\" title=\"TITLE\">LINK</a></p>\n"; STR_EQ(runner, rendered_html, expected_html, "setters work"); free(rendered_html); // Getter errors INT_EQ(runner, cmark_node_get_header_level(bullet_list), 0, "get_header_level error"); INT_EQ(runner, cmark_node_get_list_type(header), CMARK_NO_LIST, "get_list_type error"); INT_EQ(runner, cmark_node_get_list_start(code), 0, "get_list_start error"); INT_EQ(runner, cmark_node_get_list_tight(fenced), 0, "get_list_tight error"); OK(runner, cmark_node_get_literal(ordered_list) == NULL, "get_literal error"); OK(runner, cmark_node_get_fence_info(paragraph) == NULL, "get_fence_info error"); OK(runner, cmark_node_get_url(html) == NULL, "get_url error"); OK(runner, cmark_node_get_title(header) == NULL, "get_title error"); // Setter errors OK(runner, !cmark_node_set_header_level(bullet_list, 3), "set_header_level error"); OK(runner, !cmark_node_set_list_type(header, CMARK_ORDERED_LIST), "set_list_type error"); OK(runner, !cmark_node_set_list_start(code, 3), "set_list_start error"); OK(runner, !cmark_node_set_list_tight(fenced, 0), "set_list_tight error"); OK(runner, !cmark_node_set_literal(ordered_list, "content\n"), "set_literal error"); OK(runner, !cmark_node_set_fence_info(paragraph, "lang"), "set_fence_info error"); OK(runner, !cmark_node_set_url(html, "url"), "set_url error"); OK(runner, !cmark_node_set_title(header, "title"), "set_title error"); OK(runner, !cmark_node_set_header_level(header, 0), "set_header_level too small"); OK(runner, !cmark_node_set_header_level(header, 7), "set_header_level too large"); OK(runner, !cmark_node_set_list_type(bullet_list, CMARK_NO_LIST), "set_list_type invalid"); OK(runner, !cmark_node_set_list_start(bullet_list, -1), "set_list_start negative"); cmark_node_free(doc); }