Add MultiMarkdown footnotes extension

This commit is contained in:
Ben Dolman 2012-10-30 17:45:51 -06:00 committed by Devin Torres
parent 5c4d75bccb
commit 0f1b2a017f
3 changed files with 351 additions and 3 deletions

View file

@ -486,6 +486,54 @@ rndr_normal_text(struct buf *ob, const struct buf *text, void *opaque)
escape_html(ob, text->data, text->size);
}
static void
rndr_footnotes(struct buf *ob, const struct buf *text, void *opaque)
{
BUFPUTSL(ob, "<div class=\"footnotes\">\n<hr />\n<ol>\n");
if (text)
bufput(ob, text->data, text->size);
BUFPUTSL(ob, "\n</ol>\n</div>\n");
}
static void
rndr_footnote_def(struct buf *ob, const struct buf *text, unsigned int num, void *opaque)
{
size_t i = 0;
int pfound = 0;
/* insert anchor at the end of first paragraph block */
if (text) {
while ((i+3) < text->size) {
if (text->data[i++] != '<') continue;
if (text->data[i++] != '/') continue;
if (text->data[i++] != 'p' && text->data[i] != 'P') continue;
if (text->data[i] != '>') continue;
i -= 3;
pfound = 1;
break;
}
}
bufprintf(ob, "\n<li id=\"fn%d\">\n", num);
if (pfound) {
bufput(ob, text->data, i);
bufprintf(ob, "&nbsp;<a href=\"#fnref%d\" rev=\"footnote\">&#8617;</a>", num);
bufput(ob, text->data + i, text->size - i);
} else if (text) {
bufput(ob, text->data, text->size);
}
BUFPUTSL(ob, "</li>\n");
}
static int
rndr_footnote_ref(struct buf *ob, unsigned int num, void *opaque)
{
bufprintf(ob, "<sup id=\"fnref%d\"><a href=\"#fn%d\" rel=\"footnote\">%d</a></sup>", num, num, num);
return 1;
}
static void
toc_header(struct buf *ob, const struct buf *text, int level, void *opaque)
{
@ -554,6 +602,8 @@ sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *optio
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
rndr_codespan,
@ -566,6 +616,7 @@ sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *optio
rndr_triple_emphasis,
rndr_strikethrough,
rndr_superscript,
NULL,
NULL,
NULL,
@ -595,6 +646,8 @@ sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options,
rndr_table,
rndr_tablerow,
rndr_tablecell,
rndr_footnotes,
rndr_footnote_def,
rndr_autolink,
rndr_codespan,
@ -607,6 +660,7 @@ sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options,
rndr_triple_emphasis,
rndr_strikethrough,
rndr_superscript,
rndr_footnote_ref,
NULL,
rndr_normal_text,

View file

@ -55,6 +55,29 @@ struct link_ref {
struct link_ref *next;
};
/* footnote_ref: reference to a footnote */
struct footnote_ref {
unsigned int id;
int is_used;
unsigned int num;
struct buf *contents;
};
/* footnote_item: an item in a footnote_list */
struct footnote_item {
struct footnote_ref *ref;
struct footnote_item *next;
};
/* footnote_list: linked list of footnote_item */
struct footnote_list {
unsigned int count;
struct footnote_item *head;
struct footnote_item *tail;
};
/* char_trigger: function pointer to render active chars */
/* returns the number of chars taken care of */
/* data is the pointer of the beginning of the span */
@ -111,6 +134,8 @@ struct sd_markdown {
void *opaque;
struct link_ref *refs[REF_TABLE_SIZE];
struct footnote_list footnotes_found;
struct footnote_list footnotes_used;
uint8_t active_char[256];
struct stack work_bufs[2];
unsigned int ext_flags;
@ -233,6 +258,77 @@ free_link_refs(struct link_ref **references)
}
}
static struct footnote_ref *
create_footnote_ref(struct footnote_list *list, const uint8_t *name, size_t name_size)
{
struct footnote_ref *ref = calloc(1, sizeof(struct footnote_ref));
if (!ref)
return NULL;
ref->id = hash_link_ref(name, name_size);
return ref;
}
static int
add_footnote_ref(struct footnote_list *list, struct footnote_ref *ref)
{
struct footnote_item *item = calloc(1, sizeof(struct footnote_item));
if (!item)
return 0;
item->ref = ref;
if (list->head == NULL) {
list->head = list->tail = item;
} else {
list->tail->next = item;
list->tail = item;
}
list->count++;
return 1;
}
static struct footnote_ref *
find_footnote_ref(struct footnote_list *list, uint8_t *name, size_t length)
{
unsigned int hash = hash_link_ref(name, length);
struct footnote_item *item = NULL;
item = list->head;
while (item != NULL) {
if (item->ref->id == hash)
return item->ref;
item = item->next;
}
return NULL;
}
static void
free_footnote_ref(struct footnote_ref *ref)
{
bufrelease(ref->contents);
free(ref);
}
static void
free_footnote_list(struct footnote_list *list, int free_refs)
{
struct footnote_item *item = list->head;
struct footnote_item *next;
while (item) {
next = item->next;
if (free_refs)
free_footnote_ref(item->ref);
free(item);
item = next;
}
}
/*
* Check whether a char is a Markdown space.
@ -878,6 +974,34 @@ char_link(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset
txt_e = i;
i++;
/* footnote link */
if (rndr->ext_flags & MKDEXT_FOOTNOTES && data[1] == '^') {
if (txt_e < 3)
goto cleanup;
struct buf id = { 0, 0, 0, 0 };
struct footnote_ref *fr;
id.data = data + 2;
id.size = txt_e - 2;
fr = find_footnote_ref(&rndr->footnotes_found, id.data, id.size);
/* mark footnote used */
if (fr && !fr->is_used) {
if(!add_footnote_ref(&rndr->footnotes_used, fr))
goto cleanup;
fr->is_used = 1;
fr->num = rndr->footnotes_used.count;
}
/* render */
if (fr && rndr->cb.footnote_ref)
ret = rndr->cb.footnote_ref(ob, fr->num, rndr->opaque);
goto cleanup;
}
/* skip any amount of whitespace or newline */
/* (this is much more laxist than original markdown syntax) */
@ -1117,7 +1241,7 @@ char_superscript(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t
/* is_empty • returns the line length when it is empty, 0 otherwise */
static size_t
is_empty(uint8_t *data, size_t size)
is_empty(const uint8_t *data, size_t size)
{
size_t i;
@ -1819,6 +1943,44 @@ parse_atxheader(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t
return skip;
}
/* parse_footnote_def • parse a single footnote definition */
static void
parse_footnote_def(struct buf *ob, struct sd_markdown *rndr, unsigned int num, uint8_t *data, size_t size)
{
struct buf *work = 0;
work = rndr_newbuf(rndr, BUFFER_SPAN);
parse_block(work, rndr, data, size);
if (rndr->cb.footnote_def)
rndr->cb.footnote_def(ob, work, num, rndr->opaque);
rndr_popbuf(rndr, BUFFER_SPAN);
}
/* parse_footnote_list • render the contents of the footnotes */
static void
parse_footnote_list(struct buf *ob, struct sd_markdown *rndr, struct footnote_list *footnotes)
{
struct buf *work = 0;
struct footnote_item *item;
struct footnote_ref *ref;
if (footnotes->count == 0)
return;
work = rndr_newbuf(rndr, BUFFER_BLOCK);
item = footnotes->head;
while (item) {
ref = item->ref;
parse_footnote_def(work, rndr, ref->num, ref->contents->data, ref->contents->size);
item = item->next;
}
if (rndr->cb.footnotes)
rndr->cb.footnotes(ob, work, rndr->opaque);
rndr_popbuf(rndr, BUFFER_BLOCK);
}
/* htmlblock_end • checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
/* returns the length on match, 0 otherwise */
@ -2250,6 +2412,117 @@ parse_block(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size
* REFERENCE PARSING *
*********************/
/* is_footnote • returns whether a line is a footnote definition or not */
static int
is_footnote(const uint8_t *data, size_t beg, size_t end, size_t *last, struct footnote_list *list)
{
size_t i = 0;
struct buf *contents = 0;
size_t ind = 0;
int in_empty = 0;
size_t start = 0;
size_t id_offset, id_end;
/* up to 3 optional leading spaces */
if (beg + 3 >= end) return 0;
if (data[beg] == ' ') { i = 1;
if (data[beg + 1] == ' ') { i = 2;
if (data[beg + 2] == ' ') { i = 3;
if (data[beg + 3] == ' ') return 0; } } }
i += beg;
/* id part: caret followed by anything between brackets */
if (data[i] != '[') return 0;
i++;
if (i >= end || data[i] != '^') return 0;
i++;
id_offset = i;
while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']')
i++;
if (i >= end || data[i] != ']') return 0;
id_end = i;
/* spacer: colon (space | tab)* newline? (space | tab)* */
i++;
if (i >= end || data[i] != ':') return 0;
i++;
while (i < end && data[i] == ' ') i++;
if (i < end && (data[i] == '\n' || data[i] == '\r')) {
i++;
if (i < end && data[i] == '\n' && data[i - 1] == '\r') i++;
}
while (i < end && data[i] == ' ') i++;
if (i >= end || data[i] == '\n' || data[i] == '\r') return 0;
/* getting content buffer */
contents = bufnew(64);
start = i;
/* process lines similiar to a list item */
while (i < end) {
while (i < end && data[i] != '\n' && data[i] != '\r') i++;
/* process an empty line */
if (is_empty(data + start, i - start)) {
in_empty = 1;
if (i < end && (data[i] == '\n' || data[i] == '\r')) {
i++;
if (i < end && data[i] == '\n' && data[i - 1] == '\r') i++;
}
start = i;
continue;
}
/* calculating the indentation */
ind = 0;
while (ind < 4 && start + ind < end && data[start + ind] == ' ')
ind++;
/* joining only indented stuff after empty lines;
* note that now we only require 1 space of indentation
* to continue, just like lists */
if (in_empty && ind == 0) {
break;
}
else if (in_empty) {
bufputc(contents, '\n');
}
in_empty = 0;
/* adding the line into the content buffer */
bufput(contents, data + start + ind, i - start - ind);
/* add carriage return */
if (i < end) {
bufput(contents, "\n", 1);
if (i < end && (data[i] == '\n' || data[i] == '\r')) {
i++;
if (i < end && data[i] == '\n' && data[i - 1] == '\r') i++;
}
}
start = i;
}
if (last)
*last = start;
if (list) {
struct footnote_ref *ref;
ref = create_footnote_ref(list, data + id_offset, id_end - id_offset);
if (!ref)
return 0;
if (!add_footnote_ref(list, ref)) {
free_footnote_ref(ref);
return 0;
}
ref->contents = contents;
}
return 1;
}
/* is_ref • returns whether a line is a reference or not */
static int
is_ref(const uint8_t *data, size_t beg, size_t end, size_t *last, struct link_ref **refs)
@ -2471,6 +2744,14 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
/* reset the references table */
memset(&md->refs, 0x0, REF_TABLE_SIZE * sizeof(void *));
int footnotes_enabled = md->ext_flags & MKDEXT_FOOTNOTES;
/* reset the footnotes lists */
if (footnotes_enabled) {
memset(&md->footnotes_found, 0x0, sizeof(md->footnotes_found));
memset(&md->footnotes_used, 0x0, sizeof(md->footnotes_used));
}
/* first pass: looking for references, copying everything else */
beg = 0;
@ -2481,7 +2762,9 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
beg += 3;
while (beg < doc_size) /* iterating over lines */
if (is_ref(document, beg, doc_size, &end, md->refs))
if (footnotes_enabled && is_footnote(document, beg, doc_size, &end, &md->footnotes_found))
beg = end;
else if (is_ref(document, beg, doc_size, &end, md->refs))
beg = end;
else { /* skipping to the next line */
end = beg;
@ -2516,6 +2799,10 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
parse_block(ob, md, text->data, text->size);
}
/* footnotes */
if (footnotes_enabled)
parse_footnote_list(ob, md, &md->footnotes_used);
if (md->cb.doc_footer)
md->cb.doc_footer(ob, md->opaque);
@ -2523,6 +2810,10 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
/* clean-up */
bufrelease(text);
free_link_refs(md->refs);
if (footnotes_enabled) {
free_footnote_list(&md->footnotes_found, 1);
free_footnote_list(&md->footnotes_used, 0);
}
assert(md->work_bufs[BUFFER_SPAN].size == 0);
assert(md->work_bufs[BUFFER_BLOCK].size == 0);

View file

@ -59,6 +59,7 @@ enum mkd_extensions {
MKDEXT_SPACE_HEADERS = (1 << 6),
MKDEXT_SUPERSCRIPT = (1 << 7),
MKDEXT_LAX_SPACING = (1 << 8),
MKDEXT_FOOTNOTES = (1 << 9),
};
/* sd_callbacks - functions for rendering parsed data */
@ -75,7 +76,8 @@ struct sd_callbacks {
void (*table)(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque);
void (*table_row)(struct buf *ob, const struct buf *text, void *opaque);
void (*table_cell)(struct buf *ob, const struct buf *text, int flags, void *opaque);
void (*footnotes)(struct buf *ob, const struct buf *text, void *opaque);
void (*footnote_def)(struct buf *ob, const struct buf *text, unsigned int num, void *opaque);
/* span level callbacks - NULL or return 0 prints the span verbatim */
int (*autolink)(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque);
@ -89,6 +91,7 @@ struct sd_callbacks {
int (*triple_emphasis)(struct buf *ob, const struct buf *text, void *opaque);
int (*strikethrough)(struct buf *ob, const struct buf *text, void *opaque);
int (*superscript)(struct buf *ob, const struct buf *text, void *opaque);
int (*footnote_ref)(struct buf *ob, unsigned int num, void *opaque);
/* low level callbacks - NULL copies input directly into the output */
void (*entity)(struct buf *ob, const struct buf *entity, void *opaque);