static inline void outc(cmark_renderer *renderer, cmark_escaping escape, int32_t c, unsigned char nextc) { bool needs_escaping = false; needs_escaping = escape != LITERAL && ((escape == NORMAL && (c == '*' || c == '_' || c == '[' || c == ']' || c == '#' || c == '<' || c == '>' || c == '\\' || c == '`' || c == '!' || (c == '&' && isalpha(nextc)) || (c == '!' && nextc == '[') || (renderer->begin_line && (c == '-' || c == '+' || c == '=')) || ((c == '.' || c == ')') && isdigit(renderer->buffer->ptr[renderer->buffer->size - 1])))) || (escape == URL && (c == '`' || c == '<' || c == '>' || isspace(c) || c == '\\' || c == ')' || c == '(')) || (escape == TITLE && (c == '`' || c == '<' || c == '>' || c == '"' || c == '\\'))); if (needs_escaping) { if (isspace(c)) { // use percent encoding for spaces cmark_strbuf_printf(renderer->buffer, "%%%2x", c); renderer->column += 3; } else { cmark_render_ascii(renderer, "\\"); cmark_render_code_point(renderer, c); } } else { cmark_render_code_point(renderer, c); } }
static int S_render_node(cmark_node *node, cmark_event_type ev_type, struct render_state *state) { cmark_node *tmp; cmark_strbuf *man = state->man; int list_number; bool entering = (ev_type == CMARK_EVENT_ENTER); if (state->plain == node) { // back at original node state->plain = NULL; } if (state->plain != NULL) { switch(node->type) { case CMARK_NODE_TEXT: case CMARK_NODE_CODE: escape_man(man, node->as.literal.data, node->as.literal.len); break; case CMARK_NODE_LINEBREAK: case CMARK_NODE_SOFTBREAK: cmark_strbuf_putc(man, ' '); break; default: break; } return 1; } switch (node->type) { case CMARK_NODE_DOCUMENT: break; case CMARK_NODE_BLOCK_QUOTE: if (entering) { cr(man); cmark_strbuf_puts(man, ".RS"); cr(man); } else { cr(man); cmark_strbuf_puts(man, ".RE"); cr(man); } break; case CMARK_NODE_LIST: break; case CMARK_NODE_ITEM: if (entering) { cr(man); cmark_strbuf_puts(man, ".IP "); if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { cmark_strbuf_puts(man, "\\[bu] 2"); } else { list_number = cmark_node_get_list_start(node->parent); tmp = node; while (tmp->prev) { tmp = tmp->prev; list_number += 1; } cmark_strbuf_printf(man, "\"%d.\" 4", list_number); } cr(man); } else { cr(man); } break; case CMARK_NODE_HEADER: if (entering) { cr(man); cmark_strbuf_puts(man, cmark_node_get_header_level(node) == 1 ? ".SH" : ".SS"); cr(man); } else { cr(man); } break; case CMARK_NODE_CODE_BLOCK: cr(man); cmark_strbuf_puts(man, ".IP\n.nf\n\\f[C]\n"); escape_man(man, node->as.code.literal.data, node->as.code.literal.len); cr(man); cmark_strbuf_puts(man, "\\f[]\n.fi"); cr(man); break; case CMARK_NODE_HTML: break; case CMARK_NODE_HRULE: cr(man); cmark_strbuf_puts(man, ".PP\n * * * * *"); cr(man); break; case CMARK_NODE_PARAGRAPH: if (entering) { // no blank line if first paragraph in list: if (node->parent && node->parent->type == CMARK_NODE_ITEM && node->prev == NULL) { // no blank line or .PP } else { cr(man); cmark_strbuf_puts(man, ".PP\n"); } } else { cr(man); } break; case CMARK_NODE_TEXT: escape_man(man, node->as.literal.data, node->as.literal.len); break; case CMARK_NODE_LINEBREAK: cmark_strbuf_puts(man, ".PD 0\n.P\n.PD"); cr(man); break; case CMARK_NODE_SOFTBREAK: cmark_strbuf_putc(man, '\n'); break; case CMARK_NODE_CODE: cmark_strbuf_puts(man, "\\f[C]"); escape_man(man, node->as.literal.data, node->as.literal.len); cmark_strbuf_puts(man, "\\f[]"); break; case CMARK_NODE_INLINE_HTML: break; case CMARK_NODE_STRONG: if (entering) { cmark_strbuf_puts(man, "\\f[B]"); } else { cmark_strbuf_puts(man, "\\f[]"); } break; case CMARK_NODE_EMPH: if (entering) { cmark_strbuf_puts(man, "\\f[I]"); } else { cmark_strbuf_puts(man, "\\f[]"); } break; case CMARK_NODE_LINK: if (!entering) { cmark_strbuf_printf(man, " (%s)", cmark_node_get_url(node)); } break; case CMARK_NODE_IMAGE: if (entering) { cmark_strbuf_puts(man, "[IMAGE: "); state->plain = node; } else { cmark_strbuf_puts(man, "]"); } break; default: assert(false); break; } // cmark_strbuf_putc(man, 'x'); return 1; }
static int S_render_node(cmark_node *node, cmark_event_type ev_type, struct render_state *state, long options) { cmark_strbuf *xml = state->xml; bool literal = false; cmark_delim_type delim; bool entering = (ev_type == CMARK_EVENT_ENTER); if (entering) { indent(state); cmark_strbuf_printf(xml, "<%s", cmark_node_get_type_string(node)); if (options & CMARK_OPT_SOURCEPOS && node->start_line != 0) { cmark_strbuf_printf(xml, " sourcepos=\"%d:%d-%d:%d\"", node->start_line, node->start_column, node->end_line, node->end_column); } literal = false; switch (node->type) { case CMARK_NODE_TEXT: case CMARK_NODE_CODE: case CMARK_NODE_HTML: case CMARK_NODE_INLINE_HTML: cmark_strbuf_puts(xml, ">"); escape_xml(xml, node->as.literal.data, node->as.literal.len); cmark_strbuf_puts(xml, "</"); cmark_strbuf_puts(xml, cmark_node_get_type_string(node)); literal = true; break; case CMARK_NODE_LIST: switch (cmark_node_get_list_type(node)) { case CMARK_ORDERED_LIST: cmark_strbuf_puts(xml, " type=\"ordered\""); cmark_strbuf_printf(xml, " start=\"%d\"", cmark_node_get_list_start(node)); delim = cmark_node_get_list_delim(node); if (delim == CMARK_PAREN_DELIM) { cmark_strbuf_puts(xml, " delim=\"paren\""); } else if (delim == CMARK_PERIOD_DELIM) { cmark_strbuf_puts(xml, " delim=\"period\""); } break; case CMARK_BULLET_LIST: cmark_strbuf_puts(xml, " type=\"bullet\""); break; default: break; } cmark_strbuf_printf(xml, " tight=\"%s\"", (cmark_node_get_list_tight(node) ? "true" : "false")); break; case CMARK_NODE_HEADER: cmark_strbuf_printf(xml, " level=\"%d\"", node->as.header.level); break; case CMARK_NODE_CODE_BLOCK: if (node->as.code.info.len > 0) { cmark_strbuf_puts(xml, " info=\""); escape_xml(xml, node->as.code.info.data, node->as.code.info.len); cmark_strbuf_putc(xml, '"'); } cmark_strbuf_puts(xml, ">"); escape_xml(xml, node->as.code.literal.data, node->as.code.literal.len); cmark_strbuf_puts(xml, "</"); cmark_strbuf_puts(xml, cmark_node_get_type_string(node)); literal = true; break; case CMARK_NODE_LINK: case CMARK_NODE_IMAGE: cmark_strbuf_puts(xml, " destination=\""); escape_xml(xml, node->as.link.url, -1); cmark_strbuf_putc(xml, '"'); cmark_strbuf_puts(xml, " title=\""); escape_xml(xml, node->as.link.title, -1); cmark_strbuf_putc(xml, '"'); break; default: break; } if (node->first_child) { state->indent += 2; } else if (!literal) { cmark_strbuf_puts(xml, " /"); } cmark_strbuf_puts(xml, ">\n"); } else if (node->first_child) { state->indent -= 2; indent(state); cmark_strbuf_printf(xml, "</%s>\n", cmark_node_get_type_string(node)); } return 1; }
static int S_render_node(cmark_node *node, cmark_event_type ev_type, struct render_state *state, int options) { cmark_node *parent; cmark_node *grandparent; cmark_strbuf *html = state->html; char start_header[] = "<h0"; char end_header[] = "</h0"; bool tight; bool entering = (ev_type == CMARK_EVENT_ENTER); if (state->plain == node) { // back at original node state->plain = NULL; } if (state->plain != NULL) { switch(node->type) { case CMARK_NODE_TEXT: case CMARK_NODE_CODE: case CMARK_NODE_INLINE_HTML: escape_html(html, node->as.literal.data, node->as.literal.len); break; case CMARK_NODE_LINEBREAK: case CMARK_NODE_SOFTBREAK: cmark_strbuf_putc(html, ' '); break; default: break; } return 1; } switch (node->type) { case CMARK_NODE_DOCUMENT: break; case CMARK_NODE_BLOCK_QUOTE: if (entering) { cr(html); cmark_strbuf_puts(html, "<blockquote"); S_render_sourcepos(node, html, options); cmark_strbuf_puts(html, ">\n"); } else { cr(html); cmark_strbuf_puts(html, "</blockquote>\n"); } break; case CMARK_NODE_LIST: { cmark_list_type list_type = node->as.list.list_type; int start = node->as.list.start; if (entering) { cr(html); if (list_type == CMARK_BULLET_LIST) { cmark_strbuf_puts(html, "<ul"); S_render_sourcepos(node, html, options); cmark_strbuf_puts(html, ">\n"); } else if (start == 1) { cmark_strbuf_puts(html, "<ol"); S_render_sourcepos(node, html, options); cmark_strbuf_puts(html, ">\n"); } else { cmark_strbuf_printf(html, "<ol start=\"%d\"", start); S_render_sourcepos(node, html, options); cmark_strbuf_puts(html, ">\n"); } } else { cmark_strbuf_puts(html, list_type == CMARK_BULLET_LIST ? "</ul>\n" : "</ol>\n"); } break; } case CMARK_NODE_ITEM: if (entering) { cr(html); cmark_strbuf_puts(html, "<li"); S_render_sourcepos(node, html, options); cmark_strbuf_putc(html, '>'); } else { cmark_strbuf_puts(html, "</li>\n"); } break; case CMARK_NODE_HEADER: if (entering) { cr(html); start_header[2] = '0' + node->as.header.level; cmark_strbuf_puts(html, start_header); S_render_sourcepos(node, html, options); cmark_strbuf_putc(html, '>'); } else { end_header[3] = '0' + node->as.header.level; cmark_strbuf_puts(html, end_header); cmark_strbuf_puts(html, ">\n"); } break; case CMARK_NODE_CODE_BLOCK: cr(html); if (!node->as.code.fenced || node->as.code.info.len == 0) { cmark_strbuf_puts(html, "<pre"); S_render_sourcepos(node, html, options); cmark_strbuf_puts(html, "><code>"); } else { int first_tag = 0; while (first_tag < node->as.code.info.len && node->as.code.info.data[first_tag] != ' ') { first_tag += 1; } cmark_strbuf_puts(html, "<pre"); S_render_sourcepos(node, html, options); cmark_strbuf_puts(html, "><code class=\"language-"); escape_html(html, node->as.code.info.data, first_tag); cmark_strbuf_puts(html, "\">"); } escape_html(html, node->as.code.literal.data, node->as.code.literal.len); cmark_strbuf_puts(html, "</code></pre>\n"); break; case CMARK_NODE_HTML: cr(html); cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len); break; case CMARK_NODE_HRULE: cr(html); cmark_strbuf_puts(html, "<hr"); S_render_sourcepos(node, html, options); cmark_strbuf_puts(html, " />\n"); break; case CMARK_NODE_PARAGRAPH: parent = cmark_node_parent(node); grandparent = cmark_node_parent(parent); if (grandparent != NULL && grandparent->type == CMARK_NODE_LIST) { tight = grandparent->as.list.tight; } else { tight = false; } if (!tight) { if (entering) { cr(html); cmark_strbuf_puts(html, "<p"); S_render_sourcepos(node, html, options); cmark_strbuf_putc(html, '>'); } else { cmark_strbuf_puts(html, "</p>\n"); } } break; case CMARK_NODE_TEXT: escape_html(html, node->as.literal.data, node->as.literal.len); break; case CMARK_NODE_LINEBREAK: cmark_strbuf_puts(html, "<br />\n"); break; case CMARK_NODE_SOFTBREAK: if (options & CMARK_OPT_HARDBREAKS) { cmark_strbuf_puts(html, "<br />\n"); } else { cmark_strbuf_putc(html, '\n'); } break; case CMARK_NODE_CODE: cmark_strbuf_puts(html, "<code>"); escape_html(html, node->as.literal.data, node->as.literal.len); cmark_strbuf_puts(html, "</code>"); break; case CMARK_NODE_INLINE_HTML: cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len); break; case CMARK_NODE_STRONG: if (entering) { cmark_strbuf_puts(html, "<strong>"); } else { cmark_strbuf_puts(html, "</strong>"); } break; case CMARK_NODE_EMPH: if (entering) { cmark_strbuf_puts(html, "<em>"); } else { cmark_strbuf_puts(html, "</em>"); } break; case CMARK_NODE_LINK: if (entering) { cmark_strbuf_puts(html, "<a href=\""); if (node->as.link.url) escape_href(html, node->as.link.url, -1); if (node->as.link.title) { cmark_strbuf_puts(html, "\" title=\""); escape_html(html, node->as.link.title, -1); } cmark_strbuf_puts(html, "\">"); } else { cmark_strbuf_puts(html, "</a>"); } break; case CMARK_NODE_IMAGE: if (entering) { cmark_strbuf_puts(html, "<img src=\""); if (node->as.link.url) escape_href(html, node->as.link.url, -1); cmark_strbuf_puts(html, "\" alt=\""); state->plain = node; } else { if (node->as.link.title) { cmark_strbuf_puts(html, "\" title=\""); escape_html(html, node->as.link.title, -1); } cmark_strbuf_puts(html, "\" />"); } break; default: assert(false); break; } // cmark_strbuf_putc(html, 'x'); 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; }