Fossil

Check-in [1787f6df]
Login

Check-in [1787f6df]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Handle misreferences more thoroughly. Implement support of footnotes-within-footnotes with (hopefully) proper crosslinking (that's where it's getting tricky).
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | markdown-footnotes
Files: files | file ages | folders
SHA3-256: 1787f6df11bbbe4fd84a1fb5c219244dfb31bd3dd81ac99fd9941efdd429759d
User & Date: george 2022-02-06 22:53:22
Context
2022-02-08
13:39
Add const qualifier to the arguments of the blob_compare() function. ... (check-in: 2822b63b user: george tags: markdown-footnotes)
2022-02-06
22:53
Handle misreferences more thoroughly. Implement support of footnotes-within-footnotes with (hopefully) proper crosslinking (that's where it's getting tricky). ... (check-in: 1787f6df user: george tags: markdown-footnotes)
2022-02-04
23:07
Handle misreferences: a reference to undefined footnote. ... (check-in: 28e6a9cd user: george tags: markdown-footnotes)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/default.css.

1668
1669
1670
1671
1672
1673
1674










1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686

1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
  font-family: monospace;
}
div.content  div.markdown > ol.footnotes {
  font-size: 90%;
}
div.content  div.markdown > ol.footnotes > li {
  margin-bottom: 0.5em;










}
div.content  div.markdown > ol.footnotes > li > .footnote-backrefs {
  margin-right: 0.5em;
  font-weight: bold;
}
div.markdown > ol.footnotes > li > .footnote-backrefs > a:target {
  background: gold;
}
div.markdown sup > a.noteref:target {
  background: gold;
}
div.markdown sup.misref {

  color: red;
  font-size: 90%;
}
div.markdown span.notescope:hover,
div.markdown span.notescope:target {
  border-bottom: 2px solid gold;
}
div.markdown span.notescope:hover  > sup > a.noteref,
div.markdown span.notescope:target > sup > a.noteref {
  background: gold;
}

/* Objects in the "desktoponly" class are invisible on mobile */
@media screen and (max-width: 600px) {
  .desktoponly {
    display: none;







>
>
>
>
>
>
>
>
>
>








|


|
>







|
|







1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
  font-family: monospace;
}
div.content  div.markdown > ol.footnotes {
  font-size: 90%;
}
div.content  div.markdown > ol.footnotes > li {
  margin-bottom: 0.5em;
}
div.markdown ol.footnotes > li.unreferenced-footnote,
div.markdown ol.footnotes > li.misreferences {
  background: #ffdddd;
}
div.markdown ol.footnotes > li.unreferenced-footnote:first-child,
div.markdown ol.footnotes > li.misreferences {
  margin-top:     0.75em;
  padding-top:    0.25em;
  padding-bottom: 0.25em;
}
div.content  div.markdown > ol.footnotes > li > .footnote-backrefs {
  margin-right: 0.5em;
  font-weight: bold;
}
div.markdown > ol.footnotes > li > .footnote-backrefs > a:target {
  background: gold;
}
div.markdown sup.noteref > a:target {
  background: gold;
}
div.markdown sup.noteref.misref,
div.markdown sup.noteref.misref > a {
  color: red;
  font-size: 90%;
}
div.markdown span.notescope:hover,
div.markdown span.notescope:target {
  border-bottom: 2px solid gold;
}
div.markdown span.notescope:hover  > sup.noteref > a,
div.markdown span.notescope:target > sup.noteref > a {
  background: gold;
}

/* Objects in the "desktoponly" class are invisible on mobile */
@media screen and (max-width: 600px) {
  .desktoponly {
    display: none;

Changes to src/markdown.c.

125
126
127
128
129
130
131
132
133

134
135
136
137
138
139
140
141
142
143
144
145
146
147
148

149



150
151
152
153
154
155
156
157
  struct Blob *ob,
  const struct Blob *ib,
  const struct mkd_renderer *rndr);


#endif /* INTERFACE */

#define BLOB_COUNT(blob_ptr,el_type) (blob_size(blob_ptr)/sizeof(el_type))
#define COUNT_FOOTNOTES(blob_ptr) BLOB_COUNT(blob_ptr,struct footnote)


/***************
 * LOCAL TYPES *
 ***************/

/* link_ref -- reference to a link */
struct link_ref {
  struct Blob id;     /* must be the first field as in footnote struct */
  struct Blob link;
  struct Blob title;
};

struct footnote {
  struct Blob id;   /* must be the first field as in link_ref struct  */
  struct Blob text; /* footnote's content that is rendered at the end */

  int    index;     /* serial number, in the order of appearance */



  int    nUsed;     /* number of references to this note */
};


/* 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 */
/*   offset is the number of valid chars before data */







|
|
>













|
|
>
|
>
>
>
|







125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
  struct Blob *ob,
  const struct Blob *ib,
  const struct mkd_renderer *rndr);


#endif /* INTERFACE */

#define BLOB_COUNT(pBlob,el_type) (blob_size(pBlob)/sizeof(el_type))
#define COUNT_FOOTNOTES(pBlob) BLOB_COUNT(pBlob,struct footnote)
#define CAST_AS_FOOTNOTES(pBlob) ((struct footnote*)blob_buffer(pBlob))

/***************
 * LOCAL TYPES *
 ***************/

/* link_ref -- reference to a link */
struct link_ref {
  struct Blob id;     /* must be the first field as in footnote struct */
  struct Blob link;
  struct Blob title;
};

struct footnote {
  struct Blob id;      /* must be the first field as in link_ref struct  */
  struct Blob text;    /* footnote's content that is rendered at the end */
  int bRndred;         /* indicates if `text` holds a rendered content   */

  int defno;  /* serial number of definition, set during the first pass  */
  int index;  /* set to the index within array after ordering by id      */
  int iMark;  /* user-visible numeric marker, assigned upon the first use*/
  int nUsed;  /* counts references to this note, increments upon each use*/
};


/* 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 */
/*   offset is the number of valid chars before data */
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
  int iDepth;                    /* Depth of recursion */
  int nBlobCache;                /* Number of entries in aBlobCache */
  struct Blob *aBlobCache[20];   /* Cache of Blobs available for reuse */

  struct {
    Blob all;    /* array of footnotes */
    int nLbled;  /* number of labeled footnotes found during the first pass */
    int nMarks;  /* counts distinct indices found during the second pass */
    struct footnote missing; /* a dummy footnote, its negative index
                                counts missing refs */
  } notes;
};

