| | @@ -82,11 +82,12 @@ |
| 82 | 82 | int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title, |
| 83 | 83 | struct Blob *content, void *opaque); |
| 84 | 84 | int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque); |
| 85 | 85 | int (*triple_emphasis)(struct Blob *ob, struct Blob *text, |
| 86 | 86 | char c, void *opaque); |
| 87 | | - int (*footnote_ref)(struct Blob *ob, int index, int locus, void *opaque); |
| 87 | + int (*footnote_ref)(struct Blob *ob, const struct Blob *span, |
| 88 | + int index, int locus, void *opaque); |
| 88 | 89 | |
| 89 | 90 | /* low level callbacks - NULL copies input directly into the output */ |
| 90 | 91 | void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque); |
| 91 | 92 | void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque); |
| 92 | 93 | |
| | @@ -1057,10 +1058,70 @@ |
| 1057 | 1058 | assert( fn->nUsed > 0 ); |
| 1058 | 1059 | cleanup: |
| 1059 | 1060 | release_work_buffer( rndr, id ); |
| 1060 | 1061 | return fn; |
| 1061 | 1062 | } |
| 1063 | +/* Adds unlabeled footnote to the rndr. |
| 1064 | + * If text is blank then returns 0, |
| 1065 | + * otherwise returns the address of the added footnote. */ |
| 1066 | +static inline const struct footnote* add_inline_footnote( |
| 1067 | + struct render *rndr, |
| 1068 | + const char *text, |
| 1069 | + size_t size |
| 1070 | +){ |
| 1071 | + struct footnote fn = { empty_blob, empty_blob, 0, 0 }; |
| 1072 | + while(size && (*text==' ' || *text=='\t')){ text++; size--; } |
| 1073 | + if(!size) return 0; |
| 1074 | + fn.index = ++(rndr->iNotesCount); |
| 1075 | + fn.nUsed = 1; |
| 1076 | + assert( fn.index > 0 ); |
| 1077 | + blob_append(&fn.text, text, size); |
| 1078 | + blob_append(&rndr->notes, (char *)&fn, sizeof fn); |
| 1079 | + return (struct footnote*)( blob_buffer(&rndr->notes) |
| 1080 | + +( blob_size(&rndr->notes)-sizeof fn )); |
| 1081 | +} |
| 1082 | + |
| 1083 | +/* Return the offset of the matching closing bracket or 0 if not found. |
| 1084 | + * begin[0] must be either '[' or '(' */ |
| 1085 | +static inline size_t matching_bracket_offset( |
| 1086 | + const char* begin, |
| 1087 | + const char* end |
| 1088 | +){ |
| 1089 | + const char *i; |
| 1090 | + int level; |
| 1091 | + const char bra = *begin; |
| 1092 | + const char ket = bra=='[' ? ']' : ')'; |
| 1093 | + assert( bra=='[' || bra=='(' ); /* FIXME: only when debugging */ |
| 1094 | + for(i=begin+1,level=1; i!=end; i++){ |
| 1095 | + if( *i=='\n' ) /* do nothing */; |
| 1096 | + else if( i[-1]=='\\' ) continue; /* ? FIXME: what if \\( ? */ |
| 1097 | + else if( *i==bra ) level++; |
| 1098 | + else if( *i==ket ){ |
| 1099 | + if( --level<=0 ) return i-begin; |
| 1100 | + } |
| 1101 | + } |
| 1102 | + return 0; |
| 1103 | +} |
| 1104 | + |
| 1105 | +/* char_footnote -- '(': parsing a standalone inline footnote */ |
| 1106 | +static size_t char_footnote( |
| 1107 | + struct Blob *ob, |
| 1108 | + struct render *rndr, |
| 1109 | + char *data, |
| 1110 | + size_t offset, |
| 1111 | + size_t size |
| 1112 | +){ |
| 1113 | + size_t end; |
| 1114 | + const struct footnote* fn; |
| 1115 | + |
| 1116 | + if( size<4 || data[1]!='^' || !rndr->make.footnote_ref ) return 0; |
| 1117 | + end = matching_bracket_offset(data, data+size); |
| 1118 | + if( !end ) return 0; |
| 1119 | + fn = add_inline_footnote(rndr, data+2, end-2); |
| 1120 | + if(fn) rndr->make.footnote_ref(ob,0,fn->index,1,rndr->make.opaque); |
| 1121 | + return end+1; |
| 1122 | +} |
| 1062 | 1123 | |
| 1063 | 1124 | /* char_link -- '[': parsing a link or an image */ |
| 1064 | 1125 | static size_t char_link( |
| 1065 | 1126 | struct Blob *ob, |
| 1066 | 1127 | struct render *rndr, |
| | @@ -1104,92 +1165,101 @@ |
| 1104 | 1165 | title = new_work_buffer(rndr); |
| 1105 | 1166 | content = new_work_buffer(rndr); |
| 1106 | 1167 | link = new_work_buffer(rndr); |
| 1107 | 1168 | ret = 0; /* error if we don't get to the callback */ |
| 1108 | 1169 | |
| 1109 | | - /* inline style link */ |
| 1170 | + /* inline style link or span-bounded inline footnote */ |
| 1110 | 1171 | if( i<size && data[i]=='(' ){ |
| 1111 | | - size_t span_end = i; |
| 1112 | | - while( span_end<size |
| 1113 | | - && !(data[span_end]==')' && (span_end==i || data[span_end-1]!='\\')) |
| 1114 | | - ){ |
| 1115 | | - span_end++; |
| 1116 | | - } |
| 1117 | | - |
| 1118 | | - if( span_end>=size |
| 1119 | | - || get_link_inline(link, title, data+i+1, span_end-(i+1))<0 |
| 1120 | | - ){ |
| 1121 | | - goto char_link_cleanup; |
| 1122 | | - } |
| 1123 | | - |
| 1124 | | - i = span_end+1; |
| 1125 | | - |
| 1126 | | - /* reference style link */ |
| 1172 | + |
| 1173 | + /* inline footnote */ |
| 1174 | + if( i+2<size && data[i+1]=='^' ){ |
| 1175 | + |
| 1176 | + const size_t k = matching_bracket_offset(data+i, data+size); |
| 1177 | + if( !k ) goto char_link_cleanup; |
| 1178 | + fn = add_inline_footnote(rndr, data+(i+2), k-2); |
| 1179 | + i += k+1; |
| 1180 | + }else{ |
| 1181 | + size_t span_end = i; |
| 1182 | + while( span_end<size |
| 1183 | + && !(data[span_end]==')' && (span_end==i || data[span_end-1]!='\\')) |
| 1184 | + ){ |
| 1185 | + span_end++; |
| 1186 | + } |
| 1187 | + |
| 1188 | + if( span_end>=size |
| 1189 | + || get_link_inline(link, title, data+i+1, span_end-(i+1))<0 |
| 1190 | + ){ |
| 1191 | + goto char_link_cleanup; |
| 1192 | + } |
| 1193 | + |
| 1194 | + i = span_end+1; |
| 1195 | + } |
| 1196 | + |
| 1197 | + /* reference style link or span-bounded footnote reference */ |
| 1127 | 1198 | }else if( i<size && data[i]=='[' ){ |
| 1128 | 1199 | char *id_data; |
| 1129 | 1200 | size_t id_size, id_end = i; |
| 1201 | + int bFootnote; |
| 1130 | 1202 | |
| 1131 | 1203 | while( id_end<size && data[id_end]!=']' ){ id_end++; } |
| 1132 | 1204 | |
| 1133 | 1205 | if( id_end>=size ) goto char_link_cleanup; |
| 1206 | + bFootnote = data[i+1]=='^'; |
| 1134 | 1207 | |
| 1135 | | - if( i+1==id_end ){ |
| 1208 | + if( i+1==id_end || (bFootnote && i+2==id_end) ){ |
| 1136 | 1209 | /* implicit id - use the contents */ |
| 1137 | 1210 | id_data = data+1; |
| 1138 | 1211 | id_size = txt_e-1; |
| 1139 | 1212 | }else{ |
| 1140 | 1213 | /* explicit id - between brackets */ |
| 1141 | 1214 | id_data = data+i+1; |
| 1142 | 1215 | id_size = id_end-(i+1); |
| 1216 | + if( bFootnote ){ |
| 1217 | + id_data++; |
| 1218 | + id_size--; |
| 1219 | + } |
| 1143 | 1220 | } |
| 1144 | 1221 | |
| 1145 | | - if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){ |
| 1222 | + if( bFootnote ){ |
| 1223 | + fn = get_footnote(rndr, id_data, id_size); |
| 1224 | + if( !fn ) goto char_link_cleanup; |
| 1225 | + }else if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){ |
| 1146 | 1226 | goto char_link_cleanup; |
| 1147 | 1227 | } |
| 1148 | 1228 | |
| 1149 | 1229 | i = id_end+1; |
| 1150 | 1230 | |
| 1151 | | - /* shortcut reference style link */ |
| 1231 | + /* shortcut reference style link or free-standing footnote refernece */ |
| 1152 | 1232 | }else{ |
| 1153 | | - if( offset && data[-1]=='^' ){ |
| 1154 | | - |
| 1155 | | - /* free-standing inline note */ |
| 1156 | | - struct footnote note = {empty_blob,empty_blob,0,0}; |
| 1157 | | - note.index = ++(rndr->iNotesCount); |
| 1158 | | - note.nUsed = 1; |
| 1159 | | - blob_append(¬e.text,data+1,txt_e-1); |
| 1160 | | - blob_append(&rndr->notes, (char *)¬e, sizeof note); |
| 1161 | | - fn = (struct footnote*)(blob_buffer(&rndr->notes) |
| 1162 | | - + blob_size(&rndr->notes) - sizeof(note)); |
| 1163 | | - }else if(!is_img && size>2 && data[1]=='^'){ |
| 1164 | | - |
| 1165 | | - /* free-standing reference */ |
| 1166 | | - fn = get_footnote(rndr, data+1, txt_e-1); |
| 1233 | + if(!is_img && size>3 && data[1]=='^'){ |
| 1234 | + /* free-standing footnote reference */ |
| 1235 | + fn = get_footnote(rndr, data+2, txt_e-2); |
| 1167 | 1236 | if( !fn ) goto char_link_cleanup; |
| 1237 | + release_work_buffer(rndr, content); |
| 1238 | + content = 0; |
| 1168 | 1239 | }else if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){ |
| 1169 | 1240 | goto char_link_cleanup; |
| 1170 | 1241 | } |
| 1171 | 1242 | |
| 1172 | | - /* rewinding the whitespace */ |
| 1243 | + /* rewinding a closing square bracket */ |
| 1173 | 1244 | i = txt_e+1; |
| 1174 | 1245 | } |
| 1175 | 1246 | |
| 1176 | 1247 | /* building content: img alt is escaped, link content is parsed */ |
| 1177 | | - if( txt_e>1 ){ |
| 1248 | + if( txt_e>1 && content ){ |
| 1178 | 1249 | if( is_img ) blob_append(content, data+1, txt_e-1); |
| 1179 | | - else if(!fn) parse_inline(content, rndr, data+1, txt_e-1); |
| 1250 | + else parse_inline(content, rndr, data+1, txt_e-1); |
| 1180 | 1251 | } |
| 1181 | 1252 | |
| 1182 | 1253 | /* calling the relevant rendering function */ |
| 1183 | 1254 | if( is_img ){ |
| 1184 | 1255 | if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--; |
| 1185 | 1256 | ret = rndr->make.image(ob, link, title, content, rndr->make.opaque); |
| 1186 | 1257 | }else if(fn){ |
| 1187 | | - if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='^' ) ob->nUsed--; |
| 1188 | | - /* ? FIXME: the above line looks like a hack */ |
| 1189 | 1258 | if(rndr->make.footnote_ref){ |
| 1190 | | - ret = rndr->make.footnote_ref(ob,fn->index,fn->nUsed,rndr->make.opaque); |
| 1259 | + ret = rndr->make.footnote_ref(ob, content, fn->index, fn->nUsed, |
| 1260 | + rndr->make.opaque); |
| 1191 | 1261 | } |
| 1192 | 1262 | }else{ |
| 1193 | 1263 | ret = rndr->make.link(ob, link, title, content, rndr->make.opaque); |
| 1194 | 1264 | } |
| 1195 | 1265 | |
| | @@ -2288,11 +2358,11 @@ |
| 2288 | 2358 | |
| 2289 | 2359 | /* footnote definition must start at the begining of a line */ |
| 2290 | 2360 | if( data[i]!='[' ) return 0; |
| 2291 | 2361 | i++; |
| 2292 | 2362 | if( data[i]!='^' ) return 0; |
| 2293 | | - id_offset = i++; |
| 2363 | + id_offset = ++i; |
| 2294 | 2364 | |
| 2295 | 2365 | /* id part: anything but a newline between brackets */ |
| 2296 | 2366 | while( i<end && data[i]!=']' && data[i]!='\n' && data[i]!='\r' ){ i++; } |
| 2297 | 2367 | if( i>=end || data[i]!=']' ) return 0; |
| 2298 | 2368 | id_end = i++; |
| | @@ -2398,10 +2468,11 @@ |
| 2398 | 2468 | } |
| 2399 | 2469 | } |
| 2400 | 2470 | if( rndr.make.codespan ) rndr.active_char['`'] = char_codespan; |
| 2401 | 2471 | if( rndr.make.linebreak ) rndr.active_char['\n'] = char_linebreak; |
| 2402 | 2472 | if( rndr.make.image || rndr.make.link ) rndr.active_char['['] = char_link; |
| 2473 | + if( rndr.make.footnote_ref ) rndr.active_char['('] = char_footnote; |
| 2403 | 2474 | rndr.active_char['<'] = char_langle_tag; |
| 2404 | 2475 | rndr.active_char['\\'] = char_escape; |
| 2405 | 2476 | rndr.active_char['&'] = char_entity; |
| 2406 | 2477 | |
| 2407 | 2478 | /* first pass: iterate over lines looking for references, |
| 2408 | 2479 | |