aboutsummaryrefslogtreecommitdiff
path: root/src/html/html.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/html/html.c')
-rw-r--r--src/html/html.c466
1 files changed, 308 insertions, 158 deletions
diff --git a/src/html/html.c b/src/html/html.c
index fde1cb4..5f08506 100644
--- a/src/html/html.c
+++ b/src/html/html.c
@@ -8,6 +8,76 @@
#include "debug.h"
#include "html/houdini.h"
+typedef struct RenderStack {
+ struct RenderStack *previous;
+ char* literal;
+ union {
+ node_inl *inl;
+ node_block *block;
+ } 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_inline(render_stack* rstack,
+ node_inl* inl,
+ char* literal)
+{
+ render_stack* newstack;
+ newstack = (render_stack*)malloc(sizeof(render_stack));
+ if (newstack == NULL) {
+ return NULL;
+ }
+ newstack->previous = rstack;
+ newstack->next_sibling.inl = inl;
+ newstack->literal = literal;
+ newstack->tight = false;
+ newstack->trim = false;
+ return newstack;
+}
+
+static render_stack* push_block(render_stack* rstack,
+ node_block* block,
+ char* literal,
+ bool tight,
+ bool trim)
+{
+ render_stack* newstack;
+ newstack = (render_stack*)malloc(sizeof(render_stack));
+ if (newstack == NULL) {
+ return NULL;
+ }
+ newstack->previous = rstack;
+ newstack->next_sibling.block = block;
+ newstack->literal = literal;
+ newstack->tight = tight;
+ newstack->trim = trim;
+ 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 node_block and inline lists to HTML strings.
static void escape_html(strbuf *dest, const unsigned char *source, int length)
@@ -33,196 +103,276 @@ static inline void cr(strbuf *html)
}
// Convert an inline list to HTML. Returns 0 on success, and sets result.
-static void inlines_to_html(strbuf *html, node_inl* ils)
+static void inlines_to_plain_html(strbuf *html, node_inl* ils)
{
- strbuf scrap = GH_BUF_INIT;
+ node_inl* children;
+ bool visit_children;
+ render_stack* rstack = NULL;
while(ils != NULL) {
+ visit_children = false;
switch(ils->tag) {
- case INL_STRING:
- escape_html(html, ils->content.literal.data, ils->content.literal.len);
- break;
-
- case INL_LINEBREAK:
- strbuf_puts(html, "<br />\n");
- break;
-
- case INL_SOFTBREAK:
- strbuf_putc(html, '\n');
- break;
-
- case INL_CODE:
- strbuf_puts(html, "<code>");
- escape_html(html, ils->content.literal.data, ils->content.literal.len);
- strbuf_puts(html, "</code>");
- break;
-
- case INL_RAW_HTML:
- strbuf_put(html,
- ils->content.literal.data,
- ils->content.literal.len);
- break;
-
- case INL_LINK:
- strbuf_puts(html, "<a href=\"");
- if (ils->content.linkable.url)
- escape_href(html, ils->content.linkable.url, -1);
-
- if (ils->content.linkable.title) {
- strbuf_puts(html, "\" title=\"");
- escape_html(html, ils->content.linkable.title, -1);
- }
+ case INL_STRING:
+ case INL_CODE:
+ case INL_RAW_HTML:
+ escape_html(html, ils->content.literal.data, ils->content.literal.len);
+ break;
+
+ case INL_LINEBREAK:
+ case INL_SOFTBREAK:
+ strbuf_putc(html, '\n');
+ break;
+
+ case INL_LINK:
+ case INL_IMAGE:
+ children = ils->content.inlines;
+ visit_children = true;
+ rstack = push_inline(rstack, ils->next, "");
+ break;
+
+ case INL_STRONG:
+ case INL_EMPH:
+ children = ils->content.inlines;
+ visit_children = true;
+ rstack = push_inline(rstack, ils->next, "");
+ break;
+ }
+ if (visit_children) {
+ ils = children;
+ } else {
+ ils = ils->next;
+ }
+ while (ils == NULL && rstack != NULL) {
+ strbuf_puts(html, rstack->literal);
+ ils = rstack->next_sibling.inl;
+ rstack = pop_render_stack(rstack);
+ }
+ }
- strbuf_puts(html, "\">");
- inlines_to_html(html, ils->content.inlines);
- strbuf_puts(html, "</a>");
- break;
-
- case INL_IMAGE:
- strbuf_puts(html, "<img src=\"");
- if (ils->content.linkable.url)
- escape_href(html, ils->content.linkable.url, -1);
-
- inlines_to_html(&scrap, ils->content.inlines);
- strbuf_puts(html, "\" alt=\"");
- if (scrap.size)
- escape_html(html, scrap.ptr, scrap.size);
- strbuf_clear(&scrap);
-
- if (ils->content.linkable.title) {
- strbuf_puts(html, "\" title=\"");
- escape_html(html, ils->content.linkable.title, -1);
- }
+ free_render_stack(rstack);
+}
- strbuf_puts(html, "\"/>");
- break;
- case INL_STRONG:
- strbuf_puts(html, "<strong>");
- inlines_to_html(html, ils->content.inlines);
- strbuf_puts(html, "</strong>");
- break;
+// Convert an inline list to HTML. Returns 0 on success, and sets result.
+static void inlines_to_html(strbuf *html, node_inl* ils)
+{
+ node_inl* children;
+ render_stack* rstack = NULL;
- case INL_EMPH:
- strbuf_puts(html, "<em>");
- inlines_to_html(html, ils->content.inlines);
- strbuf_puts(html, "</em>");
- break;
+ while(ils != NULL) {
+ children = NULL;
+ switch(ils->tag) {
+ case INL_STRING:
+ escape_html(html, ils->content.literal.data, ils->content.literal.len);
+ break;
+
+ case INL_LINEBREAK:
+ strbuf_puts(html, "<br />\n");
+ break;
+
+ case INL_SOFTBREAK:
+ strbuf_putc(html, '\n');
+ break;
+
+ case INL_CODE:
+ strbuf_puts(html, "<code>");
+ escape_html(html, ils->content.literal.data, ils->content.literal.len);
+ strbuf_puts(html, "</code>");
+ break;
+
+ case INL_RAW_HTML:
+ strbuf_put(html,
+ ils->content.literal.data,
+ ils->content.literal.len);
+ break;
+
+ case INL_LINK:
+ strbuf_puts(html, "<a href=\"");
+ if (ils->content.linkable.url)
+ escape_href(html, ils->content.linkable.url, -1);
+
+ if (ils->content.linkable.title) {
+ strbuf_puts(html, "\" title=\"");
+ escape_html(html, ils->content.linkable.title, -1);
+ }
+
+ strbuf_puts(html, "\">");
+ children = ils->content.inlines;
+ rstack = push_inline(rstack, ils->next, "</a>");
+ break;
+
+ case INL_IMAGE:
+ strbuf_puts(html, "<img src=\"");
+ if (ils->content.linkable.url)
+ escape_href(html, ils->content.linkable.url, -1);
+
+ strbuf_puts(html, "\" alt=\"");
+ inlines_to_plain_html(html, ils->content.inlines);
+
+ if (ils->content.linkable.title) {
+ strbuf_puts(html, "\" title=\"");
+ escape_html(html, ils->content.linkable.title, -1);
+ }
+
+ strbuf_puts(html, "\"/>");
+ break;
+
+ case INL_STRONG:
+ strbuf_puts(html, "<strong>");
+ children = ils->content.inlines;
+ rstack = push_inline(rstack, ils->next, "</strong>");
+ break;
+
+ case INL_EMPH:
+ strbuf_puts(html, "<em>");
+ children = ils->content.inlines;
+ rstack = push_inline(rstack, ils->next, "</em>");
+ break;
+ }
+ if (children) {
+ ils = children;
+ } else {
+ ils = ils->next;
+ }
+ while (ils == NULL && rstack != NULL) {
+ strbuf_puts(html, rstack->literal);
+ ils = rstack->next_sibling.inl;
+ rstack = pop_render_stack(rstack);
}
- ils = ils->next;
}
- strbuf_free(&scrap);
+ free_render_stack(rstack);
}
// Convert a node_block list to HTML. Returns 0 on success, and sets result.
-static void blocks_to_html(strbuf *html, node_block *b, bool tight)
+static void blocks_to_html(strbuf *html, node_block *b)
{
struct ListData *data;
+ render_stack* rstack = NULL;
+ bool visit_children = false;
+ bool tight = false;
while(b != NULL) {
+ visit_children = false;
switch(b->tag) {
- case BLOCK_DOCUMENT:
- blocks_to_html(html, b->children, false);
- break;
-
- case BLOCK_PARAGRAPH:
- if (tight) {
- inlines_to_html(html, b->inline_content);
- } else {
- cr(html);
- strbuf_puts(html, "<p>");
- inlines_to_html(html, b->inline_content);
- strbuf_puts(html, "</p>\n");
- }
- break;
-
- case BLOCK_BQUOTE:
- cr(html);
- strbuf_puts(html, "<blockquote>\n");
- blocks_to_html(html, b->children, false);
- strbuf_puts(html, "</blockquote>\n");
- break;
-
- case BLOCK_LIST_ITEM:
- cr(html);
- strbuf_puts(html, "<li>");
- blocks_to_html(html, b->children, tight);
- strbuf_trim(html); /* TODO: rtrim */
- strbuf_puts(html, "</li>\n");
- break;
-
- case BLOCK_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 == bullet ? "ul" : "ol",
- data->start);
- } else {
- strbuf_puts(html, data->list_type == bullet ? "<ul>\n" : "<ol>\n");
- }
+ case BLOCK_DOCUMENT:
+ rstack = push_block(rstack, b->next, "", false, false);
+ visit_children = true;
+ break;
- blocks_to_html(html, b->children, data->tight);
- strbuf_puts(html, data->list_type == bullet ? "</ul>" : "</ol>");
- strbuf_putc(html, '\n');
- break;
-
- case BLOCK_ATX_HEADER:
- case BLOCK_SETEXT_HEADER:
- cr(html);
- strbuf_printf(html, "<h%d>", b->as.header.level);
+ case BLOCK_PARAGRAPH:
+ if (tight) {
inlines_to_html(html, b->inline_content);
- strbuf_printf(html, "</h%d>\n", b->as.header.level);
- break;
-
- case BLOCK_INDENTED_CODE:
- case BLOCK_FENCED_CODE:
+ } else {
cr(html);
-
- strbuf_puts(html, "<pre><code");
-
- if (b->tag == BLOCK_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_puts(html, "<p>");
+ inlines_to_html(html, b->inline_content);
+ strbuf_puts(html, "</p>\n");
+ }
+ break;
+
+ case BLOCK_BQUOTE:
+ cr(html);
+ strbuf_puts(html, "<blockquote>\n");
+ rstack = push_block(rstack, b->next, "</blockquote>\n", tight, false);
+ tight = false;
+ visit_children = true;
+ break;
+
+ case BLOCK_LIST_ITEM:
+ cr(html);
+ strbuf_puts(html, "<li>");
+ rstack = push_block(rstack, b->next, "</li>\n", tight, true);
+ visit_children = true;
+ break;
+
+ case BLOCK_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 == bullet ? "ul" : "ol",
+ data->start);
+ } else {
+ strbuf_puts(html, data->list_type == bullet ? "<ul>\n" : "<ol>\n");
+ }
+
+ rstack = push_block(rstack, b->next,
+ data->list_type == bullet ?
+ "\n</ul>\n" : "\n</ol>\n", tight, false);
+ tight = data->tight;
+ visit_children = true;
+ break;
+
+ case BLOCK_ATX_HEADER:
+ case BLOCK_SETEXT_HEADER:
+ cr(html);
+ strbuf_printf(html, "<h%d>", b->as.header.level);
+ inlines_to_html(html, b->inline_content);
+ strbuf_printf(html, "</h%d>\n", b->as.header.level);
+ break;
+
+ case BLOCK_INDENTED_CODE:
+ case BLOCK_FENCED_CODE:
+ cr(html);
+
+ strbuf_puts(html, "<pre><code");
+
+ if (b->tag == BLOCK_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;
+ strbuf_putc(html, '>');
+ escape_html(html, b->string_content.ptr, b->string_content.size);
+ strbuf_puts(html, "</code></pre>\n");
+ break;
- case BLOCK_HTML:
- strbuf_put(html, b->string_content.ptr, b->string_content.size);
- break;
+ case BLOCK_HTML:
+ strbuf_put(html, b->string_content.ptr, b->string_content.size);
+ break;
- case BLOCK_HRULE:
- strbuf_puts(html, "<hr />\n");
- break;
+ case BLOCK_HRULE:
+ strbuf_puts(html, "<hr />\n");
+ break;
- case BLOCK_REFERENCE_DEF:
- break;
+ case BLOCK_REFERENCE_DEF:
+ break;
- default:
- assert(false);
+ default:
+ assert(false);
+ }
+ if (visit_children) {
+ b = b->children;
+ } 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.block;
+ rstack = pop_render_stack(rstack);
}
-
- b = b->next;
}
+
+ free_render_stack(rstack);
}
void cmark_render_html(strbuf *html, node_block *root)
{
- blocks_to_html(html, root, false);
+ blocks_to_html(html, root);
}