Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Initial support for [forum:/forumthread/d752446a4f63f390|footnotes in Markdown]. <br>This is WIP: support of multiline notes and code clean-up are pending. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | markdown-footnotes |
| Files: | files | file ages | folders |
| SHA3-256: |
ebce0f357e0732cacdcc4105623c9438 |
| User & Date: | george 2022-01-26 14:50:15.087 |
Context
|
2022-01-27
| ||
| 19:45 | Minor code clean-up of src/markdown.c: add a few 'const' specifiers, reduce the scope of temporary variables and simplify their names. check-in: b9393a4e64 user: george tags: markdown-footnotes | |
|
2022-01-26
| ||
| 14:50 | Initial support for [forum:/forumthread/d752446a4f63f390|footnotes in Markdown]. <br>This is WIP: support of multiline notes and code clean-up are pending. check-in: ebce0f357e user: george tags: markdown-footnotes | |
|
2022-01-25
| ||
| 17:44 | Update the built-in SQLite to the latest 3.38.0 beta, for the purpose of beta testing SQLite. check-in: 605064e656 user: drh tags: trunk | |
Changes
Changes to src/default.css.
| ︙ | ︙ | |||
1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 |
}
body.branch .submenu > a.timeline-link.selected {
display: inline;
}
.monospace {
font-family: monospace;
}
/* Objects in the "desktoponly" class are invisible on mobile */
@media screen and (max-width: 600px) {
.desktoponly {
display: none;
}
| > > > > > > | 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 |
}
body.branch .submenu > a.timeline-link.selected {
display: inline;
}
.monospace {
font-family: monospace;
}
div.content div.markdown > ol.footnotes {
font-size: 90%;
}
div.content div.markdown > ol.footnotes > li {
margin-bottom: 0.5em;
}
/* Objects in the "desktoponly" class are invisible on mobile */
@media screen and (max-width: 600px) {
.desktoponly {
display: none;
}
|
| ︙ | ︙ |
Changes to src/markdown.c.
| ︙ | ︙ | |||
127 128 129 130 131 132 133 |
/***************
* LOCAL TYPES *
***************/
/* link_ref -- reference to a link */
struct link_ref {
| | > > > > > > > > < | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
/***************
* LOCAL TYPES *
***************/
/* link_ref -- reference to a link */
struct link_ref {
struct Blob id; /* must be the first field as in footnote struct */
struct Blob link;
struct Blob title;
};
struct footnote {
struct Blob id; /* must be the first field as in link_ref struct */
struct Blob text; /* footnote's content that is rendered at the end */
int index; /* serial number, in the order of appearance */
};
/* char_trigger -- function pointer to render active chars */
/* returns the number of chars taken care of */
/* data is the pointer of the beginning of the span */
/* offset is the number of valid chars before data */
struct render;
typedef size_t (*char_trigger)(
struct Blob *ob,
struct render *rndr,
char *data,
size_t offset,
size_t size);
/* render -- structure containing one particular render */
struct render {
struct mkd_renderer make;
struct Blob refs;
struct Blob footnotes;
char_trigger active_char[256];
int iDepth; /* Depth of recursion */
int nBlobCache; /* Number of entries in aBlobCache */
struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
int nLabels; /* Footnotes counter for the second pass */
};
/* html_tag -- structure for quick HTML tag search (inspired from discount) */
struct html_tag {
const char *text;
int size;
};
|
| ︙ | ︙ | |||
245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
/* cmp_link_ref_sort -- comparison function for link_ref qsort */
static int cmp_link_ref_sort(const void *a, const void *b){
struct link_ref *lra = (void *)a;
struct link_ref *lrb = (void *)b;
return blob_compare(&lra->id, &lrb->id);
}
/* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
static int cmp_html_tag(const void *a, const void *b){
const struct html_tag *hta = a;
const struct html_tag *htb = b;
int sz = hta->size;
int c;
| > > > > > > > | 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
/* cmp_link_ref_sort -- comparison function for link_ref qsort */
static int cmp_link_ref_sort(const void *a, const void *b){
struct link_ref *lra = (void *)a;
struct link_ref *lrb = (void *)b;
return blob_compare(&lra->id, &lrb->id);
}
/* cmp_footnote_sort -- comparison function for footnotes qsort */
static int cmp_footnote_sort(const void *a, const void *b){
const struct footnote *fna = (void *)a, *fnb = (void *)b;
assert( fna->index > 0 && fnb->index > 0 );
if( fna->index == fnb->index ) return 0;
return ( fna->index < fnb->index ? -1 : 1 );
}
/* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
static int cmp_html_tag(const void *a, const void *b){
const struct html_tag *hta = a;
const struct html_tag *htb = b;
int sz = hta->size;
int c;
|
| ︙ | ︙ | |||
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 |
blob_reset(link);
blob_reset(title);
blob_append(link, blob_buffer(&lr->link), blob_size(&lr->link));
blob_append(title, blob_buffer(&lr->title), blob_size(&lr->title));
return 0;
}
/* char_link -- '[': parsing a link or an image */
static size_t char_link(
struct Blob *ob,
struct render *rndr,
char *data,
size_t offset,
size_t size
){
int is_img = (offset && data[-1] == '!'), level;
size_t i = 1, txt_e;
struct Blob *content = 0;
struct Blob *link = 0;
struct Blob *title = 0;
int ret;
/* checking whether the correct renderer exists */
if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){
return 0;
}
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 |
blob_reset(link);
blob_reset(title);
blob_append(link, blob_buffer(&lr->link), blob_size(&lr->link));
blob_append(title, blob_buffer(&lr->title), blob_size(&lr->title));
return 0;
}
/* get_footnote -- resolve footnote by its id
* on success: fill text and return positive footnote's index
* on failure: return -1 */
static int get_footnote(
struct render *rndr,
struct Blob *text,
const char *data,
size_t size
){
struct footnote *fn;
blob_reset(text); /* use text for temporary storage */
if( build_ref_id(text, data, size)<0 ) return -1;
fn = bsearch(text,
blob_buffer(&rndr->footnotes),
blob_size(&rndr->footnotes)/sizeof(struct footnote),
sizeof (struct footnote),
cmp_link_ref);
if( !fn ) return -1;
if( fn->index == 0 ){ /* the first reference to the footnote */
fn->index = ++(rndr->nLabels);
}
assert( fn->index > 0 );
blob_reset(text);
blob_append(text, blob_buffer(&fn->text), blob_size(&fn->text));
return fn->index;
}
/* char_link -- '[': parsing a link or an image */
static size_t char_link(
struct Blob *ob,
struct render *rndr,
char *data,
size_t offset,
size_t size
){
int is_img = (offset && data[-1] == '!'), level;
size_t i = 1, txt_e;
struct Blob *content = 0;
struct Blob *link = 0;
struct Blob *title = 0;
const int is_note = (size && data[1] == '^');
int ret;
/* checking whether the correct renderer exists */
if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){
return 0;
}
|
| ︙ | ︙ | |||
1081 1082 1083 1084 1085 1086 1087 |
id_data = data+1;
id_size = txt_e-1;
}else{
/* explicit id - between brackets */
id_data = data+i+1;
id_size = id_end-(i+1);
}
| < < > > > > > > > > | | | 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 |
id_data = data+1;
id_size = txt_e-1;
}else{
/* explicit id - between brackets */
id_data = data+i+1;
id_size = id_end-(i+1);
}
if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
goto char_link_cleanup;
}
i = id_end+1;
/* shortcut reference style link */
}else{
if( is_note ){
const int lbl = get_footnote(rndr, link, data+1, txt_e-1);
if( lbl <= 0 ){
goto char_link_cleanup;
}
blob_reset(link);
blob_appendf(link, "#footnote-%i", lbl);
blob_appendf(content,"<sup class='footnote'>%i</sup>",lbl);
}else if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
goto char_link_cleanup;
}
/* rewinding the whitespace */
i = txt_e+1;
}
/* building content: img alt is escaped, link content is parsed */
if( txt_e>1 ){
if( is_img ) blob_append(content, data+1, txt_e-1);
else if(!is_note) parse_inline(content, rndr, data+1, txt_e-1);
}
/* calling the relevant rendering function */
if( is_img ){
if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
}else{
|
| ︙ | ︙ | |||
2093 2094 2095 2096 2097 2098 2099 |
if( data[beg+3]==' ' ) return 0;
}
}
}
i += beg;
/* id part: anything but a newline between brackets */
| | | 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 |
if( data[beg+3]==' ' ) return 0;
}
}
}
i += beg;
/* id part: anything but a newline between brackets */
if( data[i]!='[' || data[i+1]=='^' ) return 0;
i++;
id_offset = i;
while( i<end && data[i]!='\n' && data[i]!='\r' && data[i]!=']' ){ i++; }
if( i>=end || data[i]!=']' ) return 0;
id_end = i;
/* spacer: colon (space | tab)* newline? (space | tab)* */
|
| ︙ | ︙ | |||
2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 |
if( title_end>title_offset ){
blob_append(&lr.title, data+title_offset, title_end-title_offset);
}
blob_append(refs, (char *)&lr, sizeof lr);
return 1;
}
/**********************
* EXPORTED FUNCTIONS *
**********************/
/* markdown -- parses the input buffer and renders it into the output buffer */
void markdown(
struct Blob *ob, /* output blob for rendered text */
struct Blob *ib, /* input blob in markdown */
const struct mkd_renderer *rndrer /* renderer descriptor (callbacks) */
){
struct link_ref *lr;
size_t i, beg, end = 0;
struct render rndr;
char *ib_data;
Blob text = BLOB_INITIALIZER;
/* filling the render structure */
if( !rndrer ) return;
rndr.make = *rndrer;
rndr.nBlobCache = 0;
rndr.iDepth = 0;
rndr.refs = empty_blob;
for(i=0; i<256; i++) rndr.active_char[i] = 0;
if( (rndr.make.emphasis
|| rndr.make.double_emphasis
|| rndr.make.triple_emphasis)
&& rndr.make.emph_chars
){
for(i=0; rndr.make.emph_chars[i]; i++){
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 |
if( title_end>title_offset ){
blob_append(&lr.title, data+title_offset, title_end-title_offset);
}
blob_append(refs, (char *)&lr, sizeof lr);
return 1;
}
/*********************
* FOOTNOTE PARSING *
*********************/
/* is_footnote -- returns whether a line is a footnote or not */
static int is_footnote(
char *data, /* input text */
size_t beg, /* offset of the beginning of the line */
size_t end, /* offset of the end of the text */
size_t *last, /* last character of the link */
struct Blob * footnotes /* FIXME: struct render *rndr */
){
size_t i = 0;
size_t id_offset, id_end;
size_t note_offset, note_end;
size_t line_end;
struct footnote fn = { empty_blob, empty_blob, 0 };
/* footnote definition must start at the begining of a line */
if( beg+4>=end ) return 0;
i += beg;
/* id part: anything but a newline between brackets */
if( data[i]!='[' || data[i+1]!='^' ) return 0;
i++;
id_offset = i;
while( i<end && data[i]!=']' && data[i]!='\n' && data[i]!='\r' ){ i++; }
if( i>=end || data[i]!=']' ) return 0;
id_end = i;
/* spacer: colon (space | tab)* newline? (space | tab)* */
i++;
if( i>=end || data[i]!=':' ) return 0;
i++;
while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
if( i<end && (data[i]=='\n' || data[i]=='\r') ){
i++;
if( i<end && data[i]=='\r' && data[i-1] == '\n' ) i++;
}
while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
if( i>=end ) return 0;
/* note is a single line of text (FIXME: support multiline notes) */
note_offset = i;
while( i<end && data[i]!='\r' && data[i]!='\n' ){ i++; }
note_end = i;
/* computing end-of-line */
line_end = 0;
if( i >=end || data[i]=='\r' || data[ i ]=='\n' ) line_end = i;
if( i+1<end && data[i]=='\n' && data[i+1]=='\r' ) line_end = i+1;
if( !line_end ) return 0; /* garbage after the link */
/* a valid note has been found, filling-in note's text */
if( last ) *last = line_end;
if( !footnotes ) return 1;
if( build_ref_id(&fn.id, data+id_offset, id_end-id_offset)<0 ) return 0;
blob_append(&fn.text, data+note_offset, note_end-note_offset);
blob_append(footnotes, (char *)&fn, sizeof fn);
return 1;
}
/**********************
* EXPORTED FUNCTIONS *
**********************/
/* markdown -- parses the input buffer and renders it into the output buffer */
void markdown(
struct Blob *ob, /* output blob for rendered text */
struct Blob *ib, /* input blob in markdown */
const struct mkd_renderer *rndrer /* renderer descriptor (callbacks) */
){
struct link_ref *lr;
struct footnote *fn;
size_t i, beg, end = 0;
struct render rndr;
char *ib_data;
Blob text = BLOB_INITIALIZER;
/* filling the render structure */
if( !rndrer ) return;
rndr.make = *rndrer;
rndr.nBlobCache = 0;
rndr.iDepth = 0;
rndr.refs = empty_blob;
rndr.footnotes = empty_blob;
rndr.nLabels = 0;
for(i=0; i<256; i++) rndr.active_char[i] = 0;
if( (rndr.make.emphasis
|| rndr.make.double_emphasis
|| rndr.make.triple_emphasis)
&& rndr.make.emph_chars
){
for(i=0; rndr.make.emph_chars[i]; i++){
|
| ︙ | ︙ | |||
2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 |
rndr.active_char['&'] = char_entity;
/* first pass: looking for references, copying everything else */
beg = 0;
ib_data = blob_buffer(ib);
while( beg<blob_size(ib) ){ /* iterating over lines */
if( is_ref(ib_data, beg, blob_size(ib), &end, &rndr.refs) ){
beg = end;
}else{ /* skipping to the next line */
end = beg;
while( end<blob_size(ib) && ib_data[end]!='\n' && ib_data[end]!='\r' ){
end += 1;
}
/* adding the line body if present */
| > > > | 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 |
rndr.active_char['&'] = char_entity;
/* first pass: looking for references, copying everything else */
beg = 0;
ib_data = blob_buffer(ib);
while( beg<blob_size(ib) ){ /* iterating over lines */
if( is_ref(ib_data, beg, blob_size(ib), &end, &rndr.refs) ){
beg = end;
}else if( is_footnote(ib_data, beg, blob_size(ib), &end, &rndr.footnotes) ){
/* FIXME: fossil_print("\nfootnote found at %i\n", beg); */
beg = end;
}else{ /* skipping to the next line */
end = beg;
while( end<blob_size(ib) && ib_data[end]!='\n' && ib_data[end]!='\r' ){
end += 1;
}
/* adding the line body if present */
|
| ︙ | ︙ | |||
2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 |
/* sorting the reference array */
if( blob_size(&rndr.refs) ){
qsort(blob_buffer(&rndr.refs),
blob_size(&rndr.refs)/sizeof(struct link_ref),
sizeof(struct link_ref),
cmp_link_ref_sort);
}
/* second pass: actual rendering */
if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
/* clean-up */
assert( rndr.iDepth==0 );
blob_reset(&text);
lr = (struct link_ref *)blob_buffer(&rndr.refs);
end = blob_size(&rndr.refs)/sizeof(struct link_ref);
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 |
/* sorting the reference array */
if( blob_size(&rndr.refs) ){
qsort(blob_buffer(&rndr.refs),
blob_size(&rndr.refs)/sizeof(struct link_ref),
sizeof(struct link_ref),
cmp_link_ref_sort);
}
/* sorting the footnotes array by id */
if( blob_size(&rndr.footnotes) ){
qsort(blob_buffer(&rndr.footnotes),
blob_size(&rndr.footnotes)/sizeof(struct footnote),
sizeof(struct footnote),
cmp_link_ref_sort);
}
/* second pass: actual rendering */
if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
/* sorting the footnotes array by index */
if( blob_size(&rndr.footnotes) ){
qsort(blob_buffer(&rndr.footnotes),
blob_size(&rndr.footnotes)/sizeof(struct footnote),
sizeof(struct footnote),
cmp_footnote_sort);
}
/* FIXME: decouple parsing and HTML-specific rendering of footnotes */
if( rndr.nLabels ){
fn = (struct footnote *)blob_buffer(&rndr.footnotes);
end = blob_size(&rndr.footnotes)/sizeof(struct footnote);
blob_appendf(ob, "\n<ol class='footnotes'>\n");
for(i=0; i<end; i++){
if(fn[i].index == 0) continue;
blob_appendf(ob, "<li id='footnote-%i'>\n ", fn[i].index );
parse_inline(ob,&rndr,blob_buffer(&fn[i].text),blob_size(&fn[i].text));
blob_append(ob,"\n</li>\n",7);
}
blob_append(ob, "</ol>\n", 7);
}
if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
/* clean-up */
assert( rndr.iDepth==0 );
blob_reset(&text);
lr = (struct link_ref *)blob_buffer(&rndr.refs);
end = blob_size(&rndr.refs)/sizeof(struct link_ref);
|
| ︙ | ︙ |