/* renderers.h - example markdown renderers */ /* * Copyright (c) 2009, Natacha Porté * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "renderers.h" #include <strings.h> /***************************** * EXPORTED HELPER FUNCTIONS * *****************************/ /* lus_attr_escape • copy the buffer entity-escaping '<', '>', '&' and '"' */ void lus_attr_escape(struct buf *ob, const char *src, size_t size) { size_t i = 0, org; while (i < size) { /* copying directly unescaped characters */ org = i; while (i < size && src[i] != '<' && src[i] != '>' && src[i] != '&' && src[i] != '"') i += 1; if (i > org) bufput(ob, src + org, i - org); /* escaping */ if (i >= size) break; else if (src[i] == '<') BUFPUTSL(ob, "<"); else if (src[i] == '>') BUFPUTSL(ob, ">"); else if (src[i] == '&') BUFPUTSL(ob, "&"); else if (src[i] == '"') BUFPUTSL(ob, """); i += 1; } } /* lus_body_escape • copy the buffer entity-escaping '<', '>' and '&' */ void lus_body_escape(struct buf *ob, const char *src, size_t size) { size_t i = 0, org; while (i < size) { /* copying directly unescaped characters */ org = i; while (i < size && src[i] != '<' && src[i] != '>' && src[i] != '&') i += 1; if (i > org) bufput(ob, src + org, i - org); /* escaping */ if (i >= size) break; else if (src[i] == '<') BUFPUTSL(ob, "<"); else if (src[i] == '>') BUFPUTSL(ob, ">"); else if (src[i] == '&') BUFPUTSL(ob, "&"); i += 1; } } /******************** * GENERIC RENDERER * ********************/ static int rndr_autolink(struct buf *ob, struct buf *link, enum mkd_autolink type, void *opaque) { if (!link || !link->size) return 0; BUFPUTSL(ob, "<a href=\""); if (type == MKDA_IMPLICIT_EMAIL) BUFPUTSL(ob, "mailto:"); lus_attr_escape(ob, link->data, link->size); BUFPUTSL(ob, "\">"); if (type == MKDA_EXPLICIT_EMAIL && link->size > 7) lus_body_escape(ob, link->data + 7, link->size - 7); else lus_body_escape(ob, link->data, link->size); BUFPUTSL(ob, "</a>"); return 1; } static void rndr_blockcode(struct buf *ob, struct buf *text, void *opaque) { if (ob->size) bufputc(ob, '\n'); BUFPUTSL(ob, "<pre><code>"); if (text) lus_body_escape(ob, text->data, text->size); BUFPUTSL(ob, "</code></pre>\n"); } static void rndr_blockquote(struct buf *ob, struct buf *text, void *opaque) { if (ob->size) bufputc(ob, '\n'); BUFPUTSL(ob, "<blockquote>\n"); if (text) bufput(ob, text->data, text->size); BUFPUTSL(ob, "</blockquote>\n"); } static int rndr_codespan(struct buf *ob, struct buf *text, void *opaque) { BUFPUTSL(ob, "<code>"); if (text) lus_body_escape(ob, text->data, text->size); BUFPUTSL(ob, "</code>"); return 1; } static int rndr_double_emphasis(struct buf *ob, struct buf *text, char c, void *opaque) { if (!text || !text->size) return 0; BUFPUTSL(ob, "<strong>"); bufput(ob, text->data, text->size); BUFPUTSL(ob, "</strong>"); return 1; } static int rndr_emphasis(struct buf *ob, struct buf *text, char c, void *opaque) { if (!text || !text->size) return 0; BUFPUTSL(ob, "<em>"); if (text) bufput(ob, text->data, text->size); BUFPUTSL(ob, "</em>"); return 1; } static void rndr_header(struct buf *ob, struct buf *text, int level, void *opaque) { if (ob->size) bufputc(ob, '\n'); bufprintf(ob, "<h%d>", level); if (text) bufput(ob, text->data, text->size); bufprintf(ob, "</h%d>\n", level); } static int rndr_link(struct buf *ob, struct buf *link, struct buf *title, struct buf *content, void *opaque) { BUFPUTSL(ob, "<a href=\""); if (link && link->size) lus_attr_escape(ob, link->data, link->size); if (title && title->size) { BUFPUTSL(ob, "\" title=\""); lus_attr_escape(ob, title->data, title->size); } BUFPUTSL(ob, "\">"); if (content && content->size) bufput(ob, content->data, content->size); BUFPUTSL(ob, "</a>"); return 1; } static void rndr_list(struct buf *ob, struct buf *text, int flags, void *opaque) { if (ob->size) bufputc(ob, '\n'); bufput(ob, (flags & MKD_LIST_ORDERED) ? "<ol>\n" : "<ul>\n", 5); if (text) bufput(ob, text->data, text->size); bufput(ob, (flags & MKD_LIST_ORDERED) ? "</ol>\n" : "</ul>\n", 6); } static void rndr_listitem(struct buf *ob, struct buf *text, int flags, void *opaque) { BUFPUTSL(ob, "<li>"); if (text) { while (text->size && text->data[text->size - 1] == '\n') text->size -= 1; bufput(ob, text->data, text->size); } BUFPUTSL(ob, "</li>\n"); } static void rndr_normal_text(struct buf *ob, struct buf *text, void *opaque) { if (text) lus_body_escape(ob, text->data, text->size); } static void rndr_paragraph(struct buf *ob, struct buf *text, void *opaque) { if (ob->size) bufputc(ob, '\n'); BUFPUTSL(ob, "<p>"); if (text) bufput(ob, text->data, text->size); BUFPUTSL(ob, "</p>\n"); } static void rndr_raw_block(struct buf *ob, struct buf *text, void *opaque) { size_t org, sz; if (!text) return; sz = text->size; while (sz > 0 && text->data[sz - 1] == '\n') sz -= 1; org = 0; while (org < sz && text->data[org] == '\n') org += 1; if (org >= sz) return; if (ob->size) bufputc(ob, '\n'); bufput(ob, text->data + org, sz - org); bufputc(ob, '\n'); } static int rndr_raw_inline(struct buf *ob, struct buf *text, void *opaque) { bufput(ob, text->data, text->size); return 1; } static int rndr_triple_emphasis(struct buf *ob, struct buf *text, char c, void *opaque) { if (!text || !text->size) return 0; BUFPUTSL(ob, "<strong><em>"); bufput(ob, text->data, text->size); BUFPUTSL(ob, "</em></strong>"); return 1; } /******************* * HTML 4 RENDERER * *******************/ static void html_hrule(struct buf *ob, void *opaque) { if (ob->size) bufputc(ob, '\n'); BUFPUTSL(ob, "<hr>\n"); } static int html_image(struct buf *ob, struct buf *link, struct buf *title, struct buf *alt, void *opaque) { if (!link || !link->size) return 0; BUFPUTSL(ob, "<img src=\""); lus_attr_escape(ob, link->data, link->size); BUFPUTSL(ob, "\" alt=\""); if (alt && alt->size) lus_attr_escape(ob, alt->data, alt->size); if (title && title->size) { BUFPUTSL(ob, "\" title=\""); lus_attr_escape(ob, title->data, title->size); } BUFPUTSL(ob, "\">"); return 1; } static int html_linebreak(struct buf *ob, void *opaque) { BUFPUTSL(ob, "<br>\n"); return 1; } /* exported renderer structure */ const struct mkd_renderer mkd_html = { NULL, NULL, rndr_blockcode, rndr_blockquote, rndr_raw_block, rndr_header, html_hrule, rndr_list, rndr_listitem, rndr_paragraph, NULL, NULL, NULL, rndr_autolink, rndr_codespan, rndr_double_emphasis, rndr_emphasis, html_image, html_linebreak, rndr_link, rndr_raw_inline, rndr_triple_emphasis, NULL, rndr_normal_text, 64, "*_", NULL }; /********************** * XHTML 1.0 RENDERER * **********************/ static void xhtml_hrule(struct buf *ob, void *opaque) { if (ob->size) bufputc(ob, '\n'); BUFPUTSL(ob, "<hr />\n"); } static int xhtml_image(struct buf *ob, struct buf *link, struct buf *title, struct buf *alt, void *opaque) { if (!link || !link->size) return 0; BUFPUTSL(ob, "<img src=\""); lus_attr_escape(ob, link->data, link->size); BUFPUTSL(ob, "\" alt=\""); if (alt && alt->size) lus_attr_escape(ob, alt->data, alt->size); if (title && title->size) { BUFPUTSL(ob, "\" title=\""); lus_attr_escape(ob, title->data, title->size); } BUFPUTSL(ob, "\" />"); return 1; } static int xhtml_linebreak(struct buf *ob, void *opaque) { BUFPUTSL(ob, "<br />\n"); return 1; } /* exported renderer structure */ const struct mkd_renderer mkd_xhtml = { NULL, NULL, rndr_blockcode, rndr_blockquote, rndr_raw_block, rndr_header, xhtml_hrule, rndr_list, rndr_listitem, rndr_paragraph, NULL, NULL, NULL, rndr_autolink, rndr_codespan, rndr_double_emphasis, rndr_emphasis, xhtml_image, xhtml_linebreak, rndr_link, rndr_raw_inline, rndr_triple_emphasis, NULL, rndr_normal_text, 64, "*_", NULL }; /********************** * DISCOUNT RENDERERS * **********************/ static int print_link_wxh(struct buf *ob, struct buf *link) { size_t eq, ex, end; if (link->size < 1) return 0; eq = link->size - 1; while (eq > 0 && (link->data[eq - 1] != ' ' || link->data[eq] != '=')) eq -= 1; if (!eq) return 0; ex = eq + 1; while (ex < link->size && link->data[ex] >= '0' && link->data[ex] <= '9') ex += 1; if (ex >= link->size || ex == eq + 1 || link->data[ex] != 'x') return 0; end = ex + 1; while (end < link->size && link->data[end] >= '0' && link->data[end] <= '9') end += 1; if (end == ex + 1) return 0; /* everything is fine, proceeding to actual printing */ lus_attr_escape(ob, link->data, eq - 1); BUFPUTSL(ob, "\" width="); bufput(ob, link->data + eq + 1, ex - eq - 1); BUFPUTSL(ob, " height="); bufput(ob, link->data + ex + 1, end - ex - 1); return 1; } static int discount_image(struct buf *ob, struct buf *link, struct buf *title, struct buf *alt, int xhtml) { if (!link || !link->size) return 0; BUFPUTSL(ob, "<img src=\""); if (!print_link_wxh(ob, link)) { lus_attr_escape(ob, link->data, link->size); bufputc(ob, '"'); } BUFPUTSL(ob, " alt=\""); if (alt && alt->size) lus_attr_escape(ob, alt->data, alt->size); if (title && title->size) { BUFPUTSL(ob, "\" title=\""); lus_attr_escape(ob, title->data, title->size); } bufputs(ob, xhtml ? "\" />" : "\">"); return 1; } static int html_discount_image(struct buf *ob, struct buf *link, struct buf *title, struct buf *alt, void *opaque) { return discount_image(ob, link, title, alt, 0); } static int xhtml_discount_image(struct buf *ob, struct buf *link, struct buf *title, struct buf *alt, void *opaque) { return discount_image(ob, link, title, alt, 1); } static int discount_link(struct buf *ob, struct buf *link, struct buf *title, struct buf *content, void *opaque) { if (!link) return rndr_link(ob, link, title, content, opaque); else if (link->size > 5 && !strncasecmp(link->data, "abbr:", 5)) { BUFPUTSL(ob, "<abbr title=\""); lus_attr_escape(ob, link->data + 5, link->size - 5); BUFPUTSL(ob, "\">"); bufput(ob, content->data, content->size); BUFPUTSL(ob, "</abbr>"); return 1; } else if (link->size > 6 && !strncasecmp(link->data, "class:", 6)) { BUFPUTSL(ob, "<span class=\""); lus_attr_escape(ob, link->data + 6, link->size - 6); BUFPUTSL(ob, "\">"); bufput(ob, content->data, content->size); BUFPUTSL(ob, "</span>"); return 1; } else if (link->size > 3 && !strncasecmp(link->data, "id:", 3)) { BUFPUTSL(ob, "<span id=\""); lus_attr_escape(ob, link->data + 3, link->size - 3); BUFPUTSL(ob, "\">"); bufput(ob, content->data, content->size); BUFPUTSL(ob, "</span>"); return 1; } else if (link->size > 4 && !strncasecmp(link->data, "raw:", 4)) { bufput(ob, link->data + 4, link->size - 4); return 1; } return rndr_link(ob, link, title, content, opaque); } static void discount_blockquote(struct buf *ob, struct buf *text, void *opaque) { size_t i = 5, size = text->size; char *data = text->data; if (text->size < 5 || strncasecmp(text->data, "<p>%", 4)) { rndr_blockquote(ob, text, opaque); return; } while (i < size && data[i] != '\n' && data[i] != '%') i += 1; if (i >= size || data[i] != '%') { rndr_blockquote(ob, text, opaque); return; } BUFPUTSL(ob, "<div class=\""); bufput(ob, text->data + 4, i - 4); BUFPUTSL(ob, "\"><p>"); i += 1; if (i + 4 >= text->size && !strncasecmp(text->data + i, "</p>", 4)) { size_t old_i = i; i += 4; while (i + 3 < text->size && (data[i] != '<' || data[i + 1] != 'p' || data[i + 2] != '>')) i += 1; if (i + 3 >= text->size) i = old_i; } bufput(ob, text->data + i, text->size - i); BUFPUTSL(ob, "</div>\n"); } static void discount_table(struct buf *ob, struct buf *head_row, struct buf *rows, void *opaque) { if (ob->size) bufputc(ob, '\n'); BUFPUTSL(ob, "<table>\n"); if (head_row) { BUFPUTSL(ob, "<thead>\n"); bufput(ob, head_row->data, head_row->size); BUFPUTSL(ob, "</thead>\n<tbody>\n"); } if (rows) bufput(ob, rows->data, rows->size); if (head_row) BUFPUTSL(ob, "</tbody>\n"); BUFPUTSL(ob, "</table>\n"); } static void discount_table_row(struct buf *ob, struct buf *cells, int flags, void *opaque){ (void)flags; BUFPUTSL(ob, " <tr>\n"); if (cells) bufput(ob, cells->data, cells->size); BUFPUTSL(ob, " </tr>\n"); } static void discount_table_cell(struct buf *ob, struct buf *text, int flags, void *opaque){ if (flags & MKD_CELL_HEAD) BUFPUTSL(ob, " <th"); else BUFPUTSL(ob, " <td"); switch (flags & MKD_CELL_ALIGN_MASK) { case MKD_CELL_ALIGN_LEFT: BUFPUTSL(ob, " align=\"left\""); break; case MKD_CELL_ALIGN_RIGHT: BUFPUTSL(ob, " align=\"right\""); break; case MKD_CELL_ALIGN_CENTER: BUFPUTSL(ob, " align=\"center\""); break; } bufputc(ob, '>'); if (text) bufput(ob, text->data, text->size); if (flags & MKD_CELL_HEAD) BUFPUTSL(ob, "</th>\n"); else BUFPUTSL(ob, "</td>\n"); } /* exported renderer structures */ const struct mkd_renderer discount_html = { NULL, NULL, rndr_blockcode, discount_blockquote, rndr_raw_block, rndr_header, html_hrule, rndr_list, rndr_listitem, rndr_paragraph, discount_table, discount_table_cell, discount_table_row, rndr_autolink, rndr_codespan, rndr_double_emphasis, rndr_emphasis, html_discount_image, html_linebreak, discount_link, rndr_raw_inline, rndr_triple_emphasis, NULL, rndr_normal_text, 64, "*_", NULL }; const struct mkd_renderer discount_xhtml = { NULL, NULL, rndr_blockcode, discount_blockquote, rndr_raw_block, rndr_header, xhtml_hrule, rndr_list, rndr_listitem, rndr_paragraph, discount_table, discount_table_cell, discount_table_row, rndr_autolink, rndr_codespan, rndr_double_emphasis, rndr_emphasis, xhtml_discount_image, xhtml_linebreak, discount_link, rndr_raw_inline, rndr_triple_emphasis, NULL, rndr_normal_text, 64, "*_", NULL }; /**************************** * NATACHA'S OWN EXTENSIONS * ****************************/ static void nat_span(struct buf *ob, struct buf *text, char *tag) { bufprintf(ob, "<%s>", tag); bufput(ob, text->data, text->size); bufprintf(ob, "</%s>", tag); } static int nat_emphasis(struct buf *ob, struct buf *text, char c, void *opaque) { if (!text || !text->size || c == '+' || c == '-') return 0; if (c == '|') nat_span(ob, text, "span"); else nat_span(ob, text, "em"); return 1; } static int nat_double_emphasis(struct buf *ob, struct buf *text, char c, void *opaque) { if (!text || !text->size || c == '|') return 0; if (c == '+') nat_span(ob, text, "ins"); else if (c == '-') nat_span(ob, text, "del"); else nat_span(ob, text, "strong"); return 1; } static int nat_triple_emphasis(struct buf *ob, struct buf *text, char c, void *opaque) { if (!text || !text->size || c == '+' || c == '-' || c == '|') return 0; BUFPUTSL(ob, "<strong><em>"); bufput(ob, text->data, text->size); BUFPUTSL(ob, "</em></strong>"); return 1; } static void nat_header(struct buf *ob, struct buf *text, int level, void *opaque) { size_t i = 0; if (ob->size) bufputc(ob, '\n'); while (i < text->size && (text->data[i] == '-' || text->data[i] == '_' || text->data[i] == '.' || text->data[i] == ':' || (text->data[i] >= 'a' && text->data[i] <= 'z') || (text->data[i] >= 'A' && text->data[i] <= 'Z') || (text->data[i] >= '0' && text->data[i] <= '9'))) i += 1; bufprintf(ob, "<h%d", level); if (i < text->size && text->data[i] == '#') { bufprintf(ob, " id=\"%.*s\">", (int)i, text->data); i += 1; } else { bufputc(ob, '>'); i = 0; } bufput(ob, text->data + i, text->size - i); bufprintf(ob, "</h%d>\n", level); } static void nat_paragraph(struct buf *ob, struct buf *text, void *opaque) { size_t i = 0; if (ob->size) bufputc(ob, '\n'); BUFPUTSL(ob, "<p"); if (text && text->size && text->data[0] == '(') { i = 1; while (i < text->size && (text->data[i] == ' ' /* this seems to be a bit more restrictive than */ /* what is allowed for class names */ || (text->data[i] >= 'a' && text->data[i] <= 'z') || (text->data[i] >= 'A' && text->data[i] <= 'Z') || (text->data[i] >= '0' && text->data[i] <= '9'))) i += 1; if (i < text->size && text->data[i] == ')') { bufprintf(ob, " class=\"%.*s\"", (int)(i - 1), text->data + 1); i += 1; } else i = 0; } bufputc(ob, '>'); if (text) bufput(ob, text->data + i, text->size - i); BUFPUTSL(ob, "</p>\n"); } /* exported renderer structures */ const struct mkd_renderer nat_html = { NULL, NULL, rndr_blockcode, discount_blockquote, rndr_raw_block, nat_header, html_hrule, rndr_list, rndr_listitem, nat_paragraph, NULL, NULL, NULL, rndr_autolink, rndr_codespan, nat_double_emphasis, nat_emphasis, html_discount_image, html_linebreak, discount_link, rndr_raw_inline, nat_triple_emphasis, NULL, rndr_normal_text, 64, "*_-+|", NULL }; const struct mkd_renderer nat_xhtml = { NULL, NULL, rndr_blockcode, discount_blockquote, rndr_raw_block, nat_header, xhtml_hrule, rndr_list, rndr_listitem, nat_paragraph, NULL, NULL, NULL, rndr_autolink, rndr_codespan, nat_double_emphasis, nat_emphasis, xhtml_discount_image, xhtml_linebreak, discount_link, rndr_raw_inline, nat_triple_emphasis, NULL, rndr_normal_text, 64, "*_-+|", NULL };