/* html_tag -- structure for quick HTML tag search (inspired from discount) */
struct html_tag {
  const char *text;
  int size;







|
|
<







177
178
179
180
181
182
183
184
185

186
187
188
189
190
191
192
  int iDepth;                    /* Depth of recursion */
  int nBlobCache;                /* Number of entries in aBlobCache */
  struct Blob *aBlobCache[20];   /* Cache of Blobs available for reuse */

  struct {
    Blob all;    /* array of footnotes */
    int nLbled;  /* number of labeled footnotes found during the first pass */
    int nMarks;  /* counts distinct indices found during the second pass    */
    struct footnote misref; /* nUsed counts misreferences, index must be -1 */

  } notes;
};

/* html_tag -- structure for quick HTML tag search (inspired from discount) */
struct html_tag {
  const char *text;
  int size;
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
** in any way.
*/
static const struct html_tag block_tags[] = {
  { "html",         4 },
  { "pre",          3 },
  { "script",       6 },
};


/***************************
 * STATIC HELPER FUNCTIONS *
 ***************************/

/* build_ref_id -- collapse whitespace from input text to make it a ref id */
/* FIXME: does this function handle non-Unix newlines? */







<







205
206
207
208
209
210
211

212
213
214
215
216
217
218
** in any way.
*/
static const struct html_tag block_tags[] = {
  { "html",         4 },
  { "pre",          3 },
  { "script",       6 },
};


/***************************
 * STATIC HELPER FUNCTIONS *
 ***************************/

/* build_ref_id -- collapse whitespace from input text to make it a ref id */
/* FIXME: does this function handle non-Unix newlines? */
268
269
270
271
272
273
274
275

276
277

278





279




280



281

282
283
284
285
286
287
288
289
static int cmp_link_ref_sort(const void *a, const void *b){
  struct link_ref *lra = (void *)a;
  struct link_ref *lrb = (void *)b;
  return blob_compare(&lra->id, &lrb->id);
}

/* cmp_footnote_sort -- comparison function for footnotes qsort.
 * Unused footnotes (when index == 0) sort last */

static int cmp_footnote_sort(const void *a, const void *b){
  const struct footnote *fna = (void *)a, *fnb = (void *)b;

  assert( fna->index >= 0 && fnb->index >= 0 );





  if( fna->index == fnb->index ) return 0;




  if( fna->index == 0 ) return  1;



  if( fnb->index == 0 ) return -1;

  return ( fna->index < fnb->index ? -1 : 1 );
}

/* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
static int cmp_html_tag(const void *a, const void *b){
  const struct html_tag *hta = a;
  const struct html_tag *htb = b;
  int sz = hta->size;







|
>
|
|
>
|
>
>
>
>
>
|
>
>
>
>
|
>
>
>
|
>
|







271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
static int cmp_link_ref_sort(const void *a, const void *b){
  struct link_ref *lra = (void *)a;
  struct link_ref *lrb = (void *)b;
  return blob_compare(&lra->id, &lrb->id);
}

/* cmp_footnote_sort -- comparison function for footnotes qsort.
 * Unreferenced footnotes (when nUsed == 0) sort last and
 * are sorted in the order of definition in the source */
static int cmp_footnote_sort(const void *fna, const void *fnb){
  const struct footnote *a = fna, *b = fnb;
  int i, j;
  assert( a->nUsed >= 0 );
  assert( b->nUsed >= 0 );
  assert( a->defno >= 0 );
  assert( b->defno >= 0 );
  if( a->nUsed ){
    assert( a->iMark >  0 );
    if( !b->nUsed ) return -1;
    assert( b->iMark >  0 );
    i = a->iMark;
    j = b->iMark;
  }else{
    if( b->nUsed )  return  1;
    i = a->defno;
    j = b->defno;
  }
  if( i < j ) return -1;
  if( i > j ) return  1;
  return 0;
}

/* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
static int cmp_html_tag(const void *a, const void *b){
  const struct html_tag *hta = a;
  const struct html_tag *htb = b;
  int sz = hta->size;
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080

1081
1082
1083
1084
1085
1086
1087
1088
  if( build_ref_id(id, data, size)<0 ) goto cleanup;
  fn = bsearch(id, blob_buffer(&rndr->notes.all),
               rndr->notes.nLbled,
               sizeof (struct footnote),
               cmp_link_ref);
  if( !fn ) goto cleanup;

  if( fn->index == 0 ){  /* the first reference to the footnote */
    assert( fn->nUsed == 0 );
    fn->index = ++(rndr->notes.nMarks);
  }
  fn->nUsed++;
  assert( fn->index > 0 );
  assert( fn->nUsed > 0 );
