- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <assert.h>
- #include "config.h"
- #include "cmark.h"
- #include "node.h"
- #include "buffer.h"
- #include "debug.h"
- #include "html/houdini.h"
- typedef struct RenderStack {
- struct RenderStack *previous;
- const char* literal;
- cmark_node* next_sibling;
- bool tight;
- bool trim;
- } render_stack;
- static void free_render_stack(render_stack * rstack)
- {
- render_stack * tempstack;
- while (rstack) {
- tempstack = rstack;
- rstack = rstack->previous;
- free(tempstack);
- }
- }
- static render_stack* push_render_stack(render_stack* rstack,
- cmark_node* node,
- const char* literal)
- {
- render_stack* newstack;
- newstack = (render_stack*)malloc(sizeof(render_stack));
- if (newstack == NULL) {
- return NULL;
- }
- newstack->previous = rstack;
- newstack->next_sibling = node;
- newstack->literal = literal;
- newstack->tight = false;
- newstack->trim = false;
- return newstack;
- }
- static render_stack* pop_render_stack(render_stack* rstack)
- {
- render_stack* top = rstack;
- if (rstack == NULL) {
- return NULL;
- }
- rstack = rstack->previous;
- top->previous = NULL;
- free_render_stack(top);
- return rstack;
- }
- // Functions to convert cmark_node and inline lists 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');
- }
- // Convert an inline list to HTML. Returns 0 on success, and sets result.
- static void inlines_to_plain_html(strbuf *html, cmark_node* ils)
- {
- cmark_node* children;
- bool visit_children;
- render_stack* rstack = NULL;
- while(ils != NULL) {
- visit_children = false;
- switch(ils->type) {
- case NODE_STRING:
- case NODE_INLINE_CODE:
- case NODE_INLINE_HTML:
- escape_html(html, ils->as.literal.data, ils->as.literal.len);
- break;
- case NODE_LINEBREAK:
- case NODE_SOFTBREAK:
- strbuf_putc(html, '\n');
- break;
- case NODE_LINK:
- case NODE_IMAGE:
- case NODE_STRONG:
- case NODE_EMPH:
- children = ils->first_child;
- visit_children = true;
- rstack = push_render_stack(rstack, ils->next, "");
- break;
- default:
- break;
- }
- if (visit_children) {
- ils = children;
- } else {
- ils = ils->next;
- }
- while (ils == NULL && rstack != NULL) {
- strbuf_puts(html, rstack->literal);
- ils = rstack->next_sibling;
- rstack = pop_render_stack(rstack);
- }
- }
- free_render_stack(rstack);
- }
- // Convert an inline list to HTML. Returns 0 on success, and sets result.
- static void inlines_to_html(strbuf *html, cmark_node* ils)
- {
- bool visit_children;
- render_stack* rstack = NULL;
- while(ils != NULL) {
- visit_children = false;
- switch(ils->type) {
- case NODE_STRING:
- escape_html(html, ils->as.literal.data, ils->as.literal.len);
- break;
- case NODE_LINEBREAK:
- strbuf_puts(html, "<br />\n");
- break;
- case NODE_SOFTBREAK:
- strbuf_putc(html, '\n');
- break;
- case NODE_INLINE_CODE:
- strbuf_puts(html, "<code>");
- escape_html(html, ils->as.literal.data, ils->as.literal.len);
- strbuf_puts(html, "</code>");
- break;
- case NODE_INLINE_HTML:
- strbuf_put(html,
- ils->as.literal.data,
- ils->as.literal.len);
- break;
- case NODE_LINK:
- strbuf_puts(html, "<a href=\"");
- if (ils->as.link.url)
- escape_href(html, ils->as.link.url, -1);
- if (ils->as.link.title) {
- strbuf_puts(html, "\" title=\"");
- escape_html(html, ils->as.link.title, -1);
- }
- strbuf_puts(html, "\">");
- visit_children = true;
- rstack = push_render_stack(rstack, ils->next, "</a>");
- break;
- case NODE_IMAGE:
- strbuf_puts(html, "<img src=\"");
- if (ils->as.link.url)
- escape_href(html, ils->as.link.url, -1);
- strbuf_puts(html, "\" alt=\"");
- inlines_to_plain_html(html, ils->first_child);
- if (ils->as.link.title) {
- strbuf_puts(html, "\" title=\"");
- escape_html(html, ils->as.link.title, -1);
- }
- strbuf_puts(html, "\" />");
- break;
- case NODE_STRONG:
- strbuf_puts(html, "<strong>");
- visit_children = true;
- rstack = push_render_stack(rstack, ils->next, "</strong>");
- break;
- case NODE_EMPH:
- strbuf_puts(html, "<em>");
- visit_children = true;
- rstack = push_render_stack(rstack, ils->next, "</em>");
- break;
- default:
- break;
- }
- if (visit_children) {
- ils = ils->first_child;
- } else {
- ils = ils->next;
- }
- while (ils == NULL && rstack != NULL) {
- strbuf_puts(html, rstack->literal);
- ils = rstack->next_sibling;
- rstack = pop_render_stack(rstack);
- }
- }
- free_render_stack(rstack);
- }
- // Convert a cmark_node list to HTML. Returns 0 on success, and sets result.
- static void blocks_to_html(strbuf *html, cmark_node *b)
- {
- cmark_list *data;
- render_stack* rstack = NULL;
- bool visit_children = false;
- bool tight = false;
- while(b != NULL) {
- visit_children = false;
- switch(b->type) {
- case NODE_DOCUMENT:
- rstack = push_render_stack(rstack, b->next, "");
- rstack->tight = false;
- rstack->trim = false;
- visit_children = true;
- break;
- case NODE_PARAGRAPH:
- if (tight) {
- inlines_to_html(html, b->first_child);
- } else {
- cr(html);
- strbuf_puts(html, "<p>");
- inlines_to_html(html, b->first_child);
- strbuf_puts(html, "</p>\n");
- }
- break;
- case NODE_BQUOTE:
- cr(html);
- strbuf_puts(html, "<blockquote>\n");
- rstack = push_render_stack(rstack, b->next, "</blockquote>\n");
- rstack->tight = tight;
- rstack->trim = false;
- tight = false;
- visit_children = true;
- break;
- case NODE_LIST_ITEM:
- cr(html);
- strbuf_puts(html, "<li>");
- rstack = push_render_stack(rstack, b->next, "</li>\n");
- rstack->tight = tight;
- rstack->trim = true;
- visit_children = true;
- break;
- case NODE_LIST:
- // make sure a list starts at the beginning of the line:
- cr(html);
- data = &(b->as.list);
- if (data->start > 1) {
- strbuf_printf(html, "<%s start=\"%d\">\n",
- data->list_type == CMARK_BULLET_LIST ? "ul" : "ol",
- data->start);
- } else {
- strbuf_puts(html, data->list_type == CMARK_BULLET_LIST ? "<ul>\n" : "<ol>\n");
- }
- rstack = push_render_stack(rstack, b->next,
- data->list_type == CMARK_BULLET_LIST ?
- "\n</ul>\n" : "\n</ol>\n");
- rstack->tight = tight;
- rstack->trim = false;
- tight = data->tight;
- visit_children = true;
- break;
- case NODE_ATX_HEADER:
- case NODE_SETEXT_HEADER:
- cr(html);
- strbuf_printf(html, "<h%d>", b->as.header.level);
- inlines_to_html(html, b->first_child);
- strbuf_printf(html, "</h%d>\n", b->as.header.level);
- break;
- case NODE_INDENTED_CODE:
- case NODE_FENCED_CODE:
- cr(html);
- strbuf_puts(html, "<pre><code");
- if (b->type == NODE_FENCED_CODE) {
- strbuf *info = &b->as.code.info;
- if (strbuf_len(info) > 0) {
- int first_tag = strbuf_strchr(info, ' ', 0);
- if (first_tag < 0)
- first_tag = strbuf_len(info);
- strbuf_puts(html, " class=\"language-");
- escape_html(html, info->ptr, first_tag);
- strbuf_putc(html, '"');
- }
- }
- strbuf_putc(html, '>');
- escape_html(html, b->string_content.ptr, b->string_content.size);
- strbuf_puts(html, "</code></pre>\n");
- break;
- case NODE_HTML:
- strbuf_put(html, b->string_content.ptr, b->string_content.size);
- break;
- case NODE_HRULE:
- strbuf_puts(html, "<hr />\n");
- break;
- case NODE_REFERENCE_DEF:
- break;
- default:
- assert(false);
- }
- if (visit_children) {
- b = b->first_child;
- } else {
- b = b->next;
- }
- while (b == NULL && rstack != NULL) {
- strbuf_puts(html, rstack->literal);
- if (rstack->trim) {
- strbuf_rtrim(html);
- }
- tight = rstack->tight;
- b = rstack->next_sibling;
- rstack = pop_render_stack(rstack);
- }
- }
- free_render_stack(rstack);
- }
- unsigned char *cmark_render_html(cmark_node *root)
- {
- unsigned char *result;
- strbuf html = GH_BUF_INIT;
- blocks_to_html(&html, root);
- result = strbuf_detach(&html);
- strbuf_free(&html);
- return result;
- }
|