#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 bool finish_node(strbuf *html, cmark_node *node, bool tight); 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'); } // Convert the inline children of a node to a plain string. static void inlines_to_plain_html(strbuf *html, cmark_node* node) { cmark_node* cur = node->first_child; if (cur == NULL) { return; } while (true) { switch(cur->type) { case NODE_TEXT: case NODE_INLINE_CODE: case NODE_INLINE_HTML: escape_html(html, cur->as.literal.data, cur->as.literal.len); break; case NODE_LINEBREAK: case NODE_SOFTBREAK: strbuf_putc(html, ' '); break; default: break; } if (cur->first_child) { cur = cur->first_child; continue; } next_sibling: if (cur->next) { cur = cur->next; continue; } cur = cur->parent; if (cur == node) { break; } goto next_sibling; } } // Convert a cmark_node to HTML. static void node_to_html(strbuf *html, cmark_node *node) { cmark_node *cur; char start_header[] = ""; bool tight = false; bool visit_children; strbuf *info; if (node == NULL) { return; } cur = node; while (true) { // Only NODE_IMAGE wants to skip its children. visit_children = true; switch(cur->type) { case NODE_DOCUMENT: break; case NODE_PARAGRAPH: if (!tight) { cr(html); strbuf_puts(html, "

"); } break; case NODE_BLOCK_QUOTE: cr(html); strbuf_puts(html, "

\n"); // BLOCK_QUOTE doesn't use any of the 'as' structs, // so the 'list' member can be used to store the // current value of 'tight'. cur->as.list.tight = tight; tight = false; break; case NODE_LIST_ITEM: cr(html); strbuf_puts(html, "
  • "); break; case NODE_LIST: { cmark_list *list = &cur->as.list; bool tmp; // make sure a list starts at the beginning of the line: cr(html); if (list->list_type == CMARK_BULLET_LIST) { strbuf_puts(html, "
  • \n"); // Restore old 'tight' value. tight = list->tight; list->tight = false; break; } case NODE_LIST_ITEM: strbuf_puts(html, "\n"); break; case NODE_LIST: { cmark_list *list = &node->as.list; bool tmp; strbuf_puts(html, list->list_type == CMARK_BULLET_LIST ? "\n" : "\n"); // Restore old 'tight' value. tmp = tight; tight = list->tight; list->tight = tmp; break; } case NODE_HEADER: end_header[3] = '0' + node->as.header.level; strbuf_puts(html, end_header); break; case NODE_CODE_BLOCK: strbuf_puts(html, "\n"); break; case NODE_INLINE_CODE: strbuf_puts(html, ""); break; case NODE_LINK: strbuf_puts(html, ""); break; case NODE_STRONG: strbuf_puts(html, ""); break; case NODE_EMPH: strbuf_puts(html, ""); break; default: break; } return tight; } char *cmark_render_html(cmark_node *root) { char *result; strbuf html = GH_BUF_INIT; node_to_html(&html, root); result = (char *)strbuf_detach(&html); strbuf_free(&html); return result; }