static void rndr_raw_block(struct buf *ob, const struct buf *text, void *opaque) { size_t org, size; struct html_renderopt *options = opaque; if (!text) return; size = text->size; while (size > 0 && text->data[size - 1] == '\n') size--; for (org = 0; org < size && text->data[org] == '\n'; ++org) if (org >= size) return; /* Remove style tags if the `:no_styles` option is enabled */ if ((options->flags & HTML_SKIP_STYLE) != 0 && sdhtml_is_tag(text->data, size, "style")) return; if (ob->size) bufputc(ob, '\n'); bufput(ob, text->data + org, size - org); bufputc(ob, '\n'); }
static int rndr_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque) { struct html_renderopt *options = opaque; if (link != NULL && (options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size)) return 0; BUFPUTSL(ob, "<a href=\""); if (link && link->size) escape_href(ob, link->data, link->size); if (title && title->size) { BUFPUTSL(ob, "\" title=\""); escape_html(ob, title->data, title->size); } if (options->link_attributes) { bufputc(ob, '\"'); options->link_attributes(ob, link, opaque); bufputc(ob, '>'); } else { BUFPUTSL(ob, "\">"); } if (content && content->size) bufput(ob, content->data, content->size); BUFPUTSL(ob, "</a>"); return 1; }
/* parse_blockquote • hanldes parsing of a block-level code fragment */ static size_t parse_blockcode(struct buf *ob, struct render *rndr, char *data, size_t size) { size_t beg, end, pre; struct buf *work = new_work_buffer(rndr); beg = 0; while (beg < size) { for (end = beg + 1; end < size && data[end - 1] != '\n'; end += 1); pre = prefix_code(data + beg, end - beg); if (pre) beg += pre; /* skipping prefix */ else if (!is_empty(data + beg, end - beg)) /* non-empty non-prefixed line breaks the pre */ break; if (beg < end) { /* verbatim copy to the working buffer, escaping entities */ if (is_empty(data + beg, end - beg)) bufputc(work, '\n'); else bufput(work, data + beg, end - beg); } beg = end; } while (work->size && work->data[work->size - 1] == '\n') work->size -= 1; bufputc(work, '\n'); if (rndr->make.blockcode) rndr->make.blockcode(ob, work, rndr->make.opaque); release_work_buffer(rndr, work); return beg; }
static void rndr_blockcode(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque) { if (ob->size) bufputc(ob, '\n'); if (lang && lang->size) { size_t i, cls; BUFPUTSL(ob, "<pre><code class=\""); for (i = 0, cls = 0; i < lang->size; ++i, ++cls) { while (i < lang->size && isspace(lang->data[i])) i++; if (i < lang->size) { size_t org = i; while (i < lang->size && !isspace(lang->data[i])) i++; if (lang->data[org] == '.') org++; if (cls) bufputc(ob, ' '); escape_html(ob, lang->data + org, i - org); } } BUFPUTSL(ob, "\">"); } else BUFPUTSL(ob, "<pre><code>"); if (text) escape_html(ob, text->data, text->size); BUFPUTSL(ob, "</code></pre>\n"); }
void houdini_escape_js(struct buf *ob, const uint8_t *src, size_t size) { size_t i = 0, org, ch; bufgrow(ob, ESCAPE_GROW_FACTOR(size)); while (i < size) { org = i; while (i < size && JS_ESCAPE[src[i]] == 0) i++; if (i > org) bufput(ob, src + org, i - org); /* escaping */ if (i >= size) break; ch = src[i]; switch (ch) { case '/': /* * Escape only if preceded by a lt */ if (i && src[i - 1] == '<') bufputc(ob, '\\'); bufputc(ob, ch); break; case '\r': /* * Escape as \n, and skip the next \n if it's there */ if (i + 1 < size && src[i + 1] == '\n') i++; case '\n': /* * Escape actually as '\','n', not as '\', '\n' */ ch = 'n'; default: /* * Normal escaping */ bufputc(ob, '\\'); bufputc(ob, ch); break; } i++; } }
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 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_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 void rndr_blockcode(struct buf *ob, struct buf *text, void *opaque) { if (ob->size) bufputc(ob, '\n'); BUFPUTSL(ob, "<pre><code>"); if (text) lus_attr_escape(ob, text->data, text->size); BUFPUTSL(ob, "</code></pre>\n"); }
static void rndr_hrule(struct buf *ob, void *opaque) { struct html_renderopt *options = opaque; if (ob->size) bufputc(ob, '\n'); bufputs(ob, USE_XHTML(options) ? "<hr/>\n" : "<hr>\n"); }
/* build_ref_id • collapse whitespace from input text to make it a ref id */ static int build_ref_id(struct buf *id, const char *data, size_t size) { size_t beg, i; /* skip leading whitespace */ while (size > 0 && (data[0] == ' ' || data[0] == '\t' || data[0] == '\n')) { data += 1; size -= 1; } /* skip trailing whitespace */ while (size > 0 && (data[size - 1] == ' ' || data[size - 1] == '\t' || data[size - 1] == '\n')) size -= 1; if (size == 0) return -1; /* making the ref id */ i = 0; id->size = 0; while (i < size) { /* copy non-whitespace into the output buffer */ beg = i; while (i < size && !(data[i] == ' ' || data[i] == '\t' || data[i] == '\n')) i += 1; bufput(id, data + beg, i - beg); /* add a single space and skip all consecutive whitespace */ if (i < size) bufputc(id, ' '); while (i < size && (data[i] == ' ' || data[i] == '\t' || data[i] == '\n')) i += 1; } return 0; }
static size_t smartypants_cb__number(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { if (word_boundary(previous_char) && size >= 3) { if (text[0] == '1' && text[1] == '/' && text[2] == '2') { if (size == 3 || word_boundary(text[3])) { BUFPUTSL(ob, "½"); return 2; } } if (text[0] == '1' && text[1] == '/' && text[2] == '4') { if (size == 3 || word_boundary(text[3]) || (size >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h')) { BUFPUTSL(ob, "¼"); return 2; } } if (text[0] == '3' && text[1] == '/' && text[2] == '4') { if (size == 3 || word_boundary(text[3]) || (size >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's')) { BUFPUTSL(ob, "¾"); return 2; } } } bufputc(ob, text[0]); return 0; }
void houdini_escape_html0(struct buf *ob, const uint8_t *src, size_t size, int secure) { size_t i = 0, org, esc = 0; bufgrow(ob, ESCAPE_GROW_FACTOR(size)); while (i < size) { org = i; while (i < size && (esc = HTML_ESCAPE_TABLE[src[i]]) == 0) i++; if (i > org) bufput(ob, src + org, i - org); /* escaping */ if (i >= size) break; /* The forward slash is only escaped in secure mode */ if (src[i] == '/' && !secure) { bufputc(ob, '/'); } else if (HTML_ESCAPE_TABLE[src[i]] == 7) { /* skip control characters */ } else { bufputs(ob, HTML_ESCAPES[esc]); } i++; } }
/* * GitHub style code block: * * <pre lang="LANG"><code> * ... * </pre></code> * * Unlike other parsers, we store the language identifier in the <pre>, * and don't let the user generate custom classes. * * The language identifier in the <pre> block gets postprocessed and all * the code inside gets syntax highlighted with Pygments. This is much safer * than letting the user specify a CSS class for highlighting. * * Note that we only generate HTML for the first specifier. * E.g. * ~~~~ {.python .numbered} => <pre lang="python"><code> */ static void rndr_blockcode_github(struct buf *ob, struct buf *text, struct buf *lang, void *opaque) { if (ob->size) bufputc(ob, '\n'); if (lang && lang->size) { size_t i = 0; BUFPUTSL(ob, "<pre lang=\""); while (i < lang->size && !isspace(lang->data[i])) i++; if (lang->data[0] == '.') sdhtml_escape(ob, lang->data + 1, i - 1); else sdhtml_escape(ob, lang->data, i); BUFPUTSL(ob, "\"><code>"); } else BUFPUTSL(ob, "<pre><code>"); if (text) sdhtml_escape(ob, text->data, text->size); BUFPUTSL(ob, "</code></pre>\n"); }
/* * GitHub style code block: * * <pre lang="LANG"><code> * ... * </pre></code> * * Unlike other parsers, we store the language identifier in the <pre>, * and don't let the user generate custom classes. * * The language identifier in the <pre> block gets postprocessed and all * the code inside gets syntax highlighted with Pygments. This is much safer * than letting the user specify a CSS class for highlighting. * * Note that we only generate HTML for the first specifier. * E.g. * ~~~~ {.python .numbered} => <pre lang="python"><code> */ static void rndr_blockcode_github(struct buf *ob, struct buf *text, struct buf *lang, void *opaque) { if (ob->size) bufputc(ob, '\n'); if (lang && lang->size) { size_t i = 0; BUFPUTSL(ob, "<pre lang=\""); for (; i < lang->size; ++i) if (isspace(lang->data[i])) break; if (lang->data[0] == '.') bufput(ob, lang->data + 1, i - 1); else bufput(ob, lang->data, i); BUFPUTSL(ob, "\"><code>"); } else BUFPUTSL(ob, "<pre><code>"); if (text) attr_escape(ob, text->data, text->size); BUFPUTSL(ob, "</code></pre>\n"); }
static void rndr_tablecell(struct buf *ob, struct buf *text, int align, void *opaque) { if (ob->size) bufputc(ob, '\n'); switch (align) { case MKD_TABLE_ALIGN_L: BUFPUTSL(ob, "<td align=\"left\">"); break; case MKD_TABLE_ALIGN_R: BUFPUTSL(ob, "<td align=\"right\">"); break; case MKD_TABLE_ALIGN_CENTER: BUFPUTSL(ob, "<td align=\"center\">"); break; default: BUFPUTSL(ob, "<td>"); break; } if (text) bufput(ob, text->data, text->size); BUFPUTSL(ob, "</td>"); }
static size_t smartypants_cb__parens(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { if (size >= 3) { uint8_t t1 = tolower(text[1]); uint8_t t2 = tolower(text[2]); if (t1 == 'c' && t2 == ')') { BUFPUTSL(ob, "©"); return 2; } if (t1 == 'r' && t2 == ')') { BUFPUTSL(ob, "®"); return 2; } if (size >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')') { BUFPUTSL(ob, "™"); return 3; } } bufputc(ob, text[0]); return 0; }
static void rndr_blockcode_github(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque) { if (ob->size) bufputc(ob, '\n'); if (!text || !text->size) { BUFPUTSL(ob, "<pre><code></code></pre>"); return; } if (lang && lang->size) { size_t i = 0, lang_size; const char *lang_name = NULL; while (i < lang->size && !isspace(lang->data[i])) i++; if (lang->data[0] == '.') { lang_name = (char const *) (lang->data + 1); lang_size = i - 1; } else { lang_name = (char const *) lang->data; lang_size = i; } BUFPUTSL(ob, "<pre lang=\""); houdini_escape_html0(ob, (uint8_t const *) lang_name, lang_size, 0); BUFPUTSL(ob, "\"><code>"); } else { BUFPUTSL(ob, "<pre><code>"); } houdini_escape_html0(ob, text->data, text->size, 0); BUFPUTSL(ob, "</code></pre>\n"); }
static void rndr_header_anchor(struct buf *out, const struct buf *anchor) { static const char *STRIPPED = " -&+$,/:;=?@\"#{}|^~[]`\\*()%.!'"; const uint8_t *a = anchor->data; const size_t size = anchor->size; size_t i = 0; int stripped = 0, inserted = 0; for (; i < size; ++i) { // skip html tags if (a[i] == '<') { while (i < size && a[i] != '>') i++; // skip html entities } else if (a[i] == '&') { while (i < size && a[i] != ';') i++; } // replace non-ascii or invalid characters with dashes else if (!isascii(a[i]) || strchr(STRIPPED, a[i])) { if (inserted && !stripped) bufputc(out, '-'); // and do it only once stripped = 1; } else { bufputc(out, tolower(a[i])); stripped = 0; inserted++; } } // replace the last dash if there was anything added if (stripped && inserted) out->size--; // if anchor found empty, use djb2 hash for it if (!inserted && anchor->size) { unsigned long hash = 5381; for (i = 0; i < size; ++i) { hash = ((hash << 5) + hash) + a[i]; /* h * 33 + c */ } bufprintf(out, "part-%lx", hash); } }
static void rndr_blockquote(struct buf *ob, const 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 void rndr_hrule(struct buf *ob, void *opaque) { struct html_renderopt *options = opaque; if (ob->size) bufputc(ob, '\n'); BUFPUTSL(ob, "<hr"); bufputs(ob, options->close_tag); }
void houdini_unescape_js(struct buf *ob, const uint8_t *src, size_t size) { size_t i = 0, org, ch; bufgrow(ob, UNESCAPE_GROW_FACTOR(size)); while (i < size) { org = i; while (i < size && src[i] != '\\') i++; if (i > org) bufput(ob, src + org, i - org); /* escaping */ if (i == size) break; if (++i == size) { bufputc(ob, '\\'); break; } ch = src[i]; switch (ch) { case 'n': ch = '\n'; /* pass through */ case '\\': case '\'': case '\"': case '/': bufputc(ob, ch); i++; break; default: bufputc(ob, '\\'); break; } } }
static void rndr_blockcode(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque) { struct html_renderopt *options = opaque; if (ob->size) bufputc(ob, '\n'); if (lang && lang->size) { size_t i, cls; if (options->flags & HTML_PRETTIFY) { BUFPUTSL(ob, "<pre><code class=\"prettyprint lang-"); cls++; } else { BUFPUTSL(ob, "<pre><code class=\""); } for (i = 0, cls = 0; i < lang->size; ++i, ++cls) { while (i < lang->size && isspace(lang->data[i])) i++; if (i < lang->size) { size_t org = i; while (i < lang->size && !isspace(lang->data[i])) i++; if (lang->data[org] == '.') org++; if (cls) bufputc(ob, ' '); escape_html(ob, lang->data + org, i - org); } } BUFPUTSL(ob, "\">"); } else if (options->flags & HTML_PRETTIFY) { BUFPUTSL(ob, "<pre><code class=\"prettyprint\">"); } else { BUFPUTSL(ob, "<pre><code>"); } if (text) escape_html(ob, text->data, text->size); BUFPUTSL(ob, "</code></pre>\n"); }
static void rndr_tablerow(struct buf *ob, struct buf *text, void *opaque) { if (ob->size) bufputc(ob, '\n'); BUFPUTSL(ob, "<tr>\n"); if (text) bufput(ob, text->data, text->size); BUFPUTSL(ob, "\n</tr>"); }
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] <= '0'))) 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 rndr_listitem(struct buf *ob, struct buf *text, int flags, void *opaque) { if (ob->size) bufputc(ob, '\n'); BUFPUTSL(ob, "<li>\n"); if (text) { while (text->size && text->data[text->size - 1] == '\n') text->size -= 1; bufput(ob, text->data, text->size); } BUFPUTSL(ob, "</li>\n"); }
/******************** * GENERIC RENDERER * ********************/ static int rndr_autolink(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque) { struct html_renderopt *options = opaque; if (!link || !link->size) return 0; if ((options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size) && type != MKDA_EMAIL) return 0; BUFPUTSL(ob, "<a href=\""); if (type == MKDA_EMAIL) BUFPUTSL(ob, "mailto:"); escape_href(ob, link->data, link->size); if (options->link_attributes) { bufputc(ob, '\"'); options->link_attributes(ob, link, opaque); bufputc(ob, '>'); } else if (options->flags & HTML_NEW_TAB_LINKS) { BUFPUTSL(ob, "\" target=\"_blank\">"); } else { BUFPUTSL(ob, "\">"); } /* * Pretty printing: if we get an email address as * an actual URI, e.g. `mailto:[email protected]`, we don't * want to print the `mailto:` prefix */ if (bufprefix(link, "mailto:") == 0) { escape_html(ob, link->data + 7, link->size - 7); } else { escape_html(ob, link->data, link->size); } BUFPUTSL(ob, "</a>"); return 1; }
/* parse_fencedcode • hanldes parsing of a block-level code fragment */ static size_t parse_fencedcode(struct buf *ob, struct render *rndr, char *data, size_t size) { size_t beg, end; struct buf *work = 0; struct buf lang = { 0, 0, 0, 0, 0 }; beg = is_codefence(data, size, &lang); if (beg == 0) return 0; work = rndr_newbuf(rndr); while (beg < size) { size_t fence_end; fence_end = is_codefence(data + beg, size - beg, NULL); if (fence_end != 0) { beg += fence_end; break; } for (end = beg + 1; end < size && data[end - 1] != '\n'; end += 1); if (beg < end) { /* verbatim copy to the working buffer, escaping entities */ if (is_empty(data + beg, end - beg)) bufputc(work, '\n'); else bufput(work, data + beg, end - beg); } beg = end; } if (work->size && work->data[work->size - 1] != '\n') bufputc(work, '\n'); if (rndr->make.blockcode) rndr->make.blockcode(ob, work, lang.size ? &lang : NULL, rndr->make.opaque); rndr_popbuf(rndr); return beg; }
/* char_escape • '\\' backslash escape */ static size_t char_escape(struct buf *ob, struct render *rndr, char *data, size_t offset, size_t size) { struct buf work = { 0, 0, 0, 0, 0 }; if (size > 1) { if (rndr->make.normal_text) { work.data = data + 1; work.size = 1; rndr->make.normal_text(ob, &work, rndr->make.opaque); } else bufputc(ob, data[1]); } return 2; }
static inline void put_scaped_char(struct buf *ob, char c) { switch (c) { case '<': BUFPUTSL(ob, "<"); break; case '>': BUFPUTSL(ob, ">"); break; case '&': BUFPUTSL(ob, "&"); break; case '"': BUFPUTSL(ob, """); break; default: bufputc(ob, c); break; } }