Fossil SCM

Handle misreferences more thoroughly. Implement support of footnotes-within-footnotes with (hopefully) proper crosslinking (that's where it's getting tricky).

george 2022-02-06 22:53 markdown-footnotes
Commit 1787f6df11bbbe4fd84a1fb5c219244dfb31bd3dd81ac99fd9941efdd429759d
+15 -4
--- src/default.css
+++ src/default.css
@@ -1670,31 +1670,42 @@
16701670
div.content div.markdown > ol.footnotes {
16711671
font-size: 90%;
16721672
}
16731673
div.content div.markdown > ol.footnotes > li {
16741674
margin-bottom: 0.5em;
1675
+}
1676
+div.markdown ol.footnotes > li.unreferenced-footnote,
1677
+div.markdown ol.footnotes > li.misreferences {
1678
+ background: #ffdddd;
1679
+}
1680
+div.markdown ol.footnotes > li.unreferenced-footnote:first-child,
1681
+div.markdown ol.footnotes > li.misreferences {
1682
+ margin-top: 0.75em;
1683
+ padding-top: 0.25em;
1684
+ padding-bottom: 0.25em;
16751685
}
16761686
div.content div.markdown > ol.footnotes > li > .footnote-backrefs {
16771687
margin-right: 0.5em;
16781688
font-weight: bold;
16791689
}
16801690
div.markdown > ol.footnotes > li > .footnote-backrefs > a:target {
16811691
background: gold;
16821692
}
1683
-div.markdown sup > a.noteref:target {
1693
+div.markdown sup.noteref > a:target {
16841694
background: gold;
16851695
}
1686
-div.markdown sup.misref {
1696
+div.markdown sup.noteref.misref,
1697
+div.markdown sup.noteref.misref > a {
16871698
color: red;
16881699
font-size: 90%;
16891700
}
16901701
div.markdown span.notescope:hover,
16911702
div.markdown span.notescope:target {
16921703
border-bottom: 2px solid gold;
16931704
}
1694
-div.markdown span.notescope:hover > sup > a.noteref,
1695
-div.markdown span.notescope:target > sup > a.noteref {
1705
+div.markdown span.notescope:hover > sup.noteref > a,
1706
+div.markdown span.notescope:target > sup.noteref > a {
16961707
background: gold;
16971708
}
16981709
16991710
/* Objects in the "desktoponly" class are invisible on mobile */
17001711
@media screen and (max-width: 600px) {
17011712
--- src/default.css
+++ src/default.css
@@ -1670,31 +1670,42 @@
1670 div.content div.markdown > ol.footnotes {
1671 font-size: 90%;
1672 }
1673 div.content div.markdown > ol.footnotes > li {
1674 margin-bottom: 0.5em;
 
 
 
 
 
 
 
 
 
 
1675 }
1676 div.content div.markdown > ol.footnotes > li > .footnote-backrefs {
1677 margin-right: 0.5em;
1678 font-weight: bold;
1679 }
1680 div.markdown > ol.footnotes > li > .footnote-backrefs > a:target {
1681 background: gold;
1682 }
1683 div.markdown sup > a.noteref:target {
1684 background: gold;
1685 }
1686 div.markdown sup.misref {
 
1687 color: red;
1688 font-size: 90%;
1689 }
1690 div.markdown span.notescope:hover,
1691 div.markdown span.notescope:target {
1692 border-bottom: 2px solid gold;
1693 }
1694 div.markdown span.notescope:hover > sup > a.noteref,
1695 div.markdown span.notescope:target > sup > a.noteref {
1696 background: gold;
1697 }
1698
1699 /* Objects in the "desktoponly" class are invisible on mobile */
1700 @media screen and (max-width: 600px) {
1701
--- src/default.css
+++ src/default.css
@@ -1670,31 +1670,42 @@
1670 div.content div.markdown > ol.footnotes {
1671 font-size: 90%;
1672 }
1673 div.content div.markdown > ol.footnotes > li {
1674 margin-bottom: 0.5em;
1675 }
1676 div.markdown ol.footnotes > li.unreferenced-footnote,
1677 div.markdown ol.footnotes > li.misreferences {
1678 background: #ffdddd;
1679 }
1680 div.markdown ol.footnotes > li.unreferenced-footnote:first-child,
1681 div.markdown ol.footnotes > li.misreferences {
1682 margin-top: 0.75em;
1683 padding-top: 0.25em;
1684 padding-bottom: 0.25em;
1685 }
1686 div.content div.markdown > ol.footnotes > li > .footnote-backrefs {
1687 margin-right: 0.5em;
1688 font-weight: bold;
1689 }
1690 div.markdown > ol.footnotes > li > .footnote-backrefs > a:target {
1691 background: gold;
1692 }
1693 div.markdown sup.noteref > a:target {
1694 background: gold;
1695 }
1696 div.markdown sup.noteref.misref,
1697 div.markdown sup.noteref.misref > a {
1698 color: red;
1699 font-size: 90%;
1700 }
1701 div.markdown span.notescope:hover,
1702 div.markdown span.notescope:target {
1703 border-bottom: 2px solid gold;
1704 }
1705 div.markdown span.notescope:hover > sup.noteref > a,
1706 div.markdown span.notescope:target > sup.noteref > a {
1707 background: gold;
1708 }
1709
1710 /* Objects in the "desktoponly" class are invisible on mobile */
1711 @media screen and (max-width: 600px) {
1712
+129 -58
--- src/markdown.c
+++ src/markdown.c
@@ -127,12 +127,13 @@
127127
const struct mkd_renderer *rndr);
128128
129129
130130
#endif /* INTERFACE */
131131
132
-#define BLOB_COUNT(blob_ptr,el_type) (blob_size(blob_ptr)/sizeof(el_type))
133
-#define COUNT_FOOTNOTES(blob_ptr) BLOB_COUNT(blob_ptr,struct footnote)
132
+#define BLOB_COUNT(pBlob,el_type) (blob_size(pBlob)/sizeof(el_type))
133
+#define COUNT_FOOTNOTES(pBlob) BLOB_COUNT(pBlob,struct footnote)
134
+#define CAST_AS_FOOTNOTES(pBlob) ((struct footnote*)blob_buffer(pBlob))
134135
135136
/***************
136137
* LOCAL TYPES *
137138
***************/
138139
@@ -142,14 +143,18 @@
142143
struct Blob link;
143144
struct Blob title;
144145
};
145146
146147
struct footnote {
147
- struct Blob id; /* must be the first field as in link_ref struct */
148
- struct Blob text; /* footnote's content that is rendered at the end */
149
- int index; /* serial number, in the order of appearance */
150
- int nUsed; /* number of references to this note */
148
+ struct Blob id; /* must be the first field as in link_ref struct */
149
+ struct Blob text; /* footnote's content that is rendered at the end */
150
+ int bRndred; /* indicates if `text` holds a rendered content */
151
+
152
+ int defno; /* serial number of definition, set during the first pass */
153
+ int index; /* set to the index within array after ordering by id */
154
+ int iMark; /* user-visible numeric marker, assigned upon the first use*/
155
+ int nUsed; /* counts references to this note, increments upon each use*/
151156
};
152157
153158
154159
/* char_trigger -- function pointer to render active chars */
155160
/* returns the number of chars taken care of */
@@ -174,13 +179,12 @@
174179
struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
175180
176181
struct {
177182
Blob all; /* array of footnotes */
178183
int nLbled; /* number of labeled footnotes found during the first pass */
179
- int nMarks; /* counts distinct indices found during the second pass */
180
- struct footnote missing; /* a dummy footnote, its negative index
181
- counts missing refs */
184
+ int nMarks; /* counts distinct indices found during the second pass */
185
+ struct footnote misref; /* nUsed counts misreferences, index must be -1 */
182186
} notes;
183187
};
184188
185189
/* html_tag -- structure for quick HTML tag search (inspired from discount) */
186190
struct html_tag {
@@ -203,11 +207,10 @@
203207
static const struct html_tag block_tags[] = {
204208
{ "html", 4 },
205209
{ "pre", 3 },
206210
{ "script", 6 },
207211
};
208
-
209212
210213
/***************************
211214
* STATIC HELPER FUNCTIONS *
212215
***************************/
213216
@@ -270,18 +273,33 @@
270273
struct link_ref *lrb = (void *)b;
271274
return blob_compare(&lra->id, &lrb->id);
272275
}
273276
274277
/* cmp_footnote_sort -- comparison function for footnotes qsort.
275
- * Unused footnotes (when index == 0) sort last */
276
-static int cmp_footnote_sort(const void *a, const void *b){
277
- const struct footnote *fna = (void *)a, *fnb = (void *)b;
278
- assert( fna->index >= 0 && fnb->index >= 0 );
279
- if( fna->index == fnb->index ) return 0;
280
- if( fna->index == 0 ) return 1;
281
- if( fnb->index == 0 ) return -1;
282
- return ( fna->index < fnb->index ? -1 : 1 );
278
+ * Unreferenced footnotes (when nUsed == 0) sort last and
279
+ * are sorted in the order of definition in the source */
280
+static int cmp_footnote_sort(const void *fna, const void *fnb){
281
+ const struct footnote *a = fna, *b = fnb;
282
+ int i, j;
283
+ assert( a->nUsed >= 0 );
284
+ assert( b->nUsed >= 0 );
285
+ assert( a->defno >= 0 );
286
+ assert( b->defno >= 0 );
287
+ if( a->nUsed ){
288
+ assert( a->iMark > 0 );
289
+ if( !b->nUsed ) return -1;
290
+ assert( b->iMark > 0 );
291
+ i = a->iMark;
292
+ j = b->iMark;
293
+ }else{
294
+ if( b->nUsed ) return 1;
295
+ i = a->defno;
296
+ j = b->defno;
297
+ }
298
+ if( i < j ) return -1;
299
+ if( i > j ) return 1;
300
+ return 0;
283301
}
284302
285303
/* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
286304
static int cmp_html_tag(const void *a, const void *b){
287305
const struct html_tag *hta = a;
@@ -1052,16 +1070,16 @@
10521070
rndr->notes.nLbled,
10531071
sizeof (struct footnote),
10541072
cmp_link_ref);
10551073
if( !fn ) goto cleanup;
10561074
1057
- if( fn->index == 0 ){ /* the first reference to the footnote */
1058
- assert( fn->nUsed == 0 );
1059
- fn->index = ++(rndr->notes.nMarks);
1075
+ if( fn->nUsed == 0 ){ /* the first reference to the footnote */
1076
+ assert( fn->iMark == 0 );
1077
+ fn->iMark = ++(rndr->notes.nMarks);
10601078
}
10611079
fn->nUsed++;
1062
- assert( fn->index > 0 );
1080
+ assert( fn->iMark > 0 );
10631081
assert( fn->nUsed > 0 );
10641082
cleanup:
10651083
release_work_buffer( rndr, id );
10661084
return fn;
10671085
}
@@ -1071,16 +1089,17 @@
10711089
static inline const struct footnote* add_inline_footnote(
10721090
struct render *rndr,
10731091
const char *text,
10741092
size_t size
10751093
){
1076
- struct footnote fn = { empty_blob, empty_blob, 0, 0 };
1094
+ struct footnote fn = { empty_blob, empty_blob, 0, 0, 0, 0, 0 };
10771095
while(size && (*text==' ' || *text=='\t')){ text++; size--; }
10781096
if(!size) return 0;
1079
- fn.index = ++(rndr->notes.nMarks);
1080
- fn.nUsed = 1;
1081
- assert( fn.index > 0 );
1097
+ fn.iMark = ++(rndr->notes.nMarks);
1098
+ fn.nUsed = 1;
1099
+ fn.index = COUNT_FOOTNOTES(&rndr->notes.all);
1100
+ assert( fn.iMark > 0 );
10821101
blob_append(&fn.text, text, size);
10831102
blob_append(&rndr->notes.all, (char *)&fn, sizeof fn);
10841103
return (struct footnote*)( blob_buffer(&rndr->notes.all)
10851104
+( blob_size(&rndr->notes.all)-sizeof fn ));
10861105
}
@@ -1120,11 +1139,11 @@
11201139
11211140
if( size<4 || data[1]!='^' || !rndr->make.footnote_ref ) return 0;
11221141
end = matching_bracket_offset(data, data+size);
11231142
if( !end ) return 0;
11241143
fn = add_inline_footnote(rndr, data+2, end-2);
1125
- if(fn) rndr->make.footnote_ref(ob,0,fn->index,1,rndr->make.opaque);
1144
+ if(fn) rndr->make.footnote_ref(ob,0,fn->iMark,1,rndr->make.opaque);
11261145
return end+1;
11271146
}
11281147
11291148
/* char_link -- '[': parsing a link or an image */
11301149
static size_t char_link(
@@ -1225,12 +1244,12 @@
12251244
}
12261245
12271246
if( bFootnote ){
12281247
fn = get_footnote(rndr, id_data, id_size);
12291248
if( !fn ) {
1230
- rndr->notes.missing.index--;
1231
- fn = &rndr->notes.missing;
1249
+ rndr->notes.misref.nUsed++;
1250
+ fn = &rndr->notes.misref;
12321251
}
12331252
}else if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
12341253
goto char_link_cleanup;
12351254
}
12361255
@@ -1240,12 +1259,12 @@
12401259
}else{
12411260
if(!is_img && size>3 && data[1]=='^'){
12421261
/* free-standing footnote reference */
12431262
fn = get_footnote(rndr, data+2, txt_e-2);
12441263
if( !fn ) {
1245
- rndr->notes.missing.index--;
1246
- fn = &rndr->notes.missing;
1264
+ rndr->notes.misref.nUsed++;
1265
+ fn = &rndr->notes.misref;
12471266
}
12481267
release_work_buffer(rndr, content);
12491268
content = 0;
12501269
}else if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
12511270
goto char_link_cleanup;
@@ -1265,11 +1284,11 @@
12651284
if( is_img ){
12661285
if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
12671286
ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
12681287
}else if(fn){
12691288
if(rndr->make.footnote_ref){
1270
- ret = rndr->make.footnote_ref(ob, content, fn->index, fn->nUsed,
1289
+ ret = rndr->make.footnote_ref(ob, content, fn->iMark, fn->nUsed,
12711290
rndr->make.opaque);
12721291
}
12731292
}else{
12741293
ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
12751294
}
@@ -2359,11 +2378,11 @@
23592378
size_t end, /* offset of the end of the text */
23602379
size_t *last, /* last character of the link */
23612380
struct Blob * footnotes
23622381
){
23632382
size_t i, id_offset, id_end;
2364
- struct footnote fn = { empty_blob, empty_blob, 0, 0 };
2383
+ struct footnote fn = { empty_blob, empty_blob, 0, 0, 0, 0, 0 };
23652384
23662385
/* failfast if data is too short */
23672386
if( beg+5>=end ) return 0;
23682387
i = beg;
23692388
@@ -2438,11 +2457,14 @@
24382457
blob_reset(&fn.id);
24392458
return 0;
24402459
}
24412460
/* a valid note has been found */
24422461
if( last ) *last = i;
2443
- if( footnotes ) blob_append(footnotes, (char *)&fn, sizeof fn);
2462
+ if( footnotes ){
2463
+ fn.defno = COUNT_FOOTNOTES( footnotes );
2464
+ blob_append(footnotes, (char *)&fn, sizeof fn);
2465
+ }
24442466
return 1;
24452467
}
24462468
24472469
/**********************
24482470
* EXPORTED FUNCTIONS *
@@ -2466,14 +2488,14 @@
24662488
rndr.nBlobCache = 0;
24672489
rndr.iDepth = 0;
24682490
rndr.refs = empty_blob;
24692491
rndr.notes.all = empty_blob;
24702492
rndr.notes.nMarks = 0;
2471
- rndr.notes.missing.id = empty_blob;
2472
- rndr.notes.missing.text = empty_blob;
2473
- rndr.notes.missing.index = 0;
2474
- rndr.notes.missing.nUsed = 0;
2493
+ rndr.notes.misref.id = empty_blob;
2494
+ rndr.notes.misref.text = empty_blob;
2495
+ rndr.notes.misref.nUsed = 0;
2496
+ rndr.notes.misref.iMark = -1;
24752497
24762498
for(i=0; i<256; i++) rndr.active_char[i] = 0;
24772499
if( (rndr.make.emphasis
24782500
|| rndr.make.double_emphasis
24792501
|| rndr.make.triple_emphasis)
@@ -2527,36 +2549,84 @@
25272549
cmp_link_ref_sort);
25282550
}
25292551
rndr.notes.nLbled = COUNT_FOOTNOTES(&rndr.notes.all);
25302552
/* sorting the footnotes array by id */
25312553
if( rndr.notes.nLbled ){
2532
- qsort(blob_buffer(&rndr.notes.all), rndr.notes.nLbled,
2533
- sizeof(struct footnote), cmp_link_ref_sort);
2554
+ fn = CAST_AS_FOOTNOTES(&rndr.notes.all);
2555
+ qsort(fn, rndr.notes.nLbled, sizeof(struct footnote),
2556
+ cmp_link_ref_sort);
2557
+ for(i=0; i<rndr.notes.nLbled; i++){
2558
+ fn[i].index = i;
2559
+ }
2560
+ /* FIXME: handle footnotes with duplicated labels */
25342561
}
25352562
25362563
/* second pass: actual rendering */
25372564
if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
25382565
parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
25392566
2540
- fn = (struct footnote*)blob_buffer(&rndr.notes.all);
2541
- if(rndr.notes.nMarks && rndr.make.footnote_item && rndr.make.footnotes){
2542
- Blob * one_item = new_work_buffer(&rndr);
2543
- Blob * all_items = new_work_buffer(&rndr);
2544
- qsort( fn, COUNT_FOOTNOTES(&rndr.notes.all), sizeof(struct footnote),
2545
- cmp_footnote_sort /* sort footnotes by index */ );
2546
- blob_reset( all_items );
2547
- for(i=0; i<rndr.notes.nMarks; i++){
2548
- assert( fn[i].index == i+1 );
2549
-
2550
- blob_reset( one_item );
2551
- parse_inline( one_item, &rndr, blob_buffer(&fn[i].text),
2552
- blob_size(&fn[i].text));
2553
- rndr.make.footnote_item( all_items, one_item, i+1, fn[i].nUsed, rndr.make.opaque);
2554
- }
2555
- rndr.make.footnotes(ob, all_items, rndr.make.opaque );
2556
- release_work_buffer( &rndr, one_item );
2557
- release_work_buffer( &rndr, all_items );
2567
+ if( (blob_size(&rndr.notes.all) || rndr.notes.misref.nUsed) ){
2568
+
2569
+ /* Footnotes must be parsed for the correct discovery of (back)links */
2570
+ Blob *notes = new_work_buffer( &rndr );
2571
+ Blob *tmp = new_work_buffer( &rndr );
2572
+ const struct Blob *origin = &rndr.notes.all;
2573
+ int nMarks = -1;
2574
+
2575
+ /* inline notes may get appended to rndr.notes.all while rendering */
2576
+ while(1){
2577
+ struct footnote *aNotes;
2578
+ const int N = COUNT_FOOTNOTES(origin);
2579
+
2580
+ /* make a shallow copy of `origin` */
2581
+ blob_truncate(notes,0);
2582
+ blob_append(notes, blob_buffer(origin), blob_size(origin));
2583
+ aNotes = CAST_AS_FOOTNOTES(notes);
2584
+ qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort);
2585
+
2586
+ if( nMarks == rndr.notes.nMarks ) break;
2587
+ nMarks = rndr.notes.nMarks;
2588
+
2589
+ for(i=0; i<N; i++){
2590
+ const int j = aNotes[i].index;
2591
+ struct footnote *x = CAST_AS_FOOTNOTES(origin) + j;
2592
+ assert( 0<=j && j<N );
2593
+ if( x->bRndred || !x->nUsed ) continue;
2594
+ assert( x->iMark > 0 );
2595
+ assert( blob_size(&x->text) );
2596
+ blob_truncate(tmp,0);
2597
+
2598
+ /* `origin` may be altered and extended through this call */
2599
+ parse_inline(tmp, &rndr, blob_buffer(&x->text), blob_size(&x->text));
2600
+
2601
+ blob_truncate(&x->text,0);
2602
+ blob_append(&x->text, blob_buffer(tmp), blob_size(tmp));
2603
+ x->bRndred = 1;
2604
+ }
2605
+ }
2606
+ release_work_buffer(&rndr,tmp);
2607
+
2608
+ /* footnotes rendering */
2609
+ if( rndr.make.footnote_item && rndr.make.footnotes ){
2610
+ Blob *all_items = new_work_buffer(&rndr);
2611
+ for(i=0; i<COUNT_FOOTNOTES(notes); i++){
2612
+ const struct footnote* x = CAST_AS_FOOTNOTES(notes) + i;
2613
+ if( x->bRndred ){
2614
+ rndr.make.footnote_item(all_items, &x->text, x->iMark,
2615
+ x->nUsed, rndr.make.opaque);
2616
+ }
2617
+ }
2618
+ if( rndr.notes.misref.nUsed ){
2619
+ rndr.make.footnote_item(all_items, 0, -1,
2620
+ rndr.notes.misref.nUsed, rndr.make.opaque);
2621
+ }
2622
+ /* TODO: handle unreferenced (defined but not used) footnotes */
2623
+
2624
+ rndr.make.footnotes(ob, all_items, rndr.make.opaque);
2625
+ release_work_buffer(&rndr, all_items);
2626
+ }
2627
+ release_work_buffer(&rndr, notes);
25582628
}
25592629
if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
25602630
25612631
/* clean-up */
25622632
assert( rndr.iDepth==0 );
@@ -2567,10 +2637,11 @@
25672637
blob_reset(&lr[i].id);
25682638
blob_reset(&lr[i].link);
25692639
blob_reset(&lr[i].title);
25702640
}
25712641
blob_reset(&rndr.refs);
2642
+ fn = CAST_AS_FOOTNOTES(&rndr.notes.all);
25722643
end = COUNT_FOOTNOTES(&rndr.notes.all);
25732644
for(i=0; i<end; i++){
25742645
if(blob_size(&fn[i].id)) blob_reset(&fn[i].id);
25752646
blob_reset(&fn[i].text);
25762647
}
25772648
--- src/markdown.c
+++ src/markdown.c
@@ -127,12 +127,13 @@
127 const struct mkd_renderer *rndr);
128
129
130 #endif /* INTERFACE */
131
132 #define BLOB_COUNT(blob_ptr,el_type) (blob_size(blob_ptr)/sizeof(el_type))
133 #define COUNT_FOOTNOTES(blob_ptr) BLOB_COUNT(blob_ptr,struct footnote)
 
