#include #include #include #include #include "cmark_ctype.h" #include "config.h" #include "cmark.h" #include "node.h" #include "buffer.h" #include "houdini.h" #include "scanners.h" #define BUFFER_SIZE 100 // 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 CMARK_INLINE void cr(cmark_strbuf *html) { if (html->size && html->ptr[html->size - 1] != '\n') cmark_strbuf_putc(html, '\n'); } struct render_state { cmark_strbuf *html; cmark_node *plain; }; static void S_render_sourcepos(cmark_node *node, cmark_strbuf *html, int options) { char buffer[BUFFER_SIZE]; if (CMARK_OPT_SOURCEPOS & options) { snprintf(buffer, BUFFER_SIZE, " data-sourcepos=\"%d:%d-%d:%d\"", cmark_node_get_start_line(node), cmark_node_get_start_column(node), cmark_node_get_end_line(node), cmark_node_get_end_column(node)); cmark_strbuf_puts(html, buffer); } } 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_heading[] = "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_HTML_INLINE: escape_html(html, node->data, node->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, "\n"); } else { cr(html); cmark_strbuf_puts(html, "\n"); } break; case CMARK_NODE_LIST: { cmark_list_type list_type = (cmark_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, "\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) { cr(html); cmark_strbuf_puts(html, "'); } else { cmark_strbuf_puts(html, "\n"); } break; case CMARK_NODE_HEADING: if (entering) { cr(html); start_heading[2] = (char)('0' + node->as.heading.level); cmark_strbuf_puts(html, start_heading); S_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: cr(html); if (node->as.code.info == NULL || node->as.code.info[0] == 0) { cmark_strbuf_puts(html, ""); } else { bufsize_t first_tag = 0; while (node->as.code.info[first_tag] && !cmark_isspace(node->as.code.info[first_tag])) { first_tag += 1; } cmark_strbuf_puts(html, "as.code.info, first_tag); cmark_strbuf_puts(html, "\">"); } escape_html(html, node->data, node->len); cmark_strbuf_puts(html, "\n"); break; case CMARK_NODE_HTML_BLOCK: cr(html); if (!(options & CMARK_OPT_UNSAFE)) { cmark_strbuf_puts(html, ""); } else { cmark_strbuf_put(html, node->data, node->len); } cr(html); break; case CMARK_NODE_CUSTOM_BLOCK: { unsigned char *block = entering ? node->as.custom.on_enter : node->as.custom.on_exit; cr(html); if (block) { cmark_strbuf_puts(html, (char *)block); } cr(html); break; } case CMARK_NODE_THEMATIC_BREAK: 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) { cr(html); cmark_strbuf_puts(html, "'); } else { cmark_strbuf_puts(html, "

\n"); } } break; case CMARK_NODE_TEXT: escape_html(html, node->data, node->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->data, node->len); cmark_strbuf_puts(html, ""); break; case CMARK_NODE_HTML_INLINE: if (!(options & CMARK_OPT_UNSAFE)) { cmark_strbuf_puts(html, ""); } else { cmark_strbuf_put(html, node->data, node->len); } break; case CMARK_NODE_CUSTOM_INLINE: { unsigned char *block = entering ? node->as.custom.on_enter : node->as.custom.on_exit; if (block) { cmark_strbuf_puts(html, (char *)block); } 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 && ((options & CMARK_OPT_UNSAFE) || !(_scan_dangerous_url(node->as.link.url)))) { houdini_escape_href(html, node->as.link.url, strlen((char *)node->as.link.url)); } if (node->as.link.title) { cmark_strbuf_puts(html, "\" title=\""); escape_html(html, node->as.link.title, strlen((char *)node->as.link.title)); } cmark_strbuf_puts(html, "\">"); } else { cmark_strbuf_puts(html, ""); } break; case CMARK_NODE_IMAGE: if (entering) { cmark_strbuf_puts(html, "as.link.url && ((options & CMARK_OPT_UNSAFE) || !(_scan_dangerous_url(node->as.link.url)))) { houdini_escape_href(html, node->as.link.url, strlen((char *)node->as.link.url)); } 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, strlen((char *)node->as.link.title)); } cmark_strbuf_puts(html, "\" />"); } break; default: assert(false); break; } // cmark_strbuf_putc(html, 'x'); return 1; } char *cmark_render_html(cmark_node *root, int options) { char *result; cmark_strbuf html = CMARK_BUF_INIT(root->mem); cmark_event_type ev_type; cmark_node *cur; struct render_state state = {&html, NULL}; cmark_iter *iter = cmark_iter_new(root); while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { cur = cmark_iter_get_node(iter); S_render_node(cur, ev_type, &state, options); } result = (char *)cmark_strbuf_detach(&html); cmark_iter_free(iter); return result; }