755 lines
19 KiB
C
755 lines
19 KiB
C
#include "html.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
#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, "<a href=\"");
|
|
if (type == HOEDOWN_AUTOLINK_EMAIL)
|
|
HOEDOWN_BUFPUTSL(ob, "mailto:");
|
|
escape_href(ob, link->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, "</a>");
|
|
|
|
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, "<pre><code class=\"language-");
|
|
escape_html(ob, lang->data, lang->size);
|
|
HOEDOWN_BUFPUTSL(ob, "\">");
|
|
} else {
|
|
HOEDOWN_BUFPUTSL(ob, "<pre><code>");
|
|
}
|
|
|
|
if (text)
|
|
escape_html(ob, text->data, text->size);
|
|
|
|
HOEDOWN_BUFPUTSL(ob, "</code></pre>\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, "<blockquote>\n");
|
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</blockquote>\n");
|
|
}
|
|
|
|
static int
|
|
rndr_codespan(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)
|
|
{
|
|
HOEDOWN_BUFPUTSL(ob, "<code>");
|
|
if (text) escape_html(ob, text->data, text->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</code>");
|
|
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, "<del>");
|
|
hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</del>");
|
|
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, "<strong>");
|
|
hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</strong>");
|
|
|
|
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, "<em>");
|
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</em>");
|
|
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, "<u>");
|
|
hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</u>");
|
|
|
|
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, "<mark>");
|
|
hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</mark>");
|
|
|
|
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, "<q>");
|
|
hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</q>");
|
|
|
|
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) ? "<br/>\n" : "<br>\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, "<h%d id=\"toc_%d\">", level, state->toc_data.header_count++);
|
|
else
|
|
hoedown_buffer_printf(ob, "<h%d>", level);
|
|
|
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
|
hoedown_buffer_printf(ob, "</h%d>\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, "<a href=\"");
|
|
|
|
if (link && link->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, "</a>");
|
|
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 ? "<ol>\n" : "<ul>\n"), 5);
|
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
|
hoedown_buffer_put(ob, (const uint8_t *)(flags & HOEDOWN_LIST_ORDERED ? "</ol>\n" : "</ul>\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, "<li>");
|
|
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, "</li>\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, "<p>");
|
|
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, "</p>\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, "<strong><em>");
|
|
hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</em></strong>");
|
|
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) ? "<hr/>\n" : "<hr>\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, "<img src=\"");
|
|
escape_href(ob, link->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, "<table>\n");
|
|
hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</table>\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, "<thead>\n");
|
|
hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</thead>\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, "<tbody>\n");
|
|
hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</tbody>\n");
|
|
}
|
|
|
|
static void
|
|
rndr_tablerow(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
|
{
|
|
HOEDOWN_BUFPUTSL(ob, "<tr>\n");
|
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</tr>\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, "<th");
|
|
} else {
|
|
HOEDOWN_BUFPUTSL(ob, "<td");
|
|
}
|
|
|
|
switch (flags & HOEDOWN_TABLE_ALIGNMASK) {
|
|
case HOEDOWN_TABLE_ALIGN_CENTER:
|
|
HOEDOWN_BUFPUTSL(ob, " style=\"text-align: center\">");
|
|
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, "</th>\n");
|
|
} else {
|
|
HOEDOWN_BUFPUTSL(ob, "</td>\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, "<sup>");
|
|
hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</sup>");
|
|
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, "<div class=\"footnotes\">\n");
|
|
hoedown_buffer_puts(ob, USE_XHTML(state) ? "<hr/>\n" : "<hr>\n");
|
|
HOEDOWN_BUFPUTSL(ob, "<ol>\n");
|
|
|
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
|
|
|
HOEDOWN_BUFPUTSL(ob, "\n</ol>\n</div>\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<li id=\"fn%d\">\n", num);
|
|
if (pfound) {
|
|
hoedown_buffer_put(ob, content->data, i);
|
|
hoedown_buffer_printf(ob, " <a href=\"#fnref%d\" rev=\"footnote\">↩</a>", 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, "</li>\n");
|
|
}
|
|
|
|
static int
|
|
rndr_footnote_ref(hoedown_buffer *ob, unsigned int num, const hoedown_renderer_data *data)
|
|
{
|
|
hoedown_buffer_printf(ob, "<sup id=\"fnref%d\"><a href=\"#fn%d\" rel=\"footnote\">%d</a></sup>", 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, "<ul>\n<li>\n");
|
|
state->toc_data.current_level++;
|
|
}
|
|
} else if (level < state->toc_data.current_level) {
|
|
HOEDOWN_BUFPUTSL(ob, "</li>\n");
|
|
while (level < state->toc_data.current_level) {
|
|
HOEDOWN_BUFPUTSL(ob, "</ul>\n</li>\n");
|
|
state->toc_data.current_level--;
|
|
}
|
|
HOEDOWN_BUFPUTSL(ob,"<li>\n");
|
|
} else {
|
|
HOEDOWN_BUFPUTSL(ob,"</li>\n<li>\n");
|
|
}
|
|
|
|
hoedown_buffer_printf(ob, "<a href=\"#toc_%d\">", state->toc_data.header_count++);
|
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
|
HOEDOWN_BUFPUTSL(ob, "</a>\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, "</li>\n</ul>\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);
|
|
}
|