cleanup:
  release_work_buffer( rndr, id );
  return fn;
}
/* Adds unlabeled footnote to the rndr.
 * If text is blank then returns 0,
 * otherwise returns the address of the added footnote. */
static inline const struct footnote* add_inline_footnote(
  struct render *rndr,
  const char *text,
  size_t size
){
  struct footnote fn = { empty_blob, empty_blob, 0, 0 };
  while(size && (*text==' ' || *text=='\t')){ text++; size--; }
  if(!size) return 0;
  fn.index = ++(rndr->notes.nMarks);
  fn.nUsed = 1;

  assert( fn.index > 0 );
  blob_append(&fn.text, text, size);
  blob_append(&rndr->notes.all, (char *)&fn, sizeof fn);
  return (struct footnote*)( blob_buffer(&rndr->notes.all)
                            +( blob_size(&rndr->notes.all)-sizeof fn ));
}

/* Return the offset of the matching closing bracket or 0 if not found.







|
|
|


|













|


|
|
>
|







1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
  if( build_ref_id(id, data, size)<0 ) goto cleanup;
  fn = bsearch(id, blob_buffer(&rndr->notes.all),
               rndr->notes.nLbled,
               sizeof (struct footnote),
               cmp_link_ref);
  if( !fn ) goto cleanup;

  if( fn->nUsed == 0 ){  /* the first reference to the footnote */
    assert( fn->iMark == 0 );
    fn->iMark = ++(rndr->notes.nMarks);
  }
  fn->nUsed++;
  assert( fn->iMark > 0 );
  assert( fn->nUsed > 0 );
cleanup:
  release_work_buffer( rndr, id );
  return fn;
}
/* Adds unlabeled footnote to the rndr.
 * If text is blank then returns 0,
 * otherwise returns the address of the added footnote. */
static inline const struct footnote* add_inline_footnote(
  struct render *rndr,
  const char *text,
  size_t size
){
  struct footnote fn = { empty_blob, empty_blob, 0, 0, 0, 0, 0 };
  while(size && (*text==' ' || *text=='\t')){ text++; size--; }
  if(!size) return 0;
  fn.iMark = ++(rndr->notes.nMarks);
  fn.nUsed  = 1;
  fn.index  = COUNT_FOOTNOTES(&rndr->notes.all);
  assert( fn.iMark > 0 );
  blob_append(&fn.text, text, size);
  blob_append(&rndr->notes.all, (char *)&fn, sizeof fn);
  return (struct footnote*)( blob_buffer(&rndr->notes.all)
                            +( blob_size(&rndr->notes.all)-sizeof fn ));
}

/* Return the offset of the matching closing bracket or 0 if not found.
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
  size_t end;
  const struct footnote* fn;

  if( size<4 || data[1]!='^' || !rndr->make.footnote_ref ) return 0;
  end = matching_bracket_offset(data, data+size);
  if( !end ) return 0;
  fn = add_inline_footnote(rndr, data+2, end-2);
  if(fn) rndr->make.footnote_ref(ob,0,fn->index,1,rndr->make.opaque);
  return end+1;
}

/* char_link -- '[': parsing a link or an image */
static size_t char_link(
  struct Blob *ob,
  struct render *rndr,







|







1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
  size_t end;
  const struct footnote* fn;

  if( size<4 || data[1]!='^' || !rndr->make.footnote_ref ) return 0;
  end = matching_bracket_offset(data, data+size);
  if( !end ) return 0;
  fn = add_inline_footnote(rndr, data+2, end-2);
  if(fn) rndr->make.footnote_ref(ob,0,fn->iMark,1,rndr->make.opaque);
  return end+1;
}

/* char_link -- '[': parsing a link or an image */
static size_t char_link(
  struct Blob *ob,
  struct render *rndr,
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
        id_size--;
      }
    }

    if( bFootnote ){
      fn = get_footnote(rndr, id_data, id_size);
      if( !fn ) {
        rndr->notes.missing.index--;
        fn = &rndr->notes.missing;
      }
    }else if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
      goto char_link_cleanup;
    }

    i = id_end+1;

  /* shortcut reference style link or free-standing footnote refernece */
  }else{
    if(!is_img && size>3 && data[1]=='^'){
      /* free-standing footnote reference */
      fn = get_footnote(rndr, data+2, txt_e-2);
      if( !fn ) {
        rndr->notes.missing.index--;
        fn = &rndr->notes.missing;
      }
      release_work_buffer(rndr, content);
      content = 0; 
    }else if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
      goto char_link_cleanup;
    }








|
|













|
|







