redcarpet 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of redcarpet might be problematic. Click here for more details.

@@ -14,8 +14,8 @@
14
14
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
15
  */
16
16
 
17
- #ifndef UPSKIRT_HTML_H
18
- #define UPSKIRT_HTML_H
17
+ #ifndef HTML_H__
18
+ #define HTML_H__
19
19
 
20
20
  #include "markdown.h"
21
21
  #include "buffer.h"
@@ -27,9 +27,9 @@ extern "C" {
27
27
 
28
28
  struct html_renderopt {
29
29
  struct {
30
- int header_count;
31
30
  int current_level;
32
31
  int level_offset;
32
+ int nesting_level;
33
33
  } toc_data;
34
34
 
35
35
  unsigned int flags;
@@ -65,7 +65,7 @@ extern void
65
65
  sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr, unsigned int render_flags);
66
66
 
67
67
  extern void
68
- sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr);
68
+ sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr, int nesting_level);
69
69
 
70
70
  extern void
71
71
  sdhtml_smartypants(struct buf *ob, const uint8_t *text, size_t size);
@@ -55,6 +55,29 @@ struct link_ref {
55
55
  struct link_ref *next;
56
56
  };
57
57
 
58
+ /* footnote_ref: reference to a footnote */
59
+ struct footnote_ref {
60
+ unsigned int id;
61
+
62
+ int is_used;
63
+ unsigned int num;
64
+
65
+ struct buf *contents;
66
+ };
67
+
68
+ /* footnote_item: an item in a footnote_list */
69
+ struct footnote_item {
70
+ struct footnote_ref *ref;
71
+ struct footnote_item *next;
72
+ };
73
+
74
+ /* footnote_list: linked list of footnote_item */
75
+ struct footnote_list {
76
+ unsigned int count;
77
+ struct footnote_item *head;
78
+ struct footnote_item *tail;
79
+ };
80
+
58
81
  /* char_trigger: function pointer to render active chars */
59
82
  /* returns the number of chars taken care of */
60
83
  /* data is the pointer of the beginning of the span */
