- #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;
- }
|