134
135 /***************
136 * LOCAL TYPES *
137 ***************/
138
@@ -142,14 +143,18 @@
142 struct Blob link;
143 struct Blob title;
144 };
145
146 struct footnote {
147 struct Blob id; /* must be the first field as in link_ref struct */
148 struct Blob text; /* footnote's content that is rendered at the end */
149 int index; /* serial number, in the order of appearance */
150 int nUsed; /* number of references to this note */
 
 
 
 
151 };
152
153
154 /* char_trigger -- function pointer to render active chars */
155 /* returns the number of chars taken care of */
@@ -174,13 +179,12 @@
174 struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
175
176 struct {
177 Blob all; /* array of footnotes */
178 int nLbled; /* number of labeled footnotes found during the first pass */
179 int nMarks; /* counts distinct indices found during the second pass */
180 struct footnote missing; /* a dummy footnote, its negative index
181 counts missing refs */
182 } notes;
183 };
184
185 /* html_tag -- structure for quick HTML tag search (inspired from discount) */
186 struct html_tag {
@@ -203,11 +207,10 @@
203 static const struct html_tag block_tags[] = {
204 { "html", 4 },
205 { "pre", 3 },
206 { "script", 6 },
207 };
208
209
210 /***************************
211 * STATIC HELPER FUNCTIONS *
212 ***************************/
213
@@ -270,18 +273,33 @@
270 struct link_ref *lrb = (void *)b;
271 return blob_compare(&lra->id, &lrb->id);
272 }
273
274 /* cmp_footnote_sort -- comparison function for footnotes qsort.
275 * Unused footnotes (when index == 0) sort last */
276 static int cmp_footnote_sort(const void *a, const void *b){
277 const struct footnote *fna = (void *)a, *fnb = (void *)b;
278 assert( fna->index >= 0 && fnb->index >= 0 );
279 if( fna->index == fnb->index ) return 0;
280 if( fna->index == 0 ) return 1;
281 if( fnb->index == 0 ) return -1;
282 return ( fna->index < fnb->index ? -1 : 1 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283 }
284
285 /* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
286 static int cmp_html_tag(const void *a, const void *b){
287 const struct html_tag *hta = a;
@@ -1052,16 +1070,16 @@
1052 rndr->notes.nLbled,
1053 sizeof (struct footnote),
1054 cmp_link_ref);
1055 if( !fn ) goto cleanup;
1056
1057 if( fn->index == 0 ){ /* the first reference to the footnote */
1058 assert( fn->nUsed == 0 );
1059 fn->index = ++(rndr->notes.nMarks);
1060 }
1061 fn->nUsed++;
1062 assert( fn->index > 0 );
1063 assert( fn->nUsed > 0 );
1064 cleanup:
1065 release_work_buffer( rndr, id );
1066 return fn;
1067 }
@@ -1071,16 +1089,17 @@
1071 static inline const struct footnote* add_inline_footnote(
1072 struct render *rndr,
1073 const char *text,
1074 size_t size
1075 ){
1076 struct footnote fn = { empty_blob, empty_blob, 0, 0 };
1077 while(size && (*text==' ' || *text=='\t')){ text++; size--; }
1078 if(!size) return 0;
1079 fn.index = ++(rndr->notes.nMarks);
1080 fn.nUsed = 1;
1081 assert( fn.index > 0 );
 
1082 blob_append(&fn.text, text, size);
1083 blob_append(&rndr->notes.all, (char *)&fn, sizeof fn);
1084 return (struct footnote*)( blob_buffer(&rndr->notes.all)
1085 +( blob_size(&rndr->notes.all)-sizeof fn ));
1086 }
@@ -1120,11 +1139,11 @@
1120
1121 if( size<4 || data[1]!='^' || !rndr->make.footnote_ref ) return 0;
1122 end = matching_bracket_offset(data, data+size);
1123 if( !end ) return 0;
1124 fn = add_inline_footnote(rndr, data+2, end-2);
1125 if(fn) rndr->make.footnote_ref(ob,0,fn->index,1,rndr->make.opaque);
1126 return end+1;
1127 }
1128
1129 /* char_link -- '[': parsing a link or an image */
1130 static size_t char_link(
@@ -1225,12 +1244,12 @@
1225 }
1226
1227 if( bFootnote ){
1228 fn = get_footnote(rndr, id_data, id_size);
1229 if( !fn ) {
1230 rndr->notes.missing.index--;
1231 fn = &rndr->notes.missing;
1232 }
1233 }else if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
1234 goto char_link_cleanup;
1235 }
1236
@@ -1240,12 +1259,12 @@
1240 }else{
1241 if(!is_img && size>3 && data[1]=='^'){
1242 /* free-standing footnote reference */
1243 fn = get_footnote(rndr, data+2, txt_e-2);
1244 if( !fn ) {
1245 rndr->notes.missing.index--;
1246 fn = &rndr->notes.missing;
1247 }
1248 release_work_buffer(rndr, content);
1249 content = 0;
1250 }else if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
1251 goto char_link_cleanup;
@@ -1265,11 +1284,11 @@
1265 if( is_img ){
1266 if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
1267 ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
1268 }else if(fn){
1269 if(rndr->make.footnote_ref){
1270 ret = rndr->make.footnote_ref(ob, content, fn->index, fn->nUsed,
1271 rndr->make.opaque);
1272 }
1273 }else{
1274 ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
1275 }
@@ -2359,11 +2378,11 @@
2359 size_t end, /* offset of the end of the text */
2360 size_t *last, /* last character of the link */
2361 struct Blob * footnotes
2362 ){
2363 size_t i, id_offset, id_end;
2364 struct footnote fn = { empty_blob, empty_blob, 0, 0 };
2365
2366 /* failfast if data is too short */
2367 if( beg+5>=end ) return 0;
2368 i = beg;
2369
@@ -2438,11 +2457,14 @@
2438 blob_reset(&fn.id);
2439 return 0;
2440 }
2441 /* a valid note has been found */
2442 if( last ) *last = i;
2443 if( footnotes ) blob_append(footnotes, (char *)&fn, sizeof fn);
 
 
 
