diff options
Diffstat (limited to 'src/html/html.c')
-rw-r--r-- | src/html/html.c | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/src/html/html.c b/src/html/html.c new file mode 100644 index 0000000..2f160ca --- /dev/null +++ b/src/html/html.c @@ -0,0 +1,212 @@ +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <assert.h> + +#include "stmd.h" +#include "debug.h" +#include "scanners.h" +#include "html/houdini.h" + +// Functions to convert block and inline lists to HTML strings. + +static void escape_html(gh_buf *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(gh_buf *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(gh_buf *html) +{ + if (html->size && html->ptr[html->size - 1] != '\n') + gh_buf_putc(html, '\n'); +} + +// Convert a block list to HTML. Returns 0 on success, and sets result. +void blocks_to_html(gh_buf *html, block *b, bool tight) +{ + struct ListData *data; + + while(b != NULL) { + switch(b->tag) { + case document: + blocks_to_html(html, b->children, false); + break; + + case paragraph: + if (tight) { + inlines_to_html(html, b->inline_content); + } else { + cr(html); + gh_buf_puts(html, "<p>"); + inlines_to_html(html, b->inline_content); + gh_buf_puts(html, "</p>"); + cr(html); + } + break; + + case block_quote: + cr(html); + gh_buf_puts(html, "<blockquote>"); + blocks_to_html(html, b->children, false); + gh_buf_puts(html, "</blockquote>"); + cr(html); + break; + + case list_item: + cr(html); + gh_buf_puts(html, "<li>"); + blocks_to_html(html, b->children, tight); + gh_buf_trim(html); + gh_buf_puts(html, "</li>"); + cr(html); + break; + + case list: + // make sure a list starts at the beginning of the line: + cr(html); + data = &(b->attributes.list_data); + + if (data->start > 1) { + gh_buf_printf(html, "<%s start=\"%d\">\n", + data->list_type == bullet ? "ul" : "ol", + data->start); + } else { + gh_buf_puts(html, data->list_type == bullet ? "<ul>\n" : "<ol>\n"); + } + + blocks_to_html(html, b->children, data->tight); + gh_buf_puts(html, data->list_type == bullet ? "</ul>" : "</ol>"); + cr(html); + break; + + case atx_header: + case setext_header: + cr(html); + gh_buf_printf(html, "<h%d>", b->attributes.header_level); + inlines_to_html(html, b->inline_content); + gh_buf_printf(html, "</h%d>", b->attributes.header_level); + cr(html); + break; + + case indented_code: + case fenced_code: + /* TODO: fenced code lang attributes */ + cr(html); + gh_buf_puts(html, "<pre><code>"); + escape_html(html, b->string_content.ptr, b->string_content.size); + gh_buf_puts(html, "</pre></code>"); + cr(html); + break; + + case html_block: + gh_buf_put(html, b->string_content.ptr, b->string_content.size); + break; + + case hrule: + gh_buf_puts(html, "<hr />"); + cr(html); + break; + + case reference_def: + break; + + default: + assert(false); + } + + b = b->next; + } +} + +// Convert an inline list to HTML. Returns 0 on success, and sets result. +void inlines_to_html(gh_buf *html, inl* ils) +{ + gh_buf scrap = GH_BUF_INIT; + + while(ils != NULL) { + switch(ils->tag) { + case INL_STRING: + escape_html(html, ils->content.literal.data, ils->content.literal.len); + break; + + case INL_LINEBREAK: + gh_buf_puts(html, "<br />\n"); + break; + + case INL_SOFTBREAK: + gh_buf_putc(html, '\n'); + break; + + case INL_CODE: + gh_buf_puts(html, "<code>"); + escape_html(html, ils->content.literal.data, ils->content.literal.len); + gh_buf_puts(html, "</code>"); + break; + + case INL_RAW_HTML: + case INL_ENTITY: + gh_buf_put(html, + ils->content.literal.data, + ils->content.literal.len); + break; + + case INL_LINK: + gh_buf_puts(html, "<a href=\""); + escape_href(html, ils->content.linkable.url, -1); + + if (ils->content.linkable.title) { + gh_buf_puts(html, "\" title=\""); + escape_html(html, ils->content.linkable.title, -1); + } + + gh_buf_puts(html, "\">"); + inlines_to_html(html, ils->content.inlines); + gh_buf_puts(html, "</a>"); + break; + + case INL_IMAGE: + gh_buf_puts(html, "<img src=\""); + escape_href(html, ils->content.linkable.url, -1); + + inlines_to_html(&scrap, ils->content.inlines); + if (scrap.size) { + gh_buf_puts(html, "\" alt=\""); + escape_html(html, scrap.ptr, scrap.size); + } + gh_buf_clear(&scrap); + + if (ils->content.linkable.title) { + gh_buf_puts(html, "\" title=\""); + escape_html(html, ils->content.linkable.title, -1); + } + + gh_buf_puts(html, "\"/>"); + break; + + case INL_STRONG: + gh_buf_puts(html, "<strong>"); + inlines_to_html(html, ils->content.inlines); + gh_buf_puts(html, "</strong>"); + break; + + case INL_EMPH: + gh_buf_puts(html, "<em>"); + inlines_to_html(html, ils->content.inlines); + gh_buf_puts(html, "</em>"); + break; + } + ils = ils->next; + } +} |