Fossil SCM

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

Keyboard Shortcuts

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