Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Fix handling of user-provided classes for unreferenced, joined and overnested footnotes. In all these cases the tokens of user-provided classes are rendered as plain-text and no special classes are added anywhere. |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | markdown-footnotes |
Files: | files | file ages | folders |
SHA3-256: |
875472a8b0992badd11882ce25e89eb3 |
User & Date: | george 2022-02-23 07:36:25 |
Context
2022-02-23
| ||
08:21 |
Minor refactoring. Move the definition of BLOB_APPEND_BLOB()
macro from markdown_html.c to blob.c so that
it could be used outside of markdown_html.c .
Also rename it to blob_appendb() for consistency with
blob_appendf() and other API.
Within markdown.c use that newly available macro where appropriate.
Within markdown_html.c use it for footnotes-relevant code.
Other invocations of BLOB_APPEND_BLOB() within
markdown_html.c are left intact (they use an alias) in order to
simplify the potential merge with the trunk.
...
(check-in: 33a681eb user: george tags: markdown-footnotes)
| |
07:36 | Fix handling of user-provided classes for unreferenced, joined and overnested footnotes. In all these cases the tokens of user-provided classes are rendered as plain-text and no special classes are added anywhere. ... (check-in: 875472a8 user: george tags: markdown-footnotes) | |
2022-02-21
| ||
05:14 |
Add a comment for append_footnote_upc() .
Also substitute a variable of zero value with just "0" constant.
No functional changes.
...
(check-in: ae8a3dd5 user: george tags: markdown-footnotes)
| |
Changes
Changes to src/markdown.c.
︙ | ︙ | |||
140 141 142 143 144 145 146 147 148 149 150 151 152 153 | /* 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 */ struct Blob upc; /* user-provided classes .ASCII-alnum.or-hypen: */ int bRndred; /* indicates if `text` holds a rendered content */ int defno; /* serial number of definition, set during the first pass */ | > > > | 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | /* 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; }; /* A footnote's data. ** id, text, and upc fields must be in that particular order. */ 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 */ struct Blob upc; /* user-provided classes .ASCII-alnum.or-hypen: */ int bRndred; /* indicates if `text` holds a rendered content */ int defno; /* serial number of definition, set during the first pass */ |
︙ | ︙ | |||
2503 2504 2505 2506 2507 2508 2509 | upc_offset = upc_size = 0; if( data[i]!='\n' && data[i]!='\r' ){ size_t j; upc_size = is_footnote_classlist(data+i, end-i, 1); upc_offset = i; /* prevent further checks for a classlist */ i += upc_size; j = i; | | | | | | < | | | 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 | upc_offset = upc_size = 0; if( data[i]!='\n' && data[i]!='\r' ){ size_t j; upc_size = is_footnote_classlist(data+i, end-i, 1); upc_offset = i; /* prevent further checks for a classlist */ i += upc_size; j = i; while( i<end && data[i]!='\n' && data[i]!='\r' ){ i++; }; if( i!=j )blob_append(&fn.text, data+j, i-j); if( i<end ){ blob_append_char(&fn.text, data[i]); i++; if( i<end && data[i]=='\n' && data[i-1]=='\r' ){ blob_append_char(&fn.text, data[i]); i++; } } }else{ i++; if( i<end && data[i]=='\n' && data[i-1]=='\r' ) i++; } if( i<end ){ /* compute the indentation from the 2nd line */ size_t indent = i; const char *spaces = data+i; while( indent<end && data[indent]==' ' ){ indent++; } if( indent>=end ) goto footnote_finish; indent -= i; if( indent<2 ) goto footnote_finish; /* process the 2nd and the following lines */ while( i+indent<end && memcmp(data+i,spaces,indent)==0 ){ size_t j; i += indent; if( !upc_offset ){ /* a classlist must be provided no later than at the 2nd line */ upc_offset = i + sizeof_blank_prefix(data+i, end-i, 1); upc_size = is_footnote_classlist(data+upc_offset, end-upc_offset, 1); if( upc_size ){ i = upc_offset + upc_size; } } j = i; while( i<end && data[i]!='\n' && data[i]!='\r' ){ i++; } if( i!=j ) blob_append(&fn.text, data+j, i-j); if( i>=end ) break; blob_append_char(&fn.text, data[i]); i++; if( i<end && data[i]=='\n' && data[i-1]=='\r' ){ blob_append_char(&fn.text, data[i]); i++; } |
︙ | ︙ | |||
2673 2674 2675 2676 2677 2678 2679 | int nDups = 0; fn = CAST_AS_FOOTNOTES( allNotes ); qsort(fn, rndr.notes.nLbled, sizeof(struct footnote), cmp_footnote_id); /* concatenate footnotes with equal labels */ for(i=0; i<rndr.notes.nLbled ;){ struct footnote *x = fn + i; | | | > > > > | 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 | int nDups = 0; fn = CAST_AS_FOOTNOTES( allNotes ); qsort(fn, rndr.notes.nLbled, sizeof(struct footnote), cmp_footnote_id); /* concatenate footnotes with equal labels */ for(i=0; i<rndr.notes.nLbled ;){ struct footnote *x = fn + i; size_t j = i+1, k = blob_size(&x->text) + 64 + blob_size(&x->upc); while(j<rndr.notes.nLbled && !blob_compare(&x->id, &fn[j].id)){ k += blob_size(&fn[j].text) + 10 + blob_size(&fn[j].upc); j++; nDups++; } if( i+1<j ){ Blob list = empty_blob; blob_reserve(&list, k); /* must match _joined_footnote_indicator in html_footnote_item() */ blob_append_string(&list, "<ul class='fn-joined'>\n"); for(k=i; k<j; k++){ struct footnote *y = fn + k; blob_append_string(&list, "<li>"); if( blob_size(&y->upc) ){ blob_append(&list, blob_buffer(&y->upc), blob_size(&y->upc)); blob_reset(&y->upc); } blob_append(&list, blob_buffer(&y->text), blob_size(&y->text)); blob_append_string(&list, "</li>\n"); /* free memory buffer */ blob_reset(&y->text); if( k!=i ) blob_reset(&y->id); } |
︙ | ︙ | |||
2769 2770 2771 2772 2773 2774 2775 2776 2777 | } release_work_buffer(&rndr,tmp); /* footnotes rendering */ if( rndr.make.footnote_item && rndr.make.footnotes ){ Blob *all_items = new_work_buffer(&rndr); int j = -1; for(i=0; i<COUNT_FOOTNOTES(notes); i++){ const struct footnote* x = CAST_AS_FOOTNOTES(notes) + i; | > > > > > > > > > | > | | | < | < | | 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 | } release_work_buffer(&rndr,tmp); /* footnotes rendering */ if( rndr.make.footnote_item && rndr.make.footnotes ){ Blob *all_items = new_work_buffer(&rndr); int j = -1; /* Assert that the in-memory layout of id, text and upc within ** footnote struct matches the expectations of html_footnote_item() ** If it doesn't then a compiler has done something very weird. */ const struct footnote *dummy = 0; assert( &(dummy->id) == &(dummy->text) - 1 ); assert( &(dummy->upc) == &(dummy->text) + 1 ); for(i=0; i<COUNT_FOOTNOTES(notes); i++){ const struct footnote* x = CAST_AS_FOOTNOTES(notes) + i; if( !x->iMark ) break; assert( x->nUsed ); rndr.make.footnote_item(all_items, &x->text, x->iMark, x->bRndred ? x->nUsed : 0, rndr.make.opaque); j = i; } if( rndr.notes.misref.nUsed ){ rndr.make.footnote_item(all_items, 0, -1, rndr.notes.misref.nUsed, rndr.make.opaque); g.ftntsIssues[0] += rndr.notes.misref.nUsed; } while( ++j < COUNT_FOOTNOTES(notes) ){ const struct footnote* x = CAST_AS_FOOTNOTES(notes) + j; assert( !x->iMark ); assert( !x->nUsed ); assert( !x->bRndred ); rndr.make.footnote_item(all_items,&x->text,0,0,rndr.make.opaque); g.ftntsIssues[1]++; } rndr.make.footnotes(ob, all_items, rndr.make.opaque); release_work_buffer(&rndr, all_items); } release_work_buffer(&rndr, notes); |
︙ | ︙ |
Changes to src/markdown_html.c.
︙ | ︙ | |||
445 446 447 448 449 450 451 452 453 454 455 456 | /* 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 iMark, int nUsed, void *opaque ){ const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque; const char * const unique = ctx->unique.c; assert( nUsed >= 0 ); /* expect BUGs if the following yields compiler warnings */ if( iMark < 0 ){ /* misreferences */ assert( iMark == -1 ); | > > | | < < | 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 | /* 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 iMark, int nUsed, void *opaque ){ const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque; const char * const unique = ctx->unique.c; /* make.footnote_item() invocations should pass args accordingly */ const struct Blob *upc = text+1; assert( nUsed >= 0 ); /* expect BUGs if the following yields compiler warnings */ if( iMark < 0 ){ /* misreferences */ assert( iMark == -1 ); assert( nUsed ); BLOB_APPEND_LITERAL(ob,"<li class='fn-misreference'>" "<sup class='fn-backrefs'>"); if( nUsed == 1 ){ blob_appendf(ob,"<a id='misreference%s-a' href='", unique); BLOB_APPEND_URI(ob, ctx); blob_appendf(ob,"#misref%s-a'>^</a>", unique); }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='misreference%s-%c' href='", unique,c); BLOB_APPEND_URI(ob, ctx); blob_appendf(ob,"#misref%s-%c'>%c</a>", unique,c, c); } if( i < nUsed ) BLOB_APPEND_LITERAL(ob," …"); } BLOB_APPEND_LITERAL(ob,"</sup>\n<span>Misreference</span>"); }else if( iMark > 0 ){ /* regular, joined and overnested footnotes */ char pos[24]; int bJoin = 0; #define _joined_footnote_indicator "<ul class='fn-joined'>" #define _jfi_sz (sizeof(_joined_footnote_indicator)-1) assert( text ); assert( blob_size(text) ); memset(pos,0,24); sprintf(pos, "%s-%i", unique, iMark); |
︙ | ︙ | |||
519 520 521 522 523 524 525 | blob_appendf(ob," <a id='footnote%s-%s' href='", pos, l.c); BLOB_APPEND_URI(ob, ctx); blob_appendf(ob,"#noteref%s-%s'>%s</a>", pos,l.c, l.c); } if( i < nUsed ) BLOB_APPEND_LITERAL(ob," …"); } BLOB_APPEND_LITERAL(ob,"</sup>\n"); | < > > > > < | > > > | 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 | blob_appendf(ob," <a id='footnote%s-%s' href='", pos, l.c); BLOB_APPEND_URI(ob, ctx); blob_appendf(ob,"#noteref%s-%s'>%s</a>", pos,l.c, l.c); } if( i < nUsed ) BLOB_APPEND_LITERAL(ob," …"); } BLOB_APPEND_LITERAL(ob,"</sup>\n"); if( bJoin ){ BLOB_APPEND_LITERAL(ob,"<sup class='fn-joined'></sup><ul>"); blob_append(ob,blob_buffer(text)+_jfi_sz,blob_size(text)-_jfi_sz); }else if( nUsed ){ append_footnote_upc(ob, upc, 1); BLOB_APPEND_BLOB(ob, text); }else{ BLOB_APPEND_LITERAL(ob,"<i></i>\n" "<pre><code class='language-markdown'>"); if( blob_size(upc) ){ BLOB_APPEND_BLOB(ob, upc); } html_escape(ob, blob_buffer(text), blob_size(text)); BLOB_APPEND_LITERAL(ob,"</code></pre>"); } #undef _joined_footnote_indicator #undef _jfi_sz }else{ /* a footnote was defined but wasn't referenced */ /* make.footnote_item() invocations should pass args accordingly */ const struct Blob * id = text-1; assert( !nUsed ); assert( text ); assert( blob_size(text) ); assert( blob_size(id) ); BLOB_APPEND_LITERAL(ob,"<li class='fn-unreferenced'>\n[^ <code>"); html_escape(ob, blob_buffer(id), blob_size(id)); BLOB_APPEND_LITERAL(ob, "</code> ]<i></i>\n" "<pre><code class='language-markdown'>"); if( blob_size(upc) ){ BLOB_APPEND_BLOB(ob, upc); } html_escape(ob, blob_buffer(text), blob_size(text)); BLOB_APPEND_LITERAL(ob,"</code></pre>"); } BLOB_APPEND_LITERAL(ob, "\n</li>\n"); } static void html_footnotes( struct Blob *ob, const struct Blob *items, void *opaque |
︙ | ︙ |
Changes to test/markdown-test3.md.
︙ | ︙ | |||
99 100 101 102 103 104 105 106 107 108 109 110 111 112 | span.notescope.fn-upc-example sup.noteref::after { content: " ⛄"; } sup.noteref.fn-upc-example:hover::after, span.notescope.fn-upc-example sup.noteref:hover::after { content: " 👻"; } </style> It is possible to provide a list of classes for a particular footnote and all its references. This is achieved by prepending a footnote's text with a special token that starts with dot and ends with colon. (^ .alpha-Numeric123.EXAMPLE: | > > > > > > > | 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | span.notescope.fn-upc-example sup.noteref::after { content: " ⛄"; } sup.noteref.fn-upc-example:hover::after, span.notescope.fn-upc-example sup.noteref:hover::after { content: " 👻"; } li.fn-upc-l span.fn-upc { font-size: 60%; color: orange; } li.fn-upc-l span.fn-upc span.fn-upcDot { display: none; } </style> It is possible to provide a list of classes for a particular footnote and all its references. This is achieved by prepending a footnote's text with a special token that starts with dot and ends with colon. (^ .alpha-Numeric123.EXAMPLE: |
︙ | ︙ | |||
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | If a footnote consists just of a valid userclass token then this token is not interpreted as such, instead it is emitted as plain text. (^ .bare.classlist.inside.inline.footnote: )[^bare1] [^bare2] <html> Click <a href="?a=B"e='&nonASCII=😂&script=<script>alert('Broken!');</script>"> here</a> and <a href='?a=B"e="&nonASCII=😂&script=<script>alert("Broken!");</script>'> here</a> to test escaping of REQUEST_URI in the generated footnote markers. </html> A depth of nesting must be limited. (^ | > > > > | | | | | | | | 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 | If a footnote consists just of a valid userclass token then this token is not interpreted as such, instead it is emitted as plain text. (^ .bare.classlist.inside.inline.footnote: )[^bare1] [^bare2] [^duplicate]: .with.UPC.token: When duplicates are joined their UPC tokens are treated as plain-text. Blank characters between token and main text must be preserved. <html> Click <a href="?a=B"e='&nonASCII=😂&script=<script>alert('Broken!');</script>"> here</a> and <a href='?a=B"e="&nonASCII=😂&script=<script>alert("Broken!");</script>'> here</a> to test escaping of REQUEST_URI in the generated footnote markers. </html> A depth of nesting must be limited. (^ .L.1: A long chain of nested inline footnotes... (^ .L.2: is a rather unusual thing... (^ .L.3: and requires extra CPU cycles for processing. (^ .L.4: Theoretically speaking O(n<sup>2</sup>). (^ .L.5: Thus it is worth dismissing those footnotes... (^ .L.6: that are nested deeper than on a certain level. (^ .L.7: A particular value for that limit... (^ is hard-coded in src/markdown.c ... (^ in function `markdown()` ... (^ in variable named `maxDepth`. (^ |
︙ | ︙ | |||
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | [^bare1]: .at.the.1st.line.of.labeled.footnote.definition: [^bare2]: .at.the.2nd.line.of.labeled.footnote.definition: [^nostyle]: .unused.classes: In that case text of the footnote just looks like as if no special processing occured. [^ <script>alert("You have been pwned!");</script> ]: Labels are escaped [^ <textarea>"Last words here...' ]: <textarea>Content is also escaped</textarea> | > > > | 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | [^bare1]: .at.the.1st.line.of.labeled.footnote.definition: [^bare2]: .at.the.2nd.line.of.labeled.footnote.definition: [^stray with UPC]: .UPC-token: A token of user-provided classes must be rendered within strays. Aslo: this and the previous line may not have extra indentation. [^nostyle]: .unused.classes: In that case text of the footnote just looks like as if no special processing occured. [^ <script>alert("You have been pwned!");</script> ]: Labels are escaped [^ <textarea>"Last words here...' ]: <textarea>Content is also escaped</textarea> |