1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
        id_size--;
      }
    }

    if( bFootnote ){
      fn = get_footnote(rndr, id_data, id_size);
      if( !fn ) {
        rndr->notes.misref.nUsed++;
        fn = &rndr->notes.misref;
      }
    }else if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
      goto char_link_cleanup;
    }

    i = id_end+1;

  /* shortcut reference style link or free-standing footnote refernece */
  }else{
    if(!is_img && size>3 && data[1]=='^'){
      /* free-standing footnote reference */
      fn = get_footnote(rndr, data+2, txt_e-2);
      if( !fn ) {
        rndr->notes.misref.nUsed++;
        fn = &rndr->notes.misref;
      }
      release_work_buffer(rndr, content);
      content = 0; 
    }else if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
      goto char_link_cleanup;
    }

1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277

  /* calling the relevant rendering function */
  if( is_img ){
    if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
    ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
  }else if(fn){
    if(rndr->make.footnote_ref){
      ret = rndr->make.footnote_ref(ob, content, fn->index, fn->nUsed,
                                    rndr->make.opaque);
    }
  }else{
    ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
  }

  /* cleanup */







|







1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296

  /* calling the relevant rendering function */
  if( is_img ){
    if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
    ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
  }else if(fn){
    if(rndr->make.footnote_ref){
      ret = rndr->make.footnote_ref(ob, content, fn->iMark, fn->nUsed,
                                    rndr->make.opaque);
    }
  }else{
    ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
  }

  /* cleanup */
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
  const char *data,   /* input text */
  size_t beg,         /* offset of the beginning of the line */
  size_t end,         /* offset of the end of the text */
  size_t *last,       /* last character of the link */
  struct Blob * footnotes
){
  size_t i, id_offset, id_end;
  struct footnote fn = { empty_blob, empty_blob, 0, 0 };

  /* failfast if data is too short */
  if( beg+5>=end ) return 0;
  i = beg;

  /* footnote definition must start at the begining of a line */
  if( data[i]!='[' ) return 0;







|







2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
  const char *data,   /* input text */
  size_t beg,         /* offset of the beginning of the line */
  size_t end,         /* offset of the end of the text */
  size_t *last,       /* last character of the link */
  struct Blob * footnotes
){
  size_t i, id_offset, id_end;
  struct footnote fn = { empty_blob, empty_blob, 0, 0, 0, 0, 0 };

  /* failfast if data is too short */
  if( beg+5>=end ) return 0;
  i = beg;

  /* footnote definition must start at the begining of a line */
  if( data[i]!='[' ) return 0;
2436
2437
2438
2439
2440
2441
2442


2443

2444
2445
2446
2447
2448
2449
2450
footnote_finish:
  if( !blob_size(&fn.text) ){
    blob_reset(&fn.id);
    return 0;
  }
  /* a valid note has been found */
  if( last ) *last = i;


  if( footnotes ) blob_append(footnotes, (char *)&fn, sizeof fn);

  return 1;
}

/**********************
 * EXPORTED FUNCTIONS *
 **********************/








>
>
|
>







2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
footnote_finish:
  if( !blob_size(&fn.text) ){
    blob_reset(&fn.id);
    return 0;
  }
  /* a valid note has been found */
  if( last ) *last = i;
  if( footnotes ){
    fn.defno = COUNT_FOOTNOTES( footnotes );
    blob_append(footnotes, (char *)&fn, sizeof fn);
  }
  return 1;
}

/**********************
 * EXPORTED FUNCTIONS *
 **********************/

