#include #include #include #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, "

%s

", 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, "
\n%s
", 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, "
  • %s
  • ", 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", 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, "%s", 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, "
    %s
    ", escaped->data); cr(html); bdestroy(escaped); break; case fenced_code: escaped = escape_html(b->string_content, false); cr(html); bformata(html, "
    attributes.fenced_code_data.info) > 0) {
            escaped2 = escape_html(b->attributes.fenced_code_data.info, true);
            info_words = bsplit(escaped2, ' ');
            bformata(html, " class=\"language-%s\"", info_words->entry[0]->data);
            bdestroy(escaped2);
            bstrListDestroy(info_words);
          }
          bformata(html, ">%s
    ", escaped->data); cr(html); bdestroy(escaped); break; case html_block: bformata(html, "%s", b->string_content->data); break; case hrule: bformata(html, "
    "); 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, "
    \n"); break; case softbreak: bformata(html, "\n"); break; case code: escaped = escape_html(ils->content.literal, false); bformata(html, "%s", 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, "%s", 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, "\"%s\"",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, "%s", contents->data); bdestroy(contents); break; case emph: check(inlines_to_html(ils->content.inlines, &contents) == 0, "error converting inlines to html"); bformata(html, "%s", contents->data); bdestroy(contents); break; } ils = ils->next; } *result = html; return 0; error: return -1; }