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
\n
    \n"); + + if (text) + bufput(ob, text->data, text->size); + + BUFPUTSL(ob, "\n
\n
\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);