2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
  if( !rndrer ) return;
  rndr.make = *rndrer;
  rndr.nBlobCache = 0;
  rndr.iDepth = 0;
  rndr.refs  = empty_blob;
  rndr.notes.all = empty_blob;
  rndr.notes.nMarks = 0;
  rndr.notes.missing.id    = empty_blob;
  rndr.notes.missing.text  = empty_blob;
  rndr.notes.missing.index = 0;
  rndr.notes.missing.nUsed = 0;

  for(i=0; i<256; i++) rndr.active_char[i] = 0;
  if( (rndr.make.emphasis
    || rndr.make.double_emphasis
    || rndr.make.triple_emphasis)
   && rndr.make.emph_chars
  ){







|
|
|
|







2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
  if( !rndrer ) return;
  rndr.make = *rndrer;
  rndr.nBlobCache = 0;
  rndr.iDepth = 0;
  rndr.refs  = empty_blob;
  rndr.notes.all = empty_blob;
  rndr.notes.nMarks = 0;
  rndr.notes.misref.id    = empty_blob;
  rndr.notes.misref.text  = empty_blob;
  rndr.notes.misref.nUsed =  0;
  rndr.notes.misref.iMark = -1;

  for(i=0; i<256; i++) rndr.active_char[i] = 0;
  if( (rndr.make.emphasis
    || rndr.make.double_emphasis
    || rndr.make.triple_emphasis)
   && rndr.make.emph_chars
  ){
2525
2526
2527
2528
2529
2530
2531

2532
2533




2534
2535
2536
2537
2538
2539
2540
2541

2542
2543












2544
2545


2546
2547


2548




2549


2550

2551




2552











2553

2554


2555
2556

2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571

2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
          blob_size(&rndr.refs)/sizeof(struct link_ref),
          sizeof(struct link_ref),
          cmp_link_ref_sort);
  }
  rndr.notes.nLbled = COUNT_FOOTNOTES(&rndr.notes.all);
  /* sorting the footnotes array by id */
  if( rndr.notes.nLbled ){

    qsort(blob_buffer(&rndr.notes.all), rndr.notes.nLbled,
          sizeof(struct footnote), cmp_link_ref_sort);




  }

  /* second pass: actual rendering */
  if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
  parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));

  fn = (struct footnote*)blob_buffer(&rndr.notes.all);
  if(rndr.notes.nMarks && rndr.make.footnote_item && rndr.make.footnotes){

    Blob * one_item  = new_work_buffer(&rndr);
    Blob * all_items = new_work_buffer(&rndr);












    qsort( fn, COUNT_FOOTNOTES(&rndr.notes.all), sizeof(struct footnote),
           cmp_footnote_sort /* sort footnotes by index */ );


    blob_reset( all_items );
    for(i=0; i<rndr.notes.nMarks; i++){


      assert( fn[i].index == i+1 );







      blob_reset( one_item );

      parse_inline( one_item, &rndr, blob_buffer(&fn[i].text),




                    blob_size(&fn[i].text));











      rndr.make.footnote_item( all_items, one_item, i+1, fn[i].nUsed, rndr.make.opaque);

    }


    rndr.make.footnotes(ob, all_items, rndr.make.opaque );
    release_work_buffer( &rndr, one_item );

    release_work_buffer( &rndr, all_items );
  }
  if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);

  /* clean-up */
  assert( rndr.iDepth==0 );
  blob_reset(&text);
  lr = (struct link_ref *)blob_buffer(&rndr.refs);
  end = blob_size(&rndr.refs)/sizeof(struct link_ref);
  for(i=0; i<end; i++){
    blob_reset(&lr[i].id);
    blob_reset(&lr[i].link);
    blob_reset(&lr[i].title);
  }
  blob_reset(&rndr.refs);

  end = COUNT_FOOTNOTES(&rndr.notes.all);
  for(i=0; i<end; i++){
    if(blob_size(&fn[i].id)) blob_reset(&fn[i].id);
    blob_reset(&fn[i].text);
  }
  blob_reset(&rndr.notes.all);
  for(i=0; i<rndr.nBlobCache; i++){
    fossil_free(rndr.aBlobCache[i]);
  }
}







>
|
|
>
>
>
>






|
|
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
|
|
>
>
|
|
>
>
|
>
>
>
>

>
>
|
>
|
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
|
>
|
>
>
|
|
>
|














>










2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
          blob_size(&rndr.refs)/sizeof(struct link_ref),
          sizeof(struct link_ref),
          cmp_link_ref_sort);
  }
  rndr.notes.nLbled = COUNT_FOOTNOTES(&rndr.notes.all);
  /* sorting the footnotes array by id */
  if( rndr.notes.nLbled ){
    fn = CAST_AS_FOOTNOTES(&rndr.notes.all);
    qsort(fn, rndr.notes.nLbled, sizeof(struct footnote),
          cmp_link_ref_sort);
    for(i=0; i<rndr.notes.nLbled; i++){
      fn[i].index = i;
    }
    /* FIXME: handle footnotes with duplicated labels */
  }

  /* second pass: actual rendering */
  if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
  parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));

  if( (blob_size(&rndr.notes.all) || rndr.notes.misref.nUsed) ){

    /* Footnotes must be parsed for the correct discovery of (back)links */
    Blob *notes = new_work_buffer( &rndr );
    Blob *tmp   = new_work_buffer( &rndr );
    const struct Blob *origin = &rndr.notes.all;
    int nMarks = -1;

    /* inline notes may get appended to rndr.notes.all while rendering */
    while(1){
      struct footnote *aNotes;
      const int N = COUNT_FOOTNOTES(origin);

      /* make a shallow copy of `origin` */
      blob_truncate(notes,0);
      blob_append(notes, blob_buffer(origin), blob_size(origin));
      aNotes = CAST_AS_FOOTNOTES(notes);
      qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort);

      if( nMarks == rndr.notes.nMarks ) break;
      nMarks = rndr.notes.nMarks;

      for(i=0; i<N; i++){
        const int j = aNotes[i].index;
        struct footnote *x = CAST_AS_FOOTNOTES(origin) + j;
        assert( 0<=j && j<N );
        if( x->bRndred || !x->nUsed ) continue;
        assert( x->iMark > 0 );
        assert( blob_size(&x->text) );
        blob_truncate(tmp,0);

        /* `origin` may be altered and extended through this call */
        parse_inline(tmp, &rndr, blob_buffer(&x->text), blob_size(&x->text));

        blob_truncate(&x->text,0);
        blob_append(&x->text, blob_buffer(tmp), blob_size(tmp));
        x->bRndred = 1;
      }
    }
    release_work_buffer(&rndr,tmp);

    /* footnotes rendering */
    if( rndr.make.footnote_item && rndr.make.footnotes ){
      Blob *all_items = new_work_buffer(&rndr);
      for(i=0; i<COUNT_FOOTNOTES(notes); i++){
        const struct footnote* x = CAST_AS_FOOTNOTES(notes) + i;
        if( x->bRndred ){
          rndr.make.footnote_item(all_items, &x->text, x->iMark,
                                  x->nUsed, rndr.make.opaque);
        }
      }
      if( rndr.notes.misref.nUsed ){
        rndr.make.footnote_item(all_items, 0, -1,
                    rndr.notes.misref.nUsed, rndr.make.opaque);
      }
      /* TODO: handle unreferenced (defined but not used) footnotes */

      rndr.make.footnotes(ob, all_items, rndr.make.opaque);
      release_work_buffer(&rndr, all_items);
    }
    release_work_buffer(&rndr, notes);
  }
  if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);

  /* clean-up */
  assert( rndr.iDepth==0 );
  blob_reset(&text);
  lr = (struct link_ref *)blob_buffer(&rndr.refs);
  end = blob_size(&rndr.refs)/sizeof(struct link_ref);
  for(i=0; i<end; i++){
    blob_reset(&lr[i].id);
    blob_reset(&lr[i].link);
    blob_reset(&lr[i].title);
  }
  blob_reset(&rndr.refs);
  fn = CAST_AS_FOOTNOTES(&rndr.notes.all);
  end = COUNT_FOOTNOTES(&rndr.notes.all);
  for(i=0; i<end; i++){
    if(blob_size(&fn[i].id)) blob_reset(&fn[i].id);
    blob_reset(&fn[i].text);
  }
  blob_reset(&rndr.notes.all);
  for(i=0; i<rndr.nBlobCache; i++){
    fossil_free(rndr.aBlobCache[i]);
  }
}

