2 * Copyright (c) 2009, Natacha Porté
3 * Copyright (c) 2011, Vicent Marti
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
28 #define USE_XHTML(opt) (opt->flags & HTML_USE_XHTML)
31 sdhtml_is_tag(const uint8_t *tag_data, size_t tag_size, const char *tagname)
36 if (tag_size < 3 || tag_data[0] != '<')
41 if (tag_data[i] == '/') {
46 for (; i < tag_size; ++i, ++tagname) {
50 if (tag_data[i] != *tagname)
57 if (isspace(tag_data[i]) || tag_data[i] == '>')
58 return closed ? HTML_TAG_CLOSE : HTML_TAG_OPEN;
63 static inline void escape_html(struct buf *ob, const uint8_t *source, size_t length)
65 houdini_escape_html0(ob, source, length, 0);
68 static inline void escape_href(struct buf *ob, const uint8_t *source, size_t length)
70 houdini_escape_href(ob, source, length);
77 rndr_autolink(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque)
79 struct html_renderopt *options = opaque;
81 if (!link || !link->size)
84 if ((options->flags & HTML_SAFELINK) != 0 &&
85 !sd_autolink_issafe(link->data, link->size) &&
89 BUFPUTSL(ob, "<a href=\"");
90 if (type == MKDA_EMAIL)
91 BUFPUTSL(ob, "mailto:");
92 escape_href(ob, link->data, link->size);
94 if (options->link_attributes) {
96 options->link_attributes(ob, link, opaque);
103 * Pretty printing: if we get an email address as
104 * an actual URI, e.g. `mailto:foo@bar.com`, we don't
105 * want to print the `mailto:` prefix
107 if (bufprefix(link, "mailto:") == 0) {
108 escape_html(ob, link->data + 7, link->size - 7);
110 escape_html(ob, link->data, link->size);
113 BUFPUTSL(ob, "</a>");
119 rndr_blockcode(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque)
121 if (ob->size) bufputc(ob, '\n');
123 if (lang && lang->size) {
125 BUFPUTSL(ob, "<pre><code class=\"");
127 for (i = 0, cls = 0; i < lang->size; ++i, ++cls) {
128 while (i < lang->size && isspace(lang->data[i]))
131 if (i < lang->size) {
133 while (i < lang->size && !isspace(lang->data[i]))
136 if (lang->data[org] == '.')
139 if (cls) bufputc(ob, ' ');
140 escape_html(ob, lang->data + org, i - org);
146 BUFPUTSL(ob, "<pre><code>");
149 escape_html(ob, text->data, text->size);
151 BUFPUTSL(ob, "</code></pre>\n");
155 rndr_blockquote(struct buf *ob, const struct buf *text, void *opaque)
157 if (ob->size) bufputc(ob, '\n');
158 BUFPUTSL(ob, "<blockquote>\n");
159 if (text) bufput(ob, text->data, text->size);
160 BUFPUTSL(ob, "</blockquote>\n");
164 rndr_codespan(struct buf *ob, const struct buf *text, void *opaque)
166 BUFPUTSL(ob, "<code>");
167 if (text) escape_html(ob, text->data, text->size);
168 BUFPUTSL(ob, "</code>");
173 rndr_strikethrough(struct buf *ob, const struct buf *text, void *opaque)
175 if (!text || !text->size)
178 BUFPUTSL(ob, "<del>");
179 bufput(ob, text->data, text->size);
180 BUFPUTSL(ob, "</del>");
185 rndr_double_emphasis(struct buf *ob, const struct buf *text, void *opaque)
187 if (!text || !text->size)
190 BUFPUTSL(ob, "<strong>");
191 bufput(ob, text->data, text->size);
192 BUFPUTSL(ob, "</strong>");
198 rndr_emphasis(struct buf *ob, const struct buf *text, void *opaque)
200 if (!text || !text->size) return 0;
201 BUFPUTSL(ob, "<em>");
202 if (text) bufput(ob, text->data, text->size);
203 BUFPUTSL(ob, "</em>");
208 rndr_linebreak(struct buf *ob, void *opaque)
210 struct html_renderopt *options = opaque;
211 bufputs(ob, USE_XHTML(options) ? "<br/>\n" : "<br>\n");
216 rndr_header(struct buf *ob, const struct buf *text, int level, void *opaque)
218 struct html_renderopt *options = opaque;
223 if (options->flags & HTML_TOC)
224 bufprintf(ob, "<h%d id=\"toc_%d\">", level, options->toc_data.header_count++);
226 bufprintf(ob, "<h%d>", level);
228 if (text) bufput(ob, text->data, text->size);
229 bufprintf(ob, "</h%d>\n", level);
233 rndr_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
235 struct html_renderopt *options = opaque;
237 if (link != NULL && (options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size))
240 BUFPUTSL(ob, "<a href=\"");
242 if (link && link->size)
243 escape_href(ob, link->data, link->size);
245 if (title && title->size) {
246 BUFPUTSL(ob, "\" title=\"");
247 escape_html(ob, title->data, title->size);
250 if (options->link_attributes) {
252 options->link_attributes(ob, link, opaque);
258 if (content && content->size) bufput(ob, content->data, content->size);
259 BUFPUTSL(ob, "</a>");
264 rndr_list(struct buf *ob, const struct buf *text, int flags, void *opaque)
266 if (ob->size) bufputc(ob, '\n');
267 bufput(ob, flags & MKD_LIST_ORDERED ? "<ol>\n" : "<ul>\n", 5);
268 if (text) bufput(ob, text->data, text->size);
269 bufput(ob, flags & MKD_LIST_ORDERED ? "</ol>\n" : "</ul>\n", 6);
273 rndr_listitem(struct buf *ob, const struct buf *text, int flags, void *opaque)
275 BUFPUTSL(ob, "<li>");
277 size_t size = text->size;
278 while (size && text->data[size - 1] == '\n')
281 bufput(ob, text->data, size);
283 BUFPUTSL(ob, "</li>\n");
287 rndr_paragraph(struct buf *ob, const struct buf *text, void *opaque)
289 struct html_renderopt *options = opaque;
292 if (ob->size) bufputc(ob, '\n');
294 if (!text || !text->size)
297 while (i < text->size && isspace(text->data[i])) i++;
303 if (options->flags & HTML_HARD_WRAP) {
305 while (i < text->size) {
307 while (i < text->size && text->data[i] != '\n')
311 bufput(ob, text->data + org, i - org);
314 * do not insert a line break if this newline
315 * is the last character on the paragraph
317 if (i >= text->size - 1)
320 rndr_linebreak(ob, opaque);
324 bufput(ob, &text->data[i], text->size - i);
326 BUFPUTSL(ob, "</p>\n");
330 rndr_raw_block(struct buf *ob, const struct buf *text, void *opaque)
335 while (sz > 0 && text->data[sz - 1] == '\n') sz--;
337 while (org < sz && text->data[org] == '\n') org++;
338 if (org >= sz) return;
339 if (ob->size) bufputc(ob, '\n');
340 bufput(ob, text->data + org, sz - org);
345 rndr_triple_emphasis(struct buf *ob, const struct buf *text, void *opaque)
347 if (!text || !text->size) return 0;
348 BUFPUTSL(ob, "<strong><em>");
349 bufput(ob, text->data, text->size);
350 BUFPUTSL(ob, "</em></strong>");
355 rndr_hrule(struct buf *ob, void *opaque)
357 struct html_renderopt *options = opaque;
358 if (ob->size) bufputc(ob, '\n');
359 bufputs(ob, USE_XHTML(options) ? "<hr/>\n" : "<hr>\n");
363 rndr_image(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque)
365 struct html_renderopt *options = opaque;
366 if (!link || !link->size) return 0;
368 BUFPUTSL(ob, "<img src=\"");
369 escape_href(ob, link->data, link->size);
370 BUFPUTSL(ob, "\" alt=\"");
372 if (alt && alt->size)
373 escape_html(ob, alt->data, alt->size);
375 if (title && title->size) {
376 BUFPUTSL(ob, "\" title=\"");
377 escape_html(ob, title->data, title->size); }
379 bufputs(ob, USE_XHTML(options) ? "\"/>" : "\">");
384 rndr_raw_html(struct buf *ob, const struct buf *text, void *opaque)
386 struct html_renderopt *options = opaque;
388 /* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES
389 * It doens't see if there are any valid tags, just escape all of them. */
390 if((options->flags & HTML_ESCAPE) != 0) {
391 escape_html(ob, text->data, text->size);
395 if ((options->flags & HTML_SKIP_HTML) != 0)
398 if ((options->flags & HTML_SKIP_STYLE) != 0 &&
399 sdhtml_is_tag(text->data, text->size, "style"))
402 if ((options->flags & HTML_SKIP_LINKS) != 0 &&
403 sdhtml_is_tag(text->data, text->size, "a"))
406 if ((options->flags & HTML_SKIP_IMAGES) != 0 &&
407 sdhtml_is_tag(text->data, text->size, "img"))
410 bufput(ob, text->data, text->size);
415 rndr_table(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque)
417 if (ob->size) bufputc(ob, '\n');
418 BUFPUTSL(ob, "<table><thead>\n");
420 bufput(ob, header->data, header->size);
421 BUFPUTSL(ob, "</thead><tbody>\n");
423 bufput(ob, body->data, body->size);
424 BUFPUTSL(ob, "</tbody></table>\n");
428 rndr_tablerow(struct buf *ob, const struct buf *text, void *opaque)
430 BUFPUTSL(ob, "<tr>\n");
432 bufput(ob, text->data, text->size);
433 BUFPUTSL(ob, "</tr>\n");
437 rndr_tablecell(struct buf *ob, const struct buf *text, int flags, void *opaque)
439 if (flags & MKD_TABLE_HEADER) {
445 switch (flags & MKD_TABLE_ALIGNMASK) {
446 case MKD_TABLE_ALIGN_CENTER:
447 BUFPUTSL(ob, " align=\"center\">");
450 case MKD_TABLE_ALIGN_L:
451 BUFPUTSL(ob, " align=\"left\">");
454 case MKD_TABLE_ALIGN_R:
455 BUFPUTSL(ob, " align=\"right\">");
463 bufput(ob, text->data, text->size);
465 if (flags & MKD_TABLE_HEADER) {
466 BUFPUTSL(ob, "</th>\n");
468 BUFPUTSL(ob, "</td>\n");
473 rndr_superscript(struct buf *ob, const struct buf *text, void *opaque)
475 if (!text || !text->size) return 0;
476 BUFPUTSL(ob, "<sup>");
477 bufput(ob, text->data, text->size);
478 BUFPUTSL(ob, "</sup>");
483 rndr_normal_text(struct buf *ob, const struct buf *text, void *opaque)
486 escape_html(ob, text->data, text->size);
490 toc_header(struct buf *ob, const struct buf *text, int level, void *opaque)
492 struct html_renderopt *options = opaque;
494 /* set the level offset if this is the first header
495 * we're parsing for the document */
496 if (options->toc_data.current_level == 0) {
497 options->toc_data.level_offset = level - 1;
499 level -= options->toc_data.level_offset;
501 if (level > options->toc_data.current_level) {
502 while (level > options->toc_data.current_level) {
503 BUFPUTSL(ob, "<ul>\n<li>\n");
504 options->toc_data.current_level++;
506 } else if (level < options->toc_data.current_level) {
507 BUFPUTSL(ob, "</li>\n");
508 while (level < options->toc_data.current_level) {
509 BUFPUTSL(ob, "</ul>\n</li>\n");
510 options->toc_data.current_level--;
512 BUFPUTSL(ob,"<li>\n");
514 BUFPUTSL(ob,"</li>\n<li>\n");
517 bufprintf(ob, "<a href=\"#toc_%d\">", options->toc_data.header_count++);
519 escape_html(ob, text->data, text->size);
520 BUFPUTSL(ob, "</a>\n");
524 toc_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
526 if (content && content->size)
527 bufput(ob, content->data, content->size);
532 toc_finalize(struct buf *ob, void *opaque)
534 struct html_renderopt *options = opaque;
536 while (options->toc_data.current_level > 0) {
537 BUFPUTSL(ob, "</li>\n</ul>\n");
538 options->toc_data.current_level--;
543 sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options)
545 static const struct sd_callbacks cb_default = {
560 rndr_double_emphasis,
566 rndr_triple_emphasis,
577 memset(options, 0x0, sizeof(struct html_renderopt));
578 options->flags = HTML_TOC;
580 memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));
584 sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options, unsigned int render_flags)
586 static const struct sd_callbacks cb_default = {
601 rndr_double_emphasis,
607 rndr_triple_emphasis,
618 /* Prepare the options pointer */
619 memset(options, 0x0, sizeof(struct html_renderopt));
620 options->flags = render_flags;
622 /* Prepare the callbacks */
623 memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));
625 if (render_flags & HTML_SKIP_IMAGES)
626 callbacks->image = NULL;
628 if (render_flags & HTML_SKIP_LINKS) {
629 callbacks->link = NULL;
630 callbacks->autolink = NULL;
633 if (render_flags & HTML_SKIP_HTML || render_flags & HTML_ESCAPE)
634 callbacks->blockhtml = NULL;