diff --git a/html/html.c b/html/html.c
index 7f08ee8e..ba626e71 100755
--- a/html/html.c
+++ b/html/html.c
@@ -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, "
\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\n", num);
+ if (pfound) {
+ bufput(ob, text->data, i);
+ bufprintf(ob, " ↩", num);
+ bufput(ob, text->data + i, text->size - i);
+ } else if (text) {
+ bufput(ob, text->data, text->size);
+ }
+ BUFPUTSL(ob, "\n");
+}
+
+static int
+rndr_footnote_ref(struct buf *ob, unsigned int num, void *opaque)
+{
+ bufprintf(ob, "%d", 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,
diff --git a/src/markdown.c b/src/markdown.c
index ea3cf232..b78656e8 100644
--- a/src/markdown.c
+++ b/src/markdown.c
@@ -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 : [ \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);
diff --git a/src/markdown.h b/src/markdown.h
index 6f6553ec..e3ce760e 100644
--- a/src/markdown.h
+++ b/src/markdown.h
@@ -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);