Changes to src/markdown_html.c.

323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339



340

341
342
343
344
345
346
347
348
349
350
351
352
353




354
355
356
357
358
359
360

361
362
363

364
365

366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381


























382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409











410
411
412
413
414
415
416
){
  BLOB_APPEND_LITERAL(ob, "  <tr>\n");
  BLOB_APPEND_BLOB(ob, cells);
  BLOB_APPEND_LITERAL(ob, "  </tr>\n");
}

static int html_footnote_ref(
  struct Blob *ob, const struct Blob *span, int index, int locus, void *opaque
){
  const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
  /* expect BUGs if the following yields compiler warnings */

  if( index>0 && locus>0 ){
    const bitfield64_t l = to_base26(locus-1,0);
    char pos[32];
    memset(pos,0,32);
    sprintf(pos, "%s-%i-%s", ctx->unique.c, index, l.c);





    if(span && blob_size(span)) {
      BLOB_APPEND_LITERAL(ob,"<span class='notescope' id='noteref");
      blob_appendf(ob,"%s'>",pos);
      BLOB_APPEND_BLOB(ob, span);
      blob_trim(ob);
      BLOB_APPEND_LITERAL(ob,"<sup><a class='noteref' href='#footnote");
      blob_appendf(ob,"%s'>%i</a></sup></span>", pos, index);
    }else{
      blob_trim(ob);
      BLOB_APPEND_LITERAL(ob,"<sup><a class='noteref' href='#footnote");
      blob_appendf(ob,"%s' id='noteref%s'>%i</a></sup>",
                      pos,            pos,   index);
    }




  }else if(span && blob_size(span)) {
    BLOB_APPEND_LITERAL(ob, "<span class='notescope' id='misref");
    blob_appendf(ob, "%s-%i'>", ctx->unique.c, -index);
    BLOB_APPEND_BLOB(ob, span);
    blob_trim(ob);
    BLOB_APPEND_LITERAL(ob,
          "<sup class='misref'>misreference</sup></span>");

  }else{
    blob_trim(ob);
    BLOB_APPEND_LITERAL(ob, "<sup class='misref' id='misref");

    blob_appendf(ob, "%s-%i", ctx->unique.c, -index);
    BLOB_APPEND_LITERAL(ob, "'>misreference</sup>");

  }
  return 1;
}

/* Render a single item of the footnotes list.
 * Each backref gets a unique id to enable dynamic styling. */