2444 return 1;
2445 }
2446
2447 /**********************
2448 * EXPORTED FUNCTIONS *
@@ -2466,14 +2488,14 @@
2466 rndr.nBlobCache = 0;
2467 rndr.iDepth = 0;
2468 rndr.refs = empty_blob;
2469 rndr.notes.all = empty_blob;
2470 rndr.notes.nMarks = 0;
2471 rndr.notes.missing.id = empty_blob;
2472 rndr.notes.missing.text = empty_blob;
2473 rndr.notes.missing.index = 0;
2474 rndr.notes.missing.nUsed = 0;
2475
2476 for(i=0; i<256; i++) rndr.active_char[i] = 0;
2477 if( (rndr.make.emphasis
2478 || rndr.make.double_emphasis
2479 || rndr.make.triple_emphasis)
@@ -2527,36 +2549,84 @@
2527 cmp_link_ref_sort);
2528 }
2529 rndr.notes.nLbled = COUNT_FOOTNOTES(&rndr.notes.all);
2530 /* sorting the footnotes array by id */
2531 if( rndr.notes.nLbled ){
2532 qsort(blob_buffer(&rndr.notes.all), rndr.notes.nLbled,
2533 sizeof(struct footnote), cmp_link_ref_sort);
 
 
 
 
 
2534 }
2535
2536 /* second pass: actual rendering */
2537 if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
2538 parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
2539
2540 fn = (struct footnote*)blob_buffer(&rndr.notes.all);
2541 if(rndr.notes.nMarks && rndr.make.footnote_item && rndr.make.footnotes){
2542 Blob * one_item = new_work_buffer(&rndr);
2543 Blob * all_items = new_work_buffer(&rndr);
2544 qsort( fn, COUNT_FOOTNOTES(&rndr.notes.all), sizeof(struct footnote),
2545 cmp_footnote_sort /* sort footnotes by index */ );
2546 blob_reset( all_items );
2547 for(i=0; i<rndr.notes.nMarks; i++){
2548 assert( fn[i].index == i+1 );
2549
2550 blob_reset( one_item );
2551 parse_inline( one_item, &rndr, blob_buffer(&fn[i].text),
2552 blob_size(&fn[i].text));
2553 rndr.make.footnote_item( all_items, one_item, i+1, fn[i].nUsed, rndr.make.opaque);
2554 }
2555 rndr.make.footnotes(ob, all_items, rndr.make.opaque );
2556 release_work_buffer( &rndr, one_item );
2557 release_work_buffer( &rndr, all_items );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2558 }
2559 if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
2560
2561 /* clean-up */
2562 assert( rndr.iDepth==0 );
@@ -2567,10 +2637,11 @@
2567 blob_reset(&lr[i].id);
2568 blob_reset(&lr[i].link);
2569 blob_reset(&lr[i].title);
2570 }
2571 blob_reset(&rndr.refs);
 
