#include "html.h" #include #include #include #include #include "escape.h" #define USE_XHTML(opt) (opt->flags & HOEDOWN_HTML_USE_XHTML) hoedown_html_tag hoedown_html_is_tag(const uint8_t *data, size_t size, const char *tagname) { size_t i; int closed = 0; if (size < 3 || data[0] != '<') return HOEDOWN_HTML_TAG_NONE; i = 1; if (data[i] == '/') { closed = 1; i++; } for (; i < size; ++i, ++tagname) { if (*tagname == 0) break; if (data[i] != *tagname) return HOEDOWN_HTML_TAG_NONE; } if (i == size) return HOEDOWN_HTML_TAG_NONE; if (isspace(data[i]) || data[i] == '>') return closed ? HOEDOWN_HTML_TAG_CLOSE : HOEDOWN_HTML_TAG_OPEN; return HOEDOWN_HTML_TAG_NONE; } static void escape_html(hoedown_buffer *ob, const uint8_t *source, size_t length) { hoedown_escape_html(ob, source, length, 0); } static void escape_href(hoedown_buffer *ob, const uint8_t *source, size_t length) { hoedown_escape_href(ob, source, length); } /******************** * GENERIC RENDERER * ********************/ static int rndr_autolink(hoedown_buffer *ob, const hoedown_buffer *link, hoedown_autolink_type type, const hoedown_renderer_data *data) { hoedown_html_renderer_state *state = data->opaque; if (!link || !link->size) return 0; HOEDOWN_BUFPUTSL(ob, "data, link->size); if (state->link_attributes) { hoedown_buffer_putc(ob, '\"'); state->link_attributes(ob, link, data); hoedown_buffer_putc(ob, '>'); } else { HOEDOWN_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 (hoedown_buffer_prefix(link, "mailto:") == 0) { escape_html(ob, link->data + 7, link->size - 7); } else { escape_html(ob, link->data, link->size); } HOEDOWN_BUFPUTSL(ob, ""); return 1; } static void rndr_blockcode(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_buffer *lang, const hoedown_renderer_data *data) { if (ob->size) hoedown_buffer_putc(ob, '\n'); if (lang) { HOEDOWN_BUFPUTSL(ob, "
data, lang->size);
		HOEDOWN_BUFPUTSL(ob, "\">");
	} else {
		HOEDOWN_BUFPUTSL(ob, "
");
	}

	if (text)
		escape_html(ob, text->data, text->size);

	HOEDOWN_BUFPUTSL(ob, "
\n"); } static void rndr_blockquote(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (ob->size) hoedown_buffer_putc(ob, '\n'); HOEDOWN_BUFPUTSL(ob, "
\n"); if (content) hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, "
\n"); } static int rndr_codespan(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data) { HOEDOWN_BUFPUTSL(ob, ""); if (text) escape_html(ob, text->data, text->size); HOEDOWN_BUFPUTSL(ob, ""); return 1; } static int rndr_strikethrough(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (!content || !content->size) return 0; HOEDOWN_BUFPUTSL(ob, ""); hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, ""); return 1; } static int rndr_double_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (!content || !content->size) return 0; HOEDOWN_BUFPUTSL(ob, ""); hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, ""); return 1; } static int rndr_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (!content || !content->size) return 0; HOEDOWN_BUFPUTSL(ob, ""); if (content) hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, ""); return 1; } static int rndr_underline(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (!content || !content->size) return 0; HOEDOWN_BUFPUTSL(ob, ""); hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, ""); return 1; } static int rndr_highlight(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (!content || !content->size) return 0; HOEDOWN_BUFPUTSL(ob, ""); hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, ""); return 1; } static int rndr_quote(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (!content || !content->size) return 0; HOEDOWN_BUFPUTSL(ob, ""); hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, ""); return 1; } static int rndr_linebreak(hoedown_buffer *ob, const hoedown_renderer_data *data) { hoedown_html_renderer_state *state = data->opaque; hoedown_buffer_puts(ob, USE_XHTML(state) ? "
\n" : "
\n"); return 1; } static void rndr_header(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data) { hoedown_html_renderer_state *state = data->opaque; if (ob->size) hoedown_buffer_putc(ob, '\n'); if (level <= state->toc_data.nesting_level) hoedown_buffer_printf(ob, "", level, state->toc_data.header_count++); else hoedown_buffer_printf(ob, "", level); if (content) hoedown_buffer_put(ob, content->data, content->size); hoedown_buffer_printf(ob, "\n", level); } static int rndr_link(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data) { hoedown_html_renderer_state *state = data->opaque; HOEDOWN_BUFPUTSL(ob, "size) escape_href(ob, link->data, link->size); if (title && title->size) { HOEDOWN_BUFPUTSL(ob, "\" title=\""); escape_html(ob, title->data, title->size); } if (state->link_attributes) { hoedown_buffer_putc(ob, '\"'); state->link_attributes(ob, link, data); hoedown_buffer_putc(ob, '>'); } else { HOEDOWN_BUFPUTSL(ob, "\">"); } if (content && content->size) hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, ""); return 1; } static void rndr_list(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data) { if (ob->size) hoedown_buffer_putc(ob, '\n'); hoedown_buffer_put(ob, (const uint8_t *)(flags & HOEDOWN_LIST_ORDERED ? "
    \n" : "
      \n"), 5); if (content) hoedown_buffer_put(ob, content->data, content->size); hoedown_buffer_put(ob, (const uint8_t *)(flags & HOEDOWN_LIST_ORDERED ? "
\n" : "\n"), 6); } static void rndr_listitem(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data) { HOEDOWN_BUFPUTSL(ob, "
  • "); if (content) { size_t size = content->size; while (size && content->data[size - 1] == '\n') size--; hoedown_buffer_put(ob, content->data, size); } HOEDOWN_BUFPUTSL(ob, "
  • \n"); } static void rndr_paragraph(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { hoedown_html_renderer_state *state = data->opaque; size_t i = 0; if (ob->size) hoedown_buffer_putc(ob, '\n'); if (!content || !content->size) return; while (i < content->size && isspace(content->data[i])) i++; if (i == content->size) return; HOEDOWN_BUFPUTSL(ob, "

    "); if (state->flags & HOEDOWN_HTML_HARD_WRAP) { size_t org; while (i < content->size) { org = i; while (i < content->size && content->data[i] != '\n') i++; if (i > org) hoedown_buffer_put(ob, content->data + org, i - org); /* * do not insert a line break if this newline * is the last character on the paragraph */ if (i >= content->size - 1) break; rndr_linebreak(ob, data); i++; } } else { hoedown_buffer_put(ob, content->data + i, content->size - i); } HOEDOWN_BUFPUTSL(ob, "

    \n"); } static void rndr_raw_block(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data) { size_t org, sz; if (!text) return; /* FIXME: Do we *really* need to trim the HTML? How does that make a difference? */ 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) hoedown_buffer_putc(ob, '\n'); hoedown_buffer_put(ob, text->data + org, sz - org); hoedown_buffer_putc(ob, '\n'); } static int rndr_triple_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (!content || !content->size) return 0; HOEDOWN_BUFPUTSL(ob, ""); hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, ""); return 1; } static void rndr_hrule(hoedown_buffer *ob, const hoedown_renderer_data *data) { hoedown_html_renderer_state *state = data->opaque; if (ob->size) hoedown_buffer_putc(ob, '\n'); hoedown_buffer_puts(ob, USE_XHTML(state) ? "
    \n" : "
    \n"); } static int rndr_image(hoedown_buffer *ob, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_buffer *alt, const hoedown_renderer_data *data) { hoedown_html_renderer_state *state = data->opaque; if (!link || !link->size) return 0; HOEDOWN_BUFPUTSL(ob, "data, link->size); HOEDOWN_BUFPUTSL(ob, "\" alt=\""); if (alt && alt->size) escape_html(ob, alt->data, alt->size); if (title && title->size) { HOEDOWN_BUFPUTSL(ob, "\" title=\""); escape_html(ob, title->data, title->size); } hoedown_buffer_puts(ob, USE_XHTML(state) ? "\"/>" : "\">"); return 1; } static int rndr_raw_html(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data) { hoedown_html_renderer_state *state = data->opaque; /* ESCAPE overrides SKIP_HTML. It doesn't look to see if * there are any valid tags, just escapes all of them. */ if((state->flags & HOEDOWN_HTML_ESCAPE) != 0) { escape_html(ob, text->data, text->size); return 1; } if ((state->flags & HOEDOWN_HTML_SKIP_HTML) != 0) return 1; hoedown_buffer_put(ob, text->data, text->size); return 1; } static void rndr_table(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (ob->size) hoedown_buffer_putc(ob, '\n'); HOEDOWN_BUFPUTSL(ob, "\n"); hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, "
    \n"); } static void rndr_table_header(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (ob->size) hoedown_buffer_putc(ob, '\n'); HOEDOWN_BUFPUTSL(ob, "\n"); hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, "\n"); } static void rndr_table_body(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (ob->size) hoedown_buffer_putc(ob, '\n'); HOEDOWN_BUFPUTSL(ob, "\n"); hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, "\n"); } static void rndr_tablerow(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { HOEDOWN_BUFPUTSL(ob, "\n"); if (content) hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, "\n"); } static void rndr_tablecell(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_table_flags flags, const hoedown_renderer_data *data) { if (flags & HOEDOWN_TABLE_HEADER) { HOEDOWN_BUFPUTSL(ob, ""); break; case HOEDOWN_TABLE_ALIGN_LEFT: HOEDOWN_BUFPUTSL(ob, " style=\"text-align: left\">"); break; case HOEDOWN_TABLE_ALIGN_RIGHT: HOEDOWN_BUFPUTSL(ob, " style=\"text-align: right\">"); break; default: HOEDOWN_BUFPUTSL(ob, ">"); } if (content) hoedown_buffer_put(ob, content->data, content->size); if (flags & HOEDOWN_TABLE_HEADER) { HOEDOWN_BUFPUTSL(ob, "\n"); } else { HOEDOWN_BUFPUTSL(ob, "\n"); } } static int rndr_superscript(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (!content || !content->size) return 0; HOEDOWN_BUFPUTSL(ob, ""); hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, ""); return 1; } static void rndr_normal_text(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (content) escape_html(ob, content->data, content->size); } static void rndr_footnotes(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { hoedown_html_renderer_state *state = data->opaque; if (ob->size) hoedown_buffer_putc(ob, '\n'); HOEDOWN_BUFPUTSL(ob, "
    \n"); hoedown_buffer_puts(ob, USE_XHTML(state) ? "
    \n" : "
    \n"); HOEDOWN_BUFPUTSL(ob, "
      \n"); if (content) hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, "\n
    \n
    \n"); } static void rndr_footnote_def(hoedown_buffer *ob, const hoedown_buffer *content, unsigned int num, const hoedown_renderer_data *data) { size_t i = 0; int pfound = 0; /* insert anchor at the end of first paragraph block */ if (content) { while ((i+3) < content->size) { if (content->data[i++] != '<') continue; if (content->data[i++] != '/') continue; if (content->data[i++] != 'p' && content->data[i] != 'P') continue; if (content->data[i] != '>') continue; i -= 3; pfound = 1; break; } } hoedown_buffer_printf(ob, "\n
  • \n", num); if (pfound) { hoedown_buffer_put(ob, content->data, i); hoedown_buffer_printf(ob, " ", num); hoedown_buffer_put(ob, content->data + i, content->size - i); } else if (content) { hoedown_buffer_put(ob, content->data, content->size); } HOEDOWN_BUFPUTSL(ob, "
  • \n"); } static int rndr_footnote_ref(hoedown_buffer *ob, unsigned int num, const hoedown_renderer_data *data) { hoedown_buffer_printf(ob, "%d", num, num, num); return 1; } static int rndr_math(hoedown_buffer *ob, const hoedown_buffer *text, int displaymode, const hoedown_renderer_data *data) { hoedown_buffer_put(ob, (const uint8_t *)(displaymode ? "\\[" : "\\("), 2); escape_html(ob, text->data, text->size); hoedown_buffer_put(ob, (const uint8_t *)(displaymode ? "\\]" : "\\)"), 2); return 1; } static void toc_header(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data) { hoedown_html_renderer_state *state = data->opaque; if (level <= state->toc_data.nesting_level) { /* set the level offset if this is the first header * we're parsing for the document */ if (state->toc_data.current_level == 0) state->toc_data.level_offset = level - 1; level -= state->toc_data.level_offset; if (level > state->toc_data.current_level) { while (level > state->toc_data.current_level) { HOEDOWN_BUFPUTSL(ob, "
      \n
    • \n"); state->toc_data.current_level++; } } else if (level < state->toc_data.current_level) { HOEDOWN_BUFPUTSL(ob, "
    • \n"); while (level < state->toc_data.current_level) { HOEDOWN_BUFPUTSL(ob, "
    \n\n"); state->toc_data.current_level--; } HOEDOWN_BUFPUTSL(ob,"
  • \n"); } else { HOEDOWN_BUFPUTSL(ob,"
  • \n
  • \n"); } hoedown_buffer_printf(ob, "", state->toc_data.header_count++); if (content) hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, "\n"); } } static int toc_link(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data) { if (content && content->size) hoedown_buffer_put(ob, content->data, content->size); return 1; } static void toc_finalize(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data) { hoedown_html_renderer_state *state; if (inline_render) return; state = data->opaque; while (state->toc_data.current_level > 0) { HOEDOWN_BUFPUTSL(ob, "
  • \n\n"); state->toc_data.current_level--; } state->toc_data.header_count = 0; } hoedown_renderer * hoedown_html_toc_renderer_new(int nesting_level) { static const hoedown_renderer cb_default = { NULL, NULL, NULL, toc_header, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, rndr_codespan, rndr_double_emphasis, rndr_emphasis, rndr_underline, rndr_highlight, rndr_quote, NULL, NULL, toc_link, rndr_triple_emphasis, rndr_strikethrough, rndr_superscript, NULL, NULL, NULL, NULL, rndr_normal_text, NULL, toc_finalize }; hoedown_html_renderer_state *state; hoedown_renderer *renderer; /* Prepare the state pointer */ state = hoedown_malloc(sizeof(hoedown_html_renderer_state)); memset(state, 0x0, sizeof(hoedown_html_renderer_state)); state->toc_data.nesting_level = nesting_level; /* Prepare the renderer */ renderer = hoedown_malloc(sizeof(hoedown_renderer)); memcpy(renderer, &cb_default, sizeof(hoedown_renderer)); renderer->opaque = state; return renderer; } hoedown_renderer * hoedown_html_renderer_new(hoedown_html_flags render_flags, int nesting_level) { static const hoedown_renderer cb_default = { NULL, rndr_blockcode, rndr_blockquote, rndr_header, rndr_hrule, rndr_list, rndr_listitem, rndr_paragraph, rndr_table, rndr_table_header, rndr_table_body, rndr_tablerow, rndr_tablecell, rndr_footnotes, rndr_footnote_def, rndr_raw_block, rndr_autolink, rndr_codespan, rndr_double_emphasis, rndr_emphasis, rndr_underline, rndr_highlight, rndr_quote, rndr_image, rndr_linebreak, rndr_link, rndr_triple_emphasis, rndr_strikethrough, rndr_superscript, rndr_footnote_ref, rndr_math, rndr_raw_html, NULL, rndr_normal_text, NULL, NULL }; hoedown_html_renderer_state *state; hoedown_renderer *renderer; /* Prepare the state pointer */ state = hoedown_malloc(sizeof(hoedown_html_renderer_state)); memset(state, 0x0, sizeof(hoedown_html_renderer_state)); state->flags = render_flags; state->toc_data.nesting_level = nesting_level; /* Prepare the renderer */ renderer = hoedown_malloc(sizeof(hoedown_renderer)); memcpy(renderer, &cb_default, sizeof(hoedown_renderer)); if (render_flags & HOEDOWN_HTML_SKIP_HTML || render_flags & HOEDOWN_HTML_ESCAPE) renderer->blockhtml = NULL; renderer->opaque = state; return renderer; } void hoedown_html_renderer_free(hoedown_renderer *renderer) { free(renderer->opaque); free(renderer); }