#include #include #include #include #include "cmark_ctype.h" #include "config.h" #include "cmark.h" #include "houdini.h" #include "scanners.h" #include "syntax_extension.h" #include "html.h" // Functions to convert cmark_nodes to HTML strings. static void escape_html(cmark_strbuf *dest, const unsigned char *source, bufsize_t length) { houdini_escape_html0(dest, source, length, 0); } static void filter_html_block(cmark_html_renderer *renderer, uint8_t *data, size_t len) { cmark_strbuf *html = renderer->html; cmark_llist *it; cmark_syntax_extension *ext; bool filtered; uint8_t *match; while (len) { match = (uint8_t *) memchr(data, '<', len); if (!match) break; if (match != data) { cmark_strbuf_put(html, data, (bufsize_t)(match - data)); len -= (match - data); data = match; } filtered = false; for (it = renderer->filter_extensions; it; it = it->next) { ext = ((cmark_syntax_extension *) it->data); if (!ext->html_filter_func(ext, data, len)) { filtered = true; break; } } if (!filtered) { cmark_strbuf_putc(html, '<'); } else { cmark_strbuf_puts(html, "<"); } ++data; --len; } if (len) cmark_strbuf_put(html, data, (bufsize_t)len); } static int S_render_node(cmark_html_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { cmark_node *parent; cmark_node *grandparent; cmark_strbuf *html = renderer->html; cmark_llist *it; cmark_syntax_extension *ext; char start_heading[] = "plain == node) { // back at original node renderer->plain = NULL; } if (renderer->plain != NULL) { switch (node->type) { case CMARK_NODE_TEXT: case CMARK_NODE_CODE: case CMARK_NODE_HTML_INLINE: 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; } if (node->extension && node->extension->html_render_func) { node->extension->html_render_func(node->extension, renderer, node, ev_type, options); return 1; } switch (node->type) { case CMARK_NODE_DOCUMENT: break; case CMARK_NODE_BLOCK_QUOTE: if (entering) { cmark_html_render_cr(html); cmark_strbuf_puts(html, "\n"); } else { cmark_html_render_cr(html); cmark_strbuf_puts(html, "\n"); } break; case CMARK_NODE_LIST: { cmark_list_type list_type = node->as.list.list_type; int start = node->as.list.start; if (entering) { cmark_html_render_cr(html); if (list_type == CMARK_BULLET_LIST) { cmark_strbuf_puts(html, "\n"); } else if (start == 1) { cmark_strbuf_puts(html, "\n"); } else { snprintf(buffer, BUFFER_SIZE, "
    \n"); } } else { cmark_strbuf_puts(html, list_type == CMARK_BULLET_LIST ? "\n" : "
