diff options
Diffstat (limited to 'src/html.c')
-rw-r--r-- | src/html.c | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/src/html.c b/src/html.c new file mode 100644 index 0000000..56d5dbb --- /dev/null +++ b/src/html.c @@ -0,0 +1,276 @@ +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include "bstrlib.h" +#include "stmd.h" +#include "debug.h" +#include "scanners.h" + +// Functions to convert block and inline lists to HTML strings. + +// Escape special characters in HTML. More efficient than +// three calls to bfindreplace. If preserve_entities is set, +// existing entities are left alone. +static bstring escape_html(bstring inp, bool preserve_entities) +{ + int pos = 0; + int match; + char c; + bstring escapable = blk2bstr("&<>\"", 4); + bstring ent; + bstring s = bstrcpy(inp); + while ((pos = binchr(s, pos, escapable)) != BSTR_ERR) { + c = bchar(s,pos); + switch (c) { + case '<': + bdelete(s, pos, 1); + ent = blk2bstr("<", 4); + binsert(s, pos, ent, ' '); + bdestroy(ent); + pos += 4; + break; + case '>': + bdelete(s, pos, 1); + ent = blk2bstr(">", 4); + binsert(s, pos, ent, ' '); + bdestroy(ent); + pos += 4; + break; + case '&': + if (preserve_entities && (match = scan_entity(s, pos))) { + pos += match; + } else { + bdelete(s, pos, 1); + ent = blk2bstr("&", 5); + binsert(s, pos, ent, ' '); + bdestroy(ent); + pos += 5; + } + break; + case '"': + bdelete(s, pos, 1); + ent = blk2bstr(""", 6); + binsert(s, pos, ent, ' '); + bdestroy(ent); + pos += 6; + break; + default: + bdelete(s, pos, 1); + log_err("unexpected character %02x", c); + } + } + bdestroy(escapable); + return s; +} + +static inline void cr(bstring buffer) +{ + int c = bchar(buffer, blength(buffer) - 1); + if (c != '\n' && c) { + bconchar(buffer, '\n'); + } +} + +// Convert a block list to HTML. Returns 0 on success, and sets result. +extern int blocks_to_html(block* b, bstring* result, bool tight) +{ + bstring contents = NULL; + bstring escaped, escaped2; + struct bstrList * info_words; + struct ListData * data; + bstring mbstart; + bstring html = blk2bstr("", 0); + + while(b != NULL) { + switch(b->tag) { + case document: + check(blocks_to_html(b->children, &contents, false) == 0, + "error converting blocks to html"); + bformata(html, "%s", contents->data); + bdestroy(contents); + break; + case paragraph: + check(inlines_to_html(b->inline_content, &contents) == 0, + "error converting inlines to html"); + if (tight) { + bformata(html, "%s", contents->data); + } else { + cr(html); + bformata(html, "<p>%s</p>", contents->data); + cr(html); + } + bdestroy(contents); + break; + case block_quote: + check(blocks_to_html(b->children, &contents, false) == 0, + "error converting blocks to html"); + cr(html); + bformata(html, "<blockquote>\n%s</blockquote>", contents->data); + cr(html); + bdestroy(contents); + break; + case list_item: + check(blocks_to_html(b->children, &contents, tight) == 0, + "error converting blocks to html"); + brtrimws(contents); + cr(html); + bformata(html, "<li>%s</li>", contents->data); + cr(html); + bdestroy(contents); + break; + case list: + // make sure a list starts at the beginning of the line: + cr(html); + data = &(b->attributes.list_data); + check(blocks_to_html(b->children, &contents, data->tight) == 0, + "error converting blocks to html"); + mbstart = bformat(" start=\"%d\"", data->start); + bformata(html, "<%s%s>\n%s</%s>", + data->list_type == bullet ? "ul" : "ol", + data->start == 1 ? "" : (char*) mbstart->data, + contents->data, + data->list_type == bullet ? "ul" : "ol"); + cr(html); + bdestroy(contents); + bdestroy(mbstart); + break; + case atx_header: + case setext_header: + check(inlines_to_html(b->inline_content, &contents) == 0, + "error converting inlines to html"); + cr(html); + bformata(html, "<h%d>%s</h%d>", + b->attributes.header_level, + contents->data, + b->attributes.header_level); + cr(html); + bdestroy(contents); + break; + case indented_code: + escaped = escape_html(b->string_content, false); + cr(html); + bformata(html, "<pre><code>%s</code></pre>", escaped->data); + cr(html); + bdestroy(escaped); + break; + case fenced_code: + escaped = escape_html(b->string_content, false); + cr(html); + bformata(html, "<pre"); + if (blength(b->attributes.fenced_code_data.info) > 0) { + escaped2 = escape_html(b->attributes.fenced_code_data.info, true); + info_words = bsplit(escaped2, ' '); + bformata(html, " class=\"%s\"", info_words->entry[0]->data); + bdestroy(escaped2); + bstrListDestroy(info_words); + } + bformata(html, "><code>%s</code></pre>", escaped->data); + cr(html); + bdestroy(escaped); + break; + case html_block: + bformata(html, "%s", b->string_content->data); + break; + case hrule: + bformata(html, "<hr />"); + cr(html); + break; + case reference_def: + break; + default: + log_warn("block type %d not implemented\n", b->tag); + break; + } + b = b->next; + } + *result = html; + return 0; + error: + return -1; +} + +// Convert an inline list to HTML. Returns 0 on success, and sets result. +extern int inlines_to_html(inl* ils, bstring* result) +{ + bstring contents = NULL; + bstring html = blk2bstr("", 0); + bstring mbtitle, escaped, escaped2; + + while(ils != NULL) { + switch(ils->tag) { + case str: + escaped = escape_html(ils->content.literal, false); + bformata(html, "%s", escaped->data); + bdestroy(escaped); + break; + case linebreak: + bformata(html, "<br />\n"); + break; + case softbreak: + bformata(html, "\n"); + break; + case code: + escaped = escape_html(ils->content.literal, false); + bformata(html, "<code>%s</code>", escaped->data); + bdestroy(escaped); + break; + case raw_html: + case entity: + bformata(html, "%s", ils->content.literal->data); + break; + case link: + check(inlines_to_html(ils->content.inlines, &contents) == 0, + "error converting inlines to html"); + if (blength(ils->content.linkable.title) > 0) { + escaped = escape_html(ils->content.linkable.title, true); + mbtitle = bformat(" title=\"%s\"", escaped->data); + bdestroy(escaped); + } else { + mbtitle = blk2bstr("",0); + } + escaped = escape_html(ils->content.linkable.url, true); + bformata(html, "<a href=\"%s\"%s>%s</a>", + escaped->data, + mbtitle->data, + contents->data); + bdestroy(escaped); + bdestroy(mbtitle); + bdestroy(contents); + break; + case image: + check(inlines_to_html(ils->content.inlines, &contents) == 0, + "error converting inlines to html"); + escaped = escape_html(ils->content.linkable.url, true); + escaped2 = escape_html(contents, false); + bdestroy(contents); + bformata(html, "<img src=\"%s\" alt=\"%s\"", + escaped->data, escaped2->data); + bdestroy(escaped); + bdestroy(escaped2); + if (blength(ils->content.linkable.title) > 0) { + escaped = escape_html(ils->content.linkable.title, true); + bformata(html, " title=\"%s\"", escaped->data); + bdestroy(escaped); + } + bformata(html, " />"); + break; + case strong: + check(inlines_to_html(ils->content.inlines, &contents) == 0, + "error converting inlines to html"); + bformata(html, "<strong>%s</strong>", contents->data); + bdestroy(contents); + break; + case emph: + check(inlines_to_html(ils->content.inlines, &contents) == 0, + "error converting inlines to html"); + bformata(html, "<em>%s</em>", contents->data); + bdestroy(contents); + break; + } + ils = ils->next; + } + *result = html; + return 0; + error: + return -1; +} |