#include #include #include #include #include "config.h" #include "cmark.h" #include "node.h" #include "buffer.h" #include "houdini.h" // Functions to convert cmark_nodes to HTML strings. static void escape_html(strbuf *dest, const unsigned char *source, int length) { if (length < 0) length = strlen((char *)source); houdini_escape_html0(dest, source, (size_t)length, 0); } static void escape_href(strbuf *dest, const unsigned char *source, int length) { if (length < 0) length = strlen((char *)source); houdini_escape_href(dest, source, (size_t)length); } static inline void cr(strbuf *html) { if (html->size && html->ptr[html->size - 1] != '\n') strbuf_putc(html, '\n'); } struct render_state { strbuf* html; cmark_node *plain; }; static int S_render_node(cmark_node *node, cmark_event_type ev_type, void *vstate) { struct render_state *state = vstate; cmark_node *parent; cmark_node *grandparent; strbuf *html = state->html; char start_header[] = ""; char end_header[] = ""; strbuf *info; 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: strbuf_putc(html, ' '); break; default: break; } return 1; } switch (node->type) { case CMARK_NODE_BLOCK_QUOTE: if (entering) { cr(html); strbuf_puts(html, "
\n"); } else { cr(html); 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) { cr(html); if (list_type == CMARK_BULLET_LIST) { strbuf_puts(html, "
    \n"); } else if (start == 1) { strbuf_puts(html, "
      \n"); } else { strbuf_printf(html, "
        \n", start); } } else { strbuf_puts(html, list_type == CMARK_BULLET_LIST ? "
\n" : "\n"); } break; } case CMARK_NODE_LIST_ITEM: if (entering) { cr(html); strbuf_puts(html, "
  • "); } else { strbuf_puts(html, "
  • \n"); } break; case CMARK_NODE_HEADER: if (entering) { cr(html); start_header[2] = '0' + node->as.header.level; strbuf_puts(html, start_header); } else { end_header[3] = '0' + node->as.header.level; strbuf_puts(html, end_header); strbuf_putc(html, '\n'); } break; case CMARK_NODE_CODE_BLOCK: info = &node->as.code.info; cr(html); if (&node->as.code.fence_length == 0 || strbuf_len(info) == 0) { strbuf_puts(html, "
    ");
    		}
    		else {
    			int first_tag = strbuf_strchr(info, ' ', 0);
    			if (first_tag < 0)
    				first_tag = strbuf_len(info);
    
    			strbuf_puts(html, "
    ptr, first_tag);
    			strbuf_puts(html, "\">");
    		}
    
    		escape_html(html, node->string_content.ptr, node->string_content.size);
    		strbuf_puts(html, "
    \n"); break; case CMARK_NODE_HTML: cr(html); strbuf_put(html, node->string_content.ptr, node->string_content.size); break; case CMARK_NODE_HRULE: cr(html); 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); strbuf_puts(html, "

    "); } else { 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: strbuf_puts(html, "
    \n"); break; case CMARK_NODE_SOFTBREAK: strbuf_putc(html, '\n'); break; case CMARK_NODE_CODE: strbuf_puts(html, ""); escape_html(html, node->as.literal.data, node->as.literal.len); strbuf_puts(html, ""); break; case CMARK_NODE_INLINE_HTML: strbuf_put(html, node->as.literal.data, node->as.literal.len); break; case CMARK_NODE_STRONG: if (entering) { strbuf_puts(html, ""); } else { strbuf_puts(html, ""); } break; case CMARK_NODE_EMPH: if (entering) { strbuf_puts(html, ""); } else { strbuf_puts(html, ""); } break; case CMARK_NODE_LINK: if (entering) { strbuf_puts(html, "as.link.url) escape_href(html, node->as.link.url, -1); if (node->as.link.title) { strbuf_puts(html, "\" title=\""); escape_html(html, node->as.link.title, -1); } strbuf_puts(html, "\">"); } else { strbuf_puts(html, ""); } break; case CMARK_NODE_IMAGE: if (entering) { strbuf_puts(html, "as.link.url) escape_href(html, node->as.link.url, -1); strbuf_puts(html, "\" alt=\""); state->plain = node; } else { if (node->as.link.title) { strbuf_puts(html, "\" title=\""); escape_html(html, node->as.link.title, -1); } strbuf_puts(html, "\" />"); } break; default: assert(false); break; } // strbuf_putc(html, 'x'); return 1; } char *cmark_render_html(cmark_node *root) { char *result; strbuf html = GH_BUF_INIT; 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); } result = (char *)strbuf_detach(&html); cmark_iter_free(iter); strbuf_free(&html); return result; }