Fossil SCM

Decouple parsing and HTML-specific rendering. Add support for back references in the list of footnotes. WIP - inline and multiline footnotes are not yet implemented.

george 2022-01-29 00:19 markdown-footnotes
Commit e3710ccd3a5af6ad7b784f5ac42b78105f87932f85d598afe4b5daabfe905696
--- src/default.css
+++ src/default.css
@@ -1670,10 +1670,14 @@
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.content div.markdown > ol.footnotes > li > .footnote-backrefs {
1677
+ margin-right: 1em;
1678
+ font-weight: bold;
16751679
}
16761680
16771681
/* Objects in the "desktoponly" class are invisible on mobile */
16781682
@media screen and (max-width: 600px) {
16791683
.desktoponly {
16801684
--- src/default.css
+++ src/default.css
@@ -1670,10 +1670,14 @@
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
1677 /* Objects in the "desktoponly" class are invisible on mobile */
1678 @media screen and (max-width: 600px) {
1679 .desktoponly {
1680
--- src/default.css
+++ src/default.css
@@ -1670,10 +1670,14 @@
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: 1em;
1678 font-weight: bold;
1679 }
1680
1681 /* Objects in the "desktoponly" class are invisible on mobile */
1682 @media screen and (max-width: 600px) {
1683 .desktoponly {
1684
+79 -59
--- src/markdown.c
+++ src/markdown.c
@@ -45,10 +45,11 @@
4545
/* mkd_renderer -- functions for rendering parsed data */
4646
struct mkd_renderer {
4747
/* document level callbacks */
4848
void (*prolog)(struct Blob *ob, void *opaque);
4949
void (*epilog)(struct Blob *ob, void *opaque);
50
+ void (*footnotes)(struct Blob *ob, const struct Blob *items, void *opaque);
5051
5152
/* block level callbacks - NULL skips the block */
5253
void (*blockcode)(struct Blob *ob, struct Blob *text, void *opaque);
5354
void (*blockquote)(struct Blob *ob, struct Blob *text, void *opaque);
5455
void (*blockhtml)(struct Blob *ob, struct Blob *text, void *opaque);
@@ -63,10 +64,12 @@
6364
void *opaque);
6465
void (*table_cell)(struct Blob *ob, struct Blob *text, int flags,
6566
void *opaque);
6667
void (*table_row)(struct Blob *ob, struct Blob *cells, int flags,
6768
void *opaque);
69
+ void (*footnote_item)(struct Blob *ob, const struct Blob *text,
70
+ int index, int nUsed, void *opaque);
6871
6972
/* span level callbacks - NULL or return 0 prints the span verbatim */
7073
int (*autolink)(struct Blob *ob, struct Blob *link,
7174
enum mkd_autolink type, void *opaque);
7275
int (*codespan)(struct Blob *ob, struct Blob *text, int nSep, void *opaque);
@@ -79,10 +82,11 @@
7982
int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
8083
struct Blob *content, void *opaque);
8184
int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
8285
int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
8386
char c, void *opaque);
87
+ int (*footnote_ref)(struct Blob *ob, int index, int locus, void *opaque);
8488
8589
/* low level callbacks - NULL copies input directly into the output */
8690
void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
8791
void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);
8892
@@ -122,10 +126,12 @@
122126
const struct mkd_renderer *rndr);
123127
124128
125129
#endif /* INTERFACE */
126130
131
+#define BLOB_COUNT(blob_ptr,el_type) (blob_size(blob_ptr)/sizeof(el_type))
132
+#define COUNT_FOOTNOTES(blob_ptr) BLOB_COUNT(blob_ptr,struct footnote)
127133
128134
/***************
129135
* LOCAL TYPES *
130136
***************/
131137
@@ -138,10 +144,11 @@
138144
139145
struct footnote {
140146
struct Blob id; /* must be the first field as in link_ref struct */
141147
struct Blob text; /* footnote's content that is rendered at the end */
142148
int index; /* serial number, in the order of appearance */
149
+ int nUsed; /* number of references to this note */
143150
};
144151
145152
146153
/* char_trigger -- function pointer to render active chars */
147154
/* returns the number of chars taken care of */
@@ -158,16 +165,17 @@
158165
159166
/* render -- structure containing one particular render */
160167
struct render {
161168
struct mkd_renderer make;
162169
struct Blob refs;
163
- struct Blob footnotes;
164170
char_trigger active_char[256];
165171
int iDepth; /* Depth of recursion */
166172
int nBlobCache; /* Number of entries in aBlobCache */
167173
struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
168
- int nLabels; /* Footnotes counter for the second pass */
174
+
175
+ struct Blob notes; /* array of footnotes */
176
+ int iNotesCount; /* count distinct indices found in the second pass */
169177
};
170178
171179
/* html_tag -- structure for quick HTML tag search (inspired from discount) */
172180
struct html_tag {
173181
const char *text;
@@ -254,15 +262,18 @@
254262
struct link_ref *lra = (void *)a;
255263
struct link_ref *lrb = (void *)b;
256264
return blob_compare(&lra->id, &lrb->id);
257265
}
258266
259
-/* cmp_footnote_sort -- comparison function for footnotes qsort */
267
+/* cmp_footnote_sort -- comparison function for footnotes qsort.
268
+ * Unused footnotes (when index == 0) sort last */
260269
static int cmp_footnote_sort(const void *a, const void *b){
261270
const struct footnote *fna = (void *)a, *fnb = (void *)b;
262
- assert( fna->index > 0 && fnb->index > 0 );
271
+ assert( fna->index >= 0 && fnb->index >= 0 );
263272
if( fna->index == fnb->index ) return 0;
273
+ if( fna->index == 0 ) return 1;
274
+ if( fnb->index == 0 ) return -1;
264275
return ( fna->index < fnb->index ? -1 : 1 );
265276
}
266277
267278
/* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
268279
static int cmp_html_tag(const void *a, const void *b){
@@ -1017,38 +1028,37 @@
10171028
blob_append(link, blob_buffer(&lr->link), blob_size(&lr->link));
10181029
blob_append(title, blob_buffer(&lr->title), blob_size(&lr->title));
10191030
return 0;
10201031
}
10211032
1022
-/* get_footnote -- resolve footnote by its id
1033
+/* get_footnote() is invoked during the second pass
10231034
* on success: fill text and return positive footnote's index
10241035
* on failure: return -1 */
1025
-static int get_footnote(
1036
+static const struct footnote* get_footnote(
10261037
struct render *rndr,
1027
- struct Blob *text,
10281038
const char *data,
10291039
size_t size
10301040
){
1031
- struct footnote *fn;
1032
-
1033
- blob_reset(text); /* use text for temporary storage */
1034
- if( build_ref_id(text, data, size)<0 ) return -1;
1035
-
1036
- fn = bsearch(text,
1037
- blob_buffer(&rndr->footnotes),
1038
- blob_size(&rndr->footnotes)/sizeof(struct footnote),
1041
+ struct footnote *fn = NULL;
1042
+ struct Blob *id = new_work_buffer(rndr);
1043
+ if( build_ref_id(id, data, size)<0 ) goto cleanup;
1044
+ fn = bsearch(id, blob_buffer(&rndr->notes),
1045
+ COUNT_FOOTNOTES(&rndr->notes),
10391046
sizeof (struct footnote),
10401047
cmp_link_ref);
1041
- if( !fn ) return -1;
1048
+ if( !fn ) goto cleanup;
10421049
10431050
if( fn->index == 0 ){ /* the first reference to the footnote */
1044
- fn->index = ++(rndr->nLabels);
1051
+ assert( fn->nUsed == 0 );
1052
+ fn->index = ++(rndr->iNotesCount);
10451053
}
1054
+ fn->nUsed++;
10461055
assert( fn->index > 0 );
1047
- blob_reset(text);
1048
- blob_append(text, blob_buffer(&fn->text), blob_size(&fn->text));
1049
- return fn->index;
1056
+ assert( fn->nUsed > 0 );
1057
+cleanup:
1058
+ release_work_buffer( rndr, id );
1059
+ return fn;
10501060
}
10511061
10521062
/* char_link -- '[': parsing a link or an image */
10531063
static size_t char_link(
10541064
struct Blob *ob,
@@ -1055,17 +1065,19 @@
10551065
struct render *rndr,
10561066
char *data,
10571067
size_t offset,
10581068
size_t size
10591069
){
1060
- int is_img = (offset && data[-1] == '!'), level;
1070
+ const int is_img = (offset && data[-1] == '!');
1071
+ const int is_inline = (offset && data[-1]=='^');
1072
+ const int is_note = !is_img && (is_inline || (size>1 && data[1]=='^'));
10611073
size_t i = 1, txt_e;
10621074
struct Blob *content = 0;
10631075
struct Blob *link = 0;
10641076
struct Blob *title = 0;
1065
- const int is_note = (size && data[1] == '^');
1066
- int ret;
1077
+ const struct footnote *fn = 0;
1078
+ int level, ret;
10671079
10681080
/* checking whether the correct renderer exists */
10691081
if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){
10701082
return 0;
10711083
}
@@ -1135,17 +1147,16 @@
11351147
i = id_end+1;
11361148
11371149
/* shortcut reference style link */
11381150
}else{
11391151
if( is_note ){
1140
- const int lbl = get_footnote(rndr, link, data+1, txt_e-1);
1141
- if( lbl <= 0 ){
1142
- goto char_link_cleanup;
1152
+ if( is_inline ){
1153
+ //fn = put_footnote(rndr, data+1, txt_e-1);
1154
+ }else{
1155
+ fn = get_footnote(rndr, data+1, txt_e-1);
11431156
}
1144
- blob_reset(link);
1145
- blob_appendf(link, "#footnote-%i", lbl);
1146
- blob_appendf(content,"<sup class='footnote'>%i</sup>",lbl);
1157
+ if( !fn ) goto char_link_cleanup;
11471158
}else if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
11481159
goto char_link_cleanup;
11491160
}
11501161
11511162
/* rewinding the whitespace */
@@ -1153,17 +1164,21 @@
11531164
}
11541165
11551166
/* building content: img alt is escaped, link content is parsed */
11561167
if( txt_e>1 ){
11571168
if( is_img ) blob_append(content, data+1, txt_e-1);
1158
- else if(!is_note) parse_inline(content, rndr, data+1, txt_e-1);
1169
+ else if(is_inline) parse_inline(content, rndr, data+1, txt_e-1);
11591170
}
11601171
11611172
/* calling the relevant rendering function */
11621173
if( is_img ){
11631174
if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
11641175
ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
1176
+ }else if(fn){
1177
+ if(rndr->make.footnote_ref){
1178
+ ret = rndr->make.footnote_ref(ob,fn->index,fn->nUsed,rndr->make.opaque);
1179
+ }
11651180
}else{
11661181
ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
11671182
}
11681183
11691184
/* cleanup */
@@ -2250,11 +2265,11 @@
22502265
){
22512266
size_t i = 0;
22522267
size_t id_offset, id_end;
22532268
size_t note_offset, note_end;
22542269
size_t line_end;
2255
- struct footnote fn = { empty_blob, empty_blob, 0 };
2270
+ struct footnote fn = { empty_blob, empty_blob, 0, 0 };
22562271
22572272
/* footnote definition must start at the begining of a line */
22582273
if( beg+4>=end ) return 0;
22592274
i += beg;
22602275
@@ -2311,20 +2326,21 @@
23112326
){
23122327
struct link_ref *lr;
23132328
struct footnote *fn;
23142329
size_t i, beg, end = 0;
23152330
struct render rndr;
2316
- Blob text = BLOB_INITIALIZER; /* input after the first pass */
2331
+ Blob text = BLOB_INITIALIZER; /* input after the first pass */
2332
+ int nLabeled; /* number of footnotes found by the first pass */
23172333
23182334
/* filling the render structure */
23192335
if( !rndrer ) return;
23202336
rndr.make = *rndrer;
23212337
rndr.nBlobCache = 0;
23222338
rndr.iDepth = 0;
2323
- rndr.refs = empty_blob;
2324
- rndr.footnotes = empty_blob;
2325
- rndr.nLabels = 0;
2339
+ rndr.refs = empty_blob;
2340
+ rndr.notes = empty_blob;
2341
+ rndr.iNotesCount = 0;
23262342
for(i=0; i<256; i++) rndr.active_char[i] = 0;
23272343
if( (rndr.make.emphasis
23282344
|| rndr.make.double_emphasis
23292345
|| rndr.make.triple_emphasis)
23302346
&& rndr.make.emph_chars
@@ -2345,11 +2361,11 @@
23452361
beg = 0;
23462362
for(const size_t size = blob_size(ib); beg<size ;){
23472363
const char* const data = blob_buffer(ib);
23482364
if( is_ref(data, beg, size, &end, &rndr.refs) ){
23492365
beg = end;
2350
- }else if( is_footnote(data, beg, size, &end, &rndr.footnotes) ){
2366
+ }else if(is_footnote(data, beg, size, &end, &rndr.notes)){
23512367
/* FIXME: fossil_print("\nfootnote found at %i\n", beg); */
23522368
beg = end;
23532369
}else{ /* skipping to the next line */
23542370
end = beg;
23552371
while( end<size && data[end]!='\n' && data[end]!='\r' ){
@@ -2373,41 +2389,39 @@
23732389
qsort(blob_buffer(&rndr.refs),
23742390
blob_size(&rndr.refs)/sizeof(struct link_ref),
23752391
sizeof(struct link_ref),
23762392
cmp_link_ref_sort);
23772393
}
2394
+ nLabeled = COUNT_FOOTNOTES(&rndr.notes);
23782395
/* sorting the footnotes array by id */
2379
- if( blob_size(&rndr.footnotes) ){
2380
- qsort(blob_buffer(&rndr.footnotes),
2381
- blob_size(&rndr.footnotes)/sizeof(struct footnote),
2382
- sizeof(struct footnote),
2396
+ if( nLabeled ){
2397
+ qsort(blob_buffer(&rndr.notes), nLabeled, sizeof(struct footnote),
23832398
cmp_link_ref_sort);
23842399
}
23852400
23862401
/* second pass: actual rendering */
23872402
if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
23882403
parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
23892404
2390
- /* sorting the footnotes array by index */
2391
- if( blob_size(&rndr.footnotes) ){
2392
- qsort(blob_buffer(&rndr.footnotes),
2393
- blob_size(&rndr.footnotes)/sizeof(struct footnote),
2394
- sizeof(struct footnote),
2395
- cmp_footnote_sort);
2396
- }
2397
- /* FIXME: decouple parsing and HTML-specific rendering of footnotes */
2398
- if( rndr.nLabels ){
2399
- fn = (struct footnote *)blob_buffer(&rndr.footnotes);
2400
- end = blob_size(&rndr.footnotes)/sizeof(struct footnote);
2401
- blob_appendf(ob, "\n<ol class='footnotes'>\n");
2402
- for(i=0; i<end; i++){
2403
- if(fn[i].index == 0) continue;
2404
- blob_appendf(ob, "<li id='footnote-%i'>\n ", fn[i].index );
2405
- parse_inline(ob,&rndr,blob_buffer(&fn[i].text),blob_size(&fn[i].text));
2406
- blob_append(ob,"\n</li>\n",7);
2407
- }
2408
- blob_append(ob, "</ol>\n", 7);
2405
+ fn = (struct footnote*)blob_buffer(&rndr.notes);
2406
+ if(rndr.iNotesCount && rndr.make.footnote_item && rndr.make.footnotes){
2407
+ Blob * one_item = new_work_buffer(&rndr);
2408
+ Blob * all_items = new_work_buffer(&rndr);
2409
+ qsort( fn, COUNT_FOOTNOTES(&rndr.notes), sizeof(struct footnote),
2410
+ cmp_footnote_sort /* sort footnotes by index */ );
2411
+ blob_reset( all_items );
2412
+ for(i=0; i<rndr.iNotesCount; i++){
2413
+ assert( fn[i].index == i+1 );
2414
+
2415
+ blob_reset( one_item );
2416
+ parse_inline( one_item, &rndr, blob_buffer(&fn[i].text),
2417
+ blob_size(&fn[i].text));
2418
+ rndr.make.footnote_item( all_items, one_item, i+1, fn[i].nUsed, rndr.make.opaque);
2419
+ }
2420
+ rndr.make.footnotes(ob, all_items, rndr.make.opaque );
2421
+ release_work_buffer( &rndr, one_item );
2422
+ release_work_buffer( &rndr, all_items );
24092423
}
24102424
if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
24112425
24122426
/* clean-up */
24132427
assert( rndr.iDepth==0 );
@@ -2418,9 +2432,15 @@
24182432
blob_reset(&lr[i].id);
24192433
blob_reset(&lr[i].link);
24202434
blob_reset(&lr[i].title);
24212435
}
24222436
blob_reset(&rndr.refs);
2437
+ end = COUNT_FOOTNOTES(&rndr.notes);
2438
+ for(i=0; i<end; i++){
2439
+ blob_reset(&fn[i].id);
2440
+ blob_reset(&fn[i].text);
2441
+ }
2442
+ blob_reset(&rndr.notes);
24232443
for(i=0; i<rndr.nBlobCache; i++){
24242444
fossil_free(rndr.aBlobCache[i]);
24252445
}
24262446
}
24272447
--- src/markdown.c
+++ src/markdown.c
@@ -45,10 +45,11 @@
45 /* mkd_renderer -- functions for rendering parsed data */
46 struct mkd_renderer {
47 /* document level callbacks */
48 void (*prolog)(struct Blob *ob, void *opaque);
49 void (*epilog)(struct Blob *ob, void *opaque);
 
50
51 /* block level callbacks - NULL skips the block */
52 void (*blockcode)(struct Blob *ob, struct Blob *text, void *opaque);
53 void (*blockquote)(struct Blob *ob, struct Blob *text, void *opaque);
54 void (*blockhtml)(struct Blob *ob, struct Blob *text, void *opaque);
@@ -63,10 +64,12 @@
63 void *opaque);
64 void (*table_cell)(struct Blob *ob, struct Blob *text, int flags,
65 void *opaque);
66 void (*table_row)(struct Blob *ob, struct Blob *cells, int flags,
67 void *opaque);
 
 
68
69 /* span level callbacks - NULL or return 0 prints the span verbatim */
70 int (*autolink)(struct Blob *ob, struct Blob *link,
71 enum mkd_autolink type, void *opaque);
72 int (*codespan)(struct Blob *ob, struct Blob *text, int nSep, void *opaque);
@@ -79,10 +82,11 @@
79 int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
80 struct Blob *content, void *opaque);
81 int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
82 int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
83 char c, void *opaque);
 
84
85 /* low level callbacks - NULL copies input directly into the output */
86 void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
87 void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);
88
@@ -122,10 +126,12 @@
122 const struct mkd_renderer *rndr);
123
124
125 #endif /* INTERFACE */
126
 
 
127
128 /***************
129 * LOCAL TYPES *
130 ***************/
131
@@ -138,10 +144,11 @@
138
139 struct footnote {
140 struct Blob id; /* must be the first field as in link_ref struct */
141 struct Blob text; /* footnote's content that is rendered at the end */
142 int index; /* serial number, in the order of appearance */
 
143 };
144
145
146 /* char_trigger -- function pointer to render active chars */
147 /* returns the number of chars taken care of */
@@ -158,16 +165,17 @@
158
159 /* render -- structure containing one particular render */
160 struct render {
161 struct mkd_renderer make;
162 struct Blob refs;
163 struct Blob footnotes;
164 char_trigger active_char[256];
165 int iDepth; /* Depth of recursion */
166 int nBlobCache; /* Number of entries in aBlobCache */
167 struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
168 int nLabels; /* Footnotes counter for the second pass */
 
 
169 };
170
171 /* html_tag -- structure for quick HTML tag search (inspired from discount) */
172 struct html_tag {
173 const char *text;
@@ -254,15 +262,18 @@
254 struct link_ref *lra = (void *)a;
255 struct link_ref *lrb = (void *)b;
256 return blob_compare(&lra->id, &lrb->id);
257 }
258
259 /* cmp_footnote_sort -- comparison function for footnotes qsort */
 
260 static int cmp_footnote_sort(const void *a, const void *b){
261 const struct footnote *fna = (void *)a, *fnb = (void *)b;
262 assert( fna->index > 0 && fnb->index > 0 );
263 if( fna->index == fnb->index ) return 0;
 
 
264 return ( fna->index < fnb->index ? -1 : 1 );
265 }
266
267 /* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
268 static int cmp_html_tag(const void *a, const void *b){
@@ -1017,38 +1028,37 @@
1017 blob_append(link, blob_buffer(&lr->link), blob_size(&lr->link));
1018 blob_append(title, blob_buffer(&lr->title), blob_size(&lr->title));
1019 return 0;
1020 }
1021
1022 /* get_footnote -- resolve footnote by its id
1023 * on success: fill text and return positive footnote's index
1024 * on failure: return -1 */
1025 static int get_footnote(
1026 struct render *rndr,
1027 struct Blob *text,
1028 const char *data,
1029 size_t size
1030 ){
1031 struct footnote *fn;
1032
1033 blob_reset(text); /* use text for temporary storage */
1034 if( build_ref_id(text, data, size)<0 ) return -1;
1035
1036 fn = bsearch(text,
1037 blob_buffer(&rndr->footnotes),
1038 blob_size(&rndr->footnotes)/sizeof(struct footnote),
1039 sizeof (struct footnote),
1040 cmp_link_ref);
1041 if( !fn ) return -1;
1042
1043 if( fn->index == 0 ){ /* the first reference to the footnote */
1044 fn->index = ++(rndr->nLabels);
 
1045 }
 
1046 assert( fn->index > 0 );
1047 blob_reset(text);
1048 blob_append(text, blob_buffer(&fn->text), blob_size(&fn->text));
1049 return fn->index;
 
1050 }
1051
1052 /* char_link -- '[': parsing a link or an image */
1053 static size_t char_link(
1054 struct Blob *ob,
@@ -1055,17 +1065,19 @@
1055 struct render *rndr,
1056 char *data,
1057 size_t offset,
1058 size_t size
1059 ){
1060 int is_img = (offset && data[-1] == '!'), level;
 
 
1061 size_t i = 1, txt_e;
1062 struct Blob *content = 0;
1063 struct Blob *link = 0;
1064 struct Blob *title = 0;
1065 const int is_note = (size && data[1] == '^');
1066 int ret;
1067
1068 /* checking whether the correct renderer exists */
1069 if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){
1070 return 0;
1071 }
@@ -1135,17 +1147,16 @@
1135 i = id_end+1;
1136
1137 /* shortcut reference style link */
1138 }else{
1139 if( is_note ){
1140 const int lbl = get_footnote(rndr, link, data+1, txt_e-1);
1141 if( lbl <= 0 ){
1142 goto char_link_cleanup;
 
1143 }
1144 blob_reset(link);
1145 blob_appendf(link, "#footnote-%i", lbl);
1146 blob_appendf(content,"<sup class='footnote'>%i</sup>",lbl);
1147 }else if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
1148 goto char_link_cleanup;
1149 }
1150
1151 /* rewinding the whitespace */
@@ -1153,17 +1164,21 @@
1153 }
1154
1155 /* building content: img alt is escaped, link content is parsed */
1156 if( txt_e>1 ){
1157 if( is_img ) blob_append(content, data+1, txt_e-1);
1158 else if(!is_note) parse_inline(content, rndr, data+1, txt_e-1);
1159 }
1160
1161 /* calling the relevant rendering function */
1162 if( is_img ){
1163 if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
1164 ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
 
 
 
 
1165 }else{
1166 ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
1167 }
1168
1169 /* cleanup */
@@ -2250,11 +2265,11 @@
2250 ){
2251 size_t i = 0;
2252 size_t id_offset, id_end;
2253 size_t note_offset, note_end;
2254 size_t line_end;
2255 struct footnote fn = { empty_blob, empty_blob, 0 };
2256
2257 /* footnote definition must start at the begining of a line */
2258 if( beg+4>=end ) return 0;
2259 i += beg;
2260
@@ -2311,20 +2326,21 @@
2311 ){
2312 struct link_ref *lr;
2313 struct footnote *fn;
2314 size_t i, beg, end = 0;
2315 struct render rndr;
2316 Blob text = BLOB_INITIALIZER; /* input after the first pass */
 
2317
2318 /* filling the render structure */
2319 if( !rndrer ) return;
2320 rndr.make = *rndrer;
2321 rndr.nBlobCache = 0;
2322 rndr.iDepth = 0;
2323 rndr.refs = empty_blob;
2324 rndr.footnotes = empty_blob;
2325 rndr.nLabels = 0;
2326 for(i=0; i<256; i++) rndr.active_char[i] = 0;
2327 if( (rndr.make.emphasis
2328 || rndr.make.double_emphasis
2329 || rndr.make.triple_emphasis)
2330 && rndr.make.emph_chars
@@ -2345,11 +2361,11 @@
2345 beg = 0;
2346 for(const size_t size = blob_size(ib); beg<size ;){
2347 const char* const data = blob_buffer(ib);
2348 if( is_ref(data, beg, size, &end, &rndr.refs) ){
2349 beg = end;
2350 }else if( is_footnote(data, beg, size, &end, &rndr.footnotes) ){
2351 /* FIXME: fossil_print("\nfootnote found at %i\n", beg); */
2352 beg = end;
2353 }else{ /* skipping to the next line */
2354 end = beg;
2355 while( end<size && data[end]!='\n' && data[end]!='\r' ){
@@ -2373,41 +2389,39 @@
2373 qsort(blob_buffer(&rndr.refs),
2374 blob_size(&rndr.refs)/sizeof(struct link_ref),
2375 sizeof(struct link_ref),
2376 cmp_link_ref_sort);
2377 }
 
2378 /* sorting the footnotes array by id */
2379 if( blob_size(&rndr.footnotes) ){
2380 qsort(blob_buffer(&rndr.footnotes),
2381 blob_size(&rndr.footnotes)/sizeof(struct footnote),
2382 sizeof(struct footnote),
2383 cmp_link_ref_sort);
2384 }
2385
2386 /* second pass: actual rendering */
2387 if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
2388 parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
2389
2390 /* sorting the footnotes array by index */
2391 if( blob_size(&rndr.footnotes) ){
2392 qsort(blob_buffer(&rndr.footnotes),
2393 blob_size(&rndr.footnotes)/sizeof(struct footnote),
2394 sizeof(struct footnote),
2395 cmp_footnote_sort);
2396 }
2397 /* FIXME: decouple parsing and HTML-specific rendering of footnotes */
2398 if( rndr.nLabels ){
2399 fn = (struct footnote *)blob_buffer(&rndr.footnotes);
2400 end = blob_size(&rndr.footnotes)/sizeof(struct footnote);
2401 blob_appendf(ob, "\n<ol class='footnotes'>\n");
2402 for(i=0; i<end; i++){
2403 if(fn[i].index == 0) continue;
2404 blob_appendf(ob, "<li id='footnote-%i'>\n ", fn[i].index );
2405 parse_inline(ob,&rndr,blob_buffer(&fn[i].text),blob_size(&fn[i].text));
2406 blob_append(ob,"\n</li>\n",7);
2407 }
2408 blob_append(ob, "</ol>\n", 7);
2409 }
2410 if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
2411
2412 /* clean-up */
2413 assert( rndr.iDepth==0 );
@@ -2418,9 +2432,15 @@
2418 blob_reset(&lr[i].id);
2419 blob_reset(&lr[i].link);
2420 blob_reset(&lr[i].title);
2421 }
2422 blob_reset(&rndr.refs);
 
 
 
 
 
 
2423 for(i=0; i<rndr.nBlobCache; i++){
2424 fossil_free(rndr.aBlobCache[i]);
2425 }
2426 }
2427
--- src/markdown.c
+++ src/markdown.c
@@ -45,10 +45,11 @@
45 /* mkd_renderer -- functions for rendering parsed data */
46 struct mkd_renderer {
47 /* document level callbacks */
48 void (*prolog)(struct Blob *ob, void *opaque);
49 void (*epilog)(struct Blob *ob, void *opaque);
50 void (*footnotes)(struct Blob *ob, const struct Blob *items, void *opaque);
51
52 /* block level callbacks - NULL skips the block */
53 void (*blockcode)(struct Blob *ob, struct Blob *text, void *opaque);
54 void (*blockquote)(struct Blob *ob, struct Blob *text, void *opaque);
55 void (*blockhtml)(struct Blob *ob, struct Blob *text, void *opaque);
@@ -63,10 +64,12 @@
64 void *opaque);
65 void (*table_cell)(struct Blob *ob, struct Blob *text, int flags,
66 void *opaque);
67 void (*table_row)(struct Blob *ob, struct Blob *cells, int flags,
68 void *opaque);
69 void (*footnote_item)(struct Blob *ob, const struct Blob *text,
70 int index, int nUsed, void *opaque);
71
72 /* span level callbacks - NULL or return 0 prints the span verbatim */
73 int (*autolink)(struct Blob *ob, struct Blob *link,
74 enum mkd_autolink type, void *opaque);
75 int (*codespan)(struct Blob *ob, struct Blob *text, int nSep, void *opaque);
@@ -79,10 +82,11 @@
82 int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
83 struct Blob *content, void *opaque);
84 int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
85 int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
86 char c, void *opaque);
87 int (*footnote_ref)(struct Blob *ob, int index, int locus, void *opaque);
88
89 /* low level callbacks - NULL copies input directly into the output */
90 void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
91 void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);
92
@@ -122,10 +126,12 @@
126 const struct mkd_renderer *rndr);
127
128
129 #endif /* INTERFACE */
130
131 #define BLOB_COUNT(blob_ptr,el_type) (blob_size(blob_ptr)/sizeof(el_type))
132 #define COUNT_FOOTNOTES(blob_ptr) BLOB_COUNT(blob_ptr,struct footnote)
133
134 /***************
135 * LOCAL TYPES *
136 ***************/
137
@@ -138,10 +144,11 @@
144
145 struct footnote {
146 struct Blob id; /* must be the first field as in link_ref struct */
147 struct Blob text; /* footnote's content that is rendered at the end */
148 int index; /* serial number, in the order of appearance */
149 int nUsed; /* number of references to this note */
150 };
151
152
153 /* char_trigger -- function pointer to render active chars */
154 /* returns the number of chars taken care of */
@@ -158,16 +165,17 @@
165
166 /* render -- structure containing one particular render */
167 struct render {
168 struct mkd_renderer make;
169 struct Blob refs;
 
170 char_trigger active_char[256];
171 int iDepth; /* Depth of recursion */
172 int nBlobCache; /* Number of entries in aBlobCache */
173 struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
174
175 struct Blob notes; /* array of footnotes */
176 int iNotesCount; /* count distinct indices found in the second pass */
177 };
178
179 /* html_tag -- structure for quick HTML tag search (inspired from discount) */
180 struct html_tag {
181 const char *text;
@@ -254,15 +262,18 @@
262 struct link_ref *lra = (void *)a;
263 struct link_ref *lrb = (void *)b;
264 return blob_compare(&lra->id, &lrb->id);
265 }
266
267 /* cmp_footnote_sort -- comparison function for footnotes qsort.
268 * Unused footnotes (when index == 0) sort last */
269 static int cmp_footnote_sort(const void *a, const void *b){
270 const struct footnote *fna = (void *)a, *fnb = (void *)b;
271 assert( fna->index >= 0 && fnb->index >= 0 );
272 if( fna->index == fnb->index ) return 0;
273 if( fna->index == 0 ) return 1;
274 if( fnb->index == 0 ) return -1;
275 return ( fna->index < fnb->index ? -1 : 1 );
276 }
277
278 /* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
279 static int cmp_html_tag(const void *a, const void *b){
@@ -1017,38 +1028,37 @@
1028 blob_append(link, blob_buffer(&lr->link), blob_size(&lr->link));
1029 blob_append(title, blob_buffer(&lr->title), blob_size(&lr->title));
1030 return 0;
1031 }
1032
1033 /* get_footnote() is invoked during the second pass
1034 * on success: fill text and return positive footnote's index
1035 * on failure: return -1 */
1036 static const struct footnote* get_footnote(
1037 struct render *rndr,
 
1038 const char *data,
1039 size_t size
1040 ){
1041 struct footnote *fn = NULL;
1042 struct Blob *id = new_work_buffer(rndr);
1043 if( build_ref_id(id, data, size)<0 ) goto cleanup;
1044 fn = bsearch(id, blob_buffer(&rndr->notes),
1045 COUNT_FOOTNOTES(&rndr->notes),
 
 
 
1046 sizeof (struct footnote),
1047 cmp_link_ref);
1048 if( !fn ) goto cleanup;
1049
1050 if( fn->index == 0 ){ /* the first reference to the footnote */
1051 assert( fn->nUsed == 0 );
1052 fn->index = ++(rndr->iNotesCount);
1053 }
1054 fn->nUsed++;
1055 assert( fn->index > 0 );
1056 assert( fn->nUsed > 0 );
1057 cleanup:
1058 release_work_buffer( rndr, id );
1059 return fn;
1060 }
1061
1062 /* char_link -- '[': parsing a link or an image */
1063 static size_t char_link(
1064 struct Blob *ob,
@@ -1055,17 +1065,19 @@
1065 struct render *rndr,
1066 char *data,
1067 size_t offset,
1068 size_t size
1069 ){
1070 const int is_img = (offset && data[-1] == '!');
1071 const int is_inline = (offset && data[-1]=='^');
1072 const int is_note = !is_img && (is_inline || (size>1 && data[1]=='^'));
1073 size_t i = 1, txt_e;
1074 struct Blob *content = 0;
1075 struct Blob *link = 0;
1076 struct Blob *title = 0;
1077 const struct footnote *fn = 0;
1078 int level, ret;
1079
1080 /* checking whether the correct renderer exists */
1081 if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){
1082 return 0;
1083 }
@@ -1135,17 +1147,16 @@
1147 i = id_end+1;
1148
1149 /* shortcut reference style link */
1150 }else{
1151 if( is_note ){
1152 if( is_inline ){
1153 //fn = put_footnote(rndr, data+1, txt_e-1);
1154 }else{
1155 fn = get_footnote(rndr, data+1, txt_e-1);
1156 }
1157 if( !fn ) goto char_link_cleanup;
 
 
1158 }else if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
1159 goto char_link_cleanup;
1160 }
1161
1162 /* rewinding the whitespace */
@@ -1153,17 +1164,21 @@
1164 }
1165
1166 /* building content: img alt is escaped, link content is parsed */
1167 if( txt_e>1 ){
1168 if( is_img ) blob_append(content, data+1, txt_e-1);
1169 else if(is_inline) parse_inline(content, rndr, data+1, txt_e-1);
1170 }
1171
1172 /* calling the relevant rendering function */
1173 if( is_img ){
1174 if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
1175 ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
1176 }else if(fn){
1177 if(rndr->make.footnote_ref){
1178 ret = rndr->make.footnote_ref(ob,fn->index,fn->nUsed,rndr->make.opaque);
1179 }
1180 }else{
1181 ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
1182 }
1183
1184 /* cleanup */
@@ -2250,11 +2265,11 @@
2265 ){
2266 size_t i = 0;
2267 size_t id_offset, id_end;
2268 size_t note_offset, note_end;
2269 size_t line_end;
2270 struct footnote fn = { empty_blob, empty_blob, 0, 0 };
2271
2272 /* footnote definition must start at the begining of a line */
2273 if( beg+4>=end ) return 0;
2274 i += beg;
2275
@@ -2311,20 +2326,21 @@
2326 ){
2327 struct link_ref *lr;
2328 struct footnote *fn;
2329 size_t i, beg, end = 0;
2330 struct render rndr;
2331 Blob text = BLOB_INITIALIZER; /* input after the first pass */
2332 int nLabeled; /* number of footnotes found by the first pass */
2333
2334 /* filling the render structure */
2335 if( !rndrer ) return;
2336 rndr.make = *rndrer;
2337 rndr.nBlobCache = 0;
2338 rndr.iDepth = 0;
2339 rndr.refs = empty_blob;
2340 rndr.notes = empty_blob;
2341 rndr.iNotesCount = 0;
2342 for(i=0; i<256; i++) rndr.active_char[i] = 0;
2343 if( (rndr.make.emphasis
2344 || rndr.make.double_emphasis
2345 || rndr.make.triple_emphasis)
2346 && rndr.make.emph_chars
@@ -2345,11 +2361,11 @@
2361 beg = 0;
2362 for(const size_t size = blob_size(ib); beg<size ;){
2363 const char* const data = blob_buffer(ib);
2364 if( is_ref(data, beg, size, &end, &rndr.refs) ){
2365 beg = end;
2366 }else if(is_footnote(data, beg, size, &end, &rndr.notes)){
2367 /* FIXME: fossil_print("\nfootnote found at %i\n", beg); */
2368 beg = end;
2369 }else{ /* skipping to the next line */
2370 end = beg;
2371 while( end<size && data[end]!='\n' && data[end]!='\r' ){
@@ -2373,41 +2389,39 @@
2389 qsort(blob_buffer(&rndr.refs),
2390 blob_size(&rndr.refs)/sizeof(struct link_ref),
2391 sizeof(struct link_ref),
2392 cmp_link_ref_sort);
2393 }
2394 nLabeled = COUNT_FOOTNOTES(&rndr.notes);
2395 /* sorting the footnotes array by id */
2396 if( nLabeled ){
2397 qsort(blob_buffer(&rndr.notes), nLabeled, sizeof(struct footnote),
 
 
2398 cmp_link_ref_sort);
2399 }
2400
2401 /* second pass: actual rendering */
2402 if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
2403 parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
2404
2405 fn = (struct footnote*)blob_buffer(&rndr.notes);
2406 if(rndr.iNotesCount && rndr.make.footnote_item && rndr.make.footnotes){
2407 Blob * one_item = new_work_buffer(&rndr);
2408 Blob * all_items = new_work_buffer(&rndr);
2409 qsort( fn, COUNT_FOOTNOTES(&rndr.notes), sizeof(struct footnote),
2410 cmp_footnote_sort /* sort footnotes by index */ );
2411 blob_reset( all_items );
2412 for(i=0; i<rndr.iNotesCount; i++){
2413 assert( fn[i].index == i+1 );
2414
2415 blob_reset( one_item );
2416 parse_inline( one_item, &rndr, blob_buffer(&fn[i].text),
2417 blob_size(&fn[i].text));
2418 rndr.make.footnote_item( all_items, one_item, i+1, fn[i].nUsed, rndr.make.opaque);
2419 }
2420 rndr.make.footnotes(ob, all_items, rndr.make.opaque );
2421 release_work_buffer( &rndr, one_item );
2422 release_work_buffer( &rndr, all_items );
 
2423 }
2424 if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
2425
2426 /* clean-up */
2427 assert( rndr.iDepth==0 );
@@ -2418,9 +2432,15 @@
2432 blob_reset(&lr[i].id);
2433 blob_reset(&lr[i].link);
2434 blob_reset(&lr[i].title);
2435 }
2436 blob_reset(&rndr.refs);
2437 end = COUNT_FOOTNOTES(&rndr.notes);
2438 for(i=0; i<end; i++){
2439 blob_reset(&fn[i].id);
2440 blob_reset(&fn[i].text);
2441 }
2442 blob_reset(&rndr.notes);
2443 for(i=0; i<rndr.nBlobCache; i++){
2444 fossil_free(rndr.aBlobCache[i]);
2445 }
2446 }
2447
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -29,17 +29,20 @@
2929
struct Blob *output_title,
3030
struct Blob *output_body);
3131
3232
#endif /* INTERFACE */
3333
34
+typedef union { uint64_t u; char c[8]; unsigned char b[8]; } bitfield64_t;
35
+
3436
/*
3537
** An instance of the following structure is passed through the
3638
** "opaque" pointer.
3739
*/
3840
typedef struct MarkdownToHtml MarkdownToHtml;
3941
struct MarkdownToHtml {
4042
Blob *output_title; /* Store the title here */
43
+ bitfield64_t unique; /* Enables construction of unique #id elements */
4144
};
4245
4346
4447
/* INTER_BLOCK -- skip a line between block level elements */
4548
#define INTER_BLOCK(ob) \
@@ -56,10 +59,26 @@
5659
5760
/* BLOB_APPEND_BLOB -- append blob contents to another */
5861
#define BLOB_APPEND_BLOB(dest, src) \
5962
blob_append((dest), blob_buffer(src), blob_size(src))
6063
64
+/* Converts an integer to a null-terminated base26 representation
65
+ * Return empty string if that integer is negative. */
66
+static bitfield64_t to_base26(int i, int uppercase){
67
+ bitfield64_t x;
68
+ int j;
69
+ x.u = 0;
70
+ if( i >= 0 ){
71
+ for(j=7; j >= 0; j--){
72
+ x.b[j] = (unsigned char)(uppercase?'A':'a') + i%26;
73
+ if( (i /= 26) == 0 ) break;
74
+ }
75
+ x.u >>= 8*j;
76
+ }
77
+ x.c[7] = 0;
78
+ return x;
79
+}
6180
6281
/* HTML escapes
6382
**
6483
** html_escape() converts < to &lt;, > to &gt;, and & to &amp;.
6584
** html_quote() goes further and converts " into &quot; and ' in &#39;.
@@ -303,11 +322,78 @@
303322
BLOB_APPEND_LITERAL(ob, " <tr>\n");
304323
BLOB_APPEND_BLOB(ob, cells);
305324
BLOB_APPEND_LITERAL(ob, " </tr>\n");
306325
}
307326
327
+static int html_footnote_ref(
328
+ struct Blob *ob, int index, int locus, void *opaque
329
+){
330
+ const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
331
+ const bitfield64_t l = to_base26(locus-1,0);
332
+ char pos[32];
333
+
334
+ /* expect BUGs if the following yields compiler warnings */
335
+ memset(pos,0,32);
336
+ sprintf(pos, "%s%i-%s", ctx->unique.c, index, l.c);
337
+
338
+ BLOB_APPEND_LITERAL(ob,"<a class='noteref' href='#footnote-");
339
+ blob_appendf(ob,"%s' id='noteref-%s'><sup>%i</sup></a>",
340
+ pos, pos, index);
341
+ return 1;
342
+}
343
+
344
+/* Render a single item of the footnotes list.
345
+ * Each backref gets a unique id to enable dynamic styling. */
346
+static void html_footnote_item(
347
+ struct Blob *ob, const struct Blob *text, int index, int nUsed, void *opaque
348
+){
349
+ const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
350
+ char pos[24];
351
+ if( index <= 0 || nUsed < 0 || !text || !blob_size(text) ){
352
+ return;
353
+ }
354
+
355
+ /* expect BUGs if the following yields compiler warnings */
356
+ memset(pos,0,24);
357
+ sprintf(pos, "%s%i", ctx->unique.c, index);
308358
359
+ blob_appendf(ob, "<li id='footnote-%s'>", pos);
360
+ BLOB_APPEND_LITERAL(ob,"<sup class='footnote-backrefs'>");
361
+ if( nUsed <= 1 ){
362
+ blob_appendf(ob,"<a id='footnote-%s-a' "
363
+ "href='#noteref-%s-a'>^</a>", pos, pos);
364
+ }else{
365
+ int i;
366
+ blob_append_char(ob, '^');
367
+ for(i=0; i<nUsed && i<26; i++){
368
+ const int c = i + (unsigned)'a';
369
+ blob_appendf(ob," <a id='footnote-%s-%c'"
370
+ " href='#noteref-%s-%c'>%c</a>", pos,c, pos,c, c);
371
+ }
372
+ /* It's unlikely that so many backrefs will be usefull */
373
+ /* but maybe for some machine generated documents... */
374
+ for(; i<nUsed && i<676; i++){
375
+ const bitfield64_t l = to_base26(i,0);
376
+ blob_appendf(ob," <a id='footnote-%s-%s'"
377
+ " href='#noteref-%s-%s'>%s</a>",
378
+ pos,l.c, pos,l.c, l.c);
379
+ }
380
+ if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
381
+ }
382
+ BLOB_APPEND_LITERAL(ob,"</sup>\n\t");
383
+ BLOB_APPEND_BLOB(ob, text);
384
+ BLOB_APPEND_LITERAL(ob, "\n</li>\n");
385
+}
386
+static void html_footnotes(
387
+ struct Blob *ob, const struct Blob *items, void *opaque
388
+){
389
+ if( items && blob_size(items) ){
390
+ BLOB_APPEND_LITERAL(ob, "<ol class='footnotes'>\n");
391
+ BLOB_APPEND_BLOB(ob, items);
392
+ BLOB_APPEND_LITERAL(ob, "</ol>\n");
393
+ }
394
+}
309395
310396
/* HTML span tags */
311397
312398
static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){
313399
blob_append(ob, blob_buffer(text), blob_size(text));
@@ -563,10 +649,11 @@
563649
){
564650
struct mkd_renderer html_renderer = {
565651
/* prolog and epilog */
566652
html_prolog,
567653
html_epilog,
654
+ html_footnotes,
568655
569656
/* block level elements */
570657
html_blockcode,
571658
html_blockquote,
572659
html_blockhtml,
@@ -576,10 +663,11 @@
576663
html_list_item,
577664
html_paragraph,
578665
html_table,
579666
html_table_cell,
580667
html_table_row,
668
+ html_footnote_item,
581669
582670
/* span level elements */
583671
html_autolink,
584672
html_codespan,
585673
html_double_emphasis,
@@ -587,22 +675,25 @@
587675
html_image,
588676
html_linebreak,
589677
html_link,
590678
html_raw_html_tag,
591679
html_triple_emphasis,
680
+ html_footnote_ref,
592681
593682
/* low level elements */
594683
0, /* entity */
595684
html_normal_text,
596685
597686
/* misc. parameters */
598687
"*_", /* emph_chars */
599688
0 /* opaque */
600689
};
690
+ static int invocation = -1; /* no marker for the first document */
601691
MarkdownToHtml context;
602692
memset(&context, 0, sizeof(context));
603693
context.output_title = output_title;
694
+ context.unique = to_base26(invocation++,1);
604695
html_renderer.opaque = &context;
605696
if( output_title ) blob_reset(output_title);
606697
blob_reset(output_body);
607698
markdown(output_body, input_markdown, &html_renderer);
608699
}
609700
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -29,17 +29,20 @@
29 struct Blob *output_title,
30 struct Blob *output_body);
31
32 #endif /* INTERFACE */
33
 
 
34 /*
35 ** An instance of the following structure is passed through the
36 ** "opaque" pointer.
37 */
38 typedef struct MarkdownToHtml MarkdownToHtml;
39 struct MarkdownToHtml {
40 Blob *output_title; /* Store the title here */
 
41 };
42
43
44 /* INTER_BLOCK -- skip a line between block level elements */
45 #define INTER_BLOCK(ob) \
@@ -56,10 +59,26 @@
56
57 /* BLOB_APPEND_BLOB -- append blob contents to another */
58 #define BLOB_APPEND_BLOB(dest, src) \
59 blob_append((dest), blob_buffer(src), blob_size(src))
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
62 /* HTML escapes
63 **
64 ** html_escape() converts < to &lt;, > to &gt;, and & to &amp;.
65 ** html_quote() goes further and converts " into &quot; and ' in &#39;.
@@ -303,11 +322,78 @@
303 BLOB_APPEND_LITERAL(ob, " <tr>\n");
304 BLOB_APPEND_BLOB(ob, cells);
305 BLOB_APPEND_LITERAL(ob, " </tr>\n");
306 }
307
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
310 /* HTML span tags */
311
312 static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){
313 blob_append(ob, blob_buffer(text), blob_size(text));
@@ -563,10 +649,11 @@
563 ){
564 struct mkd_renderer html_renderer = {
565 /* prolog and epilog */
566 html_prolog,
567 html_epilog,
 
568
569 /* block level elements */
570 html_blockcode,
571 html_blockquote,
572 html_blockhtml,
@@ -576,10 +663,11 @@
576 html_list_item,
577 html_paragraph,
578 html_table,
579 html_table_cell,
580 html_table_row,
 
581
582 /* span level elements */
583 html_autolink,
584 html_codespan,
585 html_double_emphasis,
@@ -587,22 +675,25 @@
587 html_image,
588 html_linebreak,
589 html_link,
590 html_raw_html_tag,
591 html_triple_emphasis,
 
592
593 /* low level elements */
594 0, /* entity */
595 html_normal_text,
596
597 /* misc. parameters */
598 "*_", /* emph_chars */
599 0 /* opaque */
600 };
 
601 MarkdownToHtml context;
602 memset(&context, 0, sizeof(context));
603 context.output_title = output_title;
 
604 html_renderer.opaque = &context;
605 if( output_title ) blob_reset(output_title);
606 blob_reset(output_body);
607 markdown(output_body, input_markdown, &html_renderer);
608 }
609
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -29,17 +29,20 @@
29 struct Blob *output_title,
30 struct Blob *output_body);
31
32 #endif /* INTERFACE */
33
34 typedef union { uint64_t u; char c[8]; unsigned char b[8]; } bitfield64_t;
35
36 /*
37 ** An instance of the following structure is passed through the
38 ** "opaque" pointer.
39 */
40 typedef struct MarkdownToHtml MarkdownToHtml;
41 struct MarkdownToHtml {
42 Blob *output_title; /* Store the title here */
43 bitfield64_t unique; /* Enables construction of unique #id elements */
44 };
45
46
47 /* INTER_BLOCK -- skip a line between block level elements */
48 #define INTER_BLOCK(ob) \
@@ -56,10 +59,26 @@
59
60 /* BLOB_APPEND_BLOB -- append blob contents to another */
61 #define BLOB_APPEND_BLOB(dest, src) \
62 blob_append((dest), blob_buffer(src), blob_size(src))
63
64 /* Converts an integer to a null-terminated base26 representation
65 * Return empty string if that integer is negative. */
66 static bitfield64_t to_base26(int i, int uppercase){
67 bitfield64_t x;
68 int j;
69 x.u = 0;
70 if( i >= 0 ){
71 for(j=7; j >= 0; j--){
72 x.b[j] = (unsigned char)(uppercase?'A':'a') + i%26;
73 if( (i /= 26) == 0 ) break;
74 }
75 x.u >>= 8*j;
76 }
77 x.c[7] = 0;
78 return x;
79 }
80
81 /* HTML escapes
82 **
83 ** html_escape() converts < to &lt;, > to &gt;, and & to &amp;.
84 ** html_quote() goes further and converts " into &quot; and ' in &#39;.
@@ -303,11 +322,78 @@
322 BLOB_APPEND_LITERAL(ob, " <tr>\n");
323 BLOB_APPEND_BLOB(ob, cells);
324 BLOB_APPEND_LITERAL(ob, " </tr>\n");
325 }
326
327 static int html_footnote_ref(
328 struct Blob *ob, int index, int locus, void *opaque
329 ){
330 const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
331 const bitfield64_t l = to_base26(locus-1,0);
332 char pos[32];
333
334 /* expect BUGs if the following yields compiler warnings */
335 memset(pos,0,32);
336 sprintf(pos, "%s%i-%s", ctx->unique.c, index, l.c);
337
338 BLOB_APPEND_LITERAL(ob,"<a class='noteref' href='#footnote-");
339 blob_appendf(ob,"%s' id='noteref-%s'><sup>%i</sup></a>",
340 pos, pos, index);
341 return 1;
342 }
343
344 /* Render a single item of the footnotes list.
345 * Each backref gets a unique id to enable dynamic styling. */
346 static void html_footnote_item(
347 struct Blob *ob, const struct Blob *text, int index, int nUsed, void *opaque
348 ){
349 const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
350 char pos[24];
351 if( index <= 0 || nUsed < 0 || !text || !blob_size(text) ){
352 return;
353 }
354
355 /* expect BUGs if the following yields compiler warnings */
356 memset(pos,0,24);
357 sprintf(pos, "%s%i", ctx->unique.c, index);
358
359 blob_appendf(ob, "<li id='footnote-%s'>", pos);
360 BLOB_APPEND_LITERAL(ob,"<sup class='footnote-backrefs'>");
361 if( nUsed <= 1 ){
362 blob_appendf(ob,"<a id='footnote-%s-a' "
363 "href='#noteref-%s-a'>^</a>", pos, pos);
364 }else{
365 int i;
366 blob_append_char(ob, '^');
367 for(i=0; i<nUsed && i<26; i++){
368 const int c = i + (unsigned)'a';
369 blob_appendf(ob," <a id='footnote-%s-%c'"
370 " href='#noteref-%s-%c'>%c</a>", pos,c, pos,c, c);
371 }
372 /* It's unlikely that so many backrefs will be usefull */
373 /* but maybe for some machine generated documents... */
374 for(; i<nUsed && i<676; i++){
375 const bitfield64_t l = to_base26(i,0);
376 blob_appendf(ob," <a id='footnote-%s-%s'"
377 " href='#noteref-%s-%s'>%s</a>",
378 pos,l.c, pos,l.c, l.c);
379 }
380 if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
381 }
382 BLOB_APPEND_LITERAL(ob,"</sup>\n\t");
383 BLOB_APPEND_BLOB(ob, text);
384 BLOB_APPEND_LITERAL(ob, "\n</li>\n");
385 }
386 static void html_footnotes(
387 struct Blob *ob, const struct Blob *items, void *opaque
388 ){
389 if( items && blob_size(items) ){
390 BLOB_APPEND_LITERAL(ob, "<ol class='footnotes'>\n");
391 BLOB_APPEND_BLOB(ob, items);
392 BLOB_APPEND_LITERAL(ob, "</ol>\n");
393 }
394 }
395
396 /* HTML span tags */
397
398 static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){
399 blob_append(ob, blob_buffer(text), blob_size(text));
@@ -563,10 +649,11 @@
649 ){
650 struct mkd_renderer html_renderer = {
651 /* prolog and epilog */
652 html_prolog,
653 html_epilog,
654 html_footnotes,
655
656 /* block level elements */
657 html_blockcode,
658 html_blockquote,
659 html_blockhtml,
@@ -576,10 +663,11 @@
663 html_list_item,
664 html_paragraph,
665 html_table,
666 html_table_cell,
667 html_table_row,
668 html_footnote_item,
669
670 /* span level elements */
671 html_autolink,
672 html_codespan,
673 html_double_emphasis,
@@ -587,22 +675,25 @@
675 html_image,
676 html_linebreak,
677 html_link,
678 html_raw_html_tag,
679 html_triple_emphasis,
680 html_footnote_ref,
681
682 /* low level elements */
683 0, /* entity */
684 html_normal_text,
685
686 /* misc. parameters */
687 "*_", /* emph_chars */
688 0 /* opaque */
689 };
690 static int invocation = -1; /* no marker for the first document */
691 MarkdownToHtml context;
692 memset(&context, 0, sizeof(context));
693 context.output_title = output_title;
694 context.unique = to_base26(invocation++,1);
695 html_renderer.opaque = &context;
696 if( output_title ) blob_reset(output_title);
697 blob_reset(output_body);
698 markdown(output_body, input_markdown, &html_renderer);
699 }
700

Keyboard Shortcuts

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