@@ -66,6 +89,7 @@ typedef size_t
66
89
  static size_t char_emphasis(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
67
90
  static size_t char_underline(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
68
91
  static size_t char_highlight(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
92
+ static size_t char_quote(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
69
93
  static size_t char_linebreak(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
70
94
  static size_t char_codespan(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
71
95
  static size_t char_escape(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size);
@@ -90,6 +114,7 @@ enum markdown_char_t {
90
114
  MD_CHAR_AUTOLINK_EMAIL,
91
115
  MD_CHAR_AUTOLINK_WWW,
92
116
  MD_CHAR_SUPERSCRIPT,
117
+ MD_CHAR_QUOTE
93
118
  };
94
119
 
95
120
  static char_trigger markdown_char_ptrs[] = {
@@ -105,6 +130,7 @@ static char_trigger markdown_char_ptrs[] = {
105
130
  &char_autolink_email,
106
131
  &char_autolink_www,
107
132
  &char_superscript,
133
+ &char_quote
108
134
  };
109
135
 
110
136
  /* render • structure containing one particular render */
@@ -113,6 +139,8 @@ struct sd_markdown {
113
139
  void *opaque;
114
140
 
115
141
  struct link_ref *refs[REF_TABLE_SIZE];
142
+ struct footnote_list footnotes_found;
143
+ struct footnote_list footnotes_used;
116
144
  uint8_t active_char[256];
117
145
  struct stack work_bufs[2];
118
146
  unsigned int ext_flags;
@@ -235,6 +263,77 @@ free_link_refs(struct link_ref **references)
235
263
  }
236
264
  }
237
265
 
266
+ static struct footnote_ref *
267
+ create_footnote_ref(struct footnote_list *list, const uint8_t *name, size_t name_size)
268
+ {
269
+ struct footnote_ref *ref = calloc(1, sizeof(struct footnote_ref));
270
+ if (!ref)
271
+ return NULL;
272
+
273
+ ref->id = hash_link_ref(name, name_size);
274
+
275
+ return ref;
276
+ }
277
+
278
+ static int
279
+ add_footnote_ref(struct footnote_list *list, struct footnote_ref *ref)
280
+ {
281
+ struct footnote_item *item = calloc(1, sizeof(struct footnote_item));
282
+ if (!item)
283
+ return 0;
284
+ item->ref = ref;
285
+
286
+ if (list->head == NULL) {
287
+ list->head = list->tail = item;
288
+ } else {
289
+ list->tail->next = item;
290
+ list->tail = item;
291
+ }
292
+ list->count++;
293
+
294
+ return 1;
295
+ }
296
+
297
+ static struct footnote_ref *
298
+ find_footnote_ref(struct footnote_list *list, uint8_t *name, size_t length)
299
+ {
300
+ unsigned int hash = hash_link_ref(name, length);
301
+ struct footnote_item *item = NULL;
302
+
303
+ item = list->head;
304
+
305
+ while (item != NULL) {
306
+ if (item->ref->id == hash)
307
+ return item->ref;
308
+ item = item->next;
309
+ }
310
+
311
+ return NULL;
312
+ }
313
+
314
+ static void
315
+ free_footnote_ref(struct footnote_ref *ref)
316
+ {
317
+ bufrelease(ref->contents);
318
+ free(ref);
319
+ }
320
+
321
+ static void
322
+ free_footnote_list(struct footnote_list *list, int free_refs)
323
+ {
324
+ struct footnote_item *item = list->head;
325
+ struct footnote_item *next;
326
+
327
+ while (item) {
328
+ next = item->next;
329
+ if (free_refs)
330
+ free_footnote_ref(item->ref);
331
+ free(item);
332
+ item = next;
333
+ }
334
+ }
335
+
336
+
238
337
  /*
239
338
  * Check whether a char is a Markdown space.
240
339
 
@@ -691,6 +790,48 @@ char_codespan(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t of
691
790
  return end;
692
791
  }
693
792
 
793
+ /* char_quote • '"' parsing a quote */
794
+ static size_t
795
+ char_quote(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size)
796
+ {
797
+ size_t end, nq = 0, i, f_begin, f_end;
798
+
799
+ /* counting the number of quotes in the delimiter */
800
+ while (nq < size && data[nq] == '"')
801
+ nq++;
802
+
803
+ /* finding the next delimiter */
804
+ i = 0;
805
+ for (end = nq; end < size && i < nq; end++) {
806
+ if (data[end] == '"') i++;
807
+ else i = 0;
808
+ }
809
+
810
+ if (i < nq && end >= size)
811
+ return 0; /* no matching delimiter */
812
+
813
+ /* trimming outside whitespaces */
814
+ f_begin = nq;
815
+ while (f_begin < end && data[f_begin] == ' ')
816
+ f_begin++;
817
+
818
+ f_end = end - nq;
819
+ while (f_end > nq && data[f_end-1] == ' ')
820
+ f_end--;
821
+
822
+ /* real quote */
823
+ if (f_begin < f_end) {
824
+ struct buf work = { data + f_begin, f_end - f_begin, 0, 0 };
825
+ if (!rndr->cb.quote(ob, &work, rndr->opaque))
826
+ end = 0;
827
+ } else {
828
+ if (!rndr->cb.quote(ob, 0, rndr->opaque))
829
+ end = 0;
830
+ }
831
+
832
+ return end;
833
+ }
834
+
694
835
 
695
836
  /* char_escape • '\\' backslash escape */
696
837
  static size_t
@@ -834,7 +975,7 @@ char_autolink_url(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_
834
975
 
835
976
  link = rndr_newbuf(rndr, BUFFER_SPAN);
836
977
 
837
- if ((link_len = sd_autolink__url(&rewind, link, data, offset, size, 0)) > 0) {
978
+ if ((link_len = sd_autolink__url(&rewind, link, data, offset, size, SD_AUTOLINK_SHORT_DOMAINS)) > 0) {
838
979
  ob->size -= rewind;
839
980
  rndr->cb.autolink(ob, link, MKDA_NORMAL, rndr->opaque);
840
981
  }
@@ -885,6 +1026,34 @@ char_link(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset
885
1026
  txt_e = i;
886
1027
  i++;
887
1028
 
1029
+ /* footnote link */
1030
+ if (rndr->ext_flags & MKDEXT_FOOTNOTES && data[1] == '^') {
1031
+ if (txt_e < 3)
1032
+ goto cleanup;
1033
+
1034
+ struct buf id = { 0, 0, 0, 0 };
1035
+ struct footnote_ref *fr;
1036
+
1037
+ id.data = data + 2;
1038
+ id.size = txt_e - 2;
1039
+
1040
+ fr = find_footnote_ref(&rndr->footnotes_found, id.data, id.size);
1041
+
1042
+ /* mark footnote used */
1043
+ if (fr && !fr->is_used) {
1044
+ if(!add_footnote_ref(&rndr->footnotes_used, fr))
1045
+ goto cleanup;
1046
+ fr->is_used = 1;
1047
+ fr->num = rndr->footnotes_used.count;
1048
+ }
1049
+
1050
+ /* render */
1051
+ if (fr && rndr->cb.footnote_ref)
1052
+ ret = rndr->cb.footnote_ref(ob, fr->num, rndr->opaque);
1053
+
1054
+ goto cleanup;
1055
+ }
1056
+
888
1057
  /* skip any amount of whitespace or newline */
889
1058
  /* (this is much more laxist than original markdown syntax) */
890
1059
  while (i < size && _isspace(data[i]))
@@ -1131,7 +1300,7 @@ char_superscript(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t
1131
1300
 
1132
1301
  /* is_empty • returns the line length when it is empty, 0 otherwise */
1133
1302
  static size_t
1134
- is_empty(uint8_t *data, size_t size)
1303
+ is_empty(const uint8_t *data, size_t size)
1135
1304
  {
1136
1305
  size_t i;
1137
1306
 
@@ -1475,7 +1644,7 @@ parse_paragraph(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t
1475
1644
  * let's check to see if there's some kind of block starting
1476
1645
  * here
1477
1646
  */
1478
- if ((rndr->ext_flags & MKDEXT_LAX_SPACING) && !isalnum(data[i])) {
1647
+ if ((rndr->ext_flags & MKDEXT_LAX_SPACING) && !isalpha(data[i])) {
1479
1648
  if (prefix_oli(data + i, size - i) ||
1480
1649
  prefix_uli(data + i, size - i)) {
1481
1650
  end = i;
@@ -1543,7 +1712,7 @@ parse_paragraph(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t
1543
1712
  parse_inline(header_work, rndr, work.data, work.size);
1544
1713
 
1545
1714
  if (rndr->cb.header)
1546
- rndr->cb.header(ob, header_work, (int)level, rndr->opaque);
1715
+ rndr->cb.header(ob, header_work, (int)level, header_anchor(header_work), rndr->opaque);
1547
1716
 
1548
1717
  rndr_popbuf(rndr, BUFFER_SPAN);
1549
1718
  }
@@ -1724,10 +1893,8 @@ parse_listitem(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t s
1724
1893
  if (!sublist)
1725
1894
  sublist = work->size;
1726
1895
  }
1727
- /* joining only indented stuff after empty lines;
1728
- * note that now we only require 1 space of indentation
1729
- * to continue a list */
1730
- else if (in_empty && pre == 0) {
1896
+ /* joining only indented stuff after empty lines */
1897
+ else if (in_empty && i < 4 && data[beg] != '\t') {
1731
1898
  *flags |= MKD_LI_END;
1732
1899
  break;
1733
1900
  }
@@ -1825,7 +1992,7 @@ parse_atxheader(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t
1825
1992
  parse_inline(work, rndr, data + i, end - i);
1826
1993
 
1827
1994
  if (rndr->cb.header)
1828
- rndr->cb.header(ob, work, (int)level, rndr->opaque);
1995
+ rndr->cb.header(ob, work, (int)level, header_anchor(work), rndr->opaque);
1829
1996
 
1830
1997
  rndr_popbuf(rndr, BUFFER_SPAN);
1831
1998
  }
@@ -1833,6 +2000,44 @@ parse_atxheader(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t
1833
2000
  return skip;
1834
2001
  }
1835
2002
 
2003
+ /* parse_footnote_def • parse a single footnote definition */
2004
+ static void
2005
+ parse_footnote_def(struct buf *ob, struct sd_markdown *rndr, unsigned int num, uint8_t *data, size_t size)
2006
+ {
2007
+ struct buf *work = 0;
2008
+ work = rndr_newbuf(rndr, BUFFER_SPAN);
2009
+
2010
+ parse_block(work, rndr, data, size);
2011
+
2012
+ if (rndr->cb.footnote_def)
2013
+ rndr->cb.footnote_def(ob, work, num, rndr->opaque);
2014
+ rndr_popbuf(rndr, BUFFER_SPAN);
2015
+ }
2016
+
2017
+ /* parse_footnote_list • render the contents of the footnotes */
2018
+ static void
2019
+ parse_footnote_list(struct buf *ob, struct sd_markdown *rndr, struct footnote_list *footnotes)
2020
+ {
2021
+ struct buf *work = 0;
2022
+ struct footnote_item *item;
2023
+ struct footnote_ref *ref;
2024
+
2025
+ if (footnotes->count == 0)
2026
+ return;
2027
+
2028
+ work = rndr_newbuf(rndr, BUFFER_BLOCK);
2029
+
2030
+ item = footnotes->head;
2031
+ while (item) {
2032
+ ref = item->ref;
2033
+ parse_footnote_def(work, rndr, ref->num, ref->contents->data, ref->contents->size);
2034
+ item = item->next;
2035
+ }
2036
+
2037
+ if (rndr->cb.footnotes)
2038
+ rndr->cb.footnotes(ob, work, rndr->opaque);
2039
+ rndr_popbuf(rndr, BUFFER_BLOCK);
2040
+ }
1836
2041
 
1837
2042
  /* htmlblock_end • checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
1838
2043
  /* returns the length on match, 0 otherwise */
@@ -2264,6 +2469,111 @@ parse_block(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size
2264
2469
  * REFERENCE PARSING *
2265
2470
  *********************/
2266
2471
 
2472
+ /* is_footnote • returns whether a line is a footnote definition or not */
2473
+ static int
2474
+ is_footnote(const uint8_t *data, size_t beg, size_t end, size_t *last, struct footnote_list *list)
2475
+ {
2476
+ size_t i = 0;
2477
+ struct buf *contents = 0;
2478
+ size_t ind = 0;
2479
+ int in_empty = 0;
2480
+ size_t start = 0;
2481
+
2482
+ size_t id_offset, id_end;
2483
+
2484
+ /* up to 3 optional leading spaces */
2485
+ if (beg + 3 >= end) return 0;
2486
+ if (data[beg] == ' ') { i = 1;
2487
+ if (data[beg + 1] == ' ') { i = 2;
2488
+ if (data[beg + 2] == ' ') { i = 3;
2489
+ if (data[beg + 3] == ' ') return 0; } } }
2490
+ i += beg;
2491
+
2492
+ /* id part: caret followed by anything between brackets */
2493
+ if (data[i] != '[') return 0;
2494
+ i++;
2495
+ if (i >= end || data[i] != '^') return 0;
2496
+ i++;
2497
+ id_offset = i;
2498
+ while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']')
2499
+ i++;
2500
+ if (i >= end || data[i] != ']') return 0;
2501
+ id_end = i;
2502
+
2503
+ /* spacer: colon (space | tab)* newline? (space | tab)* */
2504
+ i++;
2505
+ if (i >= end || data[i] != ':') return 0;
2506
+ i++;
2507
+
2508
+ /* getting content buffer */
2509
+ contents = bufnew(64);
2510
+
2511
+ start = i;
2512
+
2513
+ /* process lines similiar to a list item */
2514
+ while (i < end) {
2515
+ while (i < end && data[i] != '\n' && data[i] != '\r') i++;
2516
+
2517
+ /* process an empty line */
2518
+ if (is_empty(data + start, i - start)) {
2519
+ in_empty = 1;
2520
+ if (i < end && (data[i] == '\n' || data[i] == '\r')) {
2521
+ i++;
2522
+ if (i < end && data[i] == '\n' && data[i - 1] == '\r') i++;
2523
+ }
2524
+ start = i;
2525
+ continue;
2526
+ }
2527
+
2528
+ /* calculating the indentation */
2529
+ ind = 0;
2530
+ while (ind < 4 && start + ind < end && data[start + ind] == ' ')
2531
+ ind++;
2532
+
2533
+ /* joining only indented stuff after empty lines;
2534
+ * note that now we only require 1 space of indentation
2535
+ * to continue, just like lists */
2536
+ if (ind == 0) {
2537
+ if (start == id_end + 2 && data[start] == '\t') {}
2538
+ else break;
2539
+ }
2540
+ else if (in_empty) {
2541
+ bufputc(contents, '\n');
2542
+ }
2543
+
2544
+ in_empty = 0;
2545
+
2546
+ /* adding the line into the content buffer */
2547
+ bufput(contents, data + start + ind, i - start - ind);
2548
+ /* add carriage return */
2549
+ if (i < end) {
2550
+ bufput(contents, "\n", 1);
2551
+ if (i < end && (data[i] == '\n' || data[i] == '\r')) {
2552
+ i++;
2553
+ if (i < end && data[i] == '\n' && data[i - 1] == '\r') i++;
2554
+ }
2555
+ }
2556
+ start = i;
2557
+ }
2558
+
2559
+ if (last)
2560
+ *last = start;
2561
+
2562
+ if (list) {
2563
+ struct footnote_ref *ref;
2564
+ ref = create_footnote_ref(list, data + id_offset, id_end - id_offset);
2565
+ if (!ref)
2566
+ return 0;
2567
+ if (!add_footnote_ref(list, ref)) {
2568
+ free_footnote_ref(ref);
2569
+ return 0;
2570
+ }
2571
+ ref->contents = contents;
2572
+ }
2573
+
2574
+ return 1;
2575
+ }
2576
+
2267
2577
  /* is_ref • returns whether a line is a reference or not */
2268
2578
  static int
2269
2579
  is_ref(const uint8_t *data, size_t beg, size_t end, size_t *last, struct link_ref **refs)
@@ -2296,11 +2606,11 @@ is_ref(const uint8_t *data, size_t beg, size_t end, size_t *last, struct link_re
2296
2606
  i++;
2297
2607
  if (i >= end || data[i] != ':') return 0;
2298
2608
  i++;
2299
- while (i < end && data[i] == ' ') i++;
2609
+ while (i < end && strchr("\t ", data[i])) i++;
2300
2610
  if (i < end && (data[i] == '\n' || data[i] == '\r')) {
2301
2611
  i++;
2302
2612
  if (i < end && data[i] == '\r' && data[i - 1] == '\n') i++; }
2303
- while (i < end && data[i] == ' ') i++;
2613
+ while (i < end && strchr("\t ", data[i])) i++;
2304
2614
  if (i >= end) return 0;
2305
2615
 
2306
2616
  /* link: whitespace-free sequence, optionally between angle brackets */
@@ -2316,7 +2626,7 @@ is_ref(const uint8_t *data, size_t beg, size_t end, size_t *last, struct link_re
2316
2626
  else link_end = i;
2317
2627
 
2318
2628
  /* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */
2319
- while (i < end && data[i] == ' ') i++;
2629
+ while (i < end && strchr("\t ", data[i])) i++;
2320
2630
  if (i < end && data[i] != '\n' && data[i] != '\r'
2321
2631
  && data[i] != '\'' && data[i] != '"' && data[i] != '(')
2322
2632
  return 0;
@@ -2329,7 +2639,7 @@ is_ref(const uint8_t *data, size_t beg, size_t end, size_t *last, struct link_re
2329
2639
  /* optional (space|tab)* spacer after a newline */
2330
2640
  if (line_end) {
2331
2641
  i = line_end + 1;
2332
- while (i < end && data[i] == ' ') i++; }
2642
+ while (i < end && strchr("\t ", data[i])) i++; }
2333
2643
 
2334
2644
  /* optional title: any non-newline sequence enclosed in '"()
2335
2645
  alone on its line */
@@ -2460,6 +2770,9 @@ sd_markdown_new(
2460
2770
  if (extensions & MKDEXT_SUPERSCRIPT)
2461
2771
  md->active_char['^'] = MD_CHAR_SUPERSCRIPT;
2462
2772
 
2773
+ if (extensions & MKDEXT_QUOTE)
2774
+ md->active_char['"'] = MD_CHAR_QUOTE;
2775
+
2463
2776
  /* Extension data */
2464
2777
  md->ext_flags = extensions;
2465
2778
  md->opaque = opaque;
@@ -2488,6 +2801,14 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
2488
2801
  /* reset the references table */
2489
2802
  memset(&md->refs, 0x0, REF_TABLE_SIZE * sizeof(void *));
2490
2803
 
2804
+ int footnotes_enabled = md->ext_flags & MKDEXT_FOOTNOTES;
2805
+
2806
+ /* reset the footnotes lists */
2807
+ if (footnotes_enabled) {
2808
+ memset(&md->footnotes_found, 0x0, sizeof(md->footnotes_found));
2809
+ memset(&md->footnotes_used, 0x0, sizeof(md->footnotes_used));
2810
+ }
2811
+
2491
2812
  /* first pass: looking for references, copying everything else */
2492
2813
  beg = 0;
2493
2814
 
@@ -2497,7 +2818,9 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
2497
2818
  beg += 3;
2498
2819
 
2499
2820
  while (beg < doc_size) /* iterating over lines */
2500
- if (is_ref(document, beg, doc_size, &end, md->refs))
2821
+ if (footnotes_enabled && is_footnote(document, beg, doc_size, &end, &md->footnotes_found))
2822
+ beg = end;
2823
+ else if (is_ref(document, beg, doc_size, &end, md->refs))
2501
2824
  beg = end;
2502
2825
  else { /* skipping to the next line */
2503
2826
  end = beg;
@@ -2533,12 +2856,20 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
2533
2856
  parse_block(ob, md, text->data, text->size);
2534
2857
  }
2535
2858
 
2859
+ /* footnotes */
2860
+ if (footnotes_enabled)
2861
+ parse_footnote_list(ob, md, &md->footnotes_used);
2862
+
2536
2863
  if (md->cb.doc_footer)
2537
2864
  md->cb.doc_footer(ob, md->opaque);
2538
2865
 
2539
2866
  /* clean-up */
2540
2867
  bufrelease(text);
2541
2868
  free_link_refs(md->refs);
2869
+ if (footnotes_enabled) {
2870
+ free_footnote_list(&md->footnotes_found, 1);
2871
+ free_footnote_list(&md->footnotes_used, 0);
2872
+ }
2542
2873
 
2543
2874
  assert(md->work_bufs[BUFFER_SPAN].size == 0);
2544
2875
  assert(md->work_bufs[BUFFER_BLOCK].size == 0);
@@ -2560,11 +2891,3 @@ sd_markdown_free(struct sd_markdown *md)
2560
2891
 
2561
2892
  free(md);
2562
2893
  }
2563
-
2564
- void
2565
- sd_version(int *ver_major, int *ver_minor, int *ver_revision)
2566
- {
2567
- *ver_major = SUNDOWN_VER_MAJOR;
2568
- *ver_minor = SUNDOWN_VER_MINOR;
2569
- *ver_revision = SUNDOWN_VER_REVISION;
2570
- }