Fossil SCM

fossil-scm / src / markdown_html.c
Source Blame History 1013 lines
61079c3… mistachkin 1 /*
61079c3… mistachkin 2 ** Copyright (c) 2012 D. Richard Hipp
61079c3… mistachkin 3 **
61079c3… mistachkin 4 ** This program is free software; you can redistribute it and/or
61079c3… mistachkin 5 ** modify it under the terms of the Simplified BSD License (also
61079c3… mistachkin 6 ** known as the "2-Clause License" or "FreeBSD License".)
61079c3… mistachkin 7
61079c3… mistachkin 8 ** This program is distributed in the hope that it will be useful,
61079c3… mistachkin 9 ** but without any warranty; without even the implied warranty of
61079c3… mistachkin 10 ** merchantability or fitness for a particular purpose.
61079c3… mistachkin 11 **
61079c3… mistachkin 12 ** Author contact information:
61079c3… mistachkin 13 ** [email protected]
61079c3… mistachkin 14 ** http://www.hwaci.com/drh/
61079c3… mistachkin 15 **
61079c3… mistachkin 16 *******************************************************************************
61079c3… mistachkin 17 **
61079c3… mistachkin 18 ** This file contains callbacks for the markdown parser that generate
61079c3… mistachkin 19 ** XHTML output.
61079c3… mistachkin 20 */
61079c3… mistachkin 21
61079c3… mistachkin 22 #include "config.h"
61079c3… mistachkin 23 #include "markdown_html.h"
61079c3… mistachkin 24
61079c3… mistachkin 25 #if INTERFACE
61079c3… mistachkin 26
61079c3… mistachkin 27 void markdown_to_html(
61079c3… mistachkin 28 struct Blob *input_markdown,
61079c3… mistachkin 29 struct Blob *output_title,
61079c3… mistachkin 30 struct Blob *output_body);
61079c3… mistachkin 31
61079c3… mistachkin 32 #endif /* INTERFACE */
61079c3… mistachkin 33
b61dcef… drh 34 /*
3990518… george 35 ** Markdown-internal helper for generating unique link reference IDs.
3990518… george 36 ** Fields provide typed interpretation of the underline memory buffer.
3990518… george 37 */
3990518… george 38 typedef union bitfield64_t bitfield64_t;
3990518… george 39 union bitfield64_t{
3990518… george 40 char c[8]; /* interpret as the array of signed characters */
3990518… george 41 unsigned char b[8]; /* interpret as the array of unsigned characters */
3990518… george 42 };
3990518… george 43
3990518… george 44 /*
b61dcef… drh 45 ** An instance of the following structure is passed through the
b61dcef… drh 46 ** "opaque" pointer.
b61dcef… drh 47 */
b61dcef… drh 48 typedef struct MarkdownToHtml MarkdownToHtml;
b61dcef… drh 49 struct MarkdownToHtml {
b61dcef… drh 50 Blob *output_title; /* Store the title here */
3990518… george 51 bitfield64_t unique; /* Enables construction of unique #id elements */
3990518… george 52
3990518… george 53 #ifndef FOOTNOTES_WITHOUT_URI
3990518… george 54 Blob reqURI; /* REQUEST_URI with escaped quotes */
3990518… george 55 #endif
b61dcef… drh 56 };
b61dcef… drh 57
61079c3… mistachkin 58
61079c3… mistachkin 59 /* INTER_BLOCK -- skip a line between block level elements */
61079c3… mistachkin 60 #define INTER_BLOCK(ob) \
48c47e1… drh 61 do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0)
48c47e1… drh 62
3990518… george 63 /*
3990518… george 64 ** FOOTNOTES_WITHOUT_URI macro was introduced by [2c1f8f3592ef00e0]
3990518… george 65 ** to enable flexibility in rendering of footnote-specific hyperlinks.
3990518… george 66 ** It may be defined for a particular build in order to omit
3990518… george 67 ** full REQUEST_URIs within footnote-specific (and page-local) hyperlinks.
3990518… george 68 ** This *is* used for the builds that incorporate 'base-href-fix' branch
3990518… george 69 ** (which in turn fixes footnotes on the preview tab of /wikiedit page).
3990518… george 70 */
3990518… george 71 #ifndef FOOTNOTES_WITHOUT_URI
3990518… george 72 #define BLOB_APPEND_URI(dest,ctx) blob_appendb(dest,&((ctx)->reqURI))
3990518… george 73 #else
3990518… george 74 #define BLOB_APPEND_URI(dest,ctx)
3990518… george 75 #endif
3990518… george 76
3990518… george 77 /* Converts an integer to a textual base26 representation
3990518… george 78 ** with proper null-termination.
3990518… george 79 * Return empty string if that integer is negative. */
3990518… george 80 static bitfield64_t to_base26(int i, int uppercase){
3990518… george 81 bitfield64_t x;
3990518… george 82 int j;
3990518… george 83 memset( &x, 0, sizeof(x) );
3990518… george 84 if( i >= 0 ){
3990518… george 85 for(j=7; j >= 0; j--){
3990518… george 86 x.b[j] = (unsigned char)(uppercase?'A':'a') + i%26;
3990518… george 87 if( (i /= 26) == 0 ) break;
3990518… george 88 }
3990518… george 89 assert( j > 0 ); /* because 2^32 < 26^7 */
3990518… george 90 for(i=0; i<8-j; i++) x.b[i] = x.b[i+j];
3990518… george 91 for( ; i<8 ; i++) x.b[i] = 0;
3990518… george 92 }
3990518… george 93 assert( x.c[7] == 0 );
3990518… george 94 return x;
3990518… george 95 }
7950dc2… drh 96
7950dc2… drh 97 /* HTML escapes
7950dc2… drh 98 **
7950dc2… drh 99 ** html_escape() converts < to &lt;, > to &gt;, and & to &amp;.
7950dc2… drh 100 ** html_quote() goes further and converts " into &quot; and ' in &#39;.
7950dc2… drh 101 */
7950dc2… drh 102 static void html_quote(struct Blob *ob, const char *data, size_t size){
61079c3… mistachkin 103 size_t beg = 0, i = 0;
61079c3… mistachkin 104 while( i<size ){
61079c3… mistachkin 105 beg = i;
61079c3… mistachkin 106 while( i<size
61079c3… mistachkin 107 && data[i]!='<'
61079c3… mistachkin 108 && data[i]!='>'
61079c3… mistachkin 109 && data[i]!='"'
61079c3… mistachkin 110 && data[i]!='&'
7950dc2… drh 111 && data[i]!='\''
7950dc2… drh 112 ){
7950dc2… drh 113 i++;
7950dc2… drh 114 }
7950dc2… drh 115 blob_append(ob, data+beg, i-beg);
7950dc2… drh 116 while( i<size ){
7950dc2… drh 117 if( data[i]=='<' ){
3990518… george 118 blob_append_literal(ob, "&lt;");
7950dc2… drh 119 }else if( data[i]=='>' ){
3990518… george 120 blob_append_literal(ob, "&gt;");
7950dc2… drh 121 }else if( data[i]=='&' ){
3990518… george 122 blob_append_literal(ob, "&amp;");
7950dc2… drh 123 }else if( data[i]=='"' ){
3990518… george 124 blob_append_literal(ob, "&quot;");
7950dc2… drh 125 }else if( data[i]=='\'' ){
3990518… george 126 blob_append_literal(ob, "&#39;");
7950dc2… drh 127 }else{
7950dc2… drh 128 break;
7950dc2… drh 129 }
7950dc2… drh 130 i++;
7950dc2… drh 131 }
7950dc2… drh 132 }
7950dc2… drh 133 }
7950dc2… drh 134 static void html_escape(struct Blob *ob, const char *data, size_t size){
7950dc2… drh 135 size_t beg = 0, i = 0;
7950dc2… drh 136 while( i<size ){
7950dc2… drh 137 beg = i;
7950dc2… drh 138 while( i<size
7950dc2… drh 139 && data[i]!='<'
7950dc2… drh 140 && data[i]!='>'
7950dc2… drh 141 && data[i]!='&'
61079c3… mistachkin 142 ){
61079c3… mistachkin 143 i++;
61079c3… mistachkin 144 }
61079c3… mistachkin 145 blob_append(ob, data+beg, i-beg);
61079c3… mistachkin 146 while( i<size ){
61079c3… mistachkin 147 if( data[i]=='<' ){
3990518… george 148 blob_append_literal(ob, "&lt;");
61079c3… mistachkin 149 }else if( data[i]=='>' ){
3990518… george 150 blob_append_literal(ob, "&gt;");
61079c3… mistachkin 151 }else if( data[i]=='&' ){
3990518… george 152 blob_append_literal(ob, "&amp;");
61079c3… mistachkin 153 }else{
61079c3… mistachkin 154 break;
61079c3… mistachkin 155 }
61079c3… mistachkin 156 i++;
61079c3… mistachkin 157 }
61079c3… mistachkin 158 }
61079c3… mistachkin 159 }
61079c3… mistachkin 160
61079c3… mistachkin 161
61079c3… mistachkin 162 /* HTML block tags */
61079c3… mistachkin 163
e214a57… drh 164 /* Size of the prolog: "<div class='markdown'>\n" */
e214a57… drh 165 #define PROLOG_SIZE 23
e214a57… drh 166
e061a67… mistachkin 167 static void html_prolog(struct Blob *ob, void *opaque){
e061a67… mistachkin 168 INTER_BLOCK(ob);
3990518… george 169 blob_append_literal(ob, "<div class=\"markdown\">\n");
e214a57… drh 170 assert( blob_size(ob)==PROLOG_SIZE );
e061a67… mistachkin 171 }
e061a67… mistachkin 172
e061a67… mistachkin 173 static void html_epilog(struct Blob *ob, void *opaque){
e061a67… mistachkin 174 INTER_BLOCK(ob);
3990518… george 175 blob_append_literal(ob, "</div>\n");
e061a67… mistachkin 176 }
e061a67… mistachkin 177
485fda6… drh 178 static void html_blockhtml(struct Blob *ob, struct Blob *text, void *opaque){
61079c3… mistachkin 179 char *data = blob_buffer(text);
cc7f4df… drh 180 size_t size = blob_size(text);
b61dcef… drh 181 Blob *title = ((MarkdownToHtml*)opaque)->output_title;
cc7f4df… drh 182 while( size>0 && fossil_isspace(data[0]) ){ data++; size--; }
cc7f4df… drh 183 while( size>0 && fossil_isspace(data[size-1]) ){ size--; }
cc7f4df… drh 184 /* If the first raw block is an <h1> element, then use it as the title. */
cc7f4df… drh 185 if( blob_size(ob)<=PROLOG_SIZE
cc7f4df… drh 186 && size>9
d407c38… drh 187 && title!=0
cc7f4df… drh 188 && sqlite3_strnicmp("<h1",data,3)==0
cc7f4df… drh 189 && sqlite3_strnicmp("</h1>", &data[size-5],5)==0
cc7f4df… drh 190 ){
3f99bca… drh 191 int nTag = html_tag_length(data);
cc7f4df… drh 192 blob_append(title, data+nTag, size - nTag - 5);
d407c38… drh 193 return;
cc7f4df… drh 194 }
61079c3… mistachkin 195 INTER_BLOCK(ob);
382f373… drh 196 blob_append(ob, data, size);
3990518… george 197 blob_append_literal(ob, "\n");
61079c3… mistachkin 198 }
61079c3… mistachkin 199
61079c3… mistachkin 200 static void html_blockcode(struct Blob *ob, struct Blob *text, void *opaque){
61079c3… mistachkin 201 INTER_BLOCK(ob);
3990518… george 202 blob_append_literal(ob, "<pre><code>");
61079c3… mistachkin 203 html_escape(ob, blob_buffer(text), blob_size(text));
3990518… george 204 blob_append_literal(ob, "</code></pre>\n");
61079c3… mistachkin 205 }
61079c3… mistachkin 206
61079c3… mistachkin 207 static void html_blockquote(struct Blob *ob, struct Blob *text, void *opaque){
61079c3… mistachkin 208 INTER_BLOCK(ob);
3990518… george 209 blob_append_literal(ob, "<blockquote>\n");
3990518… george 210 blob_appendb(ob, text);
3990518… george 211 blob_append_literal(ob, "</blockquote>\n");
61079c3… mistachkin 212 }
61079c3… mistachkin 213
61079c3… mistachkin 214 static void html_header(
61079c3… mistachkin 215 struct Blob *ob,
61079c3… mistachkin 216 struct Blob *text,
61079c3… mistachkin 217 int level,
61079c3… mistachkin 218 void *opaque
61079c3… mistachkin 219 ){
b61dcef… drh 220 struct Blob *title = ((MarkdownToHtml*)opaque)->output_title;
f9ead75… drh 221 char *z = 0;
f9ead75… drh 222 int i,j;
61079c3… mistachkin 223 /* The first header at the beginning of a text is considered as
61079c3… mistachkin 224 * a title and not output. */
d407c38… drh 225 if( blob_size(ob)<=PROLOG_SIZE && title!=0 && blob_size(title)==0 ){
3990518… george 226 blob_appendb(title, text);
d407c38… drh 227 return;
61079c3… mistachkin 228 }
61079c3… mistachkin 229 INTER_BLOCK(ob);
f9ead75… drh 230 z = fossil_strdup(blob_buffer(text));
f9ead75… drh 231 if( z==0 ){
f9ead75… drh 232 j = 0;
f9ead75… drh 233 }else{
f9ead75… drh 234 /*
f9ead75… drh 235 ** The GitHub "slugify" algorithm converts the text of a markdown header
f9ead75… drh 236 ** into a ID for that header. The algorithm is:
f9ead75… drh 237 **
f9ead75… drh 238 ** 1. ASCII alphanumerics -> convert to lower case
f9ead75… drh 239 ** 2. Spaces, hyphens, underscores -> convert to '-'
f9ead75… drh 240 ** 3. Non-ASCII -> preserve as-is
f9ead75… drh 241 ** 4. Other punctuation -> remove
f9ead75… drh 242 ** 5. Multiple consecutive dashes -> collapse to one
f9ead75… drh 243 ** 6. Leading and trailing dashes -> remove
f9ead75… drh 244 ** 7. Markup <...> and &...; -> remove
f9ead75… drh 245 **
f9ead75… drh 246 ** This implementation does the conversion in-place.
f9ead75… drh 247 */
f9ead75… drh 248 for(i=j=0; z[i]; i++){
f9ead75… drh 249 if( fossil_isalnum(z[i]) ){
f9ead75… drh 250 z[j++] = fossil_tolower(z[i]);
f9ead75… drh 251 }else if( fossil_isspace(z[i]) || z[i]=='-' || z[i]=='_' ){
f9ead75… drh 252 if( j>0 && z[j-1]!='-' ) z[j++] = '-';
f9ead75… drh 253 }else if( z[i]=='<' ){
f9ead75… drh 254 do{ i++; }while( z[i]!=0 && z[i]!='>' );
f9ead75… drh 255 }else if( z[i]=='&' ){
f9ead75… drh 256 do{ i++; }while( z[i]!=0 && z[i]!=';' );
f9ead75… drh 257 }else if( (z[i]&0x80)!=0 ){
f9ead75… drh 258 z[j++] = z[i];
f9ead75… drh 259 }
f9ead75… drh 260 }
f9ead75… drh 261 if( j>0 && z[j-1]=='-' ) j--;
f9ead75… drh 262 z[j] = 0;
f9ead75… drh 263 }
f9ead75… drh 264 if( j>0 ){
f9ead75… drh 265 blob_appendf(ob, "<h%d id=\"%s\">", level, z);
f9ead75… drh 266 }else{
f9ead75… drh 267 blob_appendf(ob, "<h%d>", level);
f9ead75… drh 268 }
3990518… george 269 blob_appendb(ob, text);
61079c3… mistachkin 270 blob_appendf(ob, "</h%d>", level);
f9ead75… drh 271 fossil_free(z);
61079c3… mistachkin 272 }
61079c3… mistachkin 273
61079c3… mistachkin 274 static void html_hrule(struct Blob *ob, void *opaque){
61079c3… mistachkin 275 INTER_BLOCK(ob);
f5482a0… wyoung 276 blob_append_literal(ob, "<hr>\n");
61079c3… mistachkin 277 }
61079c3… mistachkin 278
61079c3… mistachkin 279
61079c3… mistachkin 280 static void html_list(
61079c3… mistachkin 281 struct Blob *ob,
61079c3… mistachkin 282 struct Blob *text,
61079c3… mistachkin 283 int flags,
61079c3… mistachkin 284 void *opaque
61079c3… mistachkin 285 ){
61079c3… mistachkin 286 char ol[] = "ol";
61079c3… mistachkin 287 char ul[] = "ul";
61079c3… mistachkin 288 char *tag = (flags & MKD_LIST_ORDERED) ? ol : ul;
61079c3… mistachkin 289 INTER_BLOCK(ob);
61079c3… mistachkin 290 blob_appendf(ob, "<%s>\n", tag);
3990518… george 291 blob_appendb(ob, text);
61079c3… mistachkin 292 blob_appendf(ob, "</%s>\n", tag);
61079c3… mistachkin 293 }
61079c3… mistachkin 294
61079c3… mistachkin 295 static void html_list_item(
61079c3… mistachkin 296 struct Blob *ob,
61079c3… mistachkin 297 struct Blob *text,
61079c3… mistachkin 298 int flags,
61079c3… mistachkin 299 void *opaque
61079c3… mistachkin 300 ){
61079c3… mistachkin 301 char *text_data = blob_buffer(text);
61079c3… mistachkin 302 size_t text_size = blob_size(text);
61079c3… mistachkin 303 while( text_size>0 && text_data[text_size-1]=='\n' ) text_size--;
3990518… george 304 blob_append_literal(ob, "<li>");
382f373… drh 305 blob_append(ob, text_data, text_size);
3990518… george 306 blob_append_literal(ob, "</li>\n");
61079c3… mistachkin 307 }
61079c3… mistachkin 308
61079c3… mistachkin 309 static void html_paragraph(struct Blob *ob, struct Blob *text, void *opaque){
61079c3… mistachkin 310 INTER_BLOCK(ob);
3990518… george 311 blob_append_literal(ob, "<p>");
3990518… george 312 blob_appendb(ob, text);
3990518… george 313 blob_append_literal(ob, "</p>\n");
61079c3… mistachkin 314 }
61079c3… mistachkin 315
61079c3… mistachkin 316
61079c3… mistachkin 317 static void html_table(
61079c3… mistachkin 318 struct Blob *ob,
61079c3… mistachkin 319 struct Blob *head_row,
61079c3… mistachkin 320 struct Blob *rows,
61079c3… mistachkin 321 void *opaque
61079c3… mistachkin 322 ){
61079c3… mistachkin 323 INTER_BLOCK(ob);
f0d11ab… stephan 324 blob_append_literal(ob, "<table class='md-table'>\n");
61079c3… mistachkin 325 if( head_row && blob_size(head_row)>0 ){
3990518… george 326 blob_append_literal(ob, "<thead>\n");
3990518… george 327 blob_appendb(ob, head_row);
3990518… george 328 blob_append_literal(ob, "</thead>\n<tbody>\n");
61079c3… mistachkin 329 }
61079c3… mistachkin 330 if( rows ){
3990518… george 331 blob_appendb(ob, rows);
61079c3… mistachkin 332 }
61079c3… mistachkin 333 if( head_row && blob_size(head_row)>0 ){
3990518… george 334 blob_append_literal(ob, "</tbody>\n");
61079c3… mistachkin 335 }
3990518… george 336 blob_append_literal(ob, "</table>\n");
61079c3… mistachkin 337 }
61079c3… mistachkin 338
61079c3… mistachkin 339 static void html_table_cell(
61079c3… mistachkin 340 struct Blob *ob,
61079c3… mistachkin 341 struct Blob *text,
61079c3… mistachkin 342 int flags,
61079c3… mistachkin 343 void *opaque
61079c3… mistachkin 344 ){
61079c3… mistachkin 345 if( flags & MKD_CELL_HEAD ){
3990518… george 346 blob_append_literal(ob, " <th");
61079c3… mistachkin 347 }else{
3990518… george 348 blob_append_literal(ob, " <td");
61079c3… mistachkin 349 }
61079c3… mistachkin 350 switch( flags & MKD_CELL_ALIGN_MASK ){
61079c3… mistachkin 351 case MKD_CELL_ALIGN_LEFT: {
9f8bf69… wyoung 352 blob_append_literal(ob, " style=\"text-align:left\"");
61079c3… mistachkin 353 break;
61079c3… mistachkin 354 }
61079c3… mistachkin 355 case MKD_CELL_ALIGN_RIGHT: {
9f8bf69… wyoung 356 blob_append_literal(ob, " style=\"text-align:right\"");
61079c3… mistachkin 357 break;
61079c3… mistachkin 358 }
61079c3… mistachkin 359 case MKD_CELL_ALIGN_CENTER: {
9f8bf69… wyoung 360 blob_append_literal(ob, " style=\"text-align:center\"");
61079c3… mistachkin 361 break;
61079c3… mistachkin 362 }
61079c3… mistachkin 363 }
3990518… george 364 blob_append_literal(ob, ">");
3990518… george 365 blob_appendb(ob, text);
61079c3… mistachkin 366 if( flags & MKD_CELL_HEAD ){
3990518… george 367 blob_append_literal(ob, "</th>\n");
61079c3… mistachkin 368 }else{
3990518… george 369 blob_append_literal(ob, "</td>\n");
61079c3… mistachkin 370 }
61079c3… mistachkin 371 }
61079c3… mistachkin 372
61079c3… mistachkin 373 static void html_table_row(
61079c3… mistachkin 374 struct Blob *ob,
61079c3… mistachkin 375 struct Blob *cells,
61079c3… mistachkin 376 int flags,
61079c3… mistachkin 377 void *opaque
61079c3… mistachkin 378 ){
3990518… george 379 blob_append_literal(ob, " <tr>\n");
3990518… george 380 blob_appendb(ob, cells);
3990518… george 381 blob_append_literal(ob, " </tr>\n");
3990518… george 382 }
3990518… george 383
3990518… george 384 /*
3990518… george 385 ** Render a token of user provided classes.
3990518… george 386 ** If bHTML is true then render HTML for (presumably) visible text,
3990518… george 387 ** otherwise just a space-separated list of the derived classes.
3990518… george 388 */
3990518… george 389 static void append_footnote_upc(
3990518… george 390 struct Blob *ob,
3990518… george 391 const struct Blob *upc, /* token of user-provided classes */
3990518… george 392 int bHTML
3990518… george 393 ){
3990518… george 394 const char *z = blob_buffer(upc);
3990518… george 395 int i, n = blob_size(upc);
3990518… george 396
3990518… george 397 if( n<3 ) return;
3990518… george 398 assert( z[0]=='.' && z[n-1] == ':' );
3990518… george 399 if( bHTML ){
3990518… george 400 blob_append_literal(ob, "<span class='fn-upc'>"
3990518… george 401 "<span class='fn-upcDot'>.</span>");
3990518… george 402 }
3990518… george 403 n = 0;
3990518… george 404 do{
3990518… george 405 z++;
3990518… george 406 if( *z!='.' && *z!=':' ){
3990518… george 407 assert( fossil_isalnum(*z) || *z=='-' );
3990518… george 408 n++;
3990518… george 409 continue;
3990518… george 410 }
3990518… george 411 assert( n );
3990518… george 412 if( bHTML ) blob_append_literal(ob, "<span class='");
3990518… george 413 blob_append_literal(ob, "fn-upc-");
3990518… george 414
3990518… george 415 for(i=-n; i<0; i++){
3990518… george 416 blob_append_char(ob, fossil_tolower(z[i]) );
3990518… george 417 }
3990518… george 418 if( bHTML ){
3990518… george 419 blob_append_literal(ob, "'>");
3990518… george 420 blob_append(ob, z-n, n);
3990518… george 421 blob_append_literal(ob, "</span>");
3990518… george 422 }else{
3990518… george 423 blob_append_char(ob, ' ');
3990518… george 424 }
3990518… george 425 n = 0;
3990518… george 426 if( bHTML ){
3990518… george 427 if( *z==':' ){
3990518… george 428 blob_append_literal(ob,"<span class='fn-upcColon'>:</span>");
3990518… george 429 }else{
3990518… george 430 blob_append_literal(ob,"<span class='fn-upcDot'>.</span>");
3990518… george 431 }
3990518… george 432 }
3990518… george 433 }while( *z != ':' );
3990518… george 434 if( bHTML ) blob_append_literal(ob,"</span>\n");
3990518… george 435 }
3990518… george 436
3990518… george 437 static int html_footnote_ref(
3990518… george 438 struct Blob *ob, const struct Blob *span, const struct Blob *upc,
3990518… george 439 int iMark, int locus, void *opaque
3990518… george 440 ){
3990518… george 441 const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque;
3990518… george 442 const bitfield64_t l = to_base26(locus-1,0);
3990518… george 443 char pos[32];
3990518… george 444 memset(pos,0,32);
3990518… george 445 assert( locus > 0 );
3990518… george 446 /* expect BUGs if the following yields compiler warnings */
3990518… george 447 if( iMark > 0 ){ /* a regular reference to a footnote */
dfa41af… wyoung 448 sqlite3_snprintf(sizeof(pos), pos, "%s-%d-%s", ctx->unique.c, iMark, l.c);
3990518… george 449 if(span && blob_size(span)) {
3990518… george 450 blob_append_literal(ob,"<span class='");
3990518… george 451 append_footnote_upc(ob, upc, 0);
3990518… george 452 blob_append_literal(ob,"notescope' id='noteref");
3990518… george 453 blob_appendf(ob,"%s'>",pos);
3990518… george 454 blob_appendb(ob, span);
3990518… george 455 blob_trim(ob);
3990518… george 456 blob_append_literal(ob,"<sup class='noteref'><a href='");
3990518… george 457 BLOB_APPEND_URI(ob, ctx);
3990518… george 458 blob_appendf(ob,"#footnote%s'>%d</a></sup></span>", pos, iMark);
3990518… george 459 }else{
3990518… george 460 blob_trim(ob);
3990518… george 461 blob_append_literal(ob,"<sup class='");
3990518… george 462 append_footnote_upc(ob, upc, 0);
3990518… george 463 blob_append_literal(ob,"noteref'><a href='");
3990518… george 464 BLOB_APPEND_URI(ob, ctx);
3990518… george 465 blob_appendf(ob,"#footnote%s' id='noteref%s'>%d</a></sup>",
3990518… george 466 pos, pos, iMark);
3990518… george 467 }
3990518… george 468 }else{ /* misreference */
3990518… george 469 assert( iMark == -1 );
3990518… george 470
dfa41af… wyoung 471 sqlite3_snprintf(sizeof(pos), pos, "%s-%s", ctx->unique.c, l.c);
3990518… george 472 if(span && blob_size(span)) {
3990518… george 473 blob_appendf(ob, "<span class='notescope' id='misref%s'>", pos);
3990518… george 474 blob_appendb(ob, span);
3990518… george 475 blob_trim(ob);
3990518… george 476 blob_append_literal(ob, "<sup class='noteref misref'><a href='");
3990518… george 477 BLOB_APPEND_URI(ob, ctx);
3990518… george 478 blob_appendf(ob, "#misreference%s'>misref</a></sup></span>", pos);
3990518… george 479 }else{
3990518… george 480 blob_trim(ob);
3990518… george 481 blob_append_literal(ob, "<sup class='noteref misref'><a href='");
3990518… george 482 BLOB_APPEND_URI(ob, ctx);
3990518… george 483 blob_appendf(ob, "#misreference%s' id='misref%s'>", pos, pos);
3990518… george 484 blob_append_literal(ob, "misref</a></sup>");
3990518… george 485 }
3990518… george 486 }
3990518… george 487 return 1;
3990518… george 488 }
3990518… george 489
3990518… george 490 /* Render a single item of the footnotes list.
3990518… george 491 * Each backref gets a unique id to enable dynamic styling. */
3990518… george 492 static void html_footnote_item(
3990518… george 493 struct Blob *ob, const struct Blob *text, int iMark, int nUsed, void *opaque
3990518… george 494 ){
3990518… george 495 const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque;
3990518… george 496 const char * const unique = ctx->unique.c;
3990518… george 497 assert( nUsed >= 0 );
3990518… george 498 /* expect BUGs if the following yields compiler warnings */
3990518… george 499
3990518… george 500 if( iMark < 0 ){ /* misreferences */
3990518… george 501 assert( iMark == -1 );
3990518… george 502 assert( nUsed );
3990518… george 503 blob_append_literal(ob,"<li class='fn-misreference'>"
3990518… george 504 "<sup class='fn-backrefs'>");
3990518… george 505 if( nUsed == 1 ){
3990518… george 506 blob_appendf(ob,"<a id='misreference%s-a' href='", unique);
3990518… george 507 BLOB_APPEND_URI(ob, ctx);
3990518… george 508 blob_appendf(ob,"#misref%s-a'>^</a>", unique);
3990518… george 509 }else{
3990518… george 510 int i;
3990518… george 511 blob_append_char(ob, '^');
3990518… george 512 for(i=0; i<nUsed && i<26; i++){
3990518… george 513 const int c = i + (unsigned)'a';
3990518… george 514 blob_appendf(ob," <a id='misreference%s-%c' href='", unique,c);
3990518… george 515 BLOB_APPEND_URI(ob, ctx);
3990518… george 516 blob_appendf(ob,"#misref%s-%c'>%c</a>", unique,c, c);
3990518… george 517 }
3990518… george 518 if( i < nUsed ) blob_append_literal(ob," &hellip;");
3990518… george 519 }
3990518… george 520 blob_append_literal(ob,"</sup>\n<span>Misreference</span>");
3990518… george 521 }else if( iMark > 0 ){ /* regular, joined and overnested footnotes */
3990518… george 522 char pos[24];
3990518… george 523 int bJoin = 0;
3990518… george 524 #define _joined_footnote_indicator "<ul class='fn-joined'>"
3990518… george 525 #define _jfi_sz (sizeof(_joined_footnote_indicator)-1)
3990518… george 526 /* make.footnote_item() invocations should pass args accordingly */
3990518… george 527 const struct Blob *upc = text+1;
3990518… george 528 assert( text );
3990518… george 529 /* allow blob_size(text)==0 for constructs like [...](^ [] ()) */
3990518… george 530 memset(pos,0,24);
dfa41af… wyoung 531 sqlite3_snprintf(sizeof(pos), pos, "%s-%d", unique, iMark);
3990518… george 532 blob_appendf(ob, "<li id='footnote%s' class='", pos);
3990518… george 533 if( nUsed ){
3990518… george 534 if( blob_size(text)>=_jfi_sz &&
3990518… george 535 !memcmp(blob_buffer(text),_joined_footnote_indicator,_jfi_sz)){
3990518… george 536 bJoin = 1;
3990518… george 537 blob_append_literal(ob, "fn-joined ");
3990518… george 538 }
3990518… george 539 append_footnote_upc(ob, upc, 0);
3990518… george 540 }else{
3990518… george 541 blob_append_literal(ob, "fn-toodeep ");
3990518… george 542 }
3990518… george 543 if( nUsed <= 1 ){
3990518… george 544 blob_append_literal(ob, "fn-monoref'><sup class='fn-backrefs'>");
3990518… george 545 blob_appendf(ob,"<a id='footnote%s-a' href='", pos);
3990518… george 546 BLOB_APPEND_URI(ob, ctx);
3990518… george 547 blob_appendf(ob,"#noteref%s-a'>^</a>", pos);
3990518… george 548 }else{
3990518… george 549 int i;
3990518… george 550 blob_append_literal(ob, "fn-polyref'><sup class='fn-backrefs'>^");
3990518… george 551 for(i=0; i<nUsed && i<26; i++){
3990518… george 552 const int c = i + (unsigned)'a';
3990518… george 553 blob_appendf(ob," <a id='footnote%s-%c' href='", pos,c);
3990518… george 554 BLOB_APPEND_URI(ob, ctx);
3990518… george 555 blob_appendf(ob,"#noteref%s-%c'>%c</a>", pos,c, c);
3990518… george 556 }
3990518… george 557 /* It's unlikely that so many backrefs will be usefull */
3990518… george 558 /* but maybe for some machine generated documents... */
3990518… george 559 for(; i<nUsed && i<676; i++){
3990518… george 560 const bitfield64_t l = to_base26(i,0);
3990518… george 561 blob_appendf(ob," <a id='footnote%s-%s' href='", pos, l.c);
3990518… george 562 BLOB_APPEND_URI(ob, ctx);
3990518… george 563 blob_appendf(ob,"#noteref%s-%s'>%s</a>", pos,l.c, l.c);
3990518… george 564 }
3990518… george 565 if( i < nUsed ) blob_append_literal(ob," &hellip;");
3990518… george 566 }
3990518… george 567 blob_append_literal(ob,"</sup>\n");
3990518… george 568 if( bJoin ){
3990518… george 569 blob_append_literal(ob,"<sup class='fn-joined'></sup><ul>");
3990518… george 570 blob_append(ob,blob_buffer(text)+_jfi_sz,blob_size(text)-_jfi_sz);
3990518… george 571 }else if( nUsed ){
3990518… george 572 append_footnote_upc(ob, upc, 1);
3990518… george 573 blob_appendb(ob, text);
3990518… george 574 }else{
3990518… george 575 blob_append_literal(ob,"<i></i>\n"
3990518… george 576 "<pre><code class='language-markdown'>");
3990518… george 577 if( blob_size(upc) ){
3990518… george 578 blob_appendb(ob, upc);
3990518… george 579 }
3990518… george 580 html_escape(ob, blob_buffer(text), blob_size(text));
3990518… george 581 blob_append_literal(ob,"</code></pre>");
3990518… george 582 }
3990518… george 583 #undef _joined_footnote_indicator
3990518… george 584 #undef _jfi_sz
3990518… george 585 }else{ /* a footnote was defined but wasn't referenced */
3990518… george 586 /* make.footnote_item() invocations should pass args accordingly */
3990518… george 587 const struct Blob *id = text-1, *upc = text+1;
3990518… george 588 assert( !nUsed );
3990518… george 589 assert( text );
3990518… george 590 assert( blob_size(text) );
3990518… george 591 assert( blob_size(id) );
3990518… george 592 blob_append_literal(ob,"<li class='fn-unreferenced'>\n[^&nbsp;<code>");
3990518… george 593 html_escape(ob, blob_buffer(id), blob_size(id));
3990518… george 594 blob_append_literal(ob, "</code>&nbsp;]<i></i>\n"
3990518… george 595 "<pre><code class='language-markdown'>");
3990518… george 596 if( blob_size(upc) ){
3990518… george 597 blob_appendb(ob, upc);
3990518… george 598 }
3990518… george 599 html_escape(ob, blob_buffer(text), blob_size(text));
3990518… george 600 blob_append_literal(ob,"</code></pre>");
3990518… george 601 }
3990518… george 602 blob_append_literal(ob, "\n</li>\n");
61079c3… mistachkin 603 }
61079c3… mistachkin 604
3990518… george 605 static void html_footnotes(
3990518… george 606 struct Blob *ob, const struct Blob *items, void *opaque
3990518… george 607 ){
3990518… george 608 if( items && blob_size(items) ){
3990518… george 609 blob_append_literal(ob,
3990518… george 610 "\n<hr class='footnotes-separator'/>\n<ol class='footnotes'>\n");
3990518… george 611 blob_appendb(ob, items);
3990518… george 612 blob_append_literal(ob, "</ol>\n");
3990518… george 613 }
3990518… george 614 }
61079c3… mistachkin 615
61079c3… mistachkin 616 /* HTML span tags */
61079c3… mistachkin 617
485fda6… drh 618 static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){
c2875cb… drh 619 blob_append(ob, blob_buffer(text), blob_size(text));
61079c3… mistachkin 620 return 1;
61079c3… mistachkin 621 }
61079c3… mistachkin 622
61079c3… mistachkin 623 static int html_autolink(
61079c3… mistachkin 624 struct Blob *ob,
61079c3… mistachkin 625 struct Blob *link,
61079c3… mistachkin 626 enum mkd_autolink type,
61079c3… mistachkin 627 void *opaque
61079c3… mistachkin 628 ){
61079c3… mistachkin 629 if( !link || blob_size(link)<=0 ) return 0;
3990518… george 630 blob_append_literal(ob, "<a href=\"");
3990518… george 631 if( type==MKDA_IMPLICIT_EMAIL ) blob_append_literal(ob, "mailto:");
7950dc2… drh 632 html_quote(ob, blob_buffer(link), blob_size(link));
3990518… george 633 blob_append_literal(ob, "\">");
61079c3… mistachkin 634 if( type==MKDA_EXPLICIT_EMAIL && blob_size(link)>7 ){
61079c3… mistachkin 635 /* remove "mailto:" from displayed text */
61079c3… mistachkin 636 html_escape(ob, blob_buffer(link)+7, blob_size(link)-7);
61079c3… mistachkin 637 }else{
61079c3… mistachkin 638 html_escape(ob, blob_buffer(link), blob_size(link));
61079c3… mistachkin 639 }
3990518… george 640 blob_append_literal(ob, "</a>");
61079c3… mistachkin 641 return 1;
3990518… george 642 }
3990518… george 643
3990518… george 644 /*
aac2a35… stephan 645 ** Flags for use with/via pikchr_to_html_add_flags().
aac2a35… stephan 646 */
aac2a35… stephan 647 static int pikchrToHtmlFlags = 0;
aac2a35… stephan 648 /*
aac2a35… stephan 649 ** Sets additional pikchr_process() flags to use for all future calls
aac2a35… stephan 650 ** to pikch_to_html(). This is intended to be used by commands such as
aac2a35… stephan 651 ** test-wiki-render and test-markdown-render to set the
aac2a35… stephan 652 ** PIKCHR_PROCESS_DARK_MODE flag for all embedded pikchr elements.
aac2a35… stephan 653 **
aac2a35… stephan 654 ** Not all PIKCHR_PROCESS flags are legal, as pikchr_to_html()
aac2a35… stephan 655 ** hard-codes a subset of flags and passing arbitrary flags here may
aac2a35… stephan 656 ** interfere with that.
aac2a35… stephan 657 **
aac2a35… stephan 658 ** The only tested/intended use of this function is to pass it either
aac2a35… stephan 659 ** 0 or PIKCHR_PROCESS_DARK_MODE.
aac2a35… stephan 660 **
aac2a35… stephan 661 ** Design note: this is not implemented as an additional argument to
aac2a35… stephan 662 ** pikchr_to_html() because the commands for which dark-mode rendering
aac2a35… stephan 663 ** are now supported (test-wiki-render and test-markdown-render) are
aac2a35… stephan 664 ** far removed from their corresponding pikchr_to_html() calls and
aac2a35… stephan 665 ** there is no direct path from those commands to those calls. A
aac2a35… stephan 666 ** cleaner, but much more invasive, approach would be to add a flag to
aac2a35… stephan 667 ** markdown_to_html(), extend the WIKI_... flags with
aac2a35… stephan 668 ** WIKI_DARK_PIKCHR, and extend both wiki.c:Renderer and
aac2a35… stephan 669 ** markdown_html.c:MarkdownToHtml to contain and pass on that flag.
aac2a35… stephan 670 */
aac2a35… stephan 671 void pikchr_to_html_add_flags( int f ){
aac2a35… stephan 672 pikchrToHtmlFlags = f;
aac2a35… stephan 673 }
aac2a35… stephan 674
aac2a35… stephan 675 /*
a13082c… drh 676 ** The nSrc bytes at zSrc[] are Pikchr input text (allegedly). Process that
a13082c… drh 677 ** text and insert the result in place of the original.
a13082c… drh 678 */
1fc2df9… drh 679 void pikchr_to_html(
155d074… drh 680 Blob *ob, /* Write the generated SVG here */
155d074… drh 681 const char *zSrc, int nSrc, /* The Pikchr source text */
155d074… drh 682 const char *zArg, int nArg /* Addition arguments */
155d074… drh 683 ){
938bb6c… stephan 684 int pikFlags = PIKCHR_PROCESS_NONCE
938bb6c… stephan 685 | PIKCHR_PROCESS_DIV
11e7960… stephan 686 | PIKCHR_PROCESS_SRC
aac2a35… stephan 687 | PIKCHR_PROCESS_ERR_PRE
aac2a35… stephan 688 | pikchrToHtmlFlags;
938bb6c… stephan 689 Blob bSrc = empty_blob;
08fe2bd… drh 690 const char *zPikVar;
08fe2bd… drh 691 double rPikVar;
938bb6c… stephan 692
938bb6c… stephan 693 while( nArg>0 ){
938bb6c… stephan 694 int i;
938bb6c… stephan 695 for(i=0; i<nArg && !fossil_isspace(zArg[i]); i++){}
938bb6c… stephan 696 if( i==6 && strncmp(zArg, "center", 6)==0 ){
938bb6c… stephan 697 pikFlags |= PIKCHR_PROCESS_DIV_CENTER;
938bb6c… stephan 698 }else if( i==6 && strncmp(zArg, "indent", 6)==0 ){
938bb6c… stephan 699 pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
938bb6c… stephan 700 }else if( i==10 && strncmp(zArg, "float-left", 10)==0 ){
938bb6c… stephan 701 pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_LEFT;
938bb6c… stephan 702 }else if( i==11 && strncmp(zArg, "float-right", 11)==0 ){
938bb6c… stephan 703 pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_RIGHT;
938bb6c… stephan 704 }else if( i==6 && strncmp(zArg, "toggle", 6)==0 ){
938bb6c… stephan 705 pikFlags |= PIKCHR_PROCESS_DIV_TOGGLE;
938bb6c… stephan 706 }else if( i==6 && strncmp(zArg, "source", 6)==0 ){
938bb6c… stephan 707 pikFlags |= PIKCHR_PROCESS_DIV_SOURCE;
3f2c0af… stephan 708 }else if( i==13 && strncmp(zArg, "source-inline", 13)==0 ){
3f2c0af… stephan 709 pikFlags |= PIKCHR_PROCESS_DIV_SOURCE_INLINE;
938bb6c… stephan 710 }
938bb6c… stephan 711 while( i<nArg && fossil_isspace(zArg[i]) ){ i++; }
938bb6c… stephan 712 zArg += i;
938bb6c… stephan 713 nArg -= i;
938bb6c… stephan 714 }
71c4db5… drh 715 if( skin_detail_boolean("white-foreground") ){
71c4db5… drh 716 pikFlags |= 0x02; /* PIKCHR_DARK_MODE */
71c4db5… drh 717 }
08fe2bd… drh 718 zPikVar = skin_detail("pikchr-foreground");
08fe2bd… drh 719 if( zPikVar && zPikVar[0] ){
08fe2bd… drh 720 blob_appendf(&bSrc, "fgcolor = %s\n", zPikVar);
08fe2bd… drh 721 }
557f51b… drh 722 zPikVar = skin_detail("pikchr-background");
557f51b… drh 723 if( zPikVar && zPikVar[0] ){
557f51b… drh 724 blob_appendf(&bSrc, "bgcolor = %s\n", zPikVar);
557f51b… drh 725 }
08fe2bd… drh 726 zPikVar = skin_detail("pikchr-scale");
08fe2bd… drh 727 if( zPikVar
a10f931… drh 728 && (rPikVar = atof(zPikVar))>=0.1
08fe2bd… drh 729 && rPikVar<10.0
08fe2bd… drh 730 ){
08fe2bd… drh 731 blob_appendf(&bSrc, "scale = %.13g\n", rPikVar);
08fe2bd… drh 732 }
08fe2bd… drh 733 zPikVar = skin_detail("pikchr-fontscale");
08fe2bd… drh 734 if( zPikVar
a10f931… drh 735 && (rPikVar = atof(zPikVar))>=0.1
08fe2bd… drh 736 && rPikVar<10.0
08fe2bd… drh 737 ){
08fe2bd… drh 738 blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar);
0853ab7… drh 739 }
938bb6c… stephan 740 blob_append(&bSrc, zSrc, nSrc)
938bb6c… stephan 741 /*have to dup input to ensure a NUL-terminated source string */;
9164a5d… drh 742 pikchr_process(blob_str(&bSrc), pikFlags, ob);
938bb6c… stephan 743 blob_reset(&bSrc);
938bb6c… stephan 744 }
81caad6… drh 745
81caad6… drh 746 /* Invoked for `...` blocks where there are nSep grave accents in a
81caad6… drh 747 ** row that serve as the delimiter. According to CommonMark:
81caad6… drh 748 **
81caad6… drh 749 ** * https://spec.commonmark.org/0.29/#fenced-code-blocks
81caad6… drh 750 ** * https://spec.commonmark.org/0.29/#code-spans
81caad6… drh 751 **
81caad6… drh 752 ** If nSep is 1 or 2, then this is a code-span which is inline.
81caad6… drh 753 ** If nSep is 3 or more, then this is a fenced code block
81caad6… drh 754 */
485fda6… drh 755 static int html_codespan(
81caad6… drh 756 struct Blob *ob, /* Write the output here */
2077ffe… drh 757 struct Blob *text, /* The stuff in between the code span marks */
2077ffe… drh 758 int nSep, /* Number of grave accents marks as delimiters */
2077ffe… drh 759 void *opaque
2077ffe… drh 760 ){
81caad6… drh 761 if( text==0 ){
81caad6… drh 762 /* no-op */
81caad6… drh 763 }else if( nSep<=2 ){
81caad6… drh 764 /* One or two graves: an in-line code span */
3990518… george 765 blob_append_literal(ob, "<code>");
04de083… drh 766 html_escape(ob, blob_buffer(text), blob_size(text));
3990518… george 767 blob_append_literal(ob, "</code>");
81caad6… drh 768 }else{
81caad6… drh 769 /* Three or more graves: a fenced code block */
81caad6… drh 770 int n = blob_size(text);
81caad6… drh 771 const char *z = blob_buffer(text);
81caad6… drh 772 int i;
81caad6… drh 773 for(i=0; i<n && z[i]!='\n'; i++){}
81caad6… drh 774 if( i>=n ){
c7600da… drh 775 blob_appendf(ob, "<pre><code>%#h</code></pre>", n, z);
81caad6… drh 776 }else{
81caad6… drh 777 int k, j;
81caad6… drh 778 i++;
81caad6… drh 779 for(k=0; k<i && fossil_isspace(z[k]); k++){}
81caad6… drh 780 if( k==i ){
c7600da… drh 781 blob_appendf(ob, "<pre><code>%#h</code></pre>", n-i, z+i);
81caad6… drh 782 }else{
81caad6… drh 783 for(j=k+1; j<i && !fossil_isspace(z[j]); j++){}
a13082c… drh 784 if( j-k==6 && strncmp(z+k,"pikchr",6)==0 ){
155d074… drh 785 while( j<i && fossil_isspace(z[j]) ){ j++; }
1fc2df9… drh 786 pikchr_to_html(ob, z+i, n-i, z+j, i-j);
a13082c… drh 787 }else{
a13082c… drh 788 blob_appendf(ob, "<pre><code class='language-%#h'>%#h</code></pre>",
a13082c… drh 789 j-k, z+k, n-i, z+i);
a13082c… drh 790 }
81caad6… drh 791 }
81caad6… drh 792 }
04de083… drh 793 }
61079c3… mistachkin 794 return 1;
61079c3… mistachkin 795 }
61079c3… mistachkin 796
61079c3… mistachkin 797 static int html_double_emphasis(
61079c3… mistachkin 798 struct Blob *ob,
61079c3… mistachkin 799 struct Blob *text,
61079c3… mistachkin 800 char c,
61079c3… mistachkin 801 void *opaque
61079c3… mistachkin 802 ){
3990518… george 803 blob_append_literal(ob, "<strong>");
3990518… george 804 blob_appendb(ob, text);
3990518… george 805 blob_append_literal(ob, "</strong>");
61079c3… mistachkin 806 return 1;
61079c3… mistachkin 807 }
61079c3… mistachkin 808
61079c3… mistachkin 809 static int html_emphasis(
61079c3… mistachkin 810 struct Blob *ob,
61079c3… mistachkin 811 struct Blob *text,
61079c3… mistachkin 812 char c,
61079c3… mistachkin 813 void *opaque
61079c3… mistachkin 814 ){
3990518… george 815 blob_append_literal(ob, "<em>");
3990518… george 816 blob_appendb(ob, text);
3990518… george 817 blob_append_literal(ob, "</em>");
61079c3… mistachkin 818 return 1;
61079c3… mistachkin 819 }
61079c3… mistachkin 820
61079c3… mistachkin 821 static int html_image(
61079c3… mistachkin 822 struct Blob *ob,
61079c3… mistachkin 823 struct Blob *link,
61079c3… mistachkin 824 struct Blob *title,
61079c3… mistachkin 825 struct Blob *alt,
61079c3… mistachkin 826 void *opaque
61079c3… mistachkin 827 ){
3990518… george 828 blob_append_literal(ob, "<img src=\"");
7950dc2… drh 829 html_quote(ob, blob_buffer(link), blob_size(link));
3990518… george 830 blob_append_literal(ob, "\" alt=\"");
7950dc2… drh 831 html_quote(ob, blob_buffer(alt), blob_size(alt));
61079c3… mistachkin 832 if( title && blob_size(title)>0 ){
3990518… george 833 blob_append_literal(ob, "\" title=\"");
7950dc2… drh 834 html_quote(ob, blob_buffer(title), blob_size(title));
61079c3… mistachkin 835 }
f5482a0… wyoung 836 blob_append_literal(ob, "\">");
61079c3… mistachkin 837 return 1;
61079c3… mistachkin 838 }
61079c3… mistachkin 839
485fda6… drh 840 static int html_linebreak(struct Blob *ob, void *opaque){
f5482a0… wyoung 841 blob_append_literal(ob, "<br>\n");
61079c3… mistachkin 842 return 1;
61079c3… mistachkin 843 }
61079c3… mistachkin 844
61079c3… mistachkin 845 static int html_link(
61079c3… mistachkin 846 struct Blob *ob,
61079c3… mistachkin 847 struct Blob *link,
61079c3… mistachkin 848 struct Blob *title,
61079c3… mistachkin 849 struct Blob *content,
61079c3… mistachkin 850 void *opaque
61079c3… mistachkin 851 ){
38a4707… drh 852 char *zLink = blob_buffer(link);
774fb77… drh 853 char *zTitle = title!=0 && blob_size(title)>0 ? blob_str(title) : 0;
774fb77… drh 854 char zClose[20];
44545ee… drh 855
44545ee… drh 856 if( zLink==0 || zLink[0]==0 ){
44545ee… drh 857 zClose[0] = 0;
275da70… danield 858 }else{
275da70… danield 859 static const int flags =
3b10e64… drh 860 WIKI_NOBADLINKS |
3b10e64… drh 861 WIKI_MARKDOWNLINKS
3b10e64… drh 862 ;
3b10e64… drh 863 wiki_resolve_hyperlink(ob, flags, zLink, zClose, sizeof(zClose), 0, zTitle);
44545ee… drh 864 }
774fb77… drh 865 if( blob_size(content)==0 ){
3990518… george 866 if( link ) blob_appendb(ob, link);
774fb77… drh 867 }else{
3990518… george 868 blob_appendb(ob, content);
774fb77… drh 869 }
774fb77… drh 870 blob_append(ob, zClose, -1);
61079c3… mistachkin 871 return 1;
61079c3… mistachkin 872 }
61079c3… mistachkin 873
61079c3… mistachkin 874 static int html_triple_emphasis(
61079c3… mistachkin 875 struct Blob *ob,
61079c3… mistachkin 876 struct Blob *text,
61079c3… mistachkin 877 char c,
61079c3… mistachkin 878 void *opaque
61079c3… mistachkin 879 ){
3990518… george 880 blob_append_literal(ob, "<strong><em>");
3990518… george 881 blob_appendb(ob, text);
3990518… george 882 blob_append_literal(ob, "</em></strong>");
61079c3… mistachkin 883 return 1;
61079c3… mistachkin 884 }
61079c3… mistachkin 885
61079c3… mistachkin 886
61079c3… mistachkin 887 static void html_normal_text(struct Blob *ob, struct Blob *text, void *opaque){
61079c3… mistachkin 888 html_escape(ob, blob_buffer(text), blob_size(text));
61079c3… mistachkin 889 }
61079c3… mistachkin 890
d407c38… drh 891 /*
d407c38… drh 892 ** Convert markdown into HTML.
d407c38… drh 893 **
d407c38… drh 894 ** The document title is placed in output_title if not NULL. Or if
d407c38… drh 895 ** output_title is NULL, the document title appears in the body.
d407c38… drh 896 */
61079c3… mistachkin 897 void markdown_to_html(
d407c38… drh 898 struct Blob *input_markdown, /* Markdown content to be rendered */
d407c38… drh 899 struct Blob *output_title, /* Put title here. May be NULL */
d407c38… drh 900 struct Blob *output_body /* Put document body here. */
61079c3… mistachkin 901 ){
61079c3… mistachkin 902 struct mkd_renderer html_renderer = {
e061a67… mistachkin 903 /* prolog and epilog */
e061a67… mistachkin 904 html_prolog,
e061a67… mistachkin 905 html_epilog,
3990518… george 906 html_footnotes,
61079c3… mistachkin 907
61079c3… mistachkin 908 /* block level elements */
61079c3… mistachkin 909 html_blockcode,
61079c3… mistachkin 910 html_blockquote,
485fda6… drh 911 html_blockhtml,
61079c3… mistachkin 912 html_header,
61079c3… mistachkin 913 html_hrule,
61079c3… mistachkin 914 html_list,
61079c3… mistachkin 915 html_list_item,
61079c3… mistachkin 916 html_paragraph,
61079c3… mistachkin 917 html_table,
61079c3… mistachkin 918 html_table_cell,
61079c3… mistachkin 919 html_table_row,
3990518… george 920 html_footnote_item,
61079c3… mistachkin 921
61079c3… mistachkin 922 /* span level elements */
61079c3… mistachkin 923 html_autolink,
485fda6… drh 924 html_codespan,
61079c3… mistachkin 925 html_double_emphasis,
61079c3… mistachkin 926 html_emphasis,
61079c3… mistachkin 927 html_image,
485fda6… drh 928 html_linebreak,
61079c3… mistachkin 929 html_link,
485fda6… drh 930 html_raw_html_tag,
61079c3… mistachkin 931 html_triple_emphasis,
3990518… george 932 html_footnote_ref,
61079c3… mistachkin 933
61079c3… mistachkin 934 /* low level elements */
485fda6… drh 935 0, /* entity */
61079c3… mistachkin 936 html_normal_text,
61079c3… mistachkin 937
61079c3… mistachkin 938 /* misc. parameters */
485fda6… drh 939 "*_", /* emph_chars */
485fda6… drh 940 0 /* opaque */
61079c3… mistachkin 941 };
3990518… george 942 static int invocation = -1; /* no marker for the first document */
3990518… george 943 static const char* zRU = 0; /* REQUEST_URI with escaped quotes */
b61dcef… drh 944 MarkdownToHtml context;
b61dcef… drh 945 memset(&context, 0, sizeof(context));
b61dcef… drh 946 context.output_title = output_title;
3990518… george 947 context.unique = to_base26(invocation++,1);
3990518… george 948 if( !zRU ) zRU = escape_quotes(PD("REQUEST_URI",""));
3990518… george 949 #ifndef FOOTNOTES_WITHOUT_URI
3990518… george 950 blob_set( &context.reqURI, zRU );
3990518… george 951 #endif
b61dcef… drh 952 html_renderer.opaque = &context;
d407c38… drh 953 if( output_title ) blob_reset(output_title);
61079c3… mistachkin 954 blob_reset(output_body);
61079c3… mistachkin 955 markdown(output_body, input_markdown, &html_renderer);
0b24a45… drh 956 }
0b24a45… drh 957
0b24a45… drh 958 /*
0b24a45… drh 959 ** Undo HTML escapes in Blob p. In other words convert:
0b24a45… drh 960 **
0b24a45… drh 961 ** &amp; -> &
0b24a45… drh 962 ** &lt; -> <
0b24a45… drh 963 ** &gt; -> >
0b24a45… drh 964 ** &quot; -> "
0b24a45… drh 965 ** &#NNN; -> ascii character NNN
0b24a45… drh 966 */
0b24a45… drh 967 void markdown_dehtmlize_blob(Blob *p){
0b24a45… drh 968 char *z;
0b24a45… drh 969 unsigned int j, k;
0b24a45… drh 970
0b24a45… drh 971 z = p->aData;
0b24a45… drh 972 for(j=k=0; j<p->nUsed; j++){
0b24a45… drh 973 char c = z[j];
0b24a45… drh 974 if( c=='&' ){
0b24a45… drh 975 if( z[j+1]=='#' && fossil_isdigit(z[j+2]) ){
0b24a45… drh 976 int n = 3;
0b24a45… drh 977 int x = z[j+2] - '0';
0b24a45… drh 978 if( fossil_isdigit(z[j+3]) ){
0b24a45… drh 979 x = x*10 + z[j+3] - '0';
0b24a45… drh 980 n++;
0b24a45… drh 981 if( fossil_isdigit(z[j+4]) ){
0b24a45… drh 982 x = x*10 + z[j+4] - '0';
0b24a45… drh 983 n++;
0b24a45… drh 984 }
0b24a45… drh 985 }
0b24a45… drh 986 if( z[j+n]==';' ){
0b24a45… drh 987 z[k++] = (char)x;
0b24a45… drh 988 j += n;
0b24a45… drh 989 }else{
0b24a45… drh 990 z[k++] = c;
0b24a45… drh 991 }
0b24a45… drh 992 }else if( memcmp(&z[j],"&lt;",4)==0 ){
0b24a45… drh 993 z[k++] = '<';
0b24a45… drh 994 j += 3;
0b24a45… drh 995 }else if( memcmp(&z[j],"&gt;",4)==0 ){
0b24a45… drh 996 z[k++] = '>';
0b24a45… drh 997 j += 3;
0b24a45… drh 998 }else if( memcmp(&z[j],"&quot;",6)==0 ){
0b24a45… drh 999 z[k++] = '"';
0b24a45… drh 1000 j += 5;
0b24a45… drh 1001 }else if( memcmp(&z[j],"&amp;",5)==0 ){
0b24a45… drh 1002 z[k++] = '&';
0b24a45… drh 1003 j += 4;
0b24a45… drh 1004 }else{
0b24a45… drh 1005 z[k++] = c;
0b24a45… drh 1006 }
0b24a45… drh 1007 }else{
0b24a45… drh 1008 z[k++] = c;
0b24a45… drh 1009 }
0b24a45… drh 1010 }
0b24a45… drh 1011 z[k] = 0;
0b24a45… drh 1012 p->nUsed = k;
4dcea80… drh 1013 }

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button