2572 end = COUNT_FOOTNOTES(&rndr.notes.all);
2573 for(i=0; i<end; i++){
2574 if(blob_size(&fn[i].id)) blob_reset(&fn[i].id);
2575 blob_reset(&fn[i].text);
2576 }
2577
--- src/markdown.c
+++ src/markdown.c
@@ -127,12 +127,13 @@
127 const struct mkd_renderer *rndr);
128
129
130 #endif /* INTERFACE */
131
132 #define BLOB_COUNT(pBlob,el_type) (blob_size(pBlob)/sizeof(el_type))
133 #define COUNT_FOOTNOTES(pBlob) BLOB_COUNT(pBlob,struct footnote)
134 #define CAST_AS_FOOTNOTES(pBlob) ((struct footnote*)blob_buffer(pBlob))
135
136 /***************
137 * LOCAL TYPES *
138 ***************/
139
@@ -142,14 +143,18 @@
143 struct Blob link;
144 struct Blob title;
145 };
146
147 struct footnote {
148 struct Blob id; /* must be the first field as in link_ref struct */
149 struct Blob text; /* footnote's content that is rendered at the end */
150 int bRndred; /* indicates if `text` holds a rendered content */
151
152 int defno; /* serial number of definition, set during the first pass */
153 int index; /* set to the index within array after ordering by id */
154 int iMark; /* user-visible numeric marker, assigned upon the first use*/
155 int nUsed; /* counts references to this note, increments upon each use*/
156 };
157
158
159 /* char_trigger -- function pointer to render active chars */
160 /* returns the number of chars taken care of */
@@ -174,13 +179,12 @@
179 struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
180
181 struct {
182 Blob all; /* array of footnotes */
183 int nLbled; /* number of labeled footnotes found during the first pass */
184 int nMarks; /* counts distinct indices found during the second pass */
185 struct footnote misref; /* nUsed counts misreferences, index must be -1 */
 
186 } notes;
187 };
188
189 /* html_tag -- structure for quick HTML tag search (inspired from discount) */
190 struct html_tag {
@@ -203,11 +207,10 @@
207 static const struct html_tag block_tags[] = {
208 { "html", 4 },
209 { "pre", 3 },
210 { "script", 6 },
211 };
 
212
213 /***************************
214 * STATIC HELPER FUNCTIONS *
215 ***************************/
216
@@ -270,18 +273,33 @@
273 struct link_ref *lrb = (void *)b;
274 return blob_compare(&lra->id, &lrb->id);
275 }
276
277 /* cmp_footnote_sort -- comparison function for footnotes qsort.
278 * Unreferenced footnotes (when nUsed == 0) sort last and
279 * are sorted in the order of definition in the source */
280 static int cmp_footnote_sort(const void *fna, const void *fnb){
281 const struct footnote *a = fna, *b = fnb;
282 int i, j;
283 assert( a->nUsed >= 0 );
284 assert( b->nUsed >= 0 );
285 assert( a->defno >= 0 );
286 assert( b->defno >= 0 );
287 if( a->nUsed ){
288 assert( a->iMark > 0 );
289 if( !b->nUsed ) return -1;
290 assert( b->iMark > 0 );
291 i = a->iMark;
292 j = b->iMark;
293 }else{
294 if( b->nUsed ) return 1;
295 i = a->defno;
296 j = b->defno;
297 }
298 if( i < j ) return -1;
299 if( i > j ) return 1;
300 return 0;
301 }
302
303 /* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
304 static int cmp_html_tag(const void *a, const void *b){
305 const struct html_tag *hta = a;
@@ -1052,16 +1070,16 @@
1070 rndr->notes.nLbled,
1071 sizeof (struct footnote),
1072 cmp_link_ref);
1073 if( !fn ) goto cleanup;
1074
1075 if( fn->nUsed == 0 ){ /* the first reference to the footnote */
1076 assert( fn->iMark == 0 );
1077 fn->iMark = ++(rndr->notes.nMarks);
1078 }
1079 fn->nUsed++;
1080 assert( fn->iMark > 0 );
1081 assert( fn->nUsed > 0 );
1082 cleanup:
1083 release_work_buffer( rndr, id );
1084 return fn;
1085 }
@@ -1071,16 +1089,17 @@
1089 static inline const struct footnote* add_inline_footnote(
1090 struct render *rndr,
1091 const char *text,
1092 size_t size
1093 ){
1094 struct footnote fn = { empty_blob, empty_blob, 0, 0, 0, 0, 0 };
1095 while(size && (*text==' ' || *text=='\t')){ text++; size--; }
1096 if(!size) return 0;
1097 fn.iMark = ++(rndr->notes.nMarks);
1098 fn.nUsed = 1;
1099 fn.index = COUNT_FOOTNOTES(&rndr->notes.all);
1100 assert( fn.iMark > 0 );
1101 blob_append(&fn.text, text, size);
1102 blob_append(&rndr->notes.all, (char *)&fn, sizeof fn);
1103 return (struct footnote*)( blob_buffer(&rndr->notes.all)
1104 +( blob_size(&rndr->notes.all)-sizeof fn ));
1105 }
@@ -1120,11 +1139,11 @@
1139
1140 if( size<4 || data[1]!='^' || !rndr->make.footnote_ref ) return 0;
1141 end = matching_bracket_offset(data, data+size);
1142 if( !end ) return 0;
1143 fn = add_inline_footnote(rndr, data+2, end-2);
1144 if(fn) rndr->make.footnote_ref(ob,0,fn->iMark,1,rndr->make.opaque);
1145 return end+1;
1146 }
1147
1148 /* char_link -- '[': parsing a link or an image */
1149 static size_t char_link(
@@ -1225,12 +1244,12 @@
1244 }
1245
1246 if( bFootnote ){
1247 fn = get_footnote(rndr, id_data, id_size);
1248 if( !fn ) {
1249 rndr->notes.misref.nUsed++;
1250 fn = &rndr->notes.misref;
1251 }
1252 }else if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
1253 goto char_link_cleanup;
1254 }
1255
@@ -1240,12 +1259,12 @@
1259 }else{
1260 if(!is_img && size>3 && data[1]=='^'){
1261 /* free-standing footnote reference */
1262 fn = get_footnote(rndr, data+2, txt_e-2);
1263 if( !fn ) {
1264 rndr->notes.misref.nUsed++;
1265 fn = &rndr->notes.misref;
1266 }
1267 release_work_buffer(rndr, content);
1268 content = 0;
1269 }else if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
1270 goto char_link_cleanup;
@@ -1265,11 +1284,11 @@
1284 if( is_img ){
1285 if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
1286 ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
1287 }else if(fn){
1288 if(rndr->make.footnote_ref){
1289 ret = rndr->make.footnote_ref(ob, content, fn->iMark, fn->nUsed,
1290 rndr->make.opaque);
1291 }
1292 }else{
1293 ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
1294 }
@@ -2359,11 +2378,11 @@
2378 size_t end, /* offset of the end of the text */
2379 size_t *last, /* last character of the link */
2380 struct Blob * footnotes
2381 ){
2382 size_t i, id_offset, id_end;
2383 struct footnote fn = { empty_blob, empty_blob, 0, 0, 0, 0, 0 };
2384
2385 /* failfast if data is too short */
2386 if( beg+5>=end ) return 0;
2387 i = beg;
2388
@@ -2438,11 +2457,14 @@
2457 blob_reset(&fn.id);
2458 return 0;
2459 }
2460 /* a valid note has been found */
2461 if( last ) *last = i;
2462 if( footnotes ){
2463 fn.defno = COUNT_FOOTNOTES( footnotes );
2464 blob_append(footnotes, (char *)&fn, sizeof fn);
2465 }
2466 return 1;
2467 }
2468
2469 /**********************
2470 * EXPORTED FUNCTIONS *
@@ -2466,14 +2488,14 @@
2488 rndr.nBlobCache = 0;
2489 rndr.iDepth = 0;
2490 rndr.refs = empty_blob;
2491 rndr.notes.all = empty_blob;
2492 rndr.notes.nMarks = 0;
2493 rndr.notes.misref.id = empty_blob;
2494 rndr.notes.misref.text = empty_blob;
2495 rndr.notes.misref.nUsed = 0;
2496 rndr.notes.misref.iMark = -1;
2497
2498 for(i=0; i<256; i++) rndr.active_char[i] = 0;
2499 if( (rndr.make.emphasis
2500 || rndr.make.double_emphasis
2501 || rndr.make.triple_emphasis)
@@ -2527,36 +2549,84 @@
2549 cmp_link_ref_sort);
2550 }
2551 rndr.notes.nLbled = COUNT_FOOTNOTES(&rndr.notes.all);
2552 /* sorting the footnotes array by id */
2553 if( rndr.notes.nLbled ){
2554 fn = CAST_AS_FOOTNOTES(&rndr.notes.all);
2555 qsort(fn, rndr.notes.nLbled, sizeof(struct footnote),
2556 cmp_link_ref_sort);
2557 for(i=0; i<rndr.notes.nLbled; i++){
2558 fn[i].index = i;
2559 }
2560 /* FIXME: handle footnotes with duplicated labels */
2561 }
2562
2563 /* second pass: actual rendering */
2564 if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
2565 parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
2566
2567 if( (blob_size(&rndr.notes.all) || rndr.notes.misref.nUsed) ){
2568
2569 /* Footnotes must be parsed for the correct discovery of (back)links */
2570 Blob *notes = new_work_buffer( &rndr );
2571 Blob *tmp = new_work_buffer( &rndr );
2572 const struct Blob *origin = &rndr.notes.all;
2573 int nMarks = -1;
2574
2575 /* inline notes may get appended to rndr.notes.all while rendering */
2576 while(1){
2577 struct footnote *aNotes;
2578 const int N = COUNT_FOOTNOTES(origin);
2579
2580 /* make a shallow copy of `origin` */
2581 blob_truncate(notes,0);
2582 blob_append(notes, blob_buffer(origin), blob_size(origin));
2583 aNotes = CAST_AS_FOOTNOTES(notes);
2584 qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort);
2585
2586 if( nMarks == rndr.notes.nMarks ) break;
2587 nMarks = rndr.notes.nMarks;
2588
2589 for(i=0; i<N; i++){
2590 const int j = aNotes[i].index;
2591 struct footnote *x = CAST_AS_FOOTNOTES(origin) + j;
2592 assert( 0<=j && j<N );
2593 if( x->bRndred || !x->nUsed ) continue;
2594 assert( x->iMark > 0 );
2595 assert( blob_size(&x->text) );
2596 blob_truncate(tmp,0);
2597
2598 /* `origin` may be altered and extended through this call */
2599 parse_inline(tmp, &rndr, blob_buffer(&x->text), blob_size(&x->text));
2600
2601 blob_truncate(&x->text,0);
2602 blob_append(&x->text, blob_buffer(tmp), blob_size(tmp));
2603 x->bRndred = 1;
2604 }
2605 }
2606 release_work_buffer(&rndr,tmp);
2607
2608 /* footnotes rendering */
2609 if( rndr.make.footnote_item && rndr.make.footnotes ){
2610 Blob *all_items = new_work_buffer(&rndr);
2611 for(i=0; i<COUNT_FOOTNOTES(notes); i++){
2612 const struct footnote* x = CAST_AS_FOOTNOTES(notes) + i;
2613 if( x->bRndred ){
2614 rndr.make.footnote_item(all_items, &x->text, x->iMark,
2615 x->nUsed, rndr.make.opaque);
2616 }
2617 }
2618 if( rndr.notes.misref.nUsed ){
2619 rndr.make.footnote_item(all_items, 0, -1,
2620 rndr.notes.misref.nUsed, rndr.make.opaque);
2621 }
2622 /* TODO: handle unreferenced (defined but not used) footnotes */
2623
2624 rndr.make.footnotes(ob, all_items, rndr.make.opaque);
2625 release_work_buffer(&rndr, all_items);
2626 }
2627 release_work_buffer(&rndr, notes);
2628 }
2629 if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
2630
2631 /* clean-up */
2632 assert( rndr.iDepth==0 );
@@ -2567,10 +2637,11 @@
2637 blob_reset(&lr[i].id);
2638 blob_reset(&lr[i].link);
2639 blob_reset(&lr[i].title);
2640 }
2641 blob_reset(&rndr.refs);
2642 fn = CAST_AS_FOOTNOTES(&rndr.notes.all);
2643 end = COUNT_FOOTNOTES(&rndr.notes.all);
2644 for(i=0; i<end; i++){
2645 if(blob_size(&fn[i].id)) blob_reset(&fn[i].id);
2646 blob_reset(&fn[i].text);
2647 }
2648
+102 -63
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -325,90 +325,129 @@
325325
BLOB_APPEND_BLOB(ob, cells);
326326
BLOB_APPEND_LITERAL(ob, " </tr>\n");
327327
}
328328
329329
static int html_footnote_ref(
330
- struct Blob *ob, const struct Blob *span, int index, int locus, void *opaque
330
+ struct Blob *ob, const struct Blob *span, int iMark, int locus, void *opaque
331331
){
332
- const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
332
+ const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque;
333
+ const bitfield64_t l = to_base26(locus-1,0);
334
+ char pos[32];
335
+ memset(pos,0,32);
336
+ assert( locus > 0 );
333337
/* expect BUGs if the following yields compiler warnings */
338
+ if( iMark > 0 ){ /* a regular reference to a footnote */
334339
335
- if( index>0 && locus>0 ){
336
- const bitfield64_t l = to_base26(locus-1,0);
337
- char pos[32];
338
- memset(pos,0,32);
339
- sprintf(pos, "%s-%i-%s", ctx->unique.c, index, l.c);
340
-
340
+ sprintf(pos, "%s-%i-%s", ctx->unique.c, iMark, l.c);
341341
if(span && blob_size(span)) {
342342
BLOB_APPEND_LITERAL(ob,"<span class='notescope' id='noteref");
343343
blob_appendf(ob,"%s'>",pos);
344344
BLOB_APPEND_BLOB(ob, span);
345345
blob_trim(ob);
346
- BLOB_APPEND_LITERAL(ob,"<sup><a class='noteref' href='#footnote");
347
- blob_appendf(ob,"%s'>%i</a></sup></span>", pos, index);
348
- }else{
349
- blob_trim(ob);
350
- BLOB_APPEND_LITERAL(ob,"<sup><a class='noteref' href='#footnote");
351
- blob_appendf(ob,"%s' id='noteref%s'>%i</a></sup>",
352
- pos, pos, index);
353
- }
354
- }else if(span && blob_size(span)) {
355
- BLOB_APPEND_LITERAL(ob, "<span class='notescope' id='misref");
356
- blob_appendf(ob, "%s-%i'>", ctx->unique.c, -index);
357
- BLOB_APPEND_BLOB(ob, span);
358
- blob_trim(ob);
359
- BLOB_APPEND_LITERAL(ob,
360
- "<sup class='misref'>misreference</sup></span>");
361
- }else{
362
- blob_trim(ob);
363
- BLOB_APPEND_LITERAL(ob, "<sup class='misref' id='misref");
364
- blob_appendf(ob, "%s-%i", ctx->unique.c, -index);
365
- BLOB_APPEND_LITERAL(ob, "'>misreference</sup>");
346
+ BLOB_APPEND_LITERAL(ob,"<sup class='noteref'><a href='#footnote");
347
+ blob_appendf(ob,"%s'>%i</a></sup></span>", pos, iMark);
348
+ }else{
349
+ blob_trim(ob);
350
+ BLOB_APPEND_LITERAL(ob,"<sup class='noteref'><a href='#footnote");
351
+ blob_appendf(ob,"%s' id='noteref%s'>%i</a></sup>",
352
+ pos, pos, iMark);
353
+ }
354
+ }else{ /* misreference */
355
+ assert( iMark == -1 );
356
+
357
+ sprintf(pos, "%s-%s", ctx->unique.c, l.c);
358
+ if(span && blob_size(span)) {
359
+ blob_appendf(ob, "<span class='notescope' id='misref%s'>", pos);
360
+ BLOB_APPEND_BLOB(ob, span);
361
+ blob_trim(ob);
362
+ BLOB_APPEND_LITERAL(ob,
363
+ "<sup class='noteref misref'><a href='#misreference");
364
+ blob_appendf(ob, "%s'>misref</a></sup></span>", pos);
365
+ }else{
366
+ blob_trim(ob);
367
+ BLOB_APPEND_LITERAL(ob,
368
+ "<sup class='noteref misref'><a href='#misreference");
369
+ blob_appendf(ob, "%s' id='misref%s'>", pos, pos);
370
+ BLOB_APPEND_LITERAL(ob, "misref</a></sup>");
371
+ }
366372
}
367373
return 1;
368374
}
369375
370376
/* Render a single item of the footnotes list.
371377
* Each backref gets a unique id to enable dynamic styling. */
372378
static void html_footnote_item(
373
- struct Blob *ob, const struct Blob *text, int index, int nUsed, void *opaque
379
+ struct Blob *ob, const struct Blob *text, int iMark, int nUsed, void *opaque
374380
){
375
- const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
376
- char pos[24];
377
- if( index <= 0 || nUsed < 0 || !text || !blob_size(text) ){
378
- return;
379
- }
380
-
381
+ const char * const unique = ((struct MarkdownToHtml*)opaque)->unique.c;
382
+ assert( nUsed >= 0 );
381383
/* expect BUGs if the following yields compiler warnings */
382
- memset(pos,0,24);
383
- sprintf(pos, "%s-%i", ctx->unique.c, index);
384
-
385
- blob_appendf(ob, "<li id='footnote%s'>", pos);
386
- BLOB_APPEND_LITERAL(ob,"<sup class='footnote-backrefs'>");
387
- if( nUsed <= 1 ){
388
- blob_appendf(ob,"<a id='footnote%s-a' "
389
- "href='#noteref%s-a'>^</a>", pos, pos);
390
- }else{
391
- int i;
392
- blob_append_char(ob, '^');
393
- for(i=0; i<nUsed && i<26; i++){
394
- const int c = i + (unsigned)'a';
395
- blob_appendf(ob," <a id='footnote%s-%c'"
396
- " href='#noteref%s-%c'>%c</a>", pos,c, pos,c, c);
397
- }
398
- /* It's unlikely that so many backrefs will be usefull */
399
- /* but maybe for some machine generated documents... */
400
- for(; i<nUsed && i<676; i++){
401
- const bitfield64_t l = to_base26(i,0);
402
- blob_appendf(ob," <a id='footnote%s-%s'"
403
- " href='#noteref%s-%s'>%s</a>",
404
- pos,l.c, pos,l.c, l.c);
405
- }
406
- if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
407
- }
408
- BLOB_APPEND_LITERAL(ob,"</sup>\n");
409
- BLOB_APPEND_BLOB(ob, text);
384
+
385
+ if( iMark < 0 ){ /* misreferences */
386
+ assert( iMark == -1 );
387
+ if( !nUsed ) return;
388
+ BLOB_APPEND_LITERAL(ob,"<li class='misreferences'>"
389
+ "<sup class='footnote-backrefs'>");
390
+ if( nUsed == 1 ){
391
+ blob_appendf(ob,"<a id='misreference%s-a' "
392
+ "href='#misref%s-a'>^</a>", unique, unique);
393
+ }else{
394
+ int i;
395
+ blob_append_char(ob, '^');
396
+ for(i=0; i<nUsed && i<26; i++){
397
+ const int c = i + (unsigned)'a';
398
+ blob_appendf(ob," <a id='misreference%s-%c' "
399
+ "href='#misref%s-%c'>%c</a>", unique,c, unique,c, c);
400
+ }
401
+ if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
402
+ }
403
+ BLOB_APPEND_LITERAL(ob,"</sup>\nMisreference: use of undefined label.");
404
+
405
+ }else if( nUsed ){ /* a regular footnote */
406
+ char pos[24];
407
+ assert( text );
408
+ assert( blob_size(text) );
409
+
410
+ memset(pos,0,24);
411
+ sprintf(pos, "%s-%i", unique, iMark);
412
+
413
+ blob_appendf(ob, "<li id='footnote%s'>", pos);
414
+ BLOB_APPEND_LITERAL(ob,"<sup class='footnote-backrefs'>");
415
+ if( nUsed <= 1 ){
416
+ blob_appendf(ob,"<a id='footnote%s-a' "
417
+ "href='#noteref%s-a'>^</a>", pos, pos);
418
+ }else{
419
+ int i;
420
+ blob_append_char(ob, '^');
421
+ for(i=0; i<nUsed && i<26; i++){
422
+ const int c = i + (unsigned)'a';
423
+ blob_appendf(ob," <a id='footnote%s-%c'"
424
+ " href='#noteref%s-%c'>%c</a>", pos,c, pos,c, c);
425
+ }
426
+ /* It's unlikely that so many backrefs will be usefull */
427
+ /* but maybe for some machine generated documents... */
428
+ for(; i<nUsed && i<676; i++){
429
+ const bitfield64_t l = to_base26(i,0);
430
+ blob_appendf(ob," <a id='footnote%s-%s'"
431
+ " href='#noteref%s-%s'>%s</a>",
432
+ pos,l.c, pos,l.c, l.c);
433
+ }
434
+ if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
435
+ }
436
+ BLOB_APPEND_LITERAL(ob,"</sup>\n");
437
+ BLOB_APPEND_BLOB(ob, text);
438
+ }else{
439
+ /* a footnote was defined but wasn't used */
440
+ assert( text );
441
+ assert( blob_size(text) );
442
+ /* FIXME: not yet implemented */
443
+ return;
444
+ BLOB_APPEND_LITERAL(ob,
445
+ "<li class='unreferenced-footnote' id='unreferenced-footnote");
446
+ blob_appendf(ob,"%s-%i'>\n", unique, iMark);
447
+ BLOB_APPEND_BLOB(ob, text);
448
+ }
410449
BLOB_APPEND_LITERAL(ob, "\n</li>\n");
411450
}
412451
static void html_footnotes(
413452
struct Blob *ob, const struct Blob *items, void *opaque
414453
){
415454
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -325,90 +325,129 @@
325 BLOB_APPEND_BLOB(ob, cells);
326 BLOB_APPEND_LITERAL(ob, " </tr>\n");
327 }
328
329 static int html_footnote_ref(
330 struct Blob *ob, const struct Blob *span, int index, int locus, void *opaque
331 ){
332 const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
 
 
 
 
333 /* expect BUGs if the following yields compiler warnings */
 
334
335 if( index>0 && locus>0 ){
336 const bitfield64_t l = to_base26(locus-1,0);
337 char pos[32];
338 memset(pos,0,32);
339 sprintf(pos, "%s-%i-%s", ctx->unique.c, index, l.c);
340
341 if(span && blob_size(span)) {
342 BLOB_APPEND_LITERAL(ob,"<span class='notescope' id='noteref");
343 blob_appendf(ob,"%s'>",pos);
344 BLOB_APPEND_BLOB(ob, span);
345 blob_trim(ob);
346 BLOB_APPEND_LITERAL(ob,"<sup><a class='noteref' href='#footnote");
347 blob_appendf(ob,"%s'>%i</a></sup></span>", pos, index);
348 }else{
349 blob_trim(ob);
350 BLOB_APPEND_LITERAL(ob,"<sup><a class='noteref' href='#footnote");
351 blob_appendf(ob,"%s' id='noteref%s'>%i</a></sup>",
352 pos, pos, index);
353 }
354 }else if(span && blob_size(span)) {
355 BLOB_APPEND_LITERAL(ob, "<span class='notescope' id='misref");
356 blob_appendf(ob, "%s-%i'>", ctx->unique.c, -index);
357 BLOB_APPEND_BLOB(ob, span);
358 blob_trim(ob);
359 BLOB_APPEND_LITERAL(ob,
360 "<sup class='misref'>misreference</sup></span>");
361 }else{
362 blob_trim(ob);
363 BLOB_APPEND_LITERAL(ob, "<sup class='misref' id='misref");
364 blob_appendf(ob, "%s-%i", ctx->unique.c, -index);
365 BLOB_APPEND_LITERAL(ob, "'>misreference</sup>");
 
 
 
 
 
 
366 }
367 return 1;
368 }
369
370 /* Render a single item of the footnotes list.
371 * Each backref gets a unique id to enable dynamic styling. */
372 static void html_footnote_item(
373 struct Blob *ob, const struct Blob *text, int index, int nUsed, void *opaque
374 ){
375 const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
376 char pos[24];
377 if( index <= 0 || nUsed < 0 || !text || !blob_size(text) ){
378 return;
379 }
380
381 /* expect BUGs if the following yields compiler warnings */
382 memset(pos,0,24);
383 sprintf(pos, "%s-%i", ctx->unique.c, index);
384
385 blob_appendf(ob, "<li id='footnote%s'>", pos);
386 BLOB_APPEND_LITERAL(ob,"<sup class='footnote-backrefs'>");
387 if( nUsed <= 1 ){
388 blob_appendf(ob,"<a id='footnote%s-a' "
389 "href='#noteref%s-a'>^</a>", pos, pos);
390 }else{
391 int i;
392 blob_append_char(ob, '^');
393 for(i=0; i<nUsed && i<26; i++){
394 const int c = i + (unsigned)'a';
395 blob_appendf(ob," <a id='footnote%s-%c'"
396 " href='#noteref%s-%c'>%c</a>", pos,c, pos,c, c);
397 }
398 /* It's unlikely that so many backrefs will be usefull */
399 /* but maybe for some machine generated documents... */
400 for(; i<nUsed && i<676; i++){
401 const bitfield64_t l = to_base26(i,0);
402 blob_appendf(ob," <a id='footnote%s-%s'"
403 " href='#noteref%s-%s'>%s</a>",
404 pos,l.c, pos,l.c, l.c);
405 }
406 if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
407 }
408 BLOB_APPEND_LITERAL(ob,"</sup>\n");
409 BLOB_APPEND_BLOB(ob, text);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410 BLOB_APPEND_LITERAL(ob, "\n</li>\n");
411 }
412 static void html_footnotes(
413 struct Blob *ob, const struct Blob *items, void *opaque
414 ){
415
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -325,90 +325,129 @@
325 BLOB_APPEND_BLOB(ob, cells);
326 BLOB_APPEND_LITERAL(ob, " </tr>\n");
327 }
328
329 static int html_footnote_ref(
330 struct Blob *ob, const struct Blob *span, int iMark, int locus, void *opaque
331 ){
332 const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque;
333 const bitfield64_t l = to_base26(locus-1,0);
334 char pos[32];
335 memset(pos,0,32);
336 assert( locus > 0 );
337 /* expect BUGs if the following yields compiler warnings */
338 if( iMark > 0 ){ /* a regular reference to a footnote */
339
340 sprintf(pos, "%s-%i-%s", ctx->unique.c, iMark, l.c);
 
 
 
 
 
341 if(span && blob_size(span)) {
342 BLOB_APPEND_LITERAL(ob,"<span class='notescope' id='noteref");
343 blob_appendf(ob,"%s'>",pos);
344 BLOB_APPEND_BLOB(ob, span);
345 blob_trim(ob);
346 BLOB_APPEND_LITERAL(ob,"<sup class='noteref'><a href='#footnote");
347 blob_appendf(ob,"%s'>%i</a></sup></span>", pos, iMark);
348 }else{
349 blob_trim(ob);
350 BLOB_APPEND_LITERAL(ob,"<sup class='noteref'><a href='#footnote");
351 blob_appendf(ob,"%s' id='noteref%s'>%i</a></sup>",
352 pos, pos, iMark);
353 }
354 }else{ /* misreference */
355 assert( iMark == -1 );
356
357 sprintf(pos, "%s-%s", ctx->unique.c, l.c);
358 if(span && blob_size(span)) {
359 blob_appendf(ob, "<span class='notescope' id='misref%s'>", pos);
360 BLOB_APPEND_BLOB(ob, span);
361 blob_trim(ob);
362 BLOB_APPEND_LITERAL(ob,
363 "<sup class='noteref misref'><a href='#misreference");
364 blob_appendf(ob, "%s'>misref</a></sup></span>", pos);
365 }else{
366 blob_trim(ob);
367 BLOB_APPEND_LITERAL(ob,
368 "<sup class='noteref misref'><a href='#misreference");
369 blob_appendf(ob, "%s' id='misref%s'>", pos, pos);
370 BLOB_APPEND_LITERAL(ob, "misref</a></sup>");
371 }
372 }
373 return 1;
374 }
375
376 /* Render a single item of the footnotes list.
377 * Each backref gets a unique id to enable dynamic styling. */
378 static void html_footnote_item(
379 struct Blob *ob, const struct Blob *text, int iMark, int nUsed, void *opaque
380 ){
381 const char * const unique = ((struct MarkdownToHtml*)opaque)->unique.c;
382 assert( nUsed >= 0 );
 
 
 
 
383 /* expect BUGs if the following yields compiler warnings */
384
385 if( iMark < 0 ){ /* misreferences */
386 assert( iMark == -1 );
387 if( !nUsed ) return;
388 BLOB_APPEND_LITERAL(ob,"<li class='misreferences'>"
389 "<sup class='footnote-backrefs'>");
390 if( nUsed == 1 ){
391 blob_appendf(ob,"<a id='misreference%s-a' "
392 "href='#misref%s-a'>^</a>", unique, unique);
393 }else{
394 int i;
395 blob_append_char(ob, '^');
396 for(i=0; i<nUsed && i<26; i++){
397 const int c = i + (unsigned)'a';
398 blob_appendf(ob," <a id='misreference%s-%c' "
399 "href='#misref%s-%c'>%c</a>", unique,c, unique,c, c);
400 }
401 if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
402 }
403 BLOB_APPEND_LITERAL(ob,"</sup>\nMisreference: use of undefined label.");
404
405 }else if( nUsed ){ /* a regular footnote */
406 char pos[24];
407 assert( text );
408 assert( blob_size(text) );
409
410 memset(pos,0,24);
411 sprintf(pos, "%s-%i", unique, iMark);
412
413 blob_appendf(ob, "<li id='footnote%s'>", pos);
414 BLOB_APPEND_LITERAL(ob,"<sup class='footnote-backrefs'>");
415 if( nUsed <= 1 ){
416 blob_appendf(ob,"<a id='footnote%s-a' "
417 "href='#noteref%s-a'>^</a>", pos, pos);
418 }else{
419 int i;
420 blob_append_char(ob, '^');
421 for(i=0; i<nUsed && i<26; i++){
422 const int c = i + (unsigned)'a';
423 blob_appendf(ob," <a id='footnote%s-%c'"
424 " href='#noteref%s-%c'>%c</a>", pos,c, pos,c, c);
425 }
426 /* It's unlikely that so many backrefs will be usefull */
427 /* but maybe for some machine generated documents... */
428 for(; i<nUsed && i<676; i++){
429 const bitfield64_t l = to_base26(i,0);
430 blob_appendf(ob," <a id='footnote%s-%s'"
431 " href='#noteref%s-%s'>%s</a>",
432 pos,l.c, pos,l.c, l.c);
433 }
434 if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
435 }
436 BLOB_APPEND_LITERAL(ob,"</sup>\n");
437 BLOB_APPEND_BLOB(ob, text);
438 }else{
439 /* a footnote was defined but wasn't used */
440 assert( text );
441 assert( blob_size(text) );
442 /* FIXME: not yet implemented */
443 return;
444 BLOB_APPEND_LITERAL(ob,
445 "<li class='unreferenced-footnote' id='unreferenced-footnote");
446 blob_appendf(ob,"%s-%i'>\n", unique, iMark);
447 BLOB_APPEND_BLOB(ob, text);
448 }
449 BLOB_APPEND_LITERAL(ob, "\n</li>\n");
450 }
451 static void html_footnotes(
452 struct Blob *ob, const struct Blob *items, void *opaque
453 ){
454
--- test/markdown-test3.md
+++ test/markdown-test3.md
@@ -8,10 +8,12 @@
88
executable that incorporates the abovementioned branch.**[^1]
99
1010
Developers are invited to add test cases here[^here].
1111
It is suggested that the more simple is a test case the earlier it should
1212
appear in this document.[^ if glitch occurs ]
13
+
14
+[^lost3]: This note was defined at the begining of the document.
1315
1416
A footnote's label should be case insensitive[^ case INSENSITIVE ],
1517
it is whitespace-savvy and can even contain newlines.[^ a
1618
multiline
1719
label]
@@ -19,18 +21,27 @@
1921
A labeled footnote may be [referenced several times][^many-refs].
2022
2123
A footnote's text should support Markdown [markup][^].
2224
2325
Another reference[^many-refs] to the preveously used footnote.
26
+
27
+[^lost2]: This note was defined in the middle of the document.
28
+ It references [its previous][^lost3]
29
+ and [the forthcoming][^lost1] siblings.
30
+
31
+[^i am unreferenced]: If this is rendered wihin footnotes,
32
+ then there is a BUG!
2433
2534
Inline footnotes are supported.(^These may be usefull for adding
2635
<s>small</s> comments.)
2736
28
-If [undefined label is used][^] then red "`misreference`" is emited instead of
37
+If [undefined label is used][^] then red "`misref`" is emited instead of
2938
a numeric marker.[^ see it yourself ]
3039
This can be overridden by the skin though.
3140
41
+The refenrence at the end of this sentence is the sole reason of
42
+rendering of <s>`lost1` and</s> [lost2][^].
3243
3344
## Footnotes
3445
3546
[branch]: /timeline?r=markdown-footnotes&nowiki
3647
@@ -50,11 +61,17 @@
5061
on a single line.
5162
5263
5364
[^many-refs]:
5465
Each letter on the left is a back-reference to the place of use.
55
- Highlighted back-reference indicates a place from which navigation occurred.
66
+ Highlighted back-reference indicates a place from which navigation
67
+ occurred[^lost1].
68
+
69
+[^lost1]: This note was defined at the end of the document.
70
+ It defines an inline note.
71
+
72
+ (^This is inline note defined inside of [a labeled note][^lost1].)
5673
5774
[^markup]: E.g. *emphasis*, and [so on](/md_rules).
5875
5976
[^undefined label is used]: For example due to a typo.
6077
6178
--- test/markdown-test3.md
+++ test/markdown-test3.md
@@ -8,10 +8,12 @@
8 executable that incorporates the abovementioned branch.**[^1]
9
10 Developers are invited to add test cases here[^here].
11 It is suggested that the more simple is a test case the earlier it should
12 appear in this document.[^ if glitch occurs ]
 
 
13
14 A footnote's label should be case insensitive[^ case INSENSITIVE ],
15 it is whitespace-savvy and can even contain newlines.[^ a
16 multiline
17 label]
@@ -19,18 +21,27 @@
19 A labeled footnote may be [referenced several times][^many-refs].
20
21 A footnote's text should support Markdown [markup][^].
22
23 Another reference[^many-refs] to the preveously used footnote.
 
 
 
 
 
 
 
24
25 Inline footnotes are supported.(^These may be usefull for adding
26 <s>small</s> comments.)
27
28 If [undefined label is used][^] then red "`misreference`" is emited instead of
29 a numeric marker.[^ see it yourself ]
30 This can be overridden by the skin though.
31
 
 
32
33 ## Footnotes
34
35 [branch]: /timeline?r=markdown-footnotes&nowiki
36
@@ -50,11 +61,17 @@
50 on a single line.
51
52
53 [^many-refs]:
54 Each letter on the left is a back-reference to the place of use.
55 Highlighted back-reference indicates a place from which navigation occurred.
 
 
 
 
 
 
56
57 [^markup]: E.g. *emphasis*, and [so on](/md_rules).
58
59 [^undefined label is used]: For example due to a typo.
60
61
--- test/markdown-test3.md
+++ test/markdown-test3.md
@@ -8,10 +8,12 @@
8 executable that incorporates the abovementioned branch.**[^1]
9
10 Developers are invited to add test cases here[^here].
11 It is suggested that the more simple is a test case the earlier it should
12 appear in this document.[^ if glitch occurs ]
13
14 [^lost3]: This note was defined at the begining of the document.
15
16 A footnote's label should be case insensitive[^ case INSENSITIVE ],
17 it is whitespace-savvy and can even contain newlines.[^ a
18 multiline
19 label]
@@ -19,18 +21,27 @@
21 A labeled footnote may be [referenced several times][^many-refs].
22
23 A footnote's text should support Markdown [markup][^].
24
25 Another reference[^many-refs] to the preveously used footnote.
26
27 [^lost2]: This note was defined in the middle of the document.
28 It references [its previous][^lost3]
29 and [the forthcoming][^lost1] siblings.
30
31 [^i am unreferenced]: If this is rendered wihin footnotes,
32 then there is a BUG!
33
34 Inline footnotes are supported.(^These may be usefull for adding
35 <s>small</s> comments.)
36
37 If [undefined label is used][^] then red "`misref`" is emited instead of
38 a numeric marker.[^ see it yourself ]
39 This can be overridden by the skin though.
40
41 The refenrence at the end of this sentence is the sole reason of
42 rendering of <s>`lost1` and</s> [lost2][^].
43
44 ## Footnotes
45
46 [branch]: /timeline?r=markdown-footnotes&nowiki
47
@@ -50,11 +61,17 @@
61 on a single line.
62
63
64 [^many-refs]:
65 Each letter on the left is a back-reference to the place of use.
66 Highlighted back-reference indicates a place from which navigation
67 occurred[^lost1].
68
69 [^lost1]: This note was defined at the end of the document.
70 It defines an inline note.
71
72 (^This is inline note defined inside of [a labeled note][^lost1].)
73
74 [^markup]: E.g. *emphasis*, and [so on](/md_rules).
75
76 [^undefined label is used]: For example due to a typo.
77
78

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button