Fossil SCM

Initial support for [forum:/forumthread/d752446a4f63f390|footnotes in Markdown]. <br>This is WIP: support of multiline notes and code clean-up are pending.

george 2022-01-26 14:50 trunk
Commit ebce0f357e0732cacdcc4105623c94385d1691a0c9d6cfe7ef979ae71b96650f
2 files changed +6 +152 -7
--- src/default.css
+++ src/default.css
@@ -1664,10 +1664,16 @@
16641664
display: inline;
16651665
}
16661666
16671667
.monospace {
16681668
font-family: monospace;
1669
+}
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;
16691675
}
16701676
16711677
/* Objects in the "desktoponly" class are invisible on mobile */
16721678
@media screen and (max-width: 600px) {
16731679
.desktoponly {
16741680
--- src/default.css
+++ src/default.css
@@ -1664,10 +1664,16 @@
1664 display: inline;
1665 }
1666
1667 .monospace {
1668 font-family: monospace;
 
 
 
 
 
 
1669 }
1670
1671 /* Objects in the "desktoponly" class are invisible on mobile */
1672 @media screen and (max-width: 600px) {
1673 .desktoponly {
1674
--- src/default.css
+++ src/default.css
@@ -1664,10 +1664,16 @@
1664 display: inline;
1665 }
1666
1667 .monospace {
1668 font-family: monospace;
1669 }
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
+152 -7
--- src/markdown.c
+++ src/markdown.c
@@ -129,14 +129,20 @@
129129
* LOCAL TYPES *
130130
***************/
131131
132132
/* link_ref -- reference to a link */
133133
struct link_ref {
134
- struct Blob id;
134
+ struct Blob id; /* must be the first field as in footnote struct */
135135
struct Blob link;
136136
struct Blob title;
137137
};
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
+};
138144
139145
140146
/* char_trigger -- function pointer to render active chars */
141147
/* returns the number of chars taken care of */
142148
/* data is the pointer of the beginning of the span */
@@ -152,16 +158,17 @@
152158
153159
/* render -- structure containing one particular render */
154160
struct render {
155161
struct mkd_renderer make;
156162
struct Blob refs;
163
+ struct Blob footnotes;
157164
char_trigger active_char[256];
158165
int iDepth; /* Depth of recursion */
159166
int nBlobCache; /* Number of entries in aBlobCache */
160167
struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
168
+ int nLabels; /* Footnotes counter for the second pass */
161169
};
162
-
163170
164171
/* html_tag -- structure for quick HTML tag search (inspired from discount) */
165172
struct html_tag {
166173
const char *text;
167174
int size;
@@ -247,10 +254,17 @@
247254
struct link_ref *lra = (void *)a;
248255
struct link_ref *lrb = (void *)b;
249256
return blob_compare(&lra->id, &lrb->id);
250257
}
251258
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
+}
252266
253267
/* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
254268
static int cmp_html_tag(const void *a, const void *b){
255269
const struct html_tag *hta = a;
256270
const struct html_tag *htb = b;
@@ -1003,10 +1017,39 @@
10031017
blob_append(link, blob_buffer(&lr->link), blob_size(&lr->link));
10041018
blob_append(title, blob_buffer(&lr->title), blob_size(&lr->title));
10051019
return 0;
10061020
}
10071021
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
+}
10081051
10091052
/* char_link -- '[': parsing a link or an image */
10101053
static size_t char_link(
10111054
struct Blob *ob,
10121055
struct render *rndr,
@@ -1017,10 +1060,11 @@
10171060
int is_img = (offset && data[-1] == '!'), level;
10181061
size_t i = 1, txt_e;
10191062
struct Blob *content = 0;
10201063
struct Blob *link = 0;
10211064
struct Blob *title = 0;
1065
+ const int is_note = (size && data[1] == '^');
10221066
int ret;
10231067
10241068
/* checking whether the correct renderer exists */
10251069
if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){
10261070
return 0;
@@ -1083,20 +1127,26 @@
10831127
}else{
10841128
/* explicit id - between brackets */
10851129
id_data = data+i+1;
10861130
id_size = id_end-(i+1);
10871131
}
1088
-
10891132
if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
10901133
goto char_link_cleanup;
10911134
}
1092
-
10931135
i = id_end+1;
10941136
10951137
/* shortcut reference style link */
10961138
}else{
1097
- if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
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 ){
10981148
goto char_link_cleanup;
10991149
}
11001150
11011151
/* rewinding the whitespace */
11021152
i = txt_e+1;
@@ -1103,11 +1153,11 @@
11031153
}
11041154
11051155
/* building content: img alt is escaped, link content is parsed */
11061156
if( txt_e>1 ){
11071157
if( is_img ) blob_append(content, data+1, txt_e-1);
1108
- else parse_inline(content, rndr, data+1, txt_e-1);
1158
+ else if(!is_note) parse_inline(content, rndr, data+1, txt_e-1);
11091159
}
11101160
11111161
/* calling the relevant rendering function */
11121162
if( is_img ){
11131163
if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
@@ -2095,11 +2145,11 @@
20952145
}
20962146
}
20972147
i += beg;
20982148
20992149
/* id part: anything but a newline between brackets */
2100
- if( data[i]!='[' ) return 0;
2150
+ if( data[i]!='[' || data[i+1]=='^' ) return 0;
21012151
i++;
21022152
id_offset = i;
21032153
while( i<end && data[i]!='\n' && data[i]!='\r' && data[i]!=']' ){ i++; }
21042154
if( i>=end || data[i]!=']' ) return 0;
21052155
id_end = i;
@@ -2184,11 +2234,72 @@
21842234
}
21852235
blob_append(refs, (char *)&lr, sizeof lr);
21862236
return 1;
21872237
}
21882238
2239
+/*********************
2240
+ * FOOTNOTE PARSING *
2241
+ *********************/
2242
+
2243
+/* is_footnote -- returns whether a line is a footnote or not */
2244
+static int is_footnote(
2245
+ char *data, /* input text */
2246
+ size_t beg, /* offset of the beginning of the line */
2247
+ size_t end, /* offset of the end of the text */
2248
+ size_t *last, /* last character of the link */
2249
+ struct Blob * footnotes /* FIXME: struct render *rndr */
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
+
2261
+ /* id part: anything but a newline between brackets */
2262
+ if( data[i]!='[' || data[i+1]!='^' ) return 0;
2263
+ i++;
2264
+ id_offset = i;
2265
+ while( i<end && data[i]!=']' && data[i]!='\n' && data[i]!='\r' ){ i++; }
2266
+ if( i>=end || data[i]!=']' ) return 0;
2267
+ id_end = i;
2268
+
2269
+ /* spacer: colon (space | tab)* newline? (space | tab)* */
2270
+ i++;
2271
+ if( i>=end || data[i]!=':' ) return 0;
2272
+ i++;
2273
+ while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2274
+ if( i<end && (data[i]=='\n' || data[i]=='\r') ){
2275
+ i++;
2276
+ if( i<end && data[i]=='\r' && data[i-1] == '\n' ) i++;
2277
+ }
2278
+ while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2279
+ if( i>=end ) return 0;
2280
+
2281
+ /* note is a single line of text (FIXME: support multiline notes) */
2282
+ note_offset = i;
2283
+ while( i<end && data[i]!='\r' && data[i]!='\n' ){ i++; }
2284
+ note_end = i;
2285
+
2286
+ /* computing end-of-line */
2287
+ line_end = 0;
2288
+ if( i >=end || data[i]=='\r' || data[ i ]=='\n' ) line_end = i;
2289
+ if( i+1<end && data[i]=='\n' && data[i+1]=='\r' ) line_end = i+1;
2290
+
2291
+ if( !line_end ) return 0; /* garbage after the link */
21892292
2293
+ /* a valid note has been found, filling-in note's text */
2294
+ if( last ) *last = line_end;
2295
+ if( !footnotes ) return 1;
2296
+ if( build_ref_id(&fn.id, data+id_offset, id_end-id_offset)<0 ) return 0;
2297
+ blob_append(&fn.text, data+note_offset, note_end-note_offset);
2298
+ blob_append(footnotes, (char *)&fn, sizeof fn);
2299
+ return 1;
2300
+}
21902301
21912302
/**********************
21922303
* EXPORTED FUNCTIONS *
21932304
**********************/
21942305
@@ -2197,10 +2308,11 @@
21972308
struct Blob *ob, /* output blob for rendered text */
21982309
struct Blob *ib, /* input blob in markdown */
21992310
const struct mkd_renderer *rndrer /* renderer descriptor (callbacks) */
22002311
){
22012312
struct link_ref *lr;
2313
+ struct footnote *fn;
22022314
size_t i, beg, end = 0;
22032315
struct render rndr;
22042316
char *ib_data;
22052317
Blob text = BLOB_INITIALIZER;
22062318
@@ -2208,10 +2320,12 @@
22082320
if( !rndrer ) return;
22092321
rndr.make = *rndrer;
22102322
rndr.nBlobCache = 0;
22112323
rndr.iDepth = 0;
22122324
rndr.refs = empty_blob;
2325
+ rndr.footnotes = empty_blob;
2326
+ rndr.nLabels = 0;
22132327
for(i=0; i<256; i++) rndr.active_char[i] = 0;
22142328
if( (rndr.make.emphasis
22152329
|| rndr.make.double_emphasis
22162330
|| rndr.make.triple_emphasis)
22172331
&& rndr.make.emph_chars
@@ -2230,10 +2344,13 @@
22302344
/* first pass: looking for references, copying everything else */
22312345
beg = 0;
22322346
ib_data = blob_buffer(ib);
22332347
while( beg<blob_size(ib) ){ /* iterating over lines */
22342348
if( is_ref(ib_data, beg, blob_size(ib), &end, &rndr.refs) ){
2349
+ beg = end;
2350
+ }else if( is_footnote(ib_data, beg, blob_size(ib), &end, &rndr.footnotes) ){
2351
+ /* FIXME: fossil_print("\nfootnote found at %i\n", beg); */
22352352
beg = end;
22362353
}else{ /* skipping to the next line */
22372354
end = beg;
22382355
while( end<blob_size(ib) && ib_data[end]!='\n' && ib_data[end]!='\r' ){
22392356
end += 1;
@@ -2258,14 +2375,42 @@
22582375
qsort(blob_buffer(&rndr.refs),
22592376
blob_size(&rndr.refs)/sizeof(struct link_ref),
22602377
sizeof(struct link_ref),
22612378
cmp_link_ref_sort);
22622379
}
2380
+ /* sorting the footnotes array by id */
2381
+ if( blob_size(&rndr.footnotes) ){
2382
+ qsort(blob_buffer(&rndr.footnotes),
2383
+ blob_size(&rndr.footnotes)/sizeof(struct footnote),
2384
+ sizeof(struct footnote),
2385
+ cmp_link_ref_sort);
2386
+ }
22632387
22642388
/* second pass: actual rendering */
22652389
if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
22662390
parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
2391
+
2392
+ /* sorting the footnotes array by index */
2393
+ if( blob_size(&rndr.footnotes) ){
2394
+ qsort(blob_buffer(&rndr.footnotes),
2395
+ blob_size(&rndr.footnotes)/sizeof(struct footnote),
2396
+ sizeof(struct footnote),
2397
+ cmp_footnote_sort);
2398
+ }
2399
+ /* FIXME: decouple parsing and HTML-specific rendering of footnotes */
2400
+ if( rndr.nLabels ){
2401
+ fn = (struct footnote *)blob_buffer(&rndr.footnotes);
2402
+ end = blob_size(&rndr.footnotes)/sizeof(struct footnote);
2403
+ blob_appendf(ob, "\n<ol class='footnotes'>\n");
2404
+ for(i=0; i<end; i++){
2405
+ if(fn[i].index == 0) continue;
2406
+ blob_appendf(ob, "<li id='footnote-%i'>\n ", fn[i].index );
2407
+ parse_inline(ob,&rndr,blob_buffer(&fn[i].text),blob_size(&fn[i].text));
2408
+ blob_append(ob,"\n</li>\n",7);
2409
+ }
2410
+ blob_append(ob, "</ol>\n", 7);
2411
+ }
22672412
if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
22682413
22692414
/* clean-up */
22702415
assert( rndr.iDepth==0 );
22712416
blob_reset(&text);
22722417
--- src/markdown.c
+++ src/markdown.c
@@ -129,14 +129,20 @@
129 * LOCAL TYPES *
130 ***************/
131
132 /* link_ref -- reference to a link */
133 struct link_ref {
134 struct Blob id;
135 struct Blob link;
136 struct Blob title;
137 };
 
 
 
 
 
 
138
139
140 /* char_trigger -- function pointer to render active chars */
141 /* returns the number of chars taken care of */
142 /* data is the pointer of the beginning of the span */
@@ -152,16 +158,17 @@
152
153 /* render -- structure containing one particular render */
154 struct render {
155 struct mkd_renderer make;
156 struct Blob refs;
 
157 char_trigger active_char[256];
158 int iDepth; /* Depth of recursion */
159 int nBlobCache; /* Number of entries in aBlobCache */
160 struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
 
161 };
162
163
164 /* html_tag -- structure for quick HTML tag search (inspired from discount) */
165 struct html_tag {
166 const char *text;
167 int size;
@@ -247,10 +254,17 @@
247 struct link_ref *lra = (void *)a;
248 struct link_ref *lrb = (void *)b;
249 return blob_compare(&lra->id, &lrb->id);
250 }
251
 
 
 
 
 
 
 
252
253 /* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
254 static int cmp_html_tag(const void *a, const void *b){
255 const struct html_tag *hta = a;
256 const struct html_tag *htb = b;
@@ -1003,10 +1017,39 @@
1003 blob_append(link, blob_buffer(&lr->link), blob_size(&lr->link));
1004 blob_append(title, blob_buffer(&lr->title), blob_size(&lr->title));
1005 return 0;
1006 }
1007
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1008
1009 /* char_link -- '[': parsing a link or an image */
1010 static size_t char_link(
1011 struct Blob *ob,
1012 struct render *rndr,
@@ -1017,10 +1060,11 @@
1017 int is_img = (offset && data[-1] == '!'), level;
1018 size_t i = 1, txt_e;
1019 struct Blob *content = 0;
1020 struct Blob *link = 0;
1021 struct Blob *title = 0;
 
1022 int ret;
1023
1024 /* checking whether the correct renderer exists */
1025 if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){
1026 return 0;
@@ -1083,20 +1127,26 @@
1083 }else{
1084 /* explicit id - between brackets */
1085 id_data = data+i+1;
1086 id_size = id_end-(i+1);
1087 }
1088
1089 if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
1090 goto char_link_cleanup;
1091 }
1092
1093 i = id_end+1;
1094
1095 /* shortcut reference style link */
1096 }else{
1097 if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
 
 
 
 
 
 
 
 
1098 goto char_link_cleanup;
1099 }
1100
1101 /* rewinding the whitespace */
1102 i = txt_e+1;
@@ -1103,11 +1153,11 @@
1103 }
1104
1105 /* building content: img alt is escaped, link content is parsed */
1106 if( txt_e>1 ){
1107 if( is_img ) blob_append(content, data+1, txt_e-1);
1108 else parse_inline(content, rndr, data+1, txt_e-1);
1109 }
1110
1111 /* calling the relevant rendering function */
1112 if( is_img ){
1113 if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
@@ -2095,11 +2145,11 @@
2095 }
2096 }
2097 i += beg;
2098
2099 /* id part: anything but a newline between brackets */
2100 if( data[i]!='[' ) return 0;
2101 i++;
2102 id_offset = i;
2103 while( i<end && data[i]!='\n' && data[i]!='\r' && data[i]!=']' ){ i++; }
2104 if( i>=end || data[i]!=']' ) return 0;
2105 id_end = i;
@@ -2184,11 +2234,72 @@
2184 }
2185 blob_append(refs, (char *)&lr, sizeof lr);
2186 return 1;
2187 }
2188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2189
 
 
 
 
 
 
 
 
2190
2191 /**********************
2192 * EXPORTED FUNCTIONS *
2193 **********************/
2194
@@ -2197,10 +2308,11 @@
2197 struct Blob *ob, /* output blob for rendered text */
2198 struct Blob *ib, /* input blob in markdown */
2199 const struct mkd_renderer *rndrer /* renderer descriptor (callbacks) */
2200 ){
2201 struct link_ref *lr;
 
2202 size_t i, beg, end = 0;
2203 struct render rndr;
2204 char *ib_data;
2205 Blob text = BLOB_INITIALIZER;
2206
@@ -2208,10 +2320,12 @@
2208 if( !rndrer ) return;
2209 rndr.make = *rndrer;
2210 rndr.nBlobCache = 0;
2211 rndr.iDepth = 0;
2212 rndr.refs = empty_blob;
 
 
2213 for(i=0; i<256; i++) rndr.active_char[i] = 0;
2214 if( (rndr.make.emphasis
2215 || rndr.make.double_emphasis
2216 || rndr.make.triple_emphasis)
2217 && rndr.make.emph_chars
@@ -2230,10 +2344,13 @@
2230 /* first pass: looking for references, copying everything else */
2231 beg = 0;
2232 ib_data = blob_buffer(ib);
2233 while( beg<blob_size(ib) ){ /* iterating over lines */
2234 if( is_ref(ib_data, beg, blob_size(ib), &end, &rndr.refs) ){
 
 
 
2235 beg = end;
2236 }else{ /* skipping to the next line */
2237 end = beg;
2238 while( end<blob_size(ib) && ib_data[end]!='\n' && ib_data[end]!='\r' ){
2239 end += 1;
@@ -2258,14 +2375,42 @@
2258 qsort(blob_buffer(&rndr.refs),
2259 blob_size(&rndr.refs)/sizeof(struct link_ref),
2260 sizeof(struct link_ref),
2261 cmp_link_ref_sort);
2262 }
 
 
 
 
 
 
 
2263
2264 /* second pass: actual rendering */
2265 if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
2266 parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2267 if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
2268
2269 /* clean-up */
2270 assert( rndr.iDepth==0 );
2271 blob_reset(&text);
2272
--- src/markdown.c
+++ src/markdown.c
@@ -129,14 +129,20 @@
129 * LOCAL TYPES *
130 ***************/
131
132 /* link_ref -- reference to a link */
133 struct link_ref {
134 struct Blob id; /* must be the first field as in footnote struct */
135 struct Blob link;
136 struct Blob title;
137 };
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 */
148 /* data is the pointer of the beginning of the span */
@@ -152,16 +158,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;
174 int size;
@@ -247,10 +254,17 @@
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){
269 const struct html_tag *hta = a;
270 const struct html_tag *htb = b;
@@ -1003,10 +1017,39 @@
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 struct render *rndr,
@@ -1017,10 +1060,11 @@
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;
@@ -1083,20 +1127,26 @@
1127 }else{
1128 /* explicit id - between brackets */
1129 id_data = data+i+1;
1130 id_size = id_end-(i+1);
1131 }
 
1132 if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
1133 goto char_link_cleanup;
1134 }
 
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 */
1152 i = txt_e+1;
@@ -1103,11 +1153,11 @@
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--;
@@ -2095,11 +2145,11 @@
2145 }
2146 }
2147 i += beg;
2148
2149 /* id part: anything but a newline between brackets */
2150 if( data[i]!='[' || data[i+1]=='^' ) return 0;
2151 i++;
2152 id_offset = i;
2153 while( i<end && data[i]!='\n' && data[i]!='\r' && data[i]!=']' ){ i++; }
2154 if( i>=end || data[i]!=']' ) return 0;
2155 id_end = i;
@@ -2184,11 +2234,72 @@
2234 }
2235 blob_append(refs, (char *)&lr, sizeof lr);
2236 return 1;
2237 }
2238
2239 /*********************
2240 * FOOTNOTE PARSING *
2241 *********************/
2242
2243 /* is_footnote -- returns whether a line is a footnote or not */
2244 static int is_footnote(
2245 char *data, /* input text */
2246 size_t beg, /* offset of the beginning of the line */
2247 size_t end, /* offset of the end of the text */
2248 size_t *last, /* last character of the link */
2249 struct Blob * footnotes /* FIXME: struct render *rndr */
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
2261 /* id part: anything but a newline between brackets */
2262 if( data[i]!='[' || data[i+1]!='^' ) return 0;
2263 i++;
2264 id_offset = i;
2265 while( i<end && data[i]!=']' && data[i]!='\n' && data[i]!='\r' ){ i++; }
2266 if( i>=end || data[i]!=']' ) return 0;
2267 id_end = i;
2268
2269 /* spacer: colon (space | tab)* newline? (space | tab)* */
2270 i++;
2271 if( i>=end || data[i]!=':' ) return 0;
2272 i++;
2273 while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2274 if( i<end && (data[i]=='\n' || data[i]=='\r') ){
2275 i++;
2276 if( i<end && data[i]=='\r' && data[i-1] == '\n' ) i++;
2277 }
2278 while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2279 if( i>=end ) return 0;
2280
2281 /* note is a single line of text (FIXME: support multiline notes) */
2282 note_offset = i;
2283 while( i<end && data[i]!='\r' && data[i]!='\n' ){ i++; }
2284 note_end = i;
2285
2286 /* computing end-of-line */
2287 line_end = 0;
2288 if( i >=end || data[i]=='\r' || data[ i ]=='\n' ) line_end = i;
2289 if( i+1<end && data[i]=='\n' && data[i+1]=='\r' ) line_end = i+1;
2290
2291 if( !line_end ) return 0; /* garbage after the link */
2292
2293 /* a valid note has been found, filling-in note's text */
2294 if( last ) *last = line_end;
2295 if( !footnotes ) return 1;
2296 if( build_ref_id(&fn.id, data+id_offset, id_end-id_offset)<0 ) return 0;
2297 blob_append(&fn.text, data+note_offset, note_end-note_offset);
2298 blob_append(footnotes, (char *)&fn, sizeof fn);
2299 return 1;
2300 }
2301
2302 /**********************
2303 * EXPORTED FUNCTIONS *
2304 **********************/
2305
@@ -2197,10 +2308,11 @@
2308 struct Blob *ob, /* output blob for rendered text */
2309 struct Blob *ib, /* input blob in markdown */
2310 const struct mkd_renderer *rndrer /* renderer descriptor (callbacks) */
2311 ){
2312 struct link_ref *lr;
2313 struct footnote *fn;
2314 size_t i, beg, end = 0;
2315 struct render rndr;
2316 char *ib_data;
2317 Blob text = BLOB_INITIALIZER;
2318
@@ -2208,10 +2320,12 @@
2320 if( !rndrer ) return;
2321 rndr.make = *rndrer;
2322 rndr.nBlobCache = 0;
2323 rndr.iDepth = 0;
2324 rndr.refs = empty_blob;
2325 rndr.footnotes = empty_blob;
2326 rndr.nLabels = 0;
2327 for(i=0; i<256; i++) rndr.active_char[i] = 0;
2328 if( (rndr.make.emphasis
2329 || rndr.make.double_emphasis
2330 || rndr.make.triple_emphasis)
2331 && rndr.make.emph_chars
@@ -2230,10 +2344,13 @@
2344 /* first pass: looking for references, copying everything else */
2345 beg = 0;
2346 ib_data = blob_buffer(ib);
2347 while( beg<blob_size(ib) ){ /* iterating over lines */
2348 if( is_ref(ib_data, beg, blob_size(ib), &end, &rndr.refs) ){
2349 beg = end;
2350 }else if( is_footnote(ib_data, beg, blob_size(ib), &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<blob_size(ib) && ib_data[end]!='\n' && ib_data[end]!='\r' ){
2356 end += 1;
@@ -2258,14 +2375,42 @@
2375 qsort(blob_buffer(&rndr.refs),
2376 blob_size(&rndr.refs)/sizeof(struct link_ref),
2377 sizeof(struct link_ref),
2378 cmp_link_ref_sort);
2379 }
2380 /* sorting the footnotes array by id */
2381 if( blob_size(&rndr.footnotes) ){
2382 qsort(blob_buffer(&rndr.footnotes),
2383 blob_size(&rndr.footnotes)/sizeof(struct footnote),
2384 sizeof(struct footnote),
2385 cmp_link_ref_sort);
2386 }
2387
2388 /* second pass: actual rendering */
2389 if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
2390 parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
2391
2392 /* sorting the footnotes array by index */
2393 if( blob_size(&rndr.footnotes) ){
2394 qsort(blob_buffer(&rndr.footnotes),
2395 blob_size(&rndr.footnotes)/sizeof(struct footnote),
2396 sizeof(struct footnote),
2397 cmp_footnote_sort);
2398 }
2399 /* FIXME: decouple parsing and HTML-specific rendering of footnotes */
2400 if( rndr.nLabels ){
2401 fn = (struct footnote *)blob_buffer(&rndr.footnotes);
2402 end = blob_size(&rndr.footnotes)/sizeof(struct footnote);
2403 blob_appendf(ob, "\n<ol class='footnotes'>\n");
2404 for(i=0; i<end; i++){
2405 if(fn[i].index == 0) continue;
2406 blob_appendf(ob, "<li id='footnote-%i'>\n ", fn[i].index );
2407 parse_inline(ob,&rndr,blob_buffer(&fn[i].text),blob_size(&fn[i].text));
2408 blob_append(ob,"\n</li>\n",7);
2409 }
2410 blob_append(ob, "</ol>\n", 7);
2411 }
2412 if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
2413
2414 /* clean-up */
2415 assert( rndr.iDepth==0 );
2416 blob_reset(&text);
2417

Keyboard Shortcuts

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