Fossil SCM
Automatic table-of-contents generated for Markdown if there is a tag of the form: <!--markdown: toc=N --> where N is an integer that is the deepest level of content that will be added to the index. The TOC is inserted in place of the magic HTML comment.
Commit
6142e11d202d9a39c5916a7d7b00ed464fe8abeffa0f5c465c86aa9760b41eea
Parent
d9a70a1df92291d…
1 file changed
+149
-2
+149
-2
| --- src/markdown_html.c | ||
| +++ src/markdown_html.c | ||
| @@ -38,26 +38,122 @@ | ||
| 38 | 38 | typedef struct MarkdownHeading MarkdownHeading; |
| 39 | 39 | struct MarkdownHeading { |
| 40 | 40 | MarkdownHeading *pPrev, *pNext; /* List of them all */ |
| 41 | 41 | char *zTitle; /* Text as displayed */ |
| 42 | 42 | char *zTag; /* Pandoc-style tag */ |
| 43 | + int iLevel; /* Level number for this entry */ | |
| 44 | + int nth; /* This is the nth with the same tag */ | |
| 43 | 45 | }; |
| 44 | 46 | |
| 45 | 47 | /* |
| 46 | 48 | ** An instance of the following structure is passed through the |
| 47 | 49 | ** "opaque" pointer. |
| 48 | 50 | */ |
| 49 | 51 | typedef struct MarkdownToHtml MarkdownToHtml; |
| 50 | 52 | struct MarkdownToHtml { |
| 51 | 53 | Blob *output_title; /* Store the title here */ |
| 52 | - MarkdownHeading *pFirst, *pList; /* List of all headings */ | |
| 54 | + MarkdownHeading *pFirst, *pLast; /* List of all headings */ | |
| 53 | 55 | int iToc; /* Where to insert table-of-contents */ |
| 54 | 56 | int mxToc; /* Maximum table-of-content level */ |
| 57 | + int mnLevel; /* Minimum level seen over all headings */ | |
| 55 | 58 | int iHdngNums; /* True to automatically number headings */ |
| 56 | 59 | int aNum[6]; /* Most recent number at each level */ |
| 57 | 60 | }; |
| 58 | 61 | |
| 62 | +/* | |
| 63 | +** Add a new heading to the heading list. This involves generating | |
| 64 | +** a Pandoc-compatible identifier based on the heading text. | |
| 65 | +*/ | |
| 66 | +static void html_new_heading(MarkdownToHtml *pCtx, Blob *text, int iLevel){ | |
| 67 | + MarkdownHeading *pNew, *pSearch; | |
| 68 | + int nText = blob_size(text); | |
| 69 | + size_t n = sizeof(*pNew) + nText*2 + 10; | |
| 70 | + const char *zText = blob_buffer(text); | |
| 71 | + char *zTag; | |
| 72 | + int i, j; | |
| 73 | + int seenChar = 0; | |
| 74 | + | |
| 75 | + pNew = fossil_malloc( n ); | |
| 76 | + memset(pNew, 0, n); | |
| 77 | + if( pCtx->pLast ){ | |
| 78 | + pCtx->pLast->pNext = pNew; | |
| 79 | + if( pCtx->mnLevel>iLevel ) pCtx->mnLevel = iLevel; | |
| 80 | + }else{ | |
| 81 | + pCtx->mnLevel = iLevel; | |
| 82 | + } | |
| 83 | + pNew->pPrev = pCtx->pLast; | |
| 84 | + pCtx->pLast = pNew; | |
| 85 | + if( pCtx->pFirst==0 ) pCtx->pFirst = pNew; | |
| 86 | + pNew->zTitle = (char*)&pNew[1]; | |
| 87 | + memcpy(pNew->zTitle, zText, nText); | |
| 88 | + pNew->zTitle[nText] = 0; | |
| 89 | + pNew->zTag = pNew->zTitle + nText + 1; | |
| 90 | + pNew->iLevel = iLevel; | |
| 91 | + pNew->nth = 0; | |
| 92 | + | |
| 93 | + /* Generate an identifier. The identifer name is approximately the | |
| 94 | + ** same as a Pandoc identifier. | |
| 95 | + ** | |
| 96 | + ** * Skip all text up to the first letter. | |
| 97 | + ** * Remove all text past the last letter. | |
| 98 | + ** * Remove HTML markup and entities. | |
| 99 | + ** * Replace all whitespace sequences with a single "-" | |
| 100 | + ** * Remove all characters other than alphanumeric, "_", "-", and ".". | |
| 101 | + ** * Convert all alphabetics to lower case. | |
| 102 | + ** * If nothing remains, use "section" as the identifier. | |
| 103 | + */ | |
| 104 | + while( nText>0 && !fossil_isalpha(zText[nText-1]) ){ nText--; } | |
| 105 | + memcpy(pNew->zTag, zText, nText); | |
| 106 | + pNew->zTag[nText] = 0; | |
| 107 | + zTag = pNew->zTag; | |
| 108 | + for(i=j=0; zTag[i]; i++){ | |
| 109 | + if( fossil_isupper(zTag[i]) ){ | |
| 110 | + if( !seenChar ){ j = 0; seenChar = 1; } | |
| 111 | + zTag[j++] = fossil_tolower(zTag[i]); | |
| 112 | + continue; | |
| 113 | + } | |
| 114 | + if( fossil_islower(zTag[i]) ){ | |
| 115 | + if( !seenChar ){ j = 0; seenChar = 1; } | |
| 116 | + zTag[j++] = zTag[i]; | |
| 117 | + continue; | |
| 118 | + } | |
| 119 | + if( zTag[i]=='<' ){ | |
| 120 | + i += html_tag_length(zTag+i) - 1; | |
| 121 | + continue; | |
| 122 | + } | |
| 123 | + if( zTag[i]=='&' ){ | |
| 124 | + while( zTag[i] && zTag[i]!=';' ){ i++; } | |
| 125 | + if( zTag[i]==0 ) break; | |
| 126 | + continue; | |
| 127 | + } | |
| 128 | + if( fossil_isspace(zTag[i]) ){ | |
| 129 | + zTag[j++] = '-'; | |
| 130 | + while( fossil_isspace(zTag[i+1]) ){ i++; } | |
| 131 | + continue; | |
| 132 | + } | |
| 133 | + if( !fossil_isalnum(zTag[i]) && zTag[i]!='.' && zTag[i]!='_' ){ | |
| 134 | + zTag[j++] = '-'; | |
| 135 | + }else{ | |
| 136 | + zTag[j++] = zTag[i]; | |
| 137 | + } | |
| 138 | + } | |
| 139 | + if( j==0 || !seenChar ){ | |
| 140 | + memcpy(zTag, "section", 7); | |
| 141 | + j = 7; | |
| 142 | + } | |
| 143 | + while( j>0 && !fossil_isalpha(zTag[j-1]) ){ j--; } | |
| 144 | + zTag[j] = 0; | |
| 145 | + | |
| 146 | + /* Search for duplicate identifiers and disambiguate */ | |
| 147 | + pNew->nth = 0; | |
| 148 | + for(pSearch=pNew->pPrev; pSearch; pSearch=pSearch->pPrev){ | |
| 149 | + if( strcmp(pSearch->zTag,zTag)==0 ){ | |
| 150 | + pNew->nth = pSearch->nth+1; | |
| 151 | + } | |
| 152 | + } | |
| 153 | +} | |
| 154 | + | |
| 59 | 155 | |
| 60 | 156 | /* INTER_BLOCK -- skip a line between block level elements */ |
| 61 | 157 | #define INTER_BLOCK(ob) \ |
| 62 | 158 | do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0) |
| 63 | 159 | |
| @@ -246,19 +342,26 @@ | ||
| 246 | 342 | struct Blob *text, |
| 247 | 343 | int level, |
| 248 | 344 | void *opaque |
| 249 | 345 | ){ |
| 250 | 346 | MarkdownToHtml *pCtx = (MarkdownToHtml*)opaque; |
| 347 | + MarkdownHeading *pHdng; | |
| 251 | 348 | struct Blob *title = pCtx->output_title; |
| 252 | 349 | /* The first header at the beginning of a text is considered as |
| 253 | 350 | * a title and not output. */ |
| 254 | 351 | if( blob_size(ob)<=PROLOG_SIZE && title!=0 && blob_size(title)==0 ){ |
| 255 | 352 | BLOB_APPEND_BLOB(title, text); |
| 256 | 353 | return; |
| 257 | 354 | } |
| 258 | 355 | INTER_BLOCK(ob); |
| 259 | - blob_appendf(ob, "<h%d>", level); | |
| 356 | + html_new_heading(pCtx, text, level); | |
| 357 | + pHdng = pCtx->pLast; | |
| 358 | + if( pHdng->nth ){ | |
| 359 | + blob_appendf(ob, "<h%d id='%h-%d'>", level, pHdng->zTag, pHdng->nth); | |
| 360 | + }else{ | |
| 361 | + blob_appendf(ob, "<h%d id='%h'>", level, pHdng->zTag); | |
| 362 | + } | |
| 260 | 363 | if( pCtx->iHdngNums && level>=pCtx->iHdngNums ){ |
| 261 | 364 | int i; |
| 262 | 365 | for(i=pCtx->iHdngNums-1; i<level-1; i++){ |
| 263 | 366 | blob_appendf(ob,"%d.",pCtx->aNum[i]); |
| 264 | 367 | } |
| @@ -607,10 +710,53 @@ | ||
| 607 | 710 | |
| 608 | 711 | |
| 609 | 712 | static void html_normal_text(struct Blob *ob, struct Blob *text, void *opaque){ |
| 610 | 713 | html_escape(ob, blob_buffer(text), blob_size(text)); |
| 611 | 714 | } |
| 715 | + | |
| 716 | +/* | |
| 717 | +** Insert a table of contents into the body of the document. | |
| 718 | +** | |
| 719 | +** The pCtx provides the information needed to do this: | |
| 720 | +** | |
| 721 | +** pCtx->iToc Offset into pOut of where to insert the TOC | |
| 722 | +** pCtx->mxToc Maximum depth of the TOC | |
| 723 | +** pCtx->pFirst List of paragraphs to form the TOC | |
| 724 | +*/ | |
| 725 | +static void html_insert_toc(MarkdownToHtml *pCtx, Blob *pOut){ | |
| 726 | + Blob new; | |
| 727 | + MarkdownHeading *pX; | |
| 728 | + int iLevel = pCtx->mnLevel-1; | |
| 729 | + int iBase = iLevel; | |
| 730 | + blob_init(&new, 0, 0); | |
| 731 | + blob_append(&new, blob_buffer(pOut), pCtx->iToc); | |
| 732 | + blob_append(&new, "<div class='markdown-toc'>\n", -1); | |
| 733 | + for(pX=pCtx->pFirst; pX; pX=pX->pNext){ | |
| 734 | + if( pX->iLevel>pCtx->mxToc ) continue; | |
| 735 | + while( iLevel<pX->iLevel ){ | |
| 736 | + iLevel++; | |
| 737 | + blob_appendf(&new, "<ul class='markdown-toc%d markdown-toc'>\n", | |
| 738 | + iLevel - iBase); | |
| 739 | + } | |
| 740 | + while( iLevel>pX->iLevel ){ | |
| 741 | + iLevel--; | |
| 742 | + blob_appendf(&new, "</ul>\n"); | |
| 743 | + } | |
| 744 | + blob_appendf(&new,"<li><a href='#%h'>", pX->zTag); | |
| 745 | + html_to_plaintext(pX->zTitle, &new); | |
| 746 | + blob_appendf(&new,"</a></li>\n"); | |
| 747 | + } | |
| 748 | + while( iLevel>iBase ){ | |
| 749 | + iLevel--; | |
| 750 | + blob_appendf(&new, "</ul>\n"); | |
| 751 | + } | |
| 752 | + blob_appendf(&new, "</div>\n"); | |
| 753 | + blob_append(&new, blob_buffer(pOut)+pCtx->iToc, | |
| 754 | + blob_size(pOut)-pCtx->iToc); | |
| 755 | + blob_reset(pOut); | |
| 756 | + *pOut = new; | |
| 757 | +} | |
| 612 | 758 | |
| 613 | 759 | /* |
| 614 | 760 | ** Convert markdown into HTML. |
| 615 | 761 | ** |
| 616 | 762 | ** The document title is placed in output_title if not NULL. Or if |
| @@ -665,10 +811,11 @@ | ||
| 665 | 811 | context.output_title = output_title; |
| 666 | 812 | html_renderer.opaque = &context; |
| 667 | 813 | if( output_title ) blob_reset(output_title); |
| 668 | 814 | blob_reset(output_body); |
| 669 | 815 | markdown(output_body, input_markdown, &html_renderer); |
| 816 | + if( context.mxToc>0 ) html_insert_toc(&context, output_body); | |
| 670 | 817 | for(pHdng=context.pFirst; pHdng; pHdng=pNextHdng){ |
| 671 | 818 | pNextHdng = pHdng->pNext; |
| 672 | 819 | fossil_free(pHdng); |
| 673 | 820 | } |
| 674 | 821 | } |
| 675 | 822 |
| --- src/markdown_html.c | |
| +++ src/markdown_html.c | |
| @@ -38,26 +38,122 @@ | |
| 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 | |
| 45 | /* |
| 46 | ** An instance of the following structure is passed through the |
| 47 | ** "opaque" pointer. |
| 48 | */ |
| 49 | typedef struct MarkdownToHtml MarkdownToHtml; |
| 50 | struct MarkdownToHtml { |
| 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 */ |
| 57 | }; |
| 58 | |
| 59 | |
| 60 | /* INTER_BLOCK -- skip a line between block level elements */ |
| 61 | #define INTER_BLOCK(ob) \ |
| 62 | do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0) |
| 63 | |
| @@ -246,19 +342,26 @@ | |
| 246 | struct Blob *text, |
| 247 | int level, |
| 248 | void *opaque |
| 249 | ){ |
| 250 | MarkdownToHtml *pCtx = (MarkdownToHtml*)opaque; |
| 251 | struct Blob *title = pCtx->output_title; |
| 252 | /* The first header at the beginning of a text is considered as |
| 253 | * a title and not output. */ |
| 254 | if( blob_size(ob)<=PROLOG_SIZE && title!=0 && blob_size(title)==0 ){ |
| 255 | BLOB_APPEND_BLOB(title, text); |
| 256 | return; |
| 257 | } |
| 258 | INTER_BLOCK(ob); |
| 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 | } |
| @@ -607,10 +710,53 @@ | |
| 607 | |
| 608 | |
| 609 | static void html_normal_text(struct Blob *ob, struct Blob *text, void *opaque){ |
| 610 | html_escape(ob, blob_buffer(text), blob_size(text)); |
| 611 | } |
| 612 | |
| 613 | /* |
| 614 | ** Convert markdown into HTML. |
| 615 | ** |
| 616 | ** The document title is placed in output_title if not NULL. Or if |
| @@ -665,10 +811,11 @@ | |
| 665 | context.output_title = output_title; |
| 666 | html_renderer.opaque = &context; |
| 667 | if( output_title ) blob_reset(output_title); |
| 668 | blob_reset(output_body); |
| 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 | } |
| 674 | } |
| 675 |
| --- src/markdown_html.c | |
| +++ src/markdown_html.c | |
| @@ -38,26 +38,122 @@ | |
| 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 | int iLevel; /* Level number for this entry */ |
| 44 | int nth; /* This is the nth with the same tag */ |
| 45 | }; |
| 46 | |
| 47 | /* |
| 48 | ** An instance of the following structure is passed through the |
| 49 | ** "opaque" pointer. |
| 50 | */ |
| 51 | typedef struct MarkdownToHtml MarkdownToHtml; |
| 52 | struct MarkdownToHtml { |
| 53 | Blob *output_title; /* Store the title here */ |
| 54 | MarkdownHeading *pFirst, *pLast; /* List of all headings */ |
| 55 | int iToc; /* Where to insert table-of-contents */ |
| 56 | int mxToc; /* Maximum table-of-content level */ |
| 57 | int mnLevel; /* Minimum level seen over all headings */ |
| 58 | int iHdngNums; /* True to automatically number headings */ |
| 59 | int aNum[6]; /* Most recent number at each level */ |
| 60 | }; |
| 61 | |
| 62 | /* |
| 63 | ** Add a new heading to the heading list. This involves generating |
| 64 | ** a Pandoc-compatible identifier based on the heading text. |
| 65 | */ |
| 66 | static void html_new_heading(MarkdownToHtml *pCtx, Blob *text, int iLevel){ |
| 67 | MarkdownHeading *pNew, *pSearch; |
| 68 | int nText = blob_size(text); |
| 69 | size_t n = sizeof(*pNew) + nText*2 + 10; |
| 70 | const char *zText = blob_buffer(text); |
| 71 | char *zTag; |
| 72 | int i, j; |
| 73 | int seenChar = 0; |
| 74 | |
| 75 | pNew = fossil_malloc( n ); |
| 76 | memset(pNew, 0, n); |
| 77 | if( pCtx->pLast ){ |
| 78 | pCtx->pLast->pNext = pNew; |
| 79 | if( pCtx->mnLevel>iLevel ) pCtx->mnLevel = iLevel; |
| 80 | }else{ |
| 81 | pCtx->mnLevel = iLevel; |
| 82 | } |
| 83 | pNew->pPrev = pCtx->pLast; |
| 84 | pCtx->pLast = pNew; |
| 85 | if( pCtx->pFirst==0 ) pCtx->pFirst = pNew; |
| 86 | pNew->zTitle = (char*)&pNew[1]; |
| 87 | memcpy(pNew->zTitle, zText, nText); |
| 88 | pNew->zTitle[nText] = 0; |
| 89 | pNew->zTag = pNew->zTitle + nText + 1; |
| 90 | pNew->iLevel = iLevel; |
| 91 | pNew->nth = 0; |
| 92 | |
| 93 | /* Generate an identifier. The identifer name is approximately the |
| 94 | ** same as a Pandoc identifier. |
| 95 | ** |
| 96 | ** * Skip all text up to the first letter. |
| 97 | ** * Remove all text past the last letter. |
| 98 | ** * Remove HTML markup and entities. |
| 99 | ** * Replace all whitespace sequences with a single "-" |
| 100 | ** * Remove all characters other than alphanumeric, "_", "-", and ".". |
| 101 | ** * Convert all alphabetics to lower case. |
| 102 | ** * If nothing remains, use "section" as the identifier. |
| 103 | */ |
| 104 | while( nText>0 && !fossil_isalpha(zText[nText-1]) ){ nText--; } |
| 105 | memcpy(pNew->zTag, zText, nText); |
| 106 | pNew->zTag[nText] = 0; |
| 107 | zTag = pNew->zTag; |
| 108 | for(i=j=0; zTag[i]; i++){ |
| 109 | if( fossil_isupper(zTag[i]) ){ |
| 110 | if( !seenChar ){ j = 0; seenChar = 1; } |
| 111 | zTag[j++] = fossil_tolower(zTag[i]); |
| 112 | continue; |
| 113 | } |
| 114 | if( fossil_islower(zTag[i]) ){ |
| 115 | if( !seenChar ){ j = 0; seenChar = 1; } |
| 116 | zTag[j++] = zTag[i]; |
| 117 | continue; |
| 118 | } |
| 119 | if( zTag[i]=='<' ){ |
| 120 | i += html_tag_length(zTag+i) - 1; |
| 121 | continue; |
| 122 | } |
| 123 | if( zTag[i]=='&' ){ |
| 124 | while( zTag[i] && zTag[i]!=';' ){ i++; } |
| 125 | if( zTag[i]==0 ) break; |
| 126 | continue; |
| 127 | } |
| 128 | if( fossil_isspace(zTag[i]) ){ |
| 129 | zTag[j++] = '-'; |
| 130 | while( fossil_isspace(zTag[i+1]) ){ i++; } |
| 131 | continue; |
| 132 | } |
| 133 | if( !fossil_isalnum(zTag[i]) && zTag[i]!='.' && zTag[i]!='_' ){ |
| 134 | zTag[j++] = '-'; |
| 135 | }else{ |
| 136 | zTag[j++] = zTag[i]; |
| 137 | } |
| 138 | } |
| 139 | if( j==0 || !seenChar ){ |
| 140 | memcpy(zTag, "section", 7); |
| 141 | j = 7; |
| 142 | } |
| 143 | while( j>0 && !fossil_isalpha(zTag[j-1]) ){ j--; } |
| 144 | zTag[j] = 0; |
| 145 | |
| 146 | /* Search for duplicate identifiers and disambiguate */ |
| 147 | pNew->nth = 0; |
| 148 | for(pSearch=pNew->pPrev; pSearch; pSearch=pSearch->pPrev){ |
| 149 | if( strcmp(pSearch->zTag,zTag)==0 ){ |
| 150 | pNew->nth = pSearch->nth+1; |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | |
| 156 | /* INTER_BLOCK -- skip a line between block level elements */ |
| 157 | #define INTER_BLOCK(ob) \ |
| 158 | do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0) |
| 159 | |
| @@ -246,19 +342,26 @@ | |
| 342 | struct Blob *text, |
| 343 | int level, |
| 344 | void *opaque |
| 345 | ){ |
| 346 | MarkdownToHtml *pCtx = (MarkdownToHtml*)opaque; |
| 347 | MarkdownHeading *pHdng; |
| 348 | struct Blob *title = pCtx->output_title; |
| 349 | /* The first header at the beginning of a text is considered as |
| 350 | * a title and not output. */ |
| 351 | if( blob_size(ob)<=PROLOG_SIZE && title!=0 && blob_size(title)==0 ){ |
| 352 | BLOB_APPEND_BLOB(title, text); |
| 353 | return; |
| 354 | } |
| 355 | INTER_BLOCK(ob); |
| 356 | html_new_heading(pCtx, text, level); |
| 357 | pHdng = pCtx->pLast; |
| 358 | if( pHdng->nth ){ |
| 359 | blob_appendf(ob, "<h%d id='%h-%d'>", level, pHdng->zTag, pHdng->nth); |
| 360 | }else{ |
| 361 | blob_appendf(ob, "<h%d id='%h'>", level, pHdng->zTag); |
| 362 | } |
| 363 | if( pCtx->iHdngNums && level>=pCtx->iHdngNums ){ |
| 364 | int i; |
| 365 | for(i=pCtx->iHdngNums-1; i<level-1; i++){ |
| 366 | blob_appendf(ob,"%d.",pCtx->aNum[i]); |
| 367 | } |
| @@ -607,10 +710,53 @@ | |
| 710 | |
| 711 | |
| 712 | static void html_normal_text(struct Blob *ob, struct Blob *text, void *opaque){ |
| 713 | html_escape(ob, blob_buffer(text), blob_size(text)); |
| 714 | } |
| 715 | |
| 716 | /* |
| 717 | ** Insert a table of contents into the body of the document. |
| 718 | ** |
| 719 | ** The pCtx provides the information needed to do this: |
| 720 | ** |
| 721 | ** pCtx->iToc Offset into pOut of where to insert the TOC |
| 722 | ** pCtx->mxToc Maximum depth of the TOC |
| 723 | ** pCtx->pFirst List of paragraphs to form the TOC |
| 724 | */ |
| 725 | static void html_insert_toc(MarkdownToHtml *pCtx, Blob *pOut){ |
| 726 | Blob new; |
| 727 | MarkdownHeading *pX; |
| 728 | int iLevel = pCtx->mnLevel-1; |
| 729 | int iBase = iLevel; |
| 730 | blob_init(&new, 0, 0); |
| 731 | blob_append(&new, blob_buffer(pOut), pCtx->iToc); |
| 732 | blob_append(&new, "<div class='markdown-toc'>\n", -1); |
| 733 | for(pX=pCtx->pFirst; pX; pX=pX->pNext){ |
| 734 | if( pX->iLevel>pCtx->mxToc ) continue; |
| 735 | while( iLevel<pX->iLevel ){ |
| 736 | iLevel++; |
| 737 | blob_appendf(&new, "<ul class='markdown-toc%d markdown-toc'>\n", |
| 738 | iLevel - iBase); |
| 739 | } |
| 740 | while( iLevel>pX->iLevel ){ |
| 741 | iLevel--; |
| 742 | blob_appendf(&new, "</ul>\n"); |
| 743 | } |
| 744 | blob_appendf(&new,"<li><a href='#%h'>", pX->zTag); |
| 745 | html_to_plaintext(pX->zTitle, &new); |
| 746 | blob_appendf(&new,"</a></li>\n"); |
| 747 | } |
| 748 | while( iLevel>iBase ){ |
| 749 | iLevel--; |
| 750 | blob_appendf(&new, "</ul>\n"); |
| 751 | } |
| 752 | blob_appendf(&new, "</div>\n"); |
| 753 | blob_append(&new, blob_buffer(pOut)+pCtx->iToc, |
| 754 | blob_size(pOut)-pCtx->iToc); |
| 755 | blob_reset(pOut); |
| 756 | *pOut = new; |
| 757 | } |
| 758 | |
| 759 | /* |
| 760 | ** Convert markdown into HTML. |
| 761 | ** |
| 762 | ** The document title is placed in output_title if not NULL. Or if |
| @@ -665,10 +811,11 @@ | |
| 811 | context.output_title = output_title; |
| 812 | html_renderer.opaque = &context; |
| 813 | if( output_title ) blob_reset(output_title); |
| 814 | blob_reset(output_body); |
| 815 | markdown(output_body, input_markdown, &html_renderer); |
| 816 | if( context.mxToc>0 ) html_insert_toc(&context, output_body); |
| 817 | for(pHdng=context.pFirst; pHdng; pHdng=pNextHdng){ |
| 818 | pNextHdng = pHdng->pNext; |
| 819 | fossil_free(pHdng); |
| 820 | } |
| 821 | } |
| 822 |