\n"); } break; } case CMARK_NODE_ITEM: if (entering) { cmark_html_render_cr(html); cmark_strbuf_puts(html, "'); } else { cmark_strbuf_puts(html, "\n"); } break; case CMARK_NODE_HEADING: if (entering) { cmark_html_render_cr(html); start_heading[2] = (char)('0' + node->as.heading.level); cmark_strbuf_puts(html, start_heading); cmark_html_render_sourcepos(node, html, options); cmark_strbuf_putc(html, '>'); } else { end_heading[3] = (char)('0' + node->as.heading.level); cmark_strbuf_puts(html, end_heading); cmark_strbuf_puts(html, ">\n"); } break; case CMARK_NODE_CODE_BLOCK: cmark_html_render_cr(html); if (node->as.code.info.len == 0) { cmark_strbuf_puts(html, ""); } else { bufsize_t first_tag = 0; while (first_tag < node->as.code.info.len && !cmark_isspace(node->as.code.info.data[first_tag])) { first_tag += 1; } if (options & CMARK_OPT_GITHUB_PRE_LANG) { cmark_strbuf_puts(html, "as.code.info.data, first_tag); cmark_strbuf_puts(html, "\">"); } else { cmark_strbuf_puts(html, "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, "\n"); break; case CMARK_NODE_HTML_BLOCK: cmark_html_render_cr(html); if (options & CMARK_OPT_SAFE) { cmark_strbuf_puts(html, ""); } else if (renderer->filter_extensions) { filter_html_block(renderer, node->as.literal.data, node->as.literal.len); } else { cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len); } cmark_html_render_cr(html); break; case CMARK_NODE_CUSTOM_BLOCK: cmark_html_render_cr(html); if (entering) { cmark_strbuf_put(html, node->as.custom.on_enter.data, node->as.custom.on_enter.len); } else { cmark_strbuf_put(html, node->as.custom.on_exit.data, node->as.custom.on_exit.len); } cmark_html_render_cr(html); break; case CMARK_NODE_THEMATIC_BREAK: cmark_html_render_cr(html); 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) { cmark_html_render_cr(html); cmark_strbuf_puts(html, "'); } else { cmark_strbuf_puts(html, "

\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, "
\n"); break; case CMARK_NODE_SOFTBREAK: if (options & CMARK_OPT_HARDBREAKS) { cmark_strbuf_puts(html, "
\n"); } else if (options & CMARK_OPT_NOBREAKS) { cmark_strbuf_putc(html, ' '); } else { cmark_strbuf_putc(html, '\n'); } break; case CMARK_NODE_CODE: cmark_strbuf_puts(html, ""); escape_html(html, node->as.literal.data, node->as.literal.len); cmark_strbuf_puts(html, ""); break; case CMARK_NODE_HTML_INLINE: if (options & CMARK_OPT_SAFE) { cmark_strbuf_puts(html, ""); } else { filtered = false; for (it = renderer->filter_extensions; it; it = it->next) { ext = (cmark_syntax_extension *) it->data; if (!ext->html_filter_func(ext, node->as.literal.data, node->as.literal.len)) { filtered = true; break; } } if (!filtered) { cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len); } else { cmark_strbuf_puts(html, "<"); cmark_strbuf_put(html, node->as.literal.data + 1, node->as.literal.len - 1); } } break; case CMARK_NODE_CUSTOM_INLINE: if (entering) { cmark_strbuf_put(html, node->as.custom.on_enter.data, node->as.custom.on_enter.len); } else { cmark_strbuf_put(html, node->as.custom.on_exit.data, node->as.custom.on_exit.len); } break; case CMARK_NODE_STRONG: if (entering) { cmark_strbuf_puts(html, ""); } else { cmark_strbuf_puts(html, ""); } break; case CMARK_NODE_EMPH: if (entering) { cmark_strbuf_puts(html, ""); } else { cmark_strbuf_puts(html, ""); } break; case CMARK_NODE_LINK: if (entering) { cmark_strbuf_puts(html, "as.link.url, 0))) { houdini_escape_href(html, node->as.link.url.data, node->as.link.url.len); } if (node->as.link.title.len) { cmark_strbuf_puts(html, "\" title=\""); escape_html(html, node->as.link.title.data, node->as.link.title.len); } cmark_strbuf_puts(html, "\">"); } else { cmark_strbuf_puts(html, ""); } break; case CMARK_NODE_IMAGE: if (entering) { cmark_strbuf_puts(html, "as.link.url, 0))) { houdini_escape_href(html, node->as.link.url.data, node->as.link.url.len); } cmark_strbuf_puts(html, "\" alt=\""); renderer->plain = node; } else { if (node->as.link.title.len) { cmark_strbuf_puts(html, "\" title=\""); escape_html(html, node->as.link.title.data, node->as.link.title.len); } cmark_strbuf_puts(html, "\" />"); } break; default: assert(false); break; } return 1; } char *cmark_render_html(cmark_node *root, int options, cmark_llist *extensions) { return cmark_render_html_with_mem(root, options, extensions, cmark_node_mem(root)); } char *cmark_render_html_with_mem(cmark_node *root, int options, cmark_llist *extensions, cmark_mem *mem) { char *result; cmark_strbuf html = CMARK_BUF_INIT(mem); cmark_event_type ev_type; cmark_node *cur; cmark_html_renderer renderer = {&html, NULL, NULL, NULL}; cmark_iter *iter = cmark_iter_new(root); for (; extensions; extensions = extensions->next) if (((cmark_syntax_extension *) extensions->data)->html_filter_func) renderer.filter_extensions = cmark_llist_append( mem, renderer.filter_extensions, (cmark_syntax_extension *) extensions->data); while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { cur = cmark_iter_get_node(iter); S_render_node(&renderer, cur, ev_type, options); } result = (char *)cmark_strbuf_detach(&html); cmark_llist_free(mem, renderer.filter_extensions); cmark_iter_free(iter); return result; }