static void html_footnote_item(
  struct Blob *ob, const struct Blob *text, int index, int nUsed, void *opaque
){
  const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
  char pos[24];
  if( index <= 0 || nUsed < 0 || !text || !blob_size(text) ){
    return;
  }

  /* expect BUGs if the following yields compiler warnings */


























  memset(pos,0,24);
  sprintf(pos, "%s-%i", ctx->unique.c, index);

  blob_appendf(ob, "<li id='footnote%s'>", pos);
  BLOB_APPEND_LITERAL(ob,"<sup class='footnote-backrefs'>");
  if( nUsed <= 1 ){
    blob_appendf(ob,"<a id='footnote%s-a' "
                     "href='#noteref%s-a'>^</a>", pos, pos);
  }else{
    int i;
    blob_append_char(ob, '^');
    for(i=0; i<nUsed && i<26; i++){
      const int c = i + (unsigned)'a';
      blob_appendf(ob," <a id='footnote%s-%c'"
                       " href='#noteref%s-%c'>%c</a>", pos,c, pos,c, c);
    }
    /* It's unlikely that so many backrefs will be usefull */
    /* but maybe for some machine generated documents... */
    for(; i<nUsed && i<676; i++){
      const bitfield64_t l = to_base26(i,0);
      blob_appendf(ob," <a id='footnote%s-%s'"
                       " href='#noteref%s-%s'>%s</a>",
                       pos,l.c, pos,l.c, l.c);
    }
    if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
  }
  BLOB_APPEND_LITERAL(ob,"</sup>\n");
  BLOB_APPEND_BLOB(ob, text);











  BLOB_APPEND_LITERAL(ob, "\n</li>\n");
}
static void html_footnotes(
  struct Blob *ob, const struct Blob *items, void *opaque
){
  if( items && blob_size(items) ){
    BLOB_APPEND_LITERAL(ob,







|

|
<
<
<
|
|
|
<
>
>
>

>





|
|


|

|

>
>
>
>
|
|
<
|
|
|
|
>
|
|
|
>
|
|
>







|

|
<
<
<
<
|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>







323
324
325
326
327
328
329
330
331
332



333
334
335

336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359

360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381




382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
){
  BLOB_APPEND_LITERAL(ob, "  <tr>\n");
  BLOB_APPEND_BLOB(ob, cells);
  BLOB_APPEND_LITERAL(ob, "  </tr>\n");
}

static int html_footnote_ref(
  struct Blob *ob, const struct Blob *span, int iMark, int locus, void *opaque
){
  const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque;



  const bitfield64_t l = to_base26(locus-1,0);
  char pos[32];
  memset(pos,0,32);

  assert( locus > 0 );
  /* expect BUGs if the following yields compiler warnings */
  if( iMark > 0 ){      /* a regular reference to a footnote */

    sprintf(pos, "%s-%i-%s", ctx->unique.c, iMark, l.c);
    if(span && blob_size(span)) {
      BLOB_APPEND_LITERAL(ob,"<span class='notescope' id='noteref");
      blob_appendf(ob,"%s'>",pos);
      BLOB_APPEND_BLOB(ob, span);
      blob_trim(ob);
      BLOB_APPEND_LITERAL(ob,"<sup class='noteref'><a href='#footnote");
      blob_appendf(ob,"%s'>%i</a></sup></span>", pos, iMark);
    }else{
      blob_trim(ob);
      BLOB_APPEND_LITERAL(ob,"<sup class='noteref'><a href='#footnote");
      blob_appendf(ob,"%s' id='noteref%s'>%i</a></sup>",
                      pos,           pos,  iMark);
    }
  }else{              /* misreference */
    assert( iMark == -1 );

    sprintf(pos, "%s-%s", ctx->unique.c, l.c);
    if(span && blob_size(span)) {
      blob_appendf(ob, "<span class='notescope' id='misref%s'>", pos);

      BLOB_APPEND_BLOB(ob, span);
      blob_trim(ob);
      BLOB_APPEND_LITERAL(ob,
        "<sup class='noteref misref'><a href='#misreference");
      blob_appendf(ob, "%s'>misref</a></sup></span>", pos);
    }else{
      blob_trim(ob);
      BLOB_APPEND_LITERAL(ob,
        "<sup class='noteref misref'><a href='#misreference");
      blob_appendf(ob, "%s' id='misref%s'>", pos, pos);
      BLOB_APPEND_LITERAL(ob, "misref</a></sup>");
    }
  }
  return 1;
}

/* Render a single item of the footnotes list.
 * Each backref gets a unique id to enable dynamic styling. */
static void html_footnote_item(
  struct Blob *ob, const struct Blob *text, int iMark, int nUsed, void *opaque
){
  const char * const unique = ((struct MarkdownToHtml*)opaque)->unique.c;




  assert( nUsed >= 0 );
  /* expect BUGs if the following yields compiler warnings */

  if( iMark < 0 ){                     /* misreferences */
    assert( iMark == -1 );
    if( !nUsed ) return;
    BLOB_APPEND_LITERAL(ob,"<li class='misreferences'>"
                              "<sup class='footnote-backrefs'>");
    if( nUsed == 1 ){
      blob_appendf(ob,"<a id='misreference%s-a' "
                      "href='#misref%s-a'>^</a>", unique, unique);
    }else{
      int i;
      blob_append_char(ob, '^');
      for(i=0; i<nUsed && i<26; i++){
        const int c = i + (unsigned)'a';
        blob_appendf(ob," <a id='misreference%s-%c' "
              "href='#misref%s-%c'>%c</a>", unique,c, unique,c, c);
      }
      if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
    }
    BLOB_APPEND_LITERAL(ob,"</sup>\nMisreference: use of undefined label.");

  }else if( nUsed ){                   /* a regular footnote */
    char pos[24];
    assert( text );
    assert( blob_size(text) );

    memset(pos,0,24);
    sprintf(pos, "%s-%i", unique, iMark);

    blob_appendf(ob, "<li id='footnote%s'>", pos);
    BLOB_APPEND_LITERAL(ob,"<sup class='footnote-backrefs'>");
    if( nUsed <= 1 ){
      blob_appendf(ob,"<a id='footnote%s-a' "
                       "href='#noteref%s-a'>^</a>", pos, pos);
    }else{
      int i;
      blob_append_char(ob, '^');
      for(i=0; i<nUsed && i<26; i++){
        const int c = i + (unsigned)'a';
        blob_appendf(ob," <a id='footnote%s-%c'"
                         " href='#noteref%s-%c'>%c</a>", pos,c, pos,c, c);
      }
      /* It's unlikely that so many backrefs will be usefull */
      /* but maybe for some machine generated documents... */
      for(; i<nUsed && i<676; i++){
        const bitfield64_t l = to_base26(i,0);
        blob_appendf(ob," <a id='footnote%s-%s'"
                         " href='#noteref%s-%s'>%s</a>",
                         pos,l.c, pos,l.c, l.c);
      }
      if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
    }
    BLOB_APPEND_LITERAL(ob,"</sup>\n");
    BLOB_APPEND_BLOB(ob, text);
  }else{
    /* a footnote was defined but wasn't used */
    assert( text );
    assert( blob_size(text) );
    /* FIXME: not yet implemented */
    return;
    BLOB_APPEND_LITERAL(ob,
      "<li class='unreferenced-footnote' id='unreferenced-footnote");
    blob_appendf(ob,"%s-%i'>\n", unique, iMark);
    BLOB_APPEND_BLOB(ob, text);
  }
  BLOB_APPEND_LITERAL(ob, "\n</li>\n");
}
static void html_footnotes(
  struct Blob *ob, const struct Blob *items, void *opaque
){
  if( items && blob_size(items) ){
    BLOB_APPEND_LITERAL(ob,

Changes to test/markdown-test3.md.

1
2
3
4
5
6
7
8
9
10
11
12


13
14
15
16
17
18
19
20
21
22
23







24
25
26
27
28
29
30
31


32
33
34
35
36
37
38

Markdown Footnotes Test Document
================================

**This document** should help with testing of footnotes support that
is introduced by the ["`markdown-footnotes`"][branch] branch.
It **might look pretty misformatted unless rendered by the proper Fossil
executable that incorporates the abovementioned branch.**[^1]

Developers are invited to add test cases here[^here].
It is suggested that the more simple is a test case the earlier it should
appear in this document.[^ if glitch occurs	]



A footnote's label should be case insensitive[^ case INSENSITIVE ],
it is whitespace-savvy and can even contain newlines.[^ a
multiline
label]

A labeled footnote may be [referenced several times][^many-refs].

A footnote's text should support Markdown [markup][^].

Another reference[^many-refs] to the preveously used footnote.








Inline footnotes are supported.(^These may be usefull for adding
<s>small</s> comments.)

If [undefined label is used][^] then red "`misreference`" is emited instead of
a numeric marker.[^ see it yourself ]
This can be overridden by the skin though.




## Footnotes

[branch]: /timeline?r=markdown-footnotes&nowiki

[^ 1]:  Footnotes is a Fossil' extention of
        Markdown. Your other tools may have limited support for these.












>
>











>
>
>
>
>
>
>




|



>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

Markdown Footnotes Test Document
================================

**This document** should help with testing of footnotes support that
is introduced by the ["`markdown-footnotes`"][branch] branch.
It **might look pretty misformatted unless rendered by the proper Fossil
executable that incorporates the abovementioned branch.**[^1]

Developers are invited to add test cases here[^here].
It is suggested that the more simple is a test case the earlier it should
appear in this document.[^ if glitch occurs	]

[^lost3]: This note was defined at the begining of the document.

A footnote's label should be case insensitive[^ case INSENSITIVE ],
it is whitespace-savvy and can even contain newlines.[^ a
multiline
label]

A labeled footnote may be [referenced several times][^many-refs].

A footnote's text should support Markdown [markup][^].

Another reference[^many-refs] to the preveously used footnote.

[^lost2]: This note was defined in the middle of the document.
   It references [its previous][^lost3] 
   and [the forthcoming][^lost1] siblings.

[^i am unreferenced]: If this is rendered wihin footnotes,
  then there is a BUG!

Inline footnotes are supported.(^These may be usefull for adding
<s>small</s> comments.)

If [undefined label is used][^] then red "`misref`" is emited instead of
a numeric marker.[^ see it yourself ]
This can be overridden by the skin though.

The refenrence at the end of this sentence is the sole reason of
rendering of <s>`lost1` and</s> [lost2][^].

## Footnotes

[branch]: /timeline?r=markdown-footnotes&nowiki

[^ 1]:  Footnotes is a Fossil' extention of
        Markdown. Your other tools may have limited support for these.
48
49
50
51
52
53
54
55






56
57
58
59
60
[^ a multiline label ]: But at a footnote's definition it should still
    be written within square brackets
             on a single line.


[^many-refs]:
   Each letter on the left is a back-reference to the place of use.
   Highlighted back-reference indicates a place from which navigation occurred.







[^markup]:   E.g. *emphasis*, and [so on](/md_rules).

[^undefined label is used]: For example due to a typo.








|
>
>
>
>
>
>





59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
[^ a multiline label ]: But at a footnote's definition it should still
    be written within square brackets
             on a single line.


[^many-refs]:
   Each letter on the left is a back-reference to the place of use.
   Highlighted back-reference indicates a place from which navigation
   occurred[^lost1].

[^lost1]: This note was defined at the end of the document.
   It defines an inline note.
   
   (^This is inline note defined inside of [a labeled note][^lost1].)

[^markup]:   E.g. *emphasis*, and [so on](/md_rules).

[^undefined label is used]: For example due to a typo.