Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | 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. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | markdown-footnotes |
| Files: | files | file ages | folders |
| SHA3-256: |
e3710ccd3a5af6ad7b784f5ac42b7810 |
| User & Date: | george 2022-01-29 00:19:48.472 |
References
|
2022-02-01
| ||
| 20:12 | Support multiline footnote definitions and inline footnotes via <tt>^[...]</tt> syntax (this syntax is not settled yet). Fix overall link support that was broken by [e3710ccd3a5a]. ... (check-in: 78b7846b8e user: george tags: markdown-footnotes) | |
Context
|
2022-01-30
| ||
| 17:08 | Add an "eye-candy": if a footnote's mark is followed then the corresponding back-reference is highlighted, if a footnote's back-reference is followed then highlight the corresponding footnote's mark. ... (check-in: 50dcf92f85 user: george tags: markdown-footnotes) | |
|
2022-01-29
| ||
| 00:19 | 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. ... (check-in: e3710ccd3a user: george tags: markdown-footnotes) | |
|
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) | |
Changes
Changes to src/default.css.
| ︙ | ︙ | |||
1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 |
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;
}
| > > > > | 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 |
font-family: monospace;
}
div.content div.markdown > ol.footnotes {
font-size: 90%;
}
div.content div.markdown > ol.footnotes > li {
margin-bottom: 0.5em;
}
div.content div.markdown > ol.footnotes > li > .footnote-backrefs {
margin-right: 1em;
font-weight: bold;
}
/* Objects in the "desktoponly" class are invisible on mobile */
@media screen and (max-width: 600px) {
.desktoponly {
display: none;
}
|
| ︙ | ︙ |
Changes to src/markdown.c.
| ︙ | ︙ | |||
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
};
/* mkd_renderer -- functions for rendering parsed data */
struct mkd_renderer {
/* document level callbacks */
void (*prolog)(struct Blob *ob, void *opaque);
void (*epilog)(struct Blob *ob, void *opaque);
/* block level callbacks - NULL skips the block */
void (*blockcode)(struct Blob *ob, struct Blob *text, void *opaque);
void (*blockquote)(struct Blob *ob, struct Blob *text, void *opaque);
void (*blockhtml)(struct Blob *ob, struct Blob *text, void *opaque);
void (*header)(struct Blob *ob, struct Blob *text,
int level, void *opaque);
void (*hrule)(struct Blob *ob, void *opaque);
void (*list)(struct Blob *ob, struct Blob *text, int flags, void *opaque);
void (*listitem)(struct Blob *ob, struct Blob *text,
int flags, void *opaque);
void (*paragraph)(struct Blob *ob, struct Blob *text, void *opaque);
void (*table)(struct Blob *ob, struct Blob *head_row, struct Blob *rows,
void *opaque);
void (*table_cell)(struct Blob *ob, struct Blob *text, int flags,
void *opaque);
void (*table_row)(struct Blob *ob, struct Blob *cells, int flags,
void *opaque);
/* span level callbacks - NULL or return 0 prints the span verbatim */
int (*autolink)(struct Blob *ob, struct Blob *link,
enum mkd_autolink type, void *opaque);
int (*codespan)(struct Blob *ob, struct Blob *text, int nSep, void *opaque);
int (*double_emphasis)(struct Blob *ob, struct Blob *text,
char c, void *opaque);
int (*emphasis)(struct Blob *ob, struct Blob *text, char c,void*opaque);
int (*image)(struct Blob *ob, struct Blob *link, struct Blob *title,
struct Blob *alt, void *opaque);
int (*linebreak)(struct Blob *ob, void *opaque);
int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
struct Blob *content, void *opaque);
int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
char c, void *opaque);
/* low level callbacks - NULL copies input directly into the output */
void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);
/* renderer data */
const char *emph_chars; /* chars that trigger emphasis rendering */
| > > > > | 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
};
/* mkd_renderer -- functions for rendering parsed data */
struct mkd_renderer {
/* document level callbacks */
void (*prolog)(struct Blob *ob, void *opaque);
void (*epilog)(struct Blob *ob, void *opaque);
void (*footnotes)(struct Blob *ob, const struct Blob *items, void *opaque);
/* block level callbacks - NULL skips the block */
void (*blockcode)(struct Blob *ob, struct Blob *text, void *opaque);
void (*blockquote)(struct Blob *ob, struct Blob *text, void *opaque);
void (*blockhtml)(struct Blob *ob, struct Blob *text, void *opaque);
void (*header)(struct Blob *ob, struct Blob *text,
int level, void *opaque);
void (*hrule)(struct Blob *ob, void *opaque);
void (*list)(struct Blob *ob, struct Blob *text, int flags, void *opaque);
void (*listitem)(struct Blob *ob, struct Blob *text,
int flags, void *opaque);
void (*paragraph)(struct Blob *ob, struct Blob *text, void *opaque);
void (*table)(struct Blob *ob, struct Blob *head_row, struct Blob *rows,
void *opaque);
void (*table_cell)(struct Blob *ob, struct Blob *text, int flags,
void *opaque);
void (*table_row)(struct Blob *ob, struct Blob *cells, int flags,
void *opaque);
void (*footnote_item)(struct Blob *ob, const struct Blob *text,
int index, int nUsed, void *opaque);
/* span level callbacks - NULL or return 0 prints the span verbatim */
int (*autolink)(struct Blob *ob, struct Blob *link,
enum mkd_autolink type, void *opaque);
int (*codespan)(struct Blob *ob, struct Blob *text, int nSep, void *opaque);
int (*double_emphasis)(struct Blob *ob, struct Blob *text,
char c, void *opaque);
int (*emphasis)(struct Blob *ob, struct Blob *text, char c,void*opaque);
int (*image)(struct Blob *ob, struct Blob *link, struct Blob *title,
struct Blob *alt, void *opaque);
int (*linebreak)(struct Blob *ob, void *opaque);
int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
struct Blob *content, void *opaque);
int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
char c, void *opaque);
int (*footnote_ref)(struct Blob *ob, int index, int locus, void *opaque);
/* low level callbacks - NULL copies input directly into the output */
void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);
/* renderer data */
const char *emph_chars; /* chars that trigger emphasis rendering */
|
| ︙ | ︙ | |||
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
struct Blob *ob,
const struct Blob *ib,
const struct mkd_renderer *rndr);
#endif /* INTERFACE */
/***************
* 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;
| > > > < | > > | 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
struct Blob *ob,
const struct Blob *ib,
const struct mkd_renderer *rndr);
#endif /* INTERFACE */
#define BLOB_COUNT(blob_ptr,el_type) (blob_size(blob_ptr)/sizeof(el_type))
#define COUNT_FOOTNOTES(blob_ptr) BLOB_COUNT(blob_ptr,struct footnote)
/***************
* LOCAL TYPES *
***************/
/* link_ref -- reference to a link */
struct link_ref {
struct Blob id; /* must be the first field as in footnote struct */
struct Blob link;
struct Blob title;
};
struct footnote {
struct Blob id; /* must be the first field as in link_ref struct */
struct Blob text; /* footnote's content that is rendered at the end */
int index; /* serial number, in the order of appearance */
int nUsed; /* number of references to this note */
};
/* char_trigger -- function pointer to render active chars */
/* returns the number of chars taken care of */
/* data is the pointer of the beginning of the span */
/* offset is the number of valid chars before data */
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;
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 */
struct Blob notes; /* array of footnotes */
int iNotesCount; /* count distinct indices found in the second pass */
};
/* html_tag -- structure for quick HTML tag search (inspired from discount) */
struct html_tag {
const char *text;
int size;
};
|
| ︙ | ︙ | |||
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);
}
| | > | > > | 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
/* 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.
* Unused footnotes (when index == 0) sort last */
static int cmp_footnote_sort(const void *a, const void *b){
const struct footnote *fna = (void *)a, *fnb = (void *)b;
assert( fna->index >= 0 && fnb->index >= 0 );
if( fna->index == fnb->index ) return 0;
if( fna->index == 0 ) return 1;
if( fnb->index == 0 ) return -1;
return ( fna->index < fnb->index ? -1 : 1 );
}
/* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
static int cmp_html_tag(const void *a, const void *b){
const struct html_tag *hta = a;
const struct html_tag *htb = b;
|
| ︙ | ︙ | |||
1015 1016 1017 1018 1019 1020 1021 | 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; } | | | < | | < | < | | < | > | > | > | | | > > | | | 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 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 |
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() is invoked during the second pass
* on success: fill text and return positive footnote's index
* on failure: return -1 */
static const struct footnote* get_footnote(
struct render *rndr,
const char *data,
size_t size
){
struct footnote *fn = NULL;
struct Blob *id = new_work_buffer(rndr);
if( build_ref_id(id, data, size)<0 ) goto cleanup;
fn = bsearch(id, blob_buffer(&rndr->notes),
COUNT_FOOTNOTES(&rndr->notes),
sizeof (struct footnote),
cmp_link_ref);
if( !fn ) goto cleanup;
if( fn->index == 0 ){ /* the first reference to the footnote */
assert( fn->nUsed == 0 );
fn->index = ++(rndr->iNotesCount);
}
fn->nUsed++;
assert( fn->index > 0 );
assert( fn->nUsed > 0 );
cleanup:
release_work_buffer( rndr, id );
return fn;
}
/* 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
){
const int is_img = (offset && data[-1] == '!');
const int is_inline = (offset && data[-1]=='^');
const int is_note = !is_img && (is_inline || (size>1 && data[1]=='^'));
size_t i = 1, txt_e;
struct Blob *content = 0;
struct Blob *link = 0;
struct Blob *title = 0;
const struct footnote *fn = 0;
int level, ret;
/* checking whether the correct renderer exists */
if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){
return 0;
}
/* looking for the matching closing bracket */
|
| ︙ | ︙ | |||
1133 1134 1135 1136 1137 1138 1139 |
goto char_link_cleanup;
}
i = id_end+1;
/* shortcut reference style link */
}else{
if( is_note ){
| > | | < > | < < | > > > > | 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 |
goto char_link_cleanup;
}
i = id_end+1;
/* shortcut reference style link */
}else{
if( is_note ){
if( is_inline ){
//fn = put_footnote(rndr, data+1, txt_e-1);
}else{
fn = get_footnote(rndr, data+1, txt_e-1);
}
if( !fn ) goto char_link_cleanup;
}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_inline) 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 if(fn){
if(rndr->make.footnote_ref){
ret = rndr->make.footnote_ref(ob,fn->index,fn->nUsed,rndr->make.opaque);
}
}else{
ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
}
/* cleanup */
char_link_cleanup:
release_work_buffer(rndr, title);
|
| ︙ | ︙ | |||
2248 2249 2250 2251 2252 2253 2254 |
size_t *last, /* last character of the link */
struct Blob * footnotes
){
size_t i = 0;
size_t id_offset, id_end;
size_t note_offset, note_end;
size_t line_end;
| | | 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 |
size_t *last, /* last character of the link */
struct Blob * footnotes
){
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, 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;
|
| ︙ | ︙ | |||
2309 2310 2311 2312 2313 2314 2315 |
const 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;
| | > | | | | 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 |
const 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;
Blob text = BLOB_INITIALIZER; /* input after the first pass */
int nLabeled; /* number of footnotes found by the first pass */
/* filling the render structure */
if( !rndrer ) return;
rndr.make = *rndrer;
rndr.nBlobCache = 0;
rndr.iDepth = 0;
rndr.refs = empty_blob;
rndr.notes = empty_blob;
rndr.iNotesCount = 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++){
|
| ︙ | ︙ | |||
2343 2344 2345 2346 2347 2348 2349 |
/* first pass: iterate over lines looking for references,
* copying everything else into "text" */
beg = 0;
for(const size_t size = blob_size(ib); beg<size ;){
const char* const data = blob_buffer(ib);
if( is_ref(data, beg, size, &end, &rndr.refs) ){
beg = end;
| | | 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 |
/* first pass: iterate over lines looking for references,
* copying everything else into "text" */
beg = 0;
for(const size_t size = blob_size(ib); beg<size ;){
const char* const data = blob_buffer(ib);
if( is_ref(data, beg, size, &end, &rndr.refs) ){
beg = end;
}else if(is_footnote(data, beg, size, &end, &rndr.notes)){
/* FIXME: fossil_print("\nfootnote found at %i\n", beg); */
beg = end;
}else{ /* skipping to the next line */
end = beg;
while( end<size && data[end]!='\n' && data[end]!='\r' ){
end += 1;
}
|
| ︙ | ︙ | |||
2371 2372 2373 2374 2375 2376 2377 2378 |
/* 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 */
| > | | < < | | | | | | < < < < < | | | | > | | > | > > > > > > > > | 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 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 |
/* 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);
}
nLabeled = COUNT_FOOTNOTES(&rndr.notes);
/* sorting the footnotes array by id */
if( nLabeled ){
qsort(blob_buffer(&rndr.notes), nLabeled, sizeof(struct footnote),
cmp_link_ref_sort);
}
/* second pass: actual rendering */
if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
fn = (struct footnote*)blob_buffer(&rndr.notes);
if(rndr.iNotesCount && rndr.make.footnote_item && rndr.make.footnotes){
Blob * one_item = new_work_buffer(&rndr);
Blob * all_items = new_work_buffer(&rndr);
qsort( fn, COUNT_FOOTNOTES(&rndr.notes), sizeof(struct footnote),
cmp_footnote_sort /* sort footnotes by index */ );
blob_reset( all_items );
for(i=0; i<rndr.iNotesCount; i++){
assert( fn[i].index == i+1 );
blob_reset( one_item );
parse_inline( one_item, &rndr, blob_buffer(&fn[i].text),
blob_size(&fn[i].text));
rndr.make.footnote_item( all_items, one_item, i+1, fn[i].nUsed, rndr.make.opaque);
}
rndr.make.footnotes(ob, all_items, rndr.make.opaque );
release_work_buffer( &rndr, one_item );
release_work_buffer( &rndr, all_items );
}
if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
/* clean-up */
assert( rndr.iDepth==0 );
blob_reset(&text);
lr = (struct link_ref *)blob_buffer(&rndr.refs);
end = blob_size(&rndr.refs)/sizeof(struct link_ref);
for(i=0; i<end; i++){
blob_reset(&lr[i].id);
blob_reset(&lr[i].link);
blob_reset(&lr[i].title);
}
blob_reset(&rndr.refs);
end = COUNT_FOOTNOTES(&rndr.notes);
for(i=0; i<end; i++){
blob_reset(&fn[i].id);
blob_reset(&fn[i].text);
}
blob_reset(&rndr.notes);
for(i=0; i<rndr.nBlobCache; i++){
fossil_free(rndr.aBlobCache[i]);
}
}
|
Changes to src/markdown_html.c.
| ︙ | ︙ | |||
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
void markdown_to_html(
struct Blob *input_markdown,
struct Blob *output_title,
struct Blob *output_body);
#endif /* INTERFACE */
/*
** An instance of the following structure is passed through the
** "opaque" pointer.
*/
typedef struct MarkdownToHtml MarkdownToHtml;
struct MarkdownToHtml {
Blob *output_title; /* Store the title here */
};
/* INTER_BLOCK -- skip a line between block level elements */
#define INTER_BLOCK(ob) \
do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0)
/* BLOB_APPEND_LITERAL -- append a string literal to a blob */
#define BLOB_APPEND_LITERAL(blob, literal) \
blob_append((blob), "" literal, (sizeof literal)-1)
/*
* The empty string in the second argument leads to a syntax error
* when the macro is not used with a string literal. Unfortunately
* the error is not overly explicit.
*/
/* BLOB_APPEND_BLOB -- append blob contents to another */
#define BLOB_APPEND_BLOB(dest, src) \
blob_append((dest), blob_buffer(src), blob_size(src))
/* HTML escapes
**
** html_escape() converts < to <, > to >, and & to &.
** html_quote() goes further and converts " into " and ' in '.
*/
static void html_quote(struct Blob *ob, const char *data, size_t size){
| > > > > > > > > > > > > > > > > > > > | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
void markdown_to_html(
struct Blob *input_markdown,
struct Blob *output_title,
struct Blob *output_body);
#endif /* INTERFACE */
typedef union { uint64_t u; char c[8]; unsigned char b[8]; } bitfield64_t;
/*
** An instance of the following structure is passed through the
** "opaque" pointer.
*/
typedef struct MarkdownToHtml MarkdownToHtml;
struct MarkdownToHtml {
Blob *output_title; /* Store the title here */
bitfield64_t unique; /* Enables construction of unique #id elements */
};
/* INTER_BLOCK -- skip a line between block level elements */
#define INTER_BLOCK(ob) \
do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0)
/* BLOB_APPEND_LITERAL -- append a string literal to a blob */
#define BLOB_APPEND_LITERAL(blob, literal) \
blob_append((blob), "" literal, (sizeof literal)-1)
/*
* The empty string in the second argument leads to a syntax error
* when the macro is not used with a string literal. Unfortunately
* the error is not overly explicit.
*/
/* BLOB_APPEND_BLOB -- append blob contents to another */
#define BLOB_APPEND_BLOB(dest, src) \
blob_append((dest), blob_buffer(src), blob_size(src))
/* Converts an integer to a null-terminated base26 representation
* Return empty string if that integer is negative. */
static bitfield64_t to_base26(int i, int uppercase){
bitfield64_t x;
int j;
x.u = 0;
if( i >= 0 ){
for(j=7; j >= 0; j--){
x.b[j] = (unsigned char)(uppercase?'A':'a') + i%26;
if( (i /= 26) == 0 ) break;
}
x.u >>= 8*j;
}
x.c[7] = 0;
return x;
}
/* HTML escapes
**
** html_escape() converts < to <, > to >, and & to &.
** html_quote() goes further and converts " into " and ' in '.
*/
static void html_quote(struct Blob *ob, const char *data, size_t size){
|
| ︙ | ︙ | |||
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
void *opaque
){
BLOB_APPEND_LITERAL(ob, " <tr>\n");
BLOB_APPEND_BLOB(ob, cells);
BLOB_APPEND_LITERAL(ob, " </tr>\n");
}
/* HTML span tags */
static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){
blob_append(ob, blob_buffer(text), blob_size(text));
return 1;
}
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 |
void *opaque
){
BLOB_APPEND_LITERAL(ob, " <tr>\n");
BLOB_APPEND_BLOB(ob, cells);
BLOB_APPEND_LITERAL(ob, " </tr>\n");
}
static int html_footnote_ref(
struct Blob *ob, int index, int locus, void *opaque
){
const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
const bitfield64_t l = to_base26(locus-1,0);
char pos[32];
/* expect BUGs if the following yields compiler warnings */
memset(pos,0,32);
sprintf(pos, "%s%i-%s", ctx->unique.c, index, l.c);
BLOB_APPEND_LITERAL(ob,"<a class='noteref' href='#footnote-");
blob_appendf(ob,"%s' id='noteref-%s'><sup>%i</sup></a>",
pos, pos, index);
return 1;
}
/* Render a single item of the footnotes list.
* Each backref gets a unique id to enable dynamic styling. */
static void html_footnote_item(
struct Blob *ob, const struct Blob *text, int index, int nUsed, void *opaque
){
const struct MarkdownToHtml *ctx = (struct MarkdownToHtml*)opaque;
char pos[24];
if( index <= 0 || nUsed < 0 || !text || !blob_size(text) ){
return;
}
/* expect BUGs if the following yields compiler warnings */
memset(pos,0,24);
sprintf(pos, "%s%i", ctx->unique.c, index);
blob_appendf(ob, "<li id='footnote-%s'>", pos);
BLOB_APPEND_LITERAL(ob,"<sup class='footnote-backrefs'>");
if( nUsed <= 1 ){
blob_appendf(ob,"<a id='footnote-%s-a' "
"href='#noteref-%s-a'>^</a>", pos, pos);
}else{
int i;
blob_append_char(ob, '^');
for(i=0; i<nUsed && i<26; i++){
const int c = i + (unsigned)'a';
blob_appendf(ob," <a id='footnote-%s-%c'"
" href='#noteref-%s-%c'>%c</a>", pos,c, pos,c, c);
}
/* It's unlikely that so many backrefs will be usefull */
/* but maybe for some machine generated documents... */
for(; i<nUsed && i<676; i++){
const bitfield64_t l = to_base26(i,0);
blob_appendf(ob," <a id='footnote-%s-%s'"
" href='#noteref-%s-%s'>%s</a>",
pos,l.c, pos,l.c, l.c);
}
if( i < nUsed ) BLOB_APPEND_LITERAL(ob," …");
}
BLOB_APPEND_LITERAL(ob,"</sup>\n\t");
BLOB_APPEND_BLOB(ob, text);
BLOB_APPEND_LITERAL(ob, "\n</li>\n");
}
static void html_footnotes(
struct Blob *ob, const struct Blob *items, void *opaque
){
if( items && blob_size(items) ){
BLOB_APPEND_LITERAL(ob, "<ol class='footnotes'>\n");
BLOB_APPEND_BLOB(ob, items);
BLOB_APPEND_LITERAL(ob, "</ol>\n");
}
}
/* HTML span tags */
static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){
blob_append(ob, blob_buffer(text), blob_size(text));
return 1;
}
|
| ︙ | ︙ | |||
561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 |
struct Blob *output_title, /* Put title here. May be NULL */
struct Blob *output_body /* Put document body here. */
){
struct mkd_renderer html_renderer = {
/* prolog and epilog */
html_prolog,
html_epilog,
/* block level elements */
html_blockcode,
html_blockquote,
html_blockhtml,
html_header,
html_hrule,
html_list,
html_list_item,
html_paragraph,
html_table,
html_table_cell,
html_table_row,
/* span level elements */
html_autolink,
html_codespan,
html_double_emphasis,
html_emphasis,
html_image,
html_linebreak,
html_link,
html_raw_html_tag,
html_triple_emphasis,
/* low level elements */
0, /* entity */
html_normal_text,
/* misc. parameters */
"*_", /* emph_chars */
0 /* opaque */
};
MarkdownToHtml context;
memset(&context, 0, sizeof(context));
context.output_title = output_title;
html_renderer.opaque = &context;
if( output_title ) blob_reset(output_title);
blob_reset(output_body);
markdown(output_body, input_markdown, &html_renderer);
}
| > > > > > | 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 |
struct Blob *output_title, /* Put title here. May be NULL */
struct Blob *output_body /* Put document body here. */
){
struct mkd_renderer html_renderer = {
/* prolog and epilog */
html_prolog,
html_epilog,
html_footnotes,
/* block level elements */
html_blockcode,
html_blockquote,
html_blockhtml,
html_header,
html_hrule,
html_list,
html_list_item,
html_paragraph,
html_table,
html_table_cell,
html_table_row,
html_footnote_item,
/* span level elements */
html_autolink,
html_codespan,
html_double_emphasis,
html_emphasis,
html_image,
html_linebreak,
html_link,
html_raw_html_tag,
html_triple_emphasis,
html_footnote_ref,
/* low level elements */
0, /* entity */
html_normal_text,
/* misc. parameters */
"*_", /* emph_chars */
0 /* opaque */
};
static int invocation = -1; /* no marker for the first document */
MarkdownToHtml context;
memset(&context, 0, sizeof(context));
context.output_title = output_title;
context.unique = to_base26(invocation++,1);
html_renderer.opaque = &context;
if( output_title ) blob_reset(output_title);
blob_reset(output_body);
markdown(output_body, input_markdown, &html_renderer);
}
|