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);
}