Fossil SCM
Handle some corner cases more thoroughly: dismiss empty footnotes, passthrough (more carefully) user-provided classlist if the token is not followed by a blank character or if a footnote's text consists just of such token and blank characters. Also simplify a little bit a few places inside of <code>is_footnote()</code> function.
Commit
fe3157803f15b685910023749fbaf62c0aa5153ed6cea4c2dc55278144dc7a57
Parent
1f525713ff85cf5…
2 files changed
+57
-57
+27
-1
+57
-57
| --- src/markdown.c | ||
| +++ src/markdown.c | ||
| @@ -1104,18 +1104,23 @@ | ||
| 1104 | 1104 | return fn; |
| 1105 | 1105 | } |
| 1106 | 1106 | |
| 1107 | 1107 | /* Counts characters in the blank prefix within at most nHalfLines. |
| 1108 | 1108 | ** A sequence of spaces and tabs counts as odd halfline, |
| 1109 | -** a newline counts as even halfline | |
| 1109 | +** a newline counts as even halfline. | |
| 1110 | +** If nHalfLines < 0 then procceed without constrains. | |
| 1110 | 1111 | */ |
| 1111 | -static inline size_t count_whitespaces( | |
| 1112 | +static inline size_t sizeof_blank_prefix( | |
| 1112 | 1113 | const char *data, size_t size, int nHalfLines |
| 1113 | 1114 | ){ |
| 1114 | 1115 | const char *p = data; |
| 1115 | 1116 | const char * const end = data+size; |
| 1116 | - while( nHalfLines > 0 ){ | |
| 1117 | + if( nHalfLines < 0 ){ | |
| 1118 | + while( p!=end && fossil_isspace(*p) ){ | |
| 1119 | + p++; | |
| 1120 | + } | |
| 1121 | + }else while( nHalfLines > 0 ){ | |
| 1117 | 1122 | while( p!=end && (*p==' ' || *p=='\t' ) ){ p++; } |
| 1118 | 1123 | if( p==end || --nHalfLines == 0 ) break; |
| 1119 | 1124 | if( *p=='\n' || *p=='\r' ){ |
| 1120 | 1125 | p++; |
| 1121 | 1126 | if( p==end ) break; |
| @@ -1123,33 +1128,39 @@ | ||
| 1123 | 1128 | p++; |
| 1124 | 1129 | } |
| 1125 | 1130 | } |
| 1126 | 1131 | nHalfLines--; |
| 1127 | 1132 | } |
| 1128 | - return (size_t)(p-data); | |
| 1133 | + return p-data; | |
| 1129 | 1134 | } |
| 1130 | 1135 | |
| 1131 | 1136 | /* Check if the data starts with a classlist token of the special form. |
| 1132 | 1137 | ** If so then return the length of that token, otherwise return 0. |
| 1133 | 1138 | ** |
| 1134 | 1139 | ** The token must start with a dot and must end with a colon; |
| 1135 | 1140 | ** in between of these it must be a dot-separated list of words; |
| 1136 | 1141 | ** each word may contain only alphanumeric characters and hyphens. |
| 1142 | +** | |
| 1143 | +** If `bBlank` is non-zero then a blank character must follow | |
| 1144 | +** the token's ending colon: otherwise function returns 0 | |
| 1145 | +** despite of the well-formed token. | |
| 1137 | 1146 | */ |
| 1138 | -size_t is_footnote_classlist(const char * const data, size_t size){ | |
| 1147 | +size_t is_footnote_classlist(const char * const data, size_t size, int bBlank){ | |
| 1139 | 1148 | const char *p; |
| 1140 | 1149 | const char * const end = data+size; |
| 1141 | 1150 | if( data==end || *data != '.' ) return 0; |
| 1142 | 1151 | for(p=data+1; p!=end; p++){ |
| 1143 | 1152 | if( fossil_isalnum(*p) || *p=='-' ) continue; |
| 1144 | 1153 | if( *p==':' ){ |
| 1145 | - return p[-1]!='.' ? (size_t)(p-data)+1 : 0; | |
| 1146 | - } | |
| 1147 | - if( *p=='.' ){ | |
| 1148 | - if( p[-1]!='.' ) continue; | |
| 1149 | - else break; | |
| 1154 | + if( p[-1]=='.' ) break; | |
| 1155 | + p++; | |
| 1156 | + if( bBlank ){ | |
| 1157 | + if( p==end || !fossil_isspace(*p) ) break; | |
| 1158 | + } | |
| 1159 | + return p-data; | |
| 1150 | 1160 | } |
| 1161 | + if( *p=='.' && p[-1]!='.' ) continue; | |
| 1151 | 1162 | break; |
| 1152 | 1163 | } |
| 1153 | 1164 | return 0; |
| 1154 | 1165 | } |
| 1155 | 1166 | |
| @@ -1161,33 +1172,27 @@ | ||
| 1161 | 1172 | const char *text, |
| 1162 | 1173 | size_t size |
| 1163 | 1174 | ){ |
| 1164 | 1175 | struct footnote fn = FOOTNOTE_INITIALIZER; |
| 1165 | 1176 | const char *zUPC = 0; |
| 1166 | - size_t nUPC = 0, n = count_whitespaces(text, size, 3); | |
| 1177 | + size_t nUPC = 0, n = sizeof_blank_prefix(text, size, 3); | |
| 1167 | 1178 | if( n >= size ) return 0; |
| 1168 | 1179 | text += n; |
| 1169 | 1180 | size -= n; |
| 1170 | - n = is_footnote_classlist(text, size); | |
| 1171 | - if( n && n < size ){ | |
| 1172 | - nUPC = n; | |
| 1181 | + nUPC = is_footnote_classlist(text, size, 1); | |
| 1182 | + if( nUPC ){ | |
| 1183 | + assert( nUPC<size ); | |
| 1173 | 1184 | zUPC = text; |
| 1174 | 1185 | text += nUPC; |
| 1175 | 1186 | size -= nUPC; |
| 1176 | - n = count_whitespaces(text, size, 3); | |
| 1177 | - /* naked classlist is treated as plain text */ | |
| 1178 | - if( n >= size || text[n]=='\n' || text[n]=='\r' ){ | |
| 1179 | - size += nUPC; | |
| 1180 | - nUPC = 0; | |
| 1181 | - text = zUPC; | |
| 1182 | - zUPC = 0; | |
| 1183 | - }else{ | |
| 1184 | - text += n; | |
| 1185 | - size -= n; | |
| 1186 | - } | |
| 1187 | - } | |
| 1188 | - if(!size) return 0; | |
| 1187 | + } | |
| 1188 | + if( sizeof_blank_prefix(text,size,-1)==size ){ | |
| 1189 | + if( !nUPC ) return 0; /* empty inline footnote */ | |
| 1190 | + text = zUPC; | |
| 1191 | + size = nUPC; /* bare classlist is treated */ | |
| 1192 | + nUPC = 0; /* as plain text */ | |
| 1193 | + } | |
| 1189 | 1194 | fn.iMark = ++(rndr->notes.nMarks); |
| 1190 | 1195 | fn.nUsed = 1; |
| 1191 | 1196 | fn.index = COUNT_FOOTNOTES(&rndr->notes.all); |
| 1192 | 1197 | assert( fn.iMark > 0 ); |
| 1193 | 1198 | blob_append(&fn.text, text, size); |
| @@ -1237,11 +1242,12 @@ | ||
| 1237 | 1242 | |
| 1238 | 1243 | if( size<4 || data[1]!='^' || !rndr->make.footnote_ref ) return 0; |
| 1239 | 1244 | end = matching_bracket_offset(data, data+size); |
| 1240 | 1245 | if( !end ) return 0; |
| 1241 | 1246 | fn = add_inline_footnote(rndr, data+2, end-2); |
| 1242 | - if(fn) rndr->make.footnote_ref(ob,0,&fn->upc,fn->iMark,1,rndr->make.opaque); | |
| 1247 | + if( !fn ) return 0; | |
| 1248 | + rndr->make.footnote_ref(ob,0,&fn->upc,fn->iMark,1,rndr->make.opaque); | |
| 1243 | 1249 | return end+1; |
| 1244 | 1250 | } |
| 1245 | 1251 | |
| 1246 | 1252 | /* char_link -- '[': parsing a link or an image */ |
| 1247 | 1253 | static size_t char_link( |
| @@ -2462,11 +2468,11 @@ | ||
| 2462 | 2468 | size_t beg, /* offset of the beginning of the line */ |
| 2463 | 2469 | size_t end, /* offset of the end of the text */ |
| 2464 | 2470 | size_t *last, /* last character of the link */ |
| 2465 | 2471 | struct Blob * footnotes |
| 2466 | 2472 | ){ |
| 2467 | - size_t i, id_offset, id_end, upc_offset = 0, upc_size = 0; | |
| 2473 | + size_t i, id_offset, id_end, upc_offset, upc_size; | |
| 2468 | 2474 | struct footnote fn = FOOTNOTE_INITIALIZER; |
| 2469 | 2475 | |
| 2470 | 2476 | /* failfast if data is too short */ |
| 2471 | 2477 | if( beg+5>=end ) return 0; |
| 2472 | 2478 | i = beg; |
| @@ -2490,26 +2496,18 @@ | ||
| 2490 | 2496 | /* passthrough truncated footnote definition */ |
| 2491 | 2497 | if( i>=end ) return 0; |
| 2492 | 2498 | |
| 2493 | 2499 | if( build_ref_id(&fn.id, data+id_offset, id_end-id_offset)<0 ) return 0; |
| 2494 | 2500 | |
| 2495 | - /* footnote's text may start on the same line */ | |
| 2496 | - upc_offset = 0; | |
| 2497 | - if( data[i]!='\n' && data[i]!='\r' ){ | |
| 2498 | - upc_offset = i; /* prevent a retry on the second line */ | |
| 2499 | - upc_size = is_footnote_classlist(data+i,end-i); | |
| 2500 | - if( upc_size ){ | |
| 2501 | - i += upc_size; | |
| 2502 | - while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; } | |
| 2503 | - if( i>=end ){ | |
| 2504 | - upc_size = 0; /* naked classlist is treated as plain text */ | |
| 2505 | - i = upc_offset; | |
| 2506 | - } | |
| 2507 | - } | |
| 2508 | - } | |
| 2509 | - if( data[i]!='\n' && data[i]!='\r' ){ | |
| 2510 | - const size_t j = i; | |
| 2501 | + /* footnote's text may start on the same line after [^id]: */ | |
| 2502 | + upc_offset = upc_size = 0; | |
| 2503 | + if( data[i]!='\n' && data[i]!='\r' ){ | |
| 2504 | + size_t j; | |
| 2505 | + upc_size = is_footnote_classlist(data+i, end-i, 1); | |
| 2506 | + upc_offset = i; /* prevent further checks for a classlist */ | |
| 2507 | + i += upc_size; | |
| 2508 | + j = i; | |
| 2511 | 2509 | do i++; while( i<end && data[i]!='\n' && data[i]!='\r' ); |
| 2512 | 2510 | blob_append(&fn.text, data+j, i-j); |
| 2513 | 2511 | if( i<end ){ |
| 2514 | 2512 | blob_append_char(&fn.text, data[i]); |
| 2515 | 2513 | i++; |
| @@ -2535,26 +2533,19 @@ | ||
| 2535 | 2533 | |
| 2536 | 2534 | /* process the 2nd and the following lines */ |
| 2537 | 2535 | while( i+indent<end && memcmp(data+i,spaces,indent)==0 ){ |
| 2538 | 2536 | size_t j; |
| 2539 | 2537 | i += indent; |
| 2540 | - j = i; | |
| 2541 | 2538 | if( !upc_offset ){ |
| 2542 | 2539 | /* a classlist must be provided no later than at the 2nd line */ |
| 2543 | - upc_offset = i + count_whitespaces(data+i, end-i, 1); | |
| 2544 | - upc_size = is_footnote_classlist(data+upc_offset, end-upc_offset); | |
| 2540 | + upc_offset = i + sizeof_blank_prefix(data+i, end-i, 1); | |
| 2541 | + upc_size = is_footnote_classlist(data+upc_offset, end-upc_offset, 1); | |
| 2545 | 2542 | if( upc_size ){ |
| 2546 | 2543 | i = upc_offset + upc_size; |
| 2547 | - while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; } | |
| 2548 | - if( i>=end ){ | |
| 2549 | - upc_size = 0; /* naked classlist is treated as plain text */ | |
| 2550 | - i = j; | |
| 2551 | - }else{ | |
| 2552 | - j = i; | |
| 2553 | - } | |
| 2554 | 2544 | } |
| 2555 | 2545 | } |
| 2546 | + j = i; | |
| 2556 | 2547 | while( i<end && data[i]!='\n' && data[i]!='\r' ) i++; |
| 2557 | 2548 | blob_append(&fn.text, data+j, i-j); |
| 2558 | 2549 | if( i>=end ) break; |
| 2559 | 2550 | blob_append_char(&fn.text, data[i]); |
| 2560 | 2551 | i++; |
| @@ -2566,18 +2557,27 @@ | ||
| 2566 | 2557 | } |
| 2567 | 2558 | footnote_finish: |
| 2568 | 2559 | if( !blob_size(&fn.text) ){ |
| 2569 | 2560 | blob_reset(&fn.id); |
| 2570 | 2561 | return 0; |
| 2562 | + } | |
| 2563 | + if( !blob_trim(&fn.text) ){ /* if the content is all-blank */ | |
| 2564 | + if( upc_size ){ /* interpret UPC as plain text */ | |
| 2565 | + blob_append(&fn.text, data+upc_offset, upc_size); | |
| 2566 | + upc_size = 0; | |
| 2567 | + }else{ | |
| 2568 | + blob_reset(&fn.id); /* or clean up and fail */ | |
| 2569 | + blob_reset(&fn.text); | |
| 2570 | + return 0; | |
| 2571 | + } | |
| 2571 | 2572 | } |
| 2572 | 2573 | /* a valid note has been found */ |
| 2573 | 2574 | if( last ) *last = i; |
| 2574 | 2575 | if( footnotes ){ |
| 2575 | 2576 | fn.defno = COUNT_FOOTNOTES( footnotes ); |
| 2576 | 2577 | if( upc_size ){ |
| 2577 | - assert( upc_offset ); | |
| 2578 | - assert( upc_offset+upc_size < end ); | |
| 2578 | + assert( upc_offset && upc_offset+upc_size<end ); | |
| 2579 | 2579 | blob_append(&fn.upc, data+upc_offset, upc_size); |
| 2580 | 2580 | } |
| 2581 | 2581 | blob_append(footnotes, (char *)&fn, sizeof fn); |
| 2582 | 2582 | } |
| 2583 | 2583 | return 1; |
| 2584 | 2584 |
| --- src/markdown.c | |
| +++ src/markdown.c | |
| @@ -1104,18 +1104,23 @@ | |
| 1104 | return fn; |
| 1105 | } |
| 1106 | |
| 1107 | /* Counts characters in the blank prefix within at most nHalfLines. |
| 1108 | ** A sequence of spaces and tabs counts as odd halfline, |
| 1109 | ** a newline counts as even halfline |
| 1110 | */ |
| 1111 | static inline size_t count_whitespaces( |
| 1112 | const char *data, size_t size, int nHalfLines |
| 1113 | ){ |
| 1114 | const char *p = data; |
| 1115 | const char * const end = data+size; |
| 1116 | while( nHalfLines > 0 ){ |
| 1117 | while( p!=end && (*p==' ' || *p=='\t' ) ){ p++; } |
| 1118 | if( p==end || --nHalfLines == 0 ) break; |
| 1119 | if( *p=='\n' || *p=='\r' ){ |
| 1120 | p++; |
| 1121 | if( p==end ) break; |
| @@ -1123,33 +1128,39 @@ | |
| 1123 | p++; |
| 1124 | } |
| 1125 | } |
| 1126 | nHalfLines--; |
| 1127 | } |
| 1128 | return (size_t)(p-data); |
| 1129 | } |
| 1130 | |
| 1131 | /* Check if the data starts with a classlist token of the special form. |
| 1132 | ** If so then return the length of that token, otherwise return 0. |
| 1133 | ** |
| 1134 | ** The token must start with a dot and must end with a colon; |
| 1135 | ** in between of these it must be a dot-separated list of words; |
| 1136 | ** each word may contain only alphanumeric characters and hyphens. |
| 1137 | */ |
| 1138 | size_t is_footnote_classlist(const char * const data, size_t size){ |
| 1139 | const char *p; |
| 1140 | const char * const end = data+size; |
| 1141 | if( data==end || *data != '.' ) return 0; |
| 1142 | for(p=data+1; p!=end; p++){ |
| 1143 | if( fossil_isalnum(*p) || *p=='-' ) continue; |
| 1144 | if( *p==':' ){ |
| 1145 | return p[-1]!='.' ? (size_t)(p-data)+1 : 0; |
| 1146 | } |
| 1147 | if( *p=='.' ){ |
| 1148 | if( p[-1]!='.' ) continue; |
| 1149 | else break; |
| 1150 | } |
| 1151 | break; |
| 1152 | } |
| 1153 | return 0; |
| 1154 | } |
| 1155 | |
| @@ -1161,33 +1172,27 @@ | |
| 1161 | const char *text, |
| 1162 | size_t size |
| 1163 | ){ |
| 1164 | struct footnote fn = FOOTNOTE_INITIALIZER; |
| 1165 | const char *zUPC = 0; |
| 1166 | size_t nUPC = 0, n = count_whitespaces(text, size, 3); |
| 1167 | if( n >= size ) return 0; |
| 1168 | text += n; |
| 1169 | size -= n; |
| 1170 | n = is_footnote_classlist(text, size); |
| 1171 | if( n && n < size ){ |
| 1172 | nUPC = n; |
| 1173 | zUPC = text; |
| 1174 | text += nUPC; |
| 1175 | size -= nUPC; |
| 1176 | n = count_whitespaces(text, size, 3); |
| 1177 | /* naked classlist is treated as plain text */ |
| 1178 | if( n >= size || text[n]=='\n' || text[n]=='\r' ){ |
| 1179 | size += nUPC; |
| 1180 | nUPC = 0; |
| 1181 | text = zUPC; |
| 1182 | zUPC = 0; |
| 1183 | }else{ |
| 1184 | text += n; |
| 1185 | size -= n; |
| 1186 | } |
| 1187 | } |
| 1188 | if(!size) return 0; |
| 1189 | fn.iMark = ++(rndr->notes.nMarks); |
| 1190 | fn.nUsed = 1; |
| 1191 | fn.index = COUNT_FOOTNOTES(&rndr->notes.all); |
| 1192 | assert( fn.iMark > 0 ); |
| 1193 | blob_append(&fn.text, text, size); |
| @@ -1237,11 +1242,12 @@ | |
| 1237 | |
| 1238 | if( size<4 || data[1]!='^' || !rndr->make.footnote_ref ) return 0; |
| 1239 | end = matching_bracket_offset(data, data+size); |
| 1240 | if( !end ) return 0; |
| 1241 | fn = add_inline_footnote(rndr, data+2, end-2); |
| 1242 | if(fn) rndr->make.footnote_ref(ob,0,&fn->upc,fn->iMark,1,rndr->make.opaque); |
| 1243 | return end+1; |
| 1244 | } |
| 1245 | |
| 1246 | /* char_link -- '[': parsing a link or an image */ |
| 1247 | static size_t char_link( |
| @@ -2462,11 +2468,11 @@ | |
| 2462 | size_t beg, /* offset of the beginning of the line */ |
| 2463 | size_t end, /* offset of the end of the text */ |
| 2464 | size_t *last, /* last character of the link */ |
| 2465 | struct Blob * footnotes |
| 2466 | ){ |
| 2467 | size_t i, id_offset, id_end, upc_offset = 0, upc_size = 0; |
| 2468 | struct footnote fn = FOOTNOTE_INITIALIZER; |
| 2469 | |
| 2470 | /* failfast if data is too short */ |
| 2471 | if( beg+5>=end ) return 0; |
| 2472 | i = beg; |
| @@ -2490,26 +2496,18 @@ | |
| 2490 | /* passthrough truncated footnote definition */ |
| 2491 | if( i>=end ) return 0; |
| 2492 | |
| 2493 | if( build_ref_id(&fn.id, data+id_offset, id_end-id_offset)<0 ) return 0; |
| 2494 | |
| 2495 | /* footnote's text may start on the same line */ |
| 2496 | upc_offset = 0; |
| 2497 | if( data[i]!='\n' && data[i]!='\r' ){ |
| 2498 | upc_offset = i; /* prevent a retry on the second line */ |
| 2499 | upc_size = is_footnote_classlist(data+i,end-i); |
| 2500 | if( upc_size ){ |
| 2501 | i += upc_size; |
| 2502 | while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; } |
| 2503 | if( i>=end ){ |
| 2504 | upc_size = 0; /* naked classlist is treated as plain text */ |
| 2505 | i = upc_offset; |
| 2506 | } |
| 2507 | } |
| 2508 | } |
| 2509 | if( data[i]!='\n' && data[i]!='\r' ){ |
| 2510 | const size_t j = i; |
| 2511 | do i++; while( i<end && data[i]!='\n' && data[i]!='\r' ); |
| 2512 | blob_append(&fn.text, data+j, i-j); |
| 2513 | if( i<end ){ |
| 2514 | blob_append_char(&fn.text, data[i]); |
| 2515 | i++; |
| @@ -2535,26 +2533,19 @@ | |
| 2535 | |
| 2536 | /* process the 2nd and the following lines */ |
| 2537 | while( i+indent<end && memcmp(data+i,spaces,indent)==0 ){ |
| 2538 | size_t j; |
| 2539 | i += indent; |
| 2540 | j = i; |
| 2541 | if( !upc_offset ){ |
| 2542 | /* a classlist must be provided no later than at the 2nd line */ |
| 2543 | upc_offset = i + count_whitespaces(data+i, end-i, 1); |
| 2544 | upc_size = is_footnote_classlist(data+upc_offset, end-upc_offset); |
| 2545 | if( upc_size ){ |
| 2546 | i = upc_offset + upc_size; |
| 2547 | while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; } |
| 2548 | if( i>=end ){ |
| 2549 | upc_size = 0; /* naked classlist is treated as plain text */ |
| 2550 | i = j; |
| 2551 | }else{ |
| 2552 | j = i; |
| 2553 | } |
| 2554 | } |
| 2555 | } |
| 2556 | while( i<end && data[i]!='\n' && data[i]!='\r' ) i++; |
| 2557 | blob_append(&fn.text, data+j, i-j); |
| 2558 | if( i>=end ) break; |
| 2559 | blob_append_char(&fn.text, data[i]); |
| 2560 | i++; |
| @@ -2566,18 +2557,27 @@ | |
| 2566 | } |
| 2567 | footnote_finish: |
| 2568 | if( !blob_size(&fn.text) ){ |
| 2569 | blob_reset(&fn.id); |
| 2570 | return 0; |
| 2571 | } |
| 2572 | /* a valid note has been found */ |
| 2573 | if( last ) *last = i; |
| 2574 | if( footnotes ){ |
| 2575 | fn.defno = COUNT_FOOTNOTES( footnotes ); |
| 2576 | if( upc_size ){ |
| 2577 | assert( upc_offset ); |
| 2578 | assert( upc_offset+upc_size < end ); |
| 2579 | blob_append(&fn.upc, data+upc_offset, upc_size); |
| 2580 | } |
| 2581 | blob_append(footnotes, (char *)&fn, sizeof fn); |
| 2582 | } |
| 2583 | return 1; |
| 2584 |
| --- src/markdown.c | |
| +++ src/markdown.c | |
| @@ -1104,18 +1104,23 @@ | |
| 1104 | return fn; |
| 1105 | } |
| 1106 | |
| 1107 | /* Counts characters in the blank prefix within at most nHalfLines. |
| 1108 | ** A sequence of spaces and tabs counts as odd halfline, |
| 1109 | ** a newline counts as even halfline. |
| 1110 | ** If nHalfLines < 0 then procceed without constrains. |
| 1111 | */ |
| 1112 | static inline size_t sizeof_blank_prefix( |
| 1113 | const char *data, size_t size, int nHalfLines |
| 1114 | ){ |
| 1115 | const char *p = data; |
| 1116 | const char * const end = data+size; |
| 1117 | if( nHalfLines < 0 ){ |
| 1118 | while( p!=end && fossil_isspace(*p) ){ |
| 1119 | p++; |
| 1120 | } |
| 1121 | }else while( nHalfLines > 0 ){ |
| 1122 | while( p!=end && (*p==' ' || *p=='\t' ) ){ p++; } |
| 1123 | if( p==end || --nHalfLines == 0 ) break; |
| 1124 | if( *p=='\n' || *p=='\r' ){ |
| 1125 | p++; |
| 1126 | if( p==end ) break; |
| @@ -1123,33 +1128,39 @@ | |
| 1128 | p++; |
| 1129 | } |
| 1130 | } |
| 1131 | nHalfLines--; |
| 1132 | } |
| 1133 | return p-data; |
| 1134 | } |
| 1135 | |
| 1136 | /* Check if the data starts with a classlist token of the special form. |
| 1137 | ** If so then return the length of that token, otherwise return 0. |
| 1138 | ** |
| 1139 | ** The token must start with a dot and must end with a colon; |
| 1140 | ** in between of these it must be a dot-separated list of words; |
| 1141 | ** each word may contain only alphanumeric characters and hyphens. |
| 1142 | ** |
| 1143 | ** If `bBlank` is non-zero then a blank character must follow |
| 1144 | ** the token's ending colon: otherwise function returns 0 |
| 1145 | ** despite of the well-formed token. |
| 1146 | */ |
| 1147 | size_t is_footnote_classlist(const char * const data, size_t size, int bBlank){ |
| 1148 | const char *p; |
| 1149 | const char * const end = data+size; |
| 1150 | if( data==end || *data != '.' ) return 0; |
| 1151 | for(p=data+1; p!=end; p++){ |
| 1152 | if( fossil_isalnum(*p) || *p=='-' ) continue; |
| 1153 | if( *p==':' ){ |
| 1154 | if( p[-1]=='.' ) break; |
| 1155 | p++; |
| 1156 | if( bBlank ){ |
| 1157 | if( p==end || !fossil_isspace(*p) ) break; |
| 1158 | } |
| 1159 | return p-data; |
| 1160 | } |
| 1161 | if( *p=='.' && p[-1]!='.' ) continue; |
| 1162 | break; |
| 1163 | } |
| 1164 | return 0; |
| 1165 | } |
| 1166 | |
| @@ -1161,33 +1172,27 @@ | |
| 1172 | const char *text, |
| 1173 | size_t size |
| 1174 | ){ |
| 1175 | struct footnote fn = FOOTNOTE_INITIALIZER; |
| 1176 | const char *zUPC = 0; |
| 1177 | size_t nUPC = 0, n = sizeof_blank_prefix(text, size, 3); |
| 1178 | if( n >= size ) return 0; |
| 1179 | text += n; |
| 1180 | size -= n; |
| 1181 | nUPC = is_footnote_classlist(text, size, 1); |
| 1182 | if( nUPC ){ |
| 1183 | assert( nUPC<size ); |
| 1184 | zUPC = text; |
| 1185 | text += nUPC; |
| 1186 | size -= nUPC; |
| 1187 | } |
| 1188 | if( sizeof_blank_prefix(text,size,-1)==size ){ |
| 1189 | if( !nUPC ) return 0; /* empty inline footnote */ |
| 1190 | text = zUPC; |
| 1191 | size = nUPC; /* bare classlist is treated */ |
| 1192 | nUPC = 0; /* as plain text */ |
| 1193 | } |
| 1194 | fn.iMark = ++(rndr->notes.nMarks); |
| 1195 | fn.nUsed = 1; |
| 1196 | fn.index = COUNT_FOOTNOTES(&rndr->notes.all); |
| 1197 | assert( fn.iMark > 0 ); |
| 1198 | blob_append(&fn.text, text, size); |
| @@ -1237,11 +1242,12 @@ | |
| 1242 | |
| 1243 | if( size<4 || data[1]!='^' || !rndr->make.footnote_ref ) return 0; |
| 1244 | end = matching_bracket_offset(data, data+size); |
| 1245 | if( !end ) return 0; |
| 1246 | fn = add_inline_footnote(rndr, data+2, end-2); |
| 1247 | if( !fn ) return 0; |
| 1248 | rndr->make.footnote_ref(ob,0,&fn->upc,fn->iMark,1,rndr->make.opaque); |
| 1249 | return end+1; |
| 1250 | } |
| 1251 | |
| 1252 | /* char_link -- '[': parsing a link or an image */ |
| 1253 | static size_t char_link( |
| @@ -2462,11 +2468,11 @@ | |
| 2468 | size_t beg, /* offset of the beginning of the line */ |
| 2469 | size_t end, /* offset of the end of the text */ |
| 2470 | size_t *last, /* last character of the link */ |
| 2471 | struct Blob * footnotes |
| 2472 | ){ |
| 2473 | size_t i, id_offset, id_end, upc_offset, upc_size; |
| 2474 | struct footnote fn = FOOTNOTE_INITIALIZER; |
| 2475 | |
| 2476 | /* failfast if data is too short */ |
| 2477 | if( beg+5>=end ) return 0; |
| 2478 | i = beg; |
| @@ -2490,26 +2496,18 @@ | |
| 2496 | /* passthrough truncated footnote definition */ |
| 2497 | if( i>=end ) return 0; |
| 2498 | |
| 2499 | if( build_ref_id(&fn.id, data+id_offset, id_end-id_offset)<0 ) return 0; |
| 2500 | |
| 2501 | /* footnote's text may start on the same line after [^id]: */ |
| 2502 | upc_offset = upc_size = 0; |
| 2503 | if( data[i]!='\n' && data[i]!='\r' ){ |
| 2504 | size_t j; |
| 2505 | upc_size = is_footnote_classlist(data+i, end-i, 1); |
| 2506 | upc_offset = i; /* prevent further checks for a classlist */ |
| 2507 | i += upc_size; |
| 2508 | j = i; |
| 2509 | do i++; while( i<end && data[i]!='\n' && data[i]!='\r' ); |
| 2510 | blob_append(&fn.text, data+j, i-j); |
| 2511 | if( i<end ){ |
| 2512 | blob_append_char(&fn.text, data[i]); |
| 2513 | i++; |
| @@ -2535,26 +2533,19 @@ | |
| 2533 | |
| 2534 | /* process the 2nd and the following lines */ |
| 2535 | while( i+indent<end && memcmp(data+i,spaces,indent)==0 ){ |
| 2536 | size_t j; |
| 2537 | i += indent; |
| 2538 | if( !upc_offset ){ |
| 2539 | /* a classlist must be provided no later than at the 2nd line */ |
| 2540 | upc_offset = i + sizeof_blank_prefix(data+i, end-i, 1); |
| 2541 | upc_size = is_footnote_classlist(data+upc_offset, end-upc_offset, 1); |
| 2542 | if( upc_size ){ |
| 2543 | i = upc_offset + upc_size; |
| 2544 | } |
| 2545 | } |
| 2546 | j = i; |
| 2547 | while( i<end && data[i]!='\n' && data[i]!='\r' ) i++; |
| 2548 | blob_append(&fn.text, data+j, i-j); |
| 2549 | if( i>=end ) break; |
| 2550 | blob_append_char(&fn.text, data[i]); |
| 2551 | i++; |
| @@ -2566,18 +2557,27 @@ | |
| 2557 | } |
| 2558 | footnote_finish: |
| 2559 | if( !blob_size(&fn.text) ){ |
| 2560 | blob_reset(&fn.id); |
| 2561 | return 0; |
| 2562 | } |
| 2563 | if( !blob_trim(&fn.text) ){ /* if the content is all-blank */ |
| 2564 | if( upc_size ){ /* interpret UPC as plain text */ |
| 2565 | blob_append(&fn.text, data+upc_offset, upc_size); |
| 2566 | upc_size = 0; |
| 2567 | }else{ |
| 2568 | blob_reset(&fn.id); /* or clean up and fail */ |
| 2569 | blob_reset(&fn.text); |
| 2570 | return 0; |
| 2571 | } |
| 2572 | } |
| 2573 | /* a valid note has been found */ |
| 2574 | if( last ) *last = i; |
| 2575 | if( footnotes ){ |
| 2576 | fn.defno = COUNT_FOOTNOTES( footnotes ); |
| 2577 | if( upc_size ){ |
| 2578 | assert( upc_offset && upc_offset+upc_size<end ); |
| 2579 | blob_append(&fn.upc, data+upc_offset, upc_size); |
| 2580 | } |
| 2581 | blob_append(footnotes, (char *)&fn, sizeof fn); |
| 2582 | } |
| 2583 | return 1; |
| 2584 |
+27
-1
| --- test/markdown-test3.md | ||
| +++ test/markdown-test3.md | ||
| @@ -63,10 +63,20 @@ | ||
| 63 | 63 | This facilitates the usage in the usual case |
| 64 | 64 | when several footnotes are refenrenced at the end |
| 65 | 65 | of a phrase.[^scipub][^many-refs](^All these four should |
| 66 | 66 | be parsed as "free-standing" footnotes)[^Coelurosauria] |
| 67 | 67 | |
| 68 | +A footnote may not be empty(^) | |
| 69 | +or consist just of blank characters.(^ | |
| 70 | + ) | |
| 71 | + | |
| 72 | +The same holds for labeled footnotes. If definition of a labeled footnote | |
| 73 | +is blank then it is not accepted by the first pass of the parser and | |
| 74 | +is recognized during the second pass as misreference. | |
| 75 | +[^ This definition consists of just blanks ]: | |
| 76 | + | |
| 77 | + | |
| 68 | 78 | <style> |
| 69 | 79 | li.fn-upc-example span.fn-upc { |
| 70 | 80 | border: solid 2px lightgreen; |
| 71 | 81 | border-radius: 0.25em; |
| 72 | 82 | padding-left: 2px; |
| @@ -77,10 +87,13 @@ | ||
| 77 | 87 | font-weight: bold; |
| 78 | 88 | } |
| 79 | 89 | sup.noteref.fn-upc-example, |
| 80 | 90 | span.notescope.fn-upc-example sup.noteref { |
| 81 | 91 | border: solid 2px lightgreen; |
| 92 | +[^duplicate]: | |
| 93 | + Labeled footnote definition may appear anywhere. | |
| 94 | + That part came from inside of an inline style definition. | |
| 82 | 95 | border-radius: 0.4em; |
| 83 | 96 | padding: 2px; |
| 84 | 97 | } |
| 85 | 98 | sup.noteref.fn-upc-example::after, |
| 86 | 99 | span.notescope.fn-upc-example sup.noteref::after { |
| @@ -100,14 +113,20 @@ | ||
| 100 | 113 | This token defines a dot-separated list of CSS classes |
| 101 | 114 | which are added to that particular footnote and also to the |
| 102 | 115 | corresponding reference(s). Hypens ('-') are also allowed. |
| 103 | 116 | Classes from the token are tranformed to lowercase and are prepended |
| 104 | 117 | with `"fn-upc-"` to avoid collisions. |
| 105 | -) | |
| 118 | +) | |
| 106 | 119 | This feature is "*opt-in*": there is nothing wrong in starting a footnote's |
| 107 | 120 | text with a token of that form while not defining any corresponding classes |
| 108 | 121 | in the stylesheet.[^nostyle] |
| 122 | +If a footnote consists just of a valid userclass token then this token | |
| 123 | +is not interpreted as such, instead it is emitted as plain text. | |
| 124 | +(^ | |
| 125 | + .bare.classlist.inside.inline.footnote: | |
| 126 | +)[^bare1] | |
| 127 | +[^bare2] | |
| 109 | 128 | |
| 110 | 129 | ## Footnotes |
| 111 | 130 | |
| 112 | 131 | [branch]: /timeline?r=markdown-footnotes&nowiki |
| 113 | 132 | |
| @@ -146,10 +165,17 @@ | ||
| 146 | 165 | [^undefined label is used]: For example due to a typo. |
| 147 | 166 | |
| 148 | 167 | [^another stray]: Just to verify the correctness of ordering and styling. |
| 149 | 168 | |
| 150 | 169 | [^scipub]: Which is common in the scientific publications. |
| 170 | + | |
| 171 | +[^bare1]: .at.the.1st.line.of.labeled.footnote.definition: | |
| 172 | + | |
| 173 | + | |
| 174 | +[^bare2]: | |
| 175 | + .at.the.2nd.line.of.labeled.footnote.definition: | |
| 176 | + | |
| 151 | 177 | |
| 152 | 178 | [^nostyle]: |
| 153 | 179 | .unused.classes: |
| 154 | 180 | In that case text of the footnote just looks like as if |
| 155 | 181 | no special processing occured. |
| 156 | 182 |
| --- test/markdown-test3.md | |
| +++ test/markdown-test3.md | |
| @@ -63,10 +63,20 @@ | |
| 63 | This facilitates the usage in the usual case |
| 64 | when several footnotes are refenrenced at the end |
| 65 | of a phrase.[^scipub][^many-refs](^All these four should |
| 66 | be parsed as "free-standing" footnotes)[^Coelurosauria] |
| 67 | |
| 68 | <style> |
| 69 | li.fn-upc-example span.fn-upc { |
| 70 | border: solid 2px lightgreen; |
| 71 | border-radius: 0.25em; |
| 72 | padding-left: 2px; |
| @@ -77,10 +87,13 @@ | |
| 77 | font-weight: bold; |
| 78 | } |
| 79 | sup.noteref.fn-upc-example, |
| 80 | span.notescope.fn-upc-example sup.noteref { |
| 81 | border: solid 2px lightgreen; |
| 82 | border-radius: 0.4em; |
| 83 | padding: 2px; |
| 84 | } |
| 85 | sup.noteref.fn-upc-example::after, |
| 86 | span.notescope.fn-upc-example sup.noteref::after { |
| @@ -100,14 +113,20 @@ | |
| 100 | This token defines a dot-separated list of CSS classes |
| 101 | which are added to that particular footnote and also to the |
| 102 | corresponding reference(s). Hypens ('-') are also allowed. |
| 103 | Classes from the token are tranformed to lowercase and are prepended |
| 104 | with `"fn-upc-"` to avoid collisions. |
| 105 | ) |
| 106 | This feature is "*opt-in*": there is nothing wrong in starting a footnote's |
| 107 | text with a token of that form while not defining any corresponding classes |
| 108 | in the stylesheet.[^nostyle] |
| 109 | |
| 110 | ## Footnotes |
| 111 | |
| 112 | [branch]: /timeline?r=markdown-footnotes&nowiki |
| 113 | |
| @@ -146,10 +165,17 @@ | |
| 146 | [^undefined label is used]: For example due to a typo. |
| 147 | |
| 148 | [^another stray]: Just to verify the correctness of ordering and styling. |
| 149 | |
| 150 | [^scipub]: Which is common in the scientific publications. |
| 151 | |
| 152 | [^nostyle]: |
| 153 | .unused.classes: |
| 154 | In that case text of the footnote just looks like as if |
| 155 | no special processing occured. |
| 156 |
| --- test/markdown-test3.md | |
| +++ test/markdown-test3.md | |
| @@ -63,10 +63,20 @@ | |
| 63 | This facilitates the usage in the usual case |
| 64 | when several footnotes are refenrenced at the end |
| 65 | of a phrase.[^scipub][^many-refs](^All these four should |
| 66 | be parsed as "free-standing" footnotes)[^Coelurosauria] |
| 67 | |
| 68 | A footnote may not be empty(^) |
| 69 | or consist just of blank characters.(^ |
| 70 | ) |
| 71 | |
| 72 | The same holds for labeled footnotes. If definition of a labeled footnote |
| 73 | is blank then it is not accepted by the first pass of the parser and |
| 74 | is recognized during the second pass as misreference. |
| 75 | [^ This definition consists of just blanks ]: |
| 76 | |
| 77 | |
| 78 | <style> |
| 79 | li.fn-upc-example span.fn-upc { |
| 80 | border: solid 2px lightgreen; |
| 81 | border-radius: 0.25em; |
| 82 | padding-left: 2px; |
| @@ -77,10 +87,13 @@ | |
| 87 | font-weight: bold; |
| 88 | } |
| 89 | sup.noteref.fn-upc-example, |
| 90 | span.notescope.fn-upc-example sup.noteref { |
| 91 | border: solid 2px lightgreen; |
| 92 | [^duplicate]: |
| 93 | Labeled footnote definition may appear anywhere. |
| 94 | That part came from inside of an inline style definition. |
| 95 | border-radius: 0.4em; |
| 96 | padding: 2px; |
| 97 | } |
| 98 | sup.noteref.fn-upc-example::after, |
| 99 | span.notescope.fn-upc-example sup.noteref::after { |
| @@ -100,14 +113,20 @@ | |
| 113 | This token defines a dot-separated list of CSS classes |
| 114 | which are added to that particular footnote and also to the |
| 115 | corresponding reference(s). Hypens ('-') are also allowed. |
| 116 | Classes from the token are tranformed to lowercase and are prepended |
| 117 | with `"fn-upc-"` to avoid collisions. |
| 118 | ) |
| 119 | This feature is "*opt-in*": there is nothing wrong in starting a footnote's |
| 120 | text with a token of that form while not defining any corresponding classes |
| 121 | in the stylesheet.[^nostyle] |
| 122 | If a footnote consists just of a valid userclass token then this token |
| 123 | is not interpreted as such, instead it is emitted as plain text. |
| 124 | (^ |
| 125 | .bare.classlist.inside.inline.footnote: |
| 126 | )[^bare1] |
| 127 | [^bare2] |
| 128 | |
| 129 | ## Footnotes |
| 130 | |
| 131 | [branch]: /timeline?r=markdown-footnotes&nowiki |
| 132 | |
| @@ -146,10 +165,17 @@ | |
| 165 | [^undefined label is used]: For example due to a typo. |
| 166 | |
| 167 | [^another stray]: Just to verify the correctness of ordering and styling. |
| 168 | |
| 169 | [^scipub]: Which is common in the scientific publications. |
| 170 | |
| 171 | [^bare1]: .at.the.1st.line.of.labeled.footnote.definition: |
| 172 | |
| 173 | |
| 174 | [^bare2]: |
| 175 | .at.the.2nd.line.of.labeled.footnote.definition: |
| 176 | |
| 177 | |
| 178 | [^nostyle]: |
| 179 | .unused.classes: |
| 180 | In that case text of the footnote just looks like as if |
| 181 | no special processing occured. |
| 182 |