/reddish/lib/sundown/html/html.c
C | 635 lines | 489 code | 112 blank | 34 comment | 144 complexity | 31e36fdfe42803c25ff79c9aec73758a MD5 | raw file
- /*
- * Copyright (c) 2009, Natacha Porté
- * Copyright (c) 2011, Vicent Marti
- *
- * 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 "markdown.h"
- #include "html.h"
- #include <string.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <ctype.h>
- #include "houdini.h"
- #define USE_XHTML(opt) (opt->flags & HTML_USE_XHTML)
- int
- sdhtml_is_tag(const uint8_t *tag_data, size_t tag_size, const char *tagname)
- {
- size_t i;
- int closed = 0;
- if (tag_size < 3 || tag_data[0] != '<')
- return HTML_TAG_NONE;
- i = 1;
- if (tag_data[i] == '/') {
- closed = 1;
- i++;
- }
- for (; i < tag_size; ++i, ++tagname) {
- if (*tagname == 0)
- break;
- if (tag_data[i] != *tagname)
- return HTML_TAG_NONE;
- }
- if (i == tag_size)
- return HTML_TAG_NONE;
- if (isspace(tag_data[i]) || tag_data[i] == '>')
- return closed ? HTML_TAG_CLOSE : HTML_TAG_OPEN;
- return HTML_TAG_NONE;
- }
- static inline void escape_html(struct buf *ob, const uint8_t *source, size_t length)
- {
- houdini_escape_html0(ob, source, length, 0);
- }
- static inline void escape_href(struct buf *ob, const uint8_t *source, size_t length)
- {
- houdini_escape_href(ob, source, length);
- }
- /********************
- * 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 {
- BUFPUTSL(ob, "\">");
- }
- /*
- * Pretty printing: if we get an email address as
- * an actual URI, e.g. `mailto:foo@bar.com`, 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;
- }
- 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");
- }
- 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 int
- rndr_codespan(struct buf *ob, const struct buf *text, void *opaque)
- {
- BUFPUTSL(ob, "<code>");
- if (text) escape_html(ob, text->data, text->size);
- BUFPUTSL(ob, "</code>");
- return 1;
- }
- static int
- rndr_strikethrough(struct buf *ob, const struct buf *text, void *opaque)
- {
- if (!text || !text->size)
- return 0;
- BUFPUTSL(ob, "<del>");
- bufput(ob, text->data, text->size);
- BUFPUTSL(ob, "</del>");
- return 1;
- }
- static int
- rndr_double_emphasis(struct buf *ob, const struct buf *text, 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, const struct buf *text, 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 int
- rndr_linebreak(struct buf *ob, void *opaque)
- {
- struct html_renderopt *options = opaque;
- bufputs(ob, USE_XHTML(options) ? "<br/>\n" : "<br>\n");
- return 1;
- }
- static void
- rndr_header(struct buf *ob, const struct buf *text, int level, void *opaque)
- {
- struct html_renderopt *options = opaque;
- if (ob->size)
- bufputc(ob, '\n');
- if (options->flags & HTML_TOC)
- bufprintf(ob, "<h%d id=\"toc_%d\">", level, options->toc_data.header_count++);
- else
- 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, 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;
- }
- static void
- rndr_list(struct buf *ob, const 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, const struct buf *text, int flags, void *opaque)
- {
- BUFPUTSL(ob, "<li>");
- if (text) {
- size_t size = text->size;
- while (size && text->data[size - 1] == '\n')
- size--;
- bufput(ob, text->data, size);
- }
- BUFPUTSL(ob, "</li>\n");
- }
- static void
- rndr_paragraph(struct buf *ob, const struct buf *text, void *opaque)
- {
- struct html_renderopt *options = opaque;
- size_t i = 0;
- if (ob->size) bufputc(ob, '\n');
- if (!text || !text->size)
- return;
- while (i < text->size && isspace(text->data[i])) i++;
- if (i == text->size)
- return;
- BUFPUTSL(ob, "<p>");
- if (options->flags & HTML_HARD_WRAP) {
- size_t org;
- while (i < text->size) {
- org = i;
- while (i < text->size && text->data[i] != '\n')
- i++;
- if (i > org)
- bufput(ob, text->data + org, i - org);
- /*
- * do not insert a line break if this newline
- * is the last character on the paragraph
- */
- if (i >= text->size - 1)
- break;
- rndr_linebreak(ob, opaque);
- i++;
- }
- } else {
- bufput(ob, &text->data[i], text->size - i);
- }
- BUFPUTSL(ob, "</p>\n");
- }
- static void
- rndr_raw_block(struct buf *ob, const struct buf *text, void *opaque)
- {
- size_t org, sz;
- if (!text) return;
- sz = text->size;
- while (sz > 0 && text->data[sz - 1] == '\n') sz--;
- org = 0;
- while (org < sz && text->data[org] == '\n') org++;
- if (org >= sz) return;
- if (ob->size) bufputc(ob, '\n');
- bufput(ob, text->data + org, sz - org);
- bufputc(ob, '\n');
- }
- static int
- rndr_triple_emphasis(struct buf *ob, const struct buf *text, void *opaque)
- {
- if (!text || !text->size) return 0;
- BUFPUTSL(ob, "<strong><em>");
- bufput(ob, text->data, text->size);
- BUFPUTSL(ob, "</em></strong>");
- return 1;
- }
- 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");
- }
- static int
- rndr_image(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque)
- {
- struct html_renderopt *options = opaque;
- if (!link || !link->size) return 0;
- BUFPUTSL(ob, "<img src=\"");
- escape_href(ob, link->data, link->size);
- BUFPUTSL(ob, "\" alt=\"");
- if (alt && alt->size)
- escape_html(ob, alt->data, alt->size);
- if (title && title->size) {
- BUFPUTSL(ob, "\" title=\"");
- escape_html(ob, title->data, title->size); }
- bufputs(ob, USE_XHTML(options) ? "\"/>" : "\">");
- return 1;
- }
- static int
- rndr_raw_html(struct buf *ob, const struct buf *text, void *opaque)
- {
- struct html_renderopt *options = opaque;
- /* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES
- * It doens't see if there are any valid tags, just escape all of them. */
- if((options->flags & HTML_ESCAPE) != 0) {
- escape_html(ob, text->data, text->size);
- return 1;
- }
- if ((options->flags & HTML_SKIP_HTML) != 0)
- return 1;
- if ((options->flags & HTML_SKIP_STYLE) != 0 &&
- sdhtml_is_tag(text->data, text->size, "style"))
- return 1;
- if ((options->flags & HTML_SKIP_LINKS) != 0 &&
- sdhtml_is_tag(text->data, text->size, "a"))
- return 1;
- if ((options->flags & HTML_SKIP_IMAGES) != 0 &&
- sdhtml_is_tag(text->data, text->size, "img"))
- return 1;
- bufput(ob, text->data, text->size);
- return 1;
- }
- static void
- rndr_table(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque)
- {
- if (ob->size) bufputc(ob, '\n');
- BUFPUTSL(ob, "<table><thead>\n");
- if (header)
- bufput(ob, header->data, header->size);
- BUFPUTSL(ob, "</thead><tbody>\n");
- if (body)
- bufput(ob, body->data, body->size);
- BUFPUTSL(ob, "</tbody></table>\n");
- }
- static void
- rndr_tablerow(struct buf *ob, const struct buf *text, void *opaque)
- {
- BUFPUTSL(ob, "<tr>\n");
- if (text)
- bufput(ob, text->data, text->size);
- BUFPUTSL(ob, "</tr>\n");
- }
- static void
- rndr_tablecell(struct buf *ob, const struct buf *text, int flags, void *opaque)
- {
- if (flags & MKD_TABLE_HEADER) {
- BUFPUTSL(ob, "<th");
- } else {
- BUFPUTSL(ob, "<td");
- }
- switch (flags & MKD_TABLE_ALIGNMASK) {
- case MKD_TABLE_ALIGN_CENTER:
- BUFPUTSL(ob, " align=\"center\">");
- break;
- case MKD_TABLE_ALIGN_L:
- BUFPUTSL(ob, " align=\"left\">");
- break;
- case MKD_TABLE_ALIGN_R:
- BUFPUTSL(ob, " align=\"right\">");
- break;
- default:
- BUFPUTSL(ob, ">");
- }
- if (text)
- bufput(ob, text->data, text->size);
- if (flags & MKD_TABLE_HEADER) {
- BUFPUTSL(ob, "</th>\n");
- } else {
- BUFPUTSL(ob, "</td>\n");
- }
- }
- static int
- rndr_superscript(struct buf *ob, const struct buf *text, void *opaque)
- {
- if (!text || !text->size) return 0;
- BUFPUTSL(ob, "<sup>");
- bufput(ob, text->data, text->size);
- BUFPUTSL(ob, "</sup>");
- return 1;
- }
- static void
- rndr_normal_text(struct buf *ob, const struct buf *text, void *opaque)
- {
- if (text)
- escape_html(ob, text->data, text->size);
- }
- static void
- toc_header(struct buf *ob, const struct buf *text, int level, void *opaque)
- {
- struct html_renderopt *options = opaque;
- /* set the level offset if this is the first header
- * we're parsing for the document */
- if (options->toc_data.current_level == 0) {
- options->toc_data.level_offset = level - 1;
- }
- level -= options->toc_data.level_offset;
- if (level > options->toc_data.current_level) {
- while (level > options->toc_data.current_level) {
- BUFPUTSL(ob, "<ul>\n<li>\n");
- options->toc_data.current_level++;
- }
- } else if (level < options->toc_data.current_level) {
- BUFPUTSL(ob, "</li>\n");
- while (level < options->toc_data.current_level) {
- BUFPUTSL(ob, "</ul>\n</li>\n");
- options->toc_data.current_level--;
- }
- BUFPUTSL(ob,"<li>\n");
- } else {
- BUFPUTSL(ob,"</li>\n<li>\n");
- }
- bufprintf(ob, "<a href=\"#toc_%d\">", options->toc_data.header_count++);
- if (text)
- escape_html(ob, text->data, text->size);
- BUFPUTSL(ob, "</a>\n");
- }
- static int
- toc_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
- {
- if (content && content->size)
- bufput(ob, content->data, content->size);
- return 1;
- }
- static void
- toc_finalize(struct buf *ob, void *opaque)
- {
- struct html_renderopt *options = opaque;
- while (options->toc_data.current_level > 0) {
- BUFPUTSL(ob, "</li>\n</ul>\n");
- options->toc_data.current_level--;
- }
- }
- void
- sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options)
- {
- static const struct sd_callbacks cb_default = {
- NULL,
- NULL,
- NULL,
- toc_header,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- rndr_codespan,
- rndr_double_emphasis,
- rndr_emphasis,
- NULL,
- NULL,
- toc_link,
- NULL,
- rndr_triple_emphasis,
- rndr_strikethrough,
- rndr_superscript,
- NULL,
- NULL,
- NULL,
- toc_finalize,
- };
- memset(options, 0x0, sizeof(struct html_renderopt));
- options->flags = HTML_TOC;
- memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));
- }
- void
- sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options, unsigned int render_flags)
- {
- static const struct sd_callbacks cb_default = {
- rndr_blockcode,
- rndr_blockquote,
- rndr_raw_block,
- rndr_header,
- rndr_hrule,
- rndr_list,
- rndr_listitem,
- rndr_paragraph,
- rndr_table,
- rndr_tablerow,
- rndr_tablecell,
- rndr_autolink,
- rndr_codespan,
- rndr_double_emphasis,
- rndr_emphasis,
- rndr_image,
- rndr_linebreak,
- rndr_link,
- rndr_raw_html,
- rndr_triple_emphasis,
- rndr_strikethrough,
- rndr_superscript,
- NULL,
- rndr_normal_text,
- NULL,
- NULL,
- };
- /* Prepare the options pointer */
- memset(options, 0x0, sizeof(struct html_renderopt));
- options->flags = render_flags;
- /* Prepare the callbacks */
- memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));
- if (render_flags & HTML_SKIP_IMAGES)
- callbacks->image = NULL;
- if (render_flags & HTML_SKIP_LINKS) {
- callbacks->link = NULL;
- callbacks->autolink = NULL;
- }
- if (render_flags & HTML_SKIP_HTML || render_flags & HTML_ESCAPE)
- callbacks->blockhtml = NULL;
- }