| | @@ -29,17 +29,33 @@ |
| 29 | 29 | struct Blob *output_title, |
| 30 | 30 | struct Blob *output_body); |
| 31 | 31 | |
| 32 | 32 | #endif /* INTERFACE */ |
| 33 | 33 | |
| 34 | +/* |
| 35 | +** Each heading is recorded as an instance of the following |
| 36 | +** structure, in its own separate memory allocation. |
| 37 | +*/ |
| 38 | +typedef struct MarkdownHeading MarkdownHeading; |
| 39 | +struct MarkdownHeading { |
| 40 | + MarkdownHeading *pPrev, *pNext; /* List of them all */ |
| 41 | + char *zTitle; /* Text as displayed */ |
| 42 | + char *zTag; /* Pandoc-style tag */ |
| 43 | +}; |
| 44 | + |
| 34 | 45 | /* |
| 35 | 46 | ** An instance of the following structure is passed through the |
| 36 | 47 | ** "opaque" pointer. |
| 37 | 48 | */ |
| 38 | 49 | typedef struct MarkdownToHtml MarkdownToHtml; |
| 39 | 50 | struct MarkdownToHtml { |
| 40 | | - Blob *output_title; /* Store the title here */ |
| 51 | + Blob *output_title; /* Store the title here */ |
| 52 | + MarkdownHeading *pFirst, *pList; /* List of all headings */ |
| 53 | + int iToc; /* Where to insert table-of-contents */ |
| 54 | + int mxToc; /* Maximum table-of-content level */ |
| 55 | + int iHdngNums; /* True to automatically number headings */ |
| 56 | + int aNum[6]; /* Most recent number at each level */ |
| 41 | 57 | }; |
| 42 | 58 | |
| 43 | 59 | |
| 44 | 60 | /* INTER_BLOCK -- skip a line between block level elements */ |
| 45 | 61 | #define INTER_BLOCK(ob) \ |
| | @@ -137,15 +153,64 @@ |
| 137 | 153 | |
| 138 | 154 | static void html_epilog(struct Blob *ob, void *opaque){ |
| 139 | 155 | INTER_BLOCK(ob); |
| 140 | 156 | BLOB_APPEND_LITERAL(ob, "</div>\n"); |
| 141 | 157 | } |
| 158 | + |
| 159 | +/* |
| 160 | +** If text is an HTML control comment, then deal with it and return true. |
| 161 | +** Otherwise just return false without making any changes. |
| 162 | +** |
| 163 | +** We are looking for comments of the following form: |
| 164 | +** |
| 165 | +** <!--markdown: toc=N --> |
| 166 | +** <!--markdown: paragraph-numbers=on --> |
| 167 | +** <!--markdown: paragraph-numbers=N --> |
| 168 | +** |
| 169 | +** In the paragraph-numbers=N form with N>1, N-th level headings are |
| 170 | +** numbered like top-levels. N+1-th level headings are like 2nd levels. |
| 171 | +** and so forth. |
| 172 | +** |
| 173 | +** In the toc=N form, a table of contents is generated for all headings |
| 174 | +** less than or equal to leve N. |
| 175 | +*/ |
| 176 | +static int html_control_comment(Blob *ob, Blob *text, void *opaque){ |
| 177 | + Blob token, arg; |
| 178 | + MarkdownToHtml *pCtx; |
| 179 | + if( blob_size(text)<20 ) return 0; |
| 180 | + if( strncmp(blob_buffer(text),"<!--markdown:",13)!=0 ) return 0; |
| 181 | + pCtx = (MarkdownToHtml*)opaque; |
| 182 | + blob_seek(text, 13, BLOB_SEEK_SET); |
| 183 | + blob_init(&token, 0, 0); |
| 184 | + blob_init(&arg, 0, 0); |
| 185 | + while( blob_argument_token(text, &token, 0) ){ |
| 186 | + if( blob_eq_str(&token, "toc", 3) && blob_argument_token(text, &arg, 1) ){ |
| 187 | + pCtx->iToc = blob_size(ob); |
| 188 | + pCtx->mxToc = atoi(blob_str(&arg)); |
| 189 | + blob_reset(&arg); |
| 190 | + }else |
| 191 | + if( blob_eq_str(&token,"paragraph-numbers",-1) |
| 192 | + && blob_argument_token(text,&arg,1) |
| 193 | + ){ |
| 194 | + char *zArg = blob_str(&arg); |
| 195 | + pCtx->iHdngNums = fossil_isdigit(zArg[0]) ? atoi(zArg) : is_truth(zArg); |
| 196 | + blob_reset(&arg); |
| 197 | + }else |
| 198 | + if( !blob_eq_str(&token,"-->",3) ){ |
| 199 | + blob_appendf(ob, "<!--markdown: unknown-tag=\"%h\" -->", |
| 200 | + blob_str(&token)); |
| 201 | + } |
| 202 | + blob_reset(&token); |
| 203 | + } |
| 204 | + return 1; |
| 205 | +} |
| 142 | 206 | |
| 143 | 207 | static void html_blockhtml(struct Blob *ob, struct Blob *text, void *opaque){ |
| 144 | 208 | char *data = blob_buffer(text); |
| 145 | 209 | size_t size = blob_size(text); |
| 146 | 210 | Blob *title = ((MarkdownToHtml*)opaque)->output_title; |
| 211 | + if( html_control_comment(ob,text,opaque) ) return; |
| 147 | 212 | while( size>0 && fossil_isspace(data[0]) ){ data++; size--; } |
| 148 | 213 | while( size>0 && fossil_isspace(data[size-1]) ){ size--; } |
| 149 | 214 | /* If the first raw block is an <h1> element, then use it as the title. */ |
| 150 | 215 | if( blob_size(ob)<=PROLOG_SIZE |
| 151 | 216 | && size>9 |
| | @@ -180,19 +245,30 @@ |
| 180 | 245 | struct Blob *ob, |
| 181 | 246 | struct Blob *text, |
| 182 | 247 | int level, |
| 183 | 248 | void *opaque |
| 184 | 249 | ){ |
| 185 | | - struct Blob *title = ((MarkdownToHtml*)opaque)->output_title; |
| 250 | + MarkdownToHtml *pCtx = (MarkdownToHtml*)opaque; |
| 251 | + struct Blob *title = pCtx->output_title; |
| 186 | 252 | /* The first header at the beginning of a text is considered as |
| 187 | 253 | * a title and not output. */ |
| 188 | 254 | if( blob_size(ob)<=PROLOG_SIZE && title!=0 && blob_size(title)==0 ){ |
| 189 | 255 | BLOB_APPEND_BLOB(title, text); |
| 190 | 256 | return; |
| 191 | 257 | } |
| 192 | 258 | INTER_BLOCK(ob); |
| 193 | 259 | blob_appendf(ob, "<h%d>", level); |
| 260 | + if( pCtx->iHdngNums && level>=pCtx->iHdngNums ){ |
| 261 | + int i; |
| 262 | + for(i=pCtx->iHdngNums-1; i<level-1; i++){ |
| 263 | + blob_appendf(ob,"%d.",pCtx->aNum[i]); |
| 264 | + } |
| 265 | + blob_appendf(ob,"%d", ++pCtx->aNum[i]); |
| 266 | + if( i==pCtx->iHdngNums-1 ) blob_append(ob, ".0", 2); |
| 267 | + blob_append(ob, " ", 1); |
| 268 | + for(i++; i<6; i++) pCtx->aNum[i] = 0; |
| 269 | + } |
| 194 | 270 | BLOB_APPEND_BLOB(ob, text); |
| 195 | 271 | blob_appendf(ob, "</h%d>", level); |
| 196 | 272 | } |
| 197 | 273 | |
| 198 | 274 | static void html_hrule(struct Blob *ob, void *opaque){ |
| | @@ -303,16 +379,18 @@ |
| 303 | 379 | BLOB_APPEND_LITERAL(ob, " <tr>\n"); |
| 304 | 380 | BLOB_APPEND_BLOB(ob, cells); |
| 305 | 381 | BLOB_APPEND_LITERAL(ob, " </tr>\n"); |
| 306 | 382 | } |
| 307 | 383 | |
| 308 | | - |
| 309 | | - |
| 310 | 384 | /* HTML span tags */ |
| 311 | | - |
| 312 | 385 | static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){ |
| 313 | | - blob_append(ob, blob_buffer(text), blob_size(text)); |
| 386 | + if( html_control_comment(ob,text,opaque) ){ |
| 387 | + /* No-op */ |
| 388 | + }else{ |
| 389 | + /* Everything else is passed through without change */ |
| 390 | + blob_append(ob, blob_buffer(text), blob_size(text)); |
| 391 | + } |
| 314 | 392 | return 1; |
| 315 | 393 | } |
| 316 | 394 | |
| 317 | 395 | static int html_autolink( |
| 318 | 396 | struct Blob *ob, |
| | @@ -579,12 +657,18 @@ |
| 579 | 657 | /* misc. parameters */ |
| 580 | 658 | "*_", /* emph_chars */ |
| 581 | 659 | 0 /* opaque */ |
| 582 | 660 | }; |
| 583 | 661 | MarkdownToHtml context; |
| 662 | + MarkdownHeading *pHdng, *pNextHdng; |
| 663 | + |
| 584 | 664 | memset(&context, 0, sizeof(context)); |
| 585 | 665 | context.output_title = output_title; |
| 586 | 666 | html_renderer.opaque = &context; |
| 587 | 667 | if( output_title ) blob_reset(output_title); |
| 588 | 668 | blob_reset(output_body); |
| 589 | 669 | markdown(output_body, input_markdown, &html_renderer); |
| 670 | + for(pHdng=context.pFirst; pHdng; pHdng=pNextHdng){ |
| 671 | + pNextHdng = pHdng->pNext; |
| 672 | + fossil_free(pHdng); |
| 673 | + } |
| 590 | 674 | } |
| 591 | 675 | |