Fossil SCM

fossil-scm / src / markdown.c
Blame History Raw 2906 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 code to parse a blob containing markdown text,
19
** using an external renderer.
20
*/
21
22
#include "config.h"
23
#include "markdown.h"
24
25
#include <assert.h>
26
#include <string.h>
27
#include <stdlib.h>
28
29
#define MKD_LI_END 8 /* internal list flag */
30
31
/********************
32
* TYPE DEFINITIONS *
33
********************/
34
35
#if INTERFACE
36
37
/* mkd_autolink -- type of autolink */
38
enum mkd_autolink {
39
MKDA_NOT_AUTOLINK, /* used internally when it is not an autolink*/
40
MKDA_NORMAL, /* normal http/http/ftp link */
41
MKDA_EXPLICIT_EMAIL, /* e-mail link with explicit mailto: */
42
MKDA_IMPLICIT_EMAIL /* e-mail link without mailto: */
43
};
44
45
/* mkd_renderer -- functions for rendering parsed data */
46
struct mkd_renderer {
47
/* document level callbacks */
48
void (*prolog)(struct Blob *ob, void *opaque);
49
void (*epilog)(struct Blob *ob, void *opaque);
50
void (*footnotes)(struct Blob *ob, const struct Blob *items, void *opaque);
51
52
/* block level callbacks - NULL skips the block */
53
void (*blockcode)(struct Blob *ob, struct Blob *text, void *opaque);
54
void (*blockquote)(struct Blob *ob, struct Blob *text, void *opaque);
55
void (*blockhtml)(struct Blob *ob, struct Blob *text, void *opaque);
56
void (*header)(struct Blob *ob, struct Blob *text,
57
int level, void *opaque);
58
void (*hrule)(struct Blob *ob, void *opaque);
59
void (*list)(struct Blob *ob, struct Blob *text, int flags, void *opaque);
60
void (*listitem)(struct Blob *ob, struct Blob *text,
61
int flags, void *opaque);
62
void (*paragraph)(struct Blob *ob, struct Blob *text, void *opaque);
63
void (*table)(struct Blob *ob, struct Blob *head_row, struct Blob *rows,
64
void *opaque);
65
void (*table_cell)(struct Blob *ob, struct Blob *text, int flags,
66
void *opaque);
67
void (*table_row)(struct Blob *ob, struct Blob *cells, int flags,
68
void *opaque);
69
void (*footnote_item)(struct Blob *ob, const struct Blob *text,
70
int index, int nUsed, void *opaque);
71
72
/* span level callbacks - NULL or return 0 prints the span verbatim */
73
int (*autolink)(struct Blob *ob, struct Blob *link,
74
enum mkd_autolink type, void *opaque);
75
int (*codespan)(struct Blob *ob, struct Blob *text, int nSep, void *opaque);
76
int (*double_emphasis)(struct Blob *ob, struct Blob *text,
77
char c, void *opaque);
78
int (*emphasis)(struct Blob *ob, struct Blob *text, char c,void*opaque);
79
int (*image)(struct Blob *ob, struct Blob *link, struct Blob *title,
80
struct Blob *alt, void *opaque);
81
int (*linebreak)(struct Blob *ob, void *opaque);
82
int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
83
struct Blob *content, void *opaque);
84
int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
85
int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
86
char c, void *opaque);
87
int (*footnote_ref)(struct Blob *ob, const struct Blob *span,
88
const struct Blob *upc, int index, int locus, void *opaque);
89
90
/* low level callbacks - NULL copies input directly into the output */
91
void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
92
void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);
93
94
/* renderer data */
95
const char *emph_chars; /* chars that trigger emphasis rendering */
96
void *opaque; /* opaque data send to every rendering callback */
97
};
98
99
100
101
/*********
102
* FLAGS *
103
*********/
104
105
/* list/listitem flags */
106
#define MKD_LIST_ORDERED 1
107
#define MKD_LI_BLOCK 2 /* <li> containing block data */
108
109
/* table cell flags */
110
#define MKD_CELL_ALIGN_DEFAULT 0
111
#define MKD_CELL_ALIGN_LEFT 1
112
#define MKD_CELL_ALIGN_RIGHT 2
113
#define MKD_CELL_ALIGN_CENTER 3 /* LEFT | RIGHT */
114
#define MKD_CELL_ALIGN_MASK 3
115
#define MKD_CELL_HEAD 4
116
117
118
#endif /* INTERFACE */
119
120
#define BLOB_COUNT(pBlob,el_type) (blob_size(pBlob)/sizeof(el_type))
121
#define COUNT_FOOTNOTES(pBlob) BLOB_COUNT(pBlob,struct footnote)
122
#define CAST_AS_FOOTNOTES(pBlob) ((struct footnote*)blob_buffer(pBlob))
123
124
/***************
125
* LOCAL TYPES *
126
***************/
127
128
/*
129
** link_ref -- reference to a link.
130
*/
131
struct link_ref {
132
struct Blob id; /* must be the first field as in footnote struct */
133
struct Blob link;
134
struct Blob title;
135
};
136
137
/*
138
** A footnote's data.
139
** id, text, and upc fields must be in that particular order.
140
*/
141
struct footnote {
142
struct Blob id; /* must be the first field as in link_ref struct */
143
struct Blob text; /* footnote's content that is rendered at the end */
144
struct Blob upc; /* user-provided classes .ASCII-alnum.or-hypen: */
145
int bRndred; /* indicates if `text` holds a rendered content */
146
147
int defno; /* serial number of definition, set during the first pass */
148
int index; /* set to the index within array after ordering by id */
149
int iMark; /* user-visible numeric marker, assigned upon the first use*/
150
int nUsed; /* counts references to this note, increments upon each use*/
151
};
152
#define FOOTNOTE_INITIALIZER {empty_blob,empty_blob,empty_blob, 0,0,0,0,0}
153
154
/* char_trigger -- function pointer to render active chars */
155
/* returns the number of chars taken care of */
156
/* data is the pointer of the beginning of the span */
157
/* offset is the number of valid chars before data */
158
struct render;
159
typedef size_t (*char_trigger)(
160
struct Blob *ob,
161
struct render *rndr,
162
char *data,
163
size_t offset,
164
size_t size);
165
166
167
/* render -- structure containing one particular render */
168
struct render {
169
struct mkd_renderer make;
170
struct Blob refs;
171
char_trigger active_char[256];
172
int iDepth; /* Depth of recursion */
173
int nBlobCache; /* Number of entries in aBlobCache */
174
struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
175
176
struct {
177
Blob all; /* Buffer that holds array of footnotes. Its underline
178
memory may be reallocated when a new footnote is added. */
179
int nLbled; /* number of labeled footnotes found during the first pass */
180
int nMarks; /* counts distinct indices found during the second pass */
181
struct footnote misref; /* nUsed counts misreferences, iMark must be -1 */
182
} notes;
183
};
184
185
/* html_tag -- structure for quick HTML tag search (inspired from discount) */
186
struct html_tag {
187
const char *text;
188
int size;
189
};
190
191
192
193
/********************
194
* GLOBAL VARIABLES *
195
********************/
196
197
/* block_tags -- recognised block tags, sorted by cmp_html_tag.
198
**
199
** When these HTML tags are separated from other text by newlines
200
** then they are rendered verbatim. Their content is not interpreted
201
** in any way.
202
*/
203
static const struct html_tag block_tags[] = {
204
{ "html", 4 },
205
{ "pre", 3 },
206
{ "script", 6 },
207
};
208
209
/***************************
210
* STATIC HELPER FUNCTIONS *
211
***************************/
212
213
/*
214
** build_ref_id -- collapse whitespace from input text to make it a ref id.
215
** Potential TODO: maybe also handle CR+LF line endings?
216
*/
217
static int build_ref_id(struct Blob *id, const char *data, size_t size){
218
size_t beg, i;
219
char *id_data;
220
221
/* skip leading whitespace */
222
while( size>0 && (data[0]==' ' || data[0]=='\t' || data[0]=='\n') ){
223
data++;
224
size--;
225
}
226
227
/* skip trailing whitespace */
228
while( size>0 && (data[size-1]==' '
229
|| data[size-1]=='\t'
230
|| data[size-1]=='\n')
231
){
232
size--;
233
}
234
if( size==0 ) return -1;
235
236
/* making the ref id */
237
i = 0;
238
blob_reset(id);
239
while( i<size ){
240
/* copy non-whitespace into the output buffer */
241
beg = i;
242
while( i<size && !(data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){
243
i++;
244
}
245
blob_append(id, data+beg, i-beg);
246
247
/* add a single space and skip all consecutive whitespace */
248
if( i<size ) blob_append_char(id, ' ');
249
while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){ i++; }
250
}
251
252
/* turn upper-case ASCII into their lower-case counterparts */
253
id_data = blob_buffer(id);
254
for(i=0; i<blob_size(id); i++){
255
if( id_data[i]>='A' && id_data[i]<='Z' ) id_data[i] += 'a' - 'A';
256
}
257
return 0;
258
}
259
260
261
/* cmp_link_ref -- comparison function for link_ref sorted arrays */
262
static int cmp_link_ref(const void *key, const void *array_entry){
263
struct link_ref *lr = (void *)array_entry;
264
return blob_compare((void *)key, &lr->id);
265
}
266
267
268
/* cmp_link_ref_sort -- comparison function for link_ref qsort */
269
static int cmp_link_ref_sort(const void *a, const void *b){
270
struct link_ref *lra = (void *)a;
271
struct link_ref *lrb = (void *)b;
272
return blob_compare(&lra->id, &lrb->id);
273
}
274
275
/*
276
** cmp_footnote_id -- comparison function for footnotes qsort.
277
** Empty IDs sort last (in undetermined order).
278
** Equal IDs are sorted in the order of definition in the source.
279
*/
280
static int cmp_footnote_id(const void *fna, const void *fnb){
281
const struct footnote *a = fna, *b = fnb;
282
const int szA = blob_size(&a->id), szB = blob_size(&b->id);
283
if( szA ){
284
if( szB ){
285
int cmp = blob_compare(&a->id, &b->id);
286
if( cmp ) return cmp;
287
}else return -1;
288
}else return szB ? 1 : 0;
289
/* IDs are equal and non-empty */
290
if( a->defno < b->defno ) return -1;
291
if( a->defno > b->defno ) return 1;
292
assert(!"reachable");
293
return 0; /* should never reach here */
294
}
295
296
/*
297
** cmp_footnote_sort -- comparison function for footnotes qsort.
298
** Unreferenced footnotes (when nUsed == 0) sort last and
299
** are sorted in the order of definition in the source.
300
*/
301
static int cmp_footnote_sort(const void *fna, const void *fnb){
302
const struct footnote *a = fna, *b = fnb;
303
int i, j;
304
assert( a->nUsed >= 0 );
305
assert( b->nUsed >= 0 );
306
assert( a->defno >= 0 );
307
assert( b->defno >= 0 );
308
if( a->nUsed ){
309
assert( a->iMark > 0 );
310
if( !b->nUsed ) return -1;
311
assert( b->iMark > 0 );
312
i = a->iMark;
313
j = b->iMark;
314
}else{
315
if( b->nUsed ) return 1;
316
i = a->defno;
317
j = b->defno;
318
}
319
if( i < j ) return -1;
320
if( i > j ) return 1;
321
return 0;
322
}
323
324
/* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */
325
static int cmp_html_tag(const void *a, const void *b){
326
const struct html_tag *hta = a;
327
const struct html_tag *htb = b;
328
int sz = hta->size;
329
int c;
330
if( htb->size<sz ) sz = htb->size;
331
c = fossil_strnicmp(hta->text, htb->text, sz);
332
if( c==0 ) c = hta->size - htb->size;
333
return c;
334
}
335
336
337
/* find_block_tag -- returns the current block tag */
338
static const struct html_tag *find_block_tag(const char *data, size_t size){
339
size_t i = 0;
340
struct html_tag key;
341
342
/* looking for the word end */
343
while( i<size
344
&& ((data[i]>='0' && data[i]<='9')
345
|| (data[i]>='A' && data[i]<='Z')
346
|| (data[i]>='a' && data[i]<='z'))
347
){
348
i++;
349
}
350
if( i>=size ) return 0;
351
352
/* binary search of the tag */
353
key.text = data;
354
key.size = i;
355
return bsearch(&key,
356
block_tags,
357
count(block_tags),
358
sizeof block_tags[0],
359
cmp_html_tag);
360
}
361
362
/* return true if recursion has gone too deep */
363
static int too_deep(struct render *rndr){
364
return rndr->iDepth>200;
365
}
366
367
/* get a new working buffer from the cache or create one. return NULL
368
** if failIfDeep is true and the depth of recursion has gone too deep. */
369
static struct Blob *new_work_buffer(struct render *rndr){
370
struct Blob *ret;
371
rndr->iDepth++;
372
if( rndr->nBlobCache ){
373
ret = rndr->aBlobCache[--rndr->nBlobCache];
374
}else{
375
ret = fossil_malloc(sizeof(*ret));
376
}
377
*ret = empty_blob;
378
return ret;
379
}
380
381
382
/* release the given working buffer back to the cache */
383
static void release_work_buffer(struct render *rndr, struct Blob *buf){
384
if( !buf ) return;
385
rndr->iDepth--;
386
blob_reset(buf);
387
if( rndr->nBlobCache <
388
(int)(sizeof(rndr->aBlobCache)/sizeof(rndr->aBlobCache[0])) ){
389
rndr->aBlobCache[rndr->nBlobCache++] = buf;
390
}else{
391
fossil_free(buf);
392
}
393
}
394
395
396
397
/****************************
398
* INLINE PARSING FUNCTIONS *
399
****************************/
400
401
/* is_mail_autolink -- looks for the address part of a mail autolink and '>' */
402
/* this is less strict than the original markdown e-mail address matching */
403
static size_t is_mail_autolink(char *data, size_t size){
404
size_t i = 0, nb = 0;
405
/* address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@' */
406
while( i<size && (data[i]=='-'
407
|| data[i]=='.'
408
|| data[i]=='_'
409
|| data[i]=='@'
410
|| (data[i]>='a' && data[i]<='z')
411
|| (data[i]>='A' && data[i]<='Z')
412
|| (data[i]>='0' && data[i]<='9'))
413
){
414
if( data[i]=='@' ) nb++;
415
i++;
416
}
417
if( i>=size || data[i]!='>' || nb!=1 ) return 0;
418
return i+1;
419
}
420
421
422
/* tag_length -- returns the length of the given tag, or 0 if it's not valid */
423
static size_t tag_length(char *data, size_t size, enum mkd_autolink *autolink){
424
size_t i, j;
425
426
/* a valid tag can't be shorter than 3 chars */
427
if( size<3 ) return 0;
428
429
/* begins with a '<' optionally followed by '/', followed by letter */
430
if( data[0]!='<' ) return 0;
431
i = (data[1]=='/') ? 2 : 1;
432
if( (data[i]<'a' || data[i]>'z') && (data[i]<'A' || data[i]>'Z') ){
433
if( data[1]=='!' && size>=7 && data[2]=='-' && data[3]=='-' ){
434
for(i=6; i<size && (data[i]!='>'||data[i-1]!='-'|| data[i-2]!='-');i++){}
435
if( i<size ) return i;
436
}
437
return 0;
438
}
439
440
/* scheme test */
441
*autolink = MKDA_NOT_AUTOLINK;
442
if( size>6
443
&& fossil_strnicmp(data+1, "http", 4)==0
444
&& (data[5]==':'
445
|| ((data[5]=='s' || data[5]=='S') && data[6]==':'))
446
){
447
i = (data[5]==':') ? 6 : 7;
448
*autolink = MKDA_NORMAL;
449
}else if( size>5 && fossil_strnicmp(data+1, "ftp:", 4)==0 ){
450
i = 5;
451
*autolink = MKDA_NORMAL;
452
}else if( size>7 && fossil_strnicmp(data+1, "mailto:", 7)==0 ){
453
i = 8;
454
/* not changing *autolink to go to the address test */
455
}
456
457
/* completing autolink test: no whitespace or ' or " */
458
if( i>=size || i=='>' ){
459
*autolink = MKDA_NOT_AUTOLINK;
460
}else if( *autolink ){
461
j = i;
462
while( i<size
463
&& data[i]!='>'
464
&& data[i]!='\''
465
&& data[i]!='"'
466
&& data[i]!=' '
467
&& data[i]!='\t'
468
&& data[i]!='\n'
469
){
470
i++;
471
}
472
if( i>=size ) return 0;
473
if( i>j && data[i]=='>' ) return i+1;
474
/* one of the forbidden chars has been found */
475
*autolink = MKDA_NOT_AUTOLINK;
476
}else if( (j = is_mail_autolink(data+i, size-i))!=0 ){
477
*autolink = (i==8) ? MKDA_EXPLICIT_EMAIL : MKDA_IMPLICIT_EMAIL;
478
return i+j;
479
}
480
481
/* looking for something looking like a tag end */
482
while( i<size && data[i]!='>' ){ i++; }
483
if( i>=size ) return 0;
484
return i+1;
485
}
486
487
488
/* parse_inline -- parses inline markdown elements */
489
static void parse_inline(
490
struct Blob *ob,
491
struct render *rndr,
492
char *data,
493
size_t size
494
){
495
size_t i = 0, end = 0;
496
char_trigger action = 0;
497
struct Blob work = BLOB_INITIALIZER;
498
499
if( too_deep(rndr) ){
500
blob_append(ob, data, size);
501
return;
502
}
503
while( i<size ){
504
/* copying inactive chars into the output */
505
while( end<size
506
&& (action = rndr->active_char[(unsigned char)data[end]])==0
507
){
508
end++;
509
}
510
if( end>i ){
511
if( rndr->make.normal_text ){
512
blob_init(&work, data+i, end-i);
513
rndr->make.normal_text(ob, &work, rndr->make.opaque);
514
}else{
515
blob_append(ob, data+i, end-i);
516
}
517
}
518
if( end>=size ) break;
519
i = end;
520
521
/* calling the trigger */
522
end = action(ob, rndr, data+i, i, size-i);
523
if( !end ){
524
/* no action from the callback */
525
end = i+1;
526
}else{
527
i += end;
528
end = i;
529
}
530
}
531
}
532
533
/*
534
** data[*pI] should be a "`" character that introduces a code-span.
535
** The code-span boundary mark can be any number of one or more "`"
536
** characters. We do not know the size of the boundary marker, only
537
** that there is at least one "`" at data[*pI].
538
**
539
** This routine increases *pI to move it past the code-span, including
540
** the closing boundary mark. Or, if the code-span is unterminated,
541
** this routine moves *pI past the opening boundary mark only.
542
*/
543
static void skip_codespan(const char *data, size_t size, size_t *pI){
544
size_t i = *pI;
545
size_t span_nb; /* Number of "`" characters in the boundary mark */
546
size_t bt;
547
548
assert( i<size );
549
assert( data[i]=='`' );
550
data += i;
551
size -= i;
552
553
/* counting the number of opening backticks */
554
i = 0;
555
span_nb = 0;
556
while( i<size && data[i]=='`' ){
557
i++;
558
span_nb++;
559
}
560
if( i>=size ){
561
*pI += span_nb;
562
return;
563
}
564
565
/* finding the matching closing sequence */
566
bt = 0;
567
while( i<size && bt<span_nb ){
568
if( data[i]=='`' ) bt += 1; else bt = 0;
569
i++;
570
}
571
*pI += (bt == span_nb) ? i : span_nb;
572
}
573
574
575
/* find_emph_char -- looks for the next emph char, skipping other constructs */
576
static size_t find_emph_char(char *data, size_t size, char c){
577
size_t i = data[0]!='`';
578
579
while( i<size ){
580
while( i<size && data[i]!=c && data[i]!='`' && data[i]!='[' ){ i++; }
581
if( i>=size ) return 0;
582
583
/* not counting escaped chars */
584
if( i && data[i-1]=='\\' ){
585
i++;
586
continue;
587
}
588
589
if( data[i]==c ) return i;
590
591
if( data[i]=='`' ){ /* skip a code span */
592
skip_codespan(data, size, &i);
593
}else if( data[i]=='[' ){ /* skip a link */
594
size_t tmp_i = 0;
595
char cc;
596
i++;
597
while( i<size && data[i]!=']' ){
598
if( !tmp_i && data[i]==c ) tmp_i = i;
599
i++;
600
}
601
i++;
602
while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){
603
i++;
604
}
605
if( i>=size ) return tmp_i;
606
if( data[i]!='[' && data[i]!='(' ){ /* not a link*/
607
if( tmp_i ) return tmp_i; else continue;
608
}
609
cc = data[i];
610
i++;
611
while( i<size && data[i]!=cc ){
612
if( !tmp_i && data[i]==c ) tmp_i = i;
613
i++;
614
}
615
if( i>=size ) return tmp_i;
616
i++;
617
}
618
}
619
return 0;
620
}
621
622
/* CommonMark defines separate "right-flanking" and "left-flanking"
623
** deliminators for emphasis. Whether a deliminator is left- or
624
** right-flanking, or both, or neither depends on the characters
625
** immediately before and after.
626
**
627
** before after example left-flanking right-flanking
628
** ------ ----- ------- ------------- --------------
629
** space space * no no
630
** space punct *) yes no
631
** space alnum *x yes no
632
** punct space (* no yes
633
** punct punct (*) yes yes
634
** punct alnum (*x yes no
635
** alnum space a* no yes
636
** alnum punct a*( no yes
637
** alnum alnum a*x yes yes
638
**
639
** The following routines determine whether a delimiter is left
640
** or right flanking.
641
*/
642
static int left_flanking(char before, char after){
643
if( fossil_isspace(after) ) return 0;
644
if( fossil_isalnum(after) ) return 1;
645
if( fossil_isalnum(before) ) return 0;
646
return 1;
647
}
648
static int right_flanking(char before, char after){
649
if( fossil_isspace(before) ) return 0;
650
if( fossil_isalnum(before) ) return 1;
651
if( fossil_isalnum(after) ) return 0;
652
return 1;
653
}
654
655
656
/*
657
** parse_emph1 -- parsing single emphasis.
658
** closed by a symbol not preceded by whitespace and not followed by symbol.
659
*/
660
static size_t parse_emph1(
661
struct Blob *ob,
662
struct render *rndr,
663
char *data,
664
size_t size,
665
char c
666
){
667
size_t i = 0, len;
668
struct Blob *work = 0;
669
int r;
670
char after;
671
672
if( !rndr->make.emphasis ) return 0;
673
674
/* skipping one symbol if coming from emph3 */
675
if( size>1 && data[0]==c && data[1]==c ) i = 1;
676
677
while( i<size ){
678
len = find_emph_char(data+i, size-i, c);
679
if( !len ) return 0;
680
i += len;
681
if( i>=size ) return 0;
682
683
if( i+1<size && data[i+1]==c ){
684
i++;
685
continue;
686
}
687
after = i+1<size ? data[i+1] : ' ';
688
if( data[i]==c
689
&& right_flanking(data[i-1],after)
690
&& (c!='_' || !fossil_isalnum(after))
691
&& !too_deep(rndr)
692
){
693
work = new_work_buffer(rndr);
694
parse_inline(work, rndr, data, i);
695
r = rndr->make.emphasis(ob, work, c, rndr->make.opaque);
696
release_work_buffer(rndr, work);
697
return r ? i+1 : 0;
698
}
699
}
700
return 0;
701
}
702
703
/*
704
** parse_emph2 -- parsing single emphasis.
705
*/
706
static size_t parse_emph2(
707
struct Blob *ob,
708
struct render *rndr,
709
char *data,
710
size_t size,
711
char c
712
){
713
size_t i = 0, len;
714
struct Blob *work = 0;
715
int r;
716
char after;
717
718
if( !rndr->make.double_emphasis ) return 0;
719
720
while( i<size ){
721
len = find_emph_char(data+i, size-i, c);
722
if( !len ) return 0;
723
i += len;
724
after = i+2<size ? data[i+2] : ' ';
725
if( i+1<size
726
&& data[i]==c
727
&& data[i+1]==c
728
&& right_flanking(data[i-1],after)
729
&& (c!='_' || !fossil_isalnum(after))
730
&& !too_deep(rndr)
731
){
732
work = new_work_buffer(rndr);
733
parse_inline(work, rndr, data, i);
734
r = rndr->make.double_emphasis(ob, work, c, rndr->make.opaque);
735
release_work_buffer(rndr, work);
736
return r ? i+2 : 0;
737
}
738
i++;
739
}
740
return 0;
741
}
742
743
/*
744
** parse_emph3 -- parsing single emphasis.
745
** finds the first closing tag, and delegates to the other emph.
746
*/
747
static size_t parse_emph3(
748
struct Blob *ob,
749
struct render *rndr,
750
char *data,
751
size_t size,
752
char c
753
){
754
size_t i = 0, len;
755
int r;
756
757
while( i<size ){
758
len = find_emph_char(data+i, size-i, c);
759
if( !len ) return 0;
760
i += len;
761
762
/* skip whitespace preceded symbols */
763
if( data[i]!=c || data[i-1]==' ' || data[i-1]=='\t' || data[i-1]=='\n' ){
764
continue;
765
}
766
767
if( i+2<size
768
&& data[i+1]==c
769
&& data[i+2] == c
770
&& rndr->make.triple_emphasis
771
&& !too_deep(rndr)
772
){
773
/* triple symbol found */
774
struct Blob *work = new_work_buffer(rndr);
775
parse_inline(work, rndr, data, i);
776
r = rndr->make.triple_emphasis(ob, work, c, rndr->make.opaque);
777
release_work_buffer(rndr, work);
778
return r ? i+3 : 0;
779
}else if( i+1<size && data[i+1]==c ){
780
/* double symbol found, handing over to emph1 */
781
len = parse_emph1(ob, rndr, data-2, size+2, c);
782
return len ? len-2 : 0;
783
}else{
784
/* single symbol found, handing over to emph2 */
785
len = parse_emph2(ob, rndr, data-1, size+1, c);
786
return len ? len-1 : 0;
787
}
788
}
789
return 0;
790
}
791
792
/*
793
** char_emphasis -- single and double emphasis parsing.
794
*/
795
static size_t char_emphasis(
796
struct Blob *ob,
797
struct render *rndr,
798
char *data,
799
size_t offset,
800
size_t size
801
){
802
char c = data[0];
803
char before = offset>0 ? data[-1] : ' ';
804
size_t ret;
805
806
if( size>2 && data[1]!=c ){
807
if( !left_flanking(before, data[1])
808
|| (c=='_' && fossil_isalnum(before))
809
|| (ret = parse_emph1(ob, rndr, data+1, size-1, c))==0
810
){
811
return 0;
812
}
813
return ret+1;
814
}
815
816
if( size>3 && data[1]==c && data[2]!=c ){
817
if( !left_flanking(before, data[2])
818
|| (c=='_' && fossil_isalnum(before))
819
|| (ret = parse_emph2(ob, rndr, data+2, size-2, c))==0
820
){
821
return 0;
822
}
823
return ret+2;
824
}
825
826
if( size>4 && data[1]==c && data[2]==c && data[3]!=c ){
827
if( !left_flanking(before, data[3])
828
|| (c=='_' && fossil_isalnum(before))
829
|| (ret = parse_emph3(ob, rndr, data+3, size-3, c))==0
830
){
831
return 0;
832
}
833
return ret+3;
834
}
835
return 0;
836
}
837
838
/*
839
** char_linebreak -- '\n' preceded by two spaces (assuming linebreak != 0).
840
*/
841
static size_t char_linebreak(
842
struct Blob *ob,
843
struct render *rndr,
844
char *data,
845
size_t offset,
846
size_t size
847
){
848
if( offset<2 || data[-1]!=' ' || data[-2]!=' ' ) return 0;
849
/* removing the last space from ob and rendering */
850
if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]==' ' ) ob->nUsed--;
851
return rndr->make.linebreak(ob, rndr->make.opaque) ? 1 : 0;
852
}
853
854
/*
855
** char_codespan -- '`' parsing a code span (assuming codespan != 0).
856
*/
857
static size_t char_codespan(
858
struct Blob *ob,
859
struct render *rndr,
860
char *data,
861
size_t offset,
862
size_t size
863
){
864
size_t end, nb = 0, i, f_begin, f_end;
865
char delim = data[0];
866
867
/* counting the number of backticks in the delimiter */
868
while( nb<size && data[nb]==delim ){ nb++; }
869
870
/* finding the next delimiter */
871
i = 0;
872
for(end=nb; end<size && i<nb; end++){
873
if( data[end]==delim ) i++; else i = 0;
874
}
875
if( i<nb && end>=size ) return 0; /* no matching delimiter */
876
877
/* trimming outside whitespaces */
878
f_begin = nb;
879
while( f_begin<end && (data[f_begin]==' ' || data[f_begin]=='\t') ){
880
f_begin++;
881
}
882
f_end = end-nb;
883
while( f_end>nb && (data[f_end-1]==' ' || data[f_end-1]=='\t') ){ f_end--; }
884
885
/* real code span */
886
if( f_begin<f_end ){
887
struct Blob work = BLOB_INITIALIZER;
888
blob_init(&work, data+f_begin, f_end-f_begin);
889
if( !rndr->make.codespan(ob, &work, nb, rndr->make.opaque) ) end = 0;
890
}else{
891
if( !rndr->make.codespan(ob, 0, nb, rndr->make.opaque) ) end = 0;
892
}
893
return end;
894
}
895
896
897
/*
898
** char_escape -- '\\' backslash escape.
899
*/
900
static size_t char_escape(
901
struct Blob *ob,
902
struct render *rndr,
903
char *data,
904
size_t offset,
905
size_t size
906
){
907
struct Blob work = BLOB_INITIALIZER;
908
if( size>1 ){
909
if( rndr->make.normal_text ){
910
blob_init(&work, data+1,1);
911
rndr->make.normal_text(ob, &work, rndr->make.opaque);
912
}else{
913
blob_append(ob, data+1, 1);
914
}
915
}
916
return 2;
917
}
918
919
/*
920
** char_entity -- '&' escaped when it doesn't belong to an entity.
921
** valid entities are assumed to be anything matching &#?[A-Za-z0-9]+;
922
*/
923
static size_t char_entity(
924
struct Blob *ob,
925
struct render *rndr,
926
char *data,
927
size_t offset,
928
size_t size
929
){
930
size_t end = 1;
931
struct Blob work = BLOB_INITIALIZER;
932
if( end<size && data[end]=='#' ) end++;
933
while( end<size
934
&& ((data[end]>='0' && data[end]<='9')
935
|| (data[end]>='a' && data[end]<='z')
936
|| (data[end]>='A' && data[end]<='Z'))
937
){
938
end++;
939
}
940
if( end<size && data[end]==';' ){
941
/* real entity */
942
end++;
943
}else{
944
/* lone '&' */
945
return 0;
946
}
947
if( rndr->make.entity ){
948
blob_init(&work, data, end);
949
rndr->make.entity(ob, &work, rndr->make.opaque);
950
}else{
951
blob_append(ob, data, end);
952
}
953
return end;
954
}
955
956
/*
957
** char_langle_tag -- '<' when tags or autolinks are allowed.
958
*/
959
static size_t char_langle_tag(
960
struct Blob *ob,
961
struct render *rndr,
962
char *data,
963
size_t offset,
964
size_t size
965
){
966
enum mkd_autolink altype = MKDA_NOT_AUTOLINK;
967
size_t end = tag_length(data, size, &altype);
968
struct Blob work = BLOB_INITIALIZER;
969
int ret = 0;
970
if( end ){
971
if( rndr->make.autolink && altype!=MKDA_NOT_AUTOLINK ){
972
blob_init(&work, data+1, end-2);
973
ret = rndr->make.autolink(ob, &work, altype, rndr->make.opaque);
974
}else if( rndr->make.raw_html_tag ){
975
blob_init(&work, data, end);
976
ret = rndr->make.raw_html_tag(ob, &work, rndr->make.opaque);
977
}
978
}
979
980
if( !ret ){
981
return 0;
982
}else{
983
return end;
984
}
985
}
986
987
/*
988
** get_link_inline -- extract inline-style link and title from
989
** parenthesed data
990
*/
991
static int get_link_inline(
992
struct Blob *link,
993
struct Blob *title,
994
char *data,
995
size_t size
996
){
997
size_t i = 0, mark;
998
size_t link_b, link_e;
999
size_t title_b = 0, title_e = 0;
1000
1001
/* skipping initial whitespace */
1002
while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){ i++; }
1003
link_b = i;
1004
1005
/* looking for link end: ' " */
1006
while( i<size && data[i]!='\'' && data[i]!='"' ){ i++; }
1007
link_e = i;
1008
1009
/* looking for title end if present */
1010
if( data[i]=='\'' || data[i]=='"' ){
1011
i++;
1012
title_b = i;
1013
1014
/* skipping whitespaces after title */
1015
title_e = size-1;
1016
while( title_e>title_b
1017
&& (data[title_e]==' '
1018
|| data[title_e]=='\t'
1019
|| data[title_e]=='\n')
1020
){
1021
title_e--;
1022
}
1023
1024
/* checking for closing quote presence */
1025
if (data[title_e] != '\'' && data[title_e] != '"') {
1026
title_b = title_e = 0;
1027
link_e = i;
1028
}
1029
}
1030
1031
/* remove whitespace at the end of the link */
1032
while( link_e>link_b
1033
&& (data[link_e-1]==' '
1034
|| data[link_e-1]=='\t'
1035
|| data[link_e-1]=='\n')
1036
){
1037
link_e--;
1038
}
1039
1040
/* remove optional angle brackets around the link */
1041
if( data[link_b]=='<' ) link_b += 1;
1042
if( link_e && data[link_e-1]=='>' ) link_e -= 1;
1043
1044
/* escape backslashed character from link */
1045
blob_reset(link);
1046
i = link_b;
1047
while( i<link_e ){
1048
mark = i;
1049
while( i<link_e && data[i]!='\\' ){ i++; }
1050
blob_append(link, data+mark, i-mark);
1051
while( i<link_e && data[i]=='\\' ){ i++; }
1052
}
1053
1054
/* handing back title */
1055
blob_reset(title);
1056
if( title_e>title_b ) blob_append(title, data+title_b, title_e-title_b);
1057
1058
/* this function always succeed */
1059
return 0;
1060
}
1061
1062
1063
/*
1064
** get_link_ref -- extract referenced link and title from id.
1065
*/
1066
static int get_link_ref(
1067
struct render *rndr,
1068
struct Blob *link,
1069
struct Blob *title,
1070
char *data,
1071
size_t size
1072
){
1073
struct link_ref *lr;
1074
const size_t sz = blob_size(&rndr->refs);
1075
1076
/* find the link from its id (stored temporarily in link) */
1077
blob_reset(link);
1078
if( !sz || build_ref_id(link, data, size)<0 ) return -1;
1079
lr = bsearch(link,
1080
blob_buffer(&rndr->refs),
1081
sz/sizeof(struct link_ref),
1082
sizeof (struct link_ref),
1083
cmp_link_ref);
1084
if( !lr ) return -1;
1085
1086
/* fill the output buffers */
1087
blob_reset(link);
1088
blob_reset(title);
1089
blob_appendb(link, &lr->link);
1090
blob_appendb(title, &lr->title);
1091
return 0;
1092
}
1093
1094
/*
1095
** get_footnote() -- find a footnote by label, invoked during the 2nd pass.
1096
** If found then return a shallow copy of the corresponding footnote;
1097
** otherwise return a shallow copy of rndr->notes.misref.
1098
** In both cases corresponding `nUsed` field is incremented before return.
1099
*/
1100
static struct footnote get_footnote(
1101
struct render *rndr,
1102
const char *data,
1103
size_t size
1104
){
1105
struct footnote *fn = 0;
1106
struct Blob *id;
1107
if( !rndr->notes.nLbled ) goto fallback;
1108
id = new_work_buffer(rndr);
1109
if( build_ref_id(id, data, size)<0 ) goto cleanup;
1110
fn = bsearch(id, blob_buffer(&rndr->notes.all),
1111
rndr->notes.nLbled,
1112
sizeof (struct footnote),
1113
cmp_link_ref);
1114
if( !fn ) goto cleanup;
1115
1116
if( fn->nUsed == 0 ){ /* the first reference to the footnote */
1117
assert( fn->iMark == 0 );
1118
fn->iMark = ++(rndr->notes.nMarks);
1119
}
1120
assert( fn->iMark > 0 );
1121
cleanup:
1122
release_work_buffer( rndr, id );
1123
fallback:
1124
if( !fn ) fn = &rndr->notes.misref;
1125
fn->nUsed++;
1126
assert( fn->nUsed > 0 );
1127
return *fn;
1128
}
1129
1130
/*
1131
** Counts characters in the blank prefix within at most nHalfLines.
1132
** A sequence of spaces and tabs counts as odd halfline,
1133
** a newline counts as even halfline.
1134
** If nHalfLines < 0 then proceed without constraints.
1135
*/
1136
static inline size_t sizeof_blank_prefix(
1137
const char *data, size_t size, int nHalfLines
1138
){
1139
const char *p = data;
1140
const char * const end = data+size;
1141
if( nHalfLines < 0 ){
1142
while( p!=end && fossil_isspace(*p) ){
1143
p++;
1144
}
1145
}else while( nHalfLines > 0 ){
1146
while( p!=end && (*p==' ' || *p=='\t' ) ){ p++; }
1147
if( p==end || --nHalfLines == 0 ) break;
1148
if( *p=='\n' || *p=='\r' ){
1149
p++;
1150
if( p==end ) break;
1151
if( *p=='\n' && p[-1]=='\r' ){
1152
p++;
1153
}
1154
}
1155
nHalfLines--;
1156
}
1157
return p-data;
1158
}
1159
1160
/*
1161
** Check if the data starts with a classlist token of the special form.
1162
** If so then return the length of that token, otherwise return 0.
1163
**
1164
** The token must start with a dot and must end with a colon;
1165
** in between of these it must be a dot-separated list of words;
1166
** each word may contain only alphanumeric characters and hyphens.
1167
**
1168
** If `bBlank` is non-zero then a blank character must follow
1169
** the token's ending colon: otherwise function returns 0
1170
** despite the well-formed token.
1171
*/
1172
static size_t is_footnote_classlist(const char * const data, size_t size,
1173
int bBlank){
1174
const char *p;
1175
const char * const end = data+size;
1176
if( data==end || *data != '.' ) return 0;
1177
for(p=data+1; p!=end; p++){
1178
if( fossil_isalnum(*p) || *p=='-' ) continue;
1179
if( p[-1]=='.' ) break;
1180
if( *p==':' ){
1181
p++;
1182
if( bBlank ){
1183
if( p==end || !fossil_isspace(*p) ) break;
1184
}
1185
return p-data;
1186
}
1187
if( *p!='.' ) break;
1188
}
1189
return 0;
1190
}
1191
1192
/*
1193
** Adds unlabeled footnote to the rndr->notes.all.
1194
** On success puts a shallow copy of the constructed footnote into pFN
1195
** and returns 1, otherwise pFN is unchanged and 0 is returned.
1196
*/
1197
static inline int add_inline_footnote(
1198
struct render *rndr,
1199
const char *text,
1200
size_t size,
1201
struct footnote* pFN
1202
){
1203
struct footnote fn = FOOTNOTE_INITIALIZER, *last;
1204
const char *zUPC = 0;
1205
size_t nUPC = 0, n = sizeof_blank_prefix(text, size, 3);
1206
if( n >= size ) return 0;
1207
text += n;
1208
size -= n;
1209
nUPC = is_footnote_classlist(text, size, 1);
1210
if( nUPC ){
1211
assert( nUPC<size );
1212
zUPC = text;
1213
text += nUPC;
1214
size -= nUPC;
1215
}
1216
if( sizeof_blank_prefix(text,size,-1)==size ){
1217
if( !nUPC ) return 0; /* empty inline footnote */
1218
text = zUPC;
1219
size = nUPC; /* bare classlist is treated */
1220
nUPC = 0; /* as plain text */
1221
}
1222
fn.iMark = ++(rndr->notes.nMarks);
1223
fn.nUsed = 1;
1224
fn.index = COUNT_FOOTNOTES(&rndr->notes.all);
1225
assert( fn.iMark > 0 );
1226
blob_append(&fn.text, text, size);
1227
if(nUPC) blob_append(&fn.upc, zUPC, nUPC);
1228
blob_append(&rndr->notes.all, (char *)&fn, sizeof fn);
1229
last = (struct footnote*)( blob_buffer(&rndr->notes.all)
1230
+( blob_size(&rndr->notes.all)-sizeof fn ));
1231
assert( pFN );
1232
memcpy( pFN, last, sizeof fn );
1233
return 1;
1234
}
1235
1236
/*
1237
** Return the byte offset of the matching closing bracket or 0 if not
1238
** found. begin[0] must be either '[' or '('.
1239
**
1240
** TODO: It seems that things like "\\(" are not handled correctly.
1241
** That is historical behavior for a corner-case,
1242
** so it's left as it is until somebody complains.
1243
*/
1244
static inline size_t matching_bracket_offset(
1245
const char* begin,
1246
const char* end
1247
){
1248
const char *i;
1249
int level;
1250
const char bra = *begin;
1251
const char ket = bra=='[' ? ']' : ')';
1252
assert( bra=='[' || bra=='(' );
1253
for(i=begin+1,level=1; i!=end; i++){
1254
if( *i=='\n' ) /* do nothing */;
1255
else if( i[-1]=='\\' ) continue;
1256
else if( *i==bra ) level++;
1257
else if( *i==ket ){
1258
if( --level<=0 ) return i-begin;
1259
}
1260
}
1261
return 0;
1262
}
1263
1264
/*
1265
** char_footnote -- '(': parsing a standalone inline footnote.
1266
*/
1267
static size_t char_footnote(
1268
struct Blob *ob,
1269
struct render *rndr,
1270
char *data,
1271
size_t offset,
1272
size_t size
1273
){
1274
size_t end;
1275
struct footnote fn;
1276
1277
if( size<4 || data[1]!='^' ) return 0;
1278
end = matching_bracket_offset(data, data+size);
1279
if( !end ) return 0;
1280
if( !add_inline_footnote(rndr, data+2, end-2, &fn) ) return 0;
1281
if( rndr->make.footnote_ref ){
1282
rndr->make.footnote_ref(ob,0,&fn.upc,fn.iMark,1,rndr->make.opaque);
1283
}
1284
return end+1;
1285
}
1286
1287
/*
1288
** char_link -- '[': parsing a link or an image.
1289
*/
1290
static size_t char_link(
1291
struct Blob *ob,
1292
struct render *rndr,
1293
char *data,
1294
size_t offset,
1295
size_t size /* parse_inline() ensures that size > 0 */
1296
){
1297
const int bFsfn = (size>3 && data[1]=='^'); /*free-standing footnote ref*/
1298
const int bImg = !bFsfn && (offset && data[-1] == '!');
1299
size_t i, txt_e;
1300
struct Blob *content;
1301
struct Blob *link;
1302
struct Blob *title;
1303
struct footnote fn;
1304
int ret;
1305
1306
/* checking whether the correct renderer exists */
1307
if( !bFsfn ){
1308
if( (bImg && !rndr->make.image) || (!bImg && !rndr->make.link) ){
1309
return 0;
1310
}
1311
}
1312
1313
/* looking for the matching closing bracket */
1314
txt_e = matching_bracket_offset(data, data+size);
1315
if( !txt_e ) return 0;
1316
i = txt_e + 1;
1317
ret = 0; /* error if we don't get to the callback */
1318
1319
/* free-standing footnote reference */
1320
if( bFsfn ){
1321
fn = get_footnote(rndr, data+2, txt_e-2);
1322
content = link = title = 0;
1323
}else{
1324
fn.nUsed = 0;
1325
1326
/* skip "inter-bracket-whitespace" - any amount of whitespace or newline */
1327
/* (this is much more lax than original markdown syntax) */
1328
while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){ i++; }
1329
1330
/* allocate temporary buffers to store content, link and title */
1331
title = new_work_buffer(rndr);
1332
content = new_work_buffer(rndr);
1333
link = new_work_buffer(rndr);
1334
1335
if( i<size && data[i]=='(' ){
1336
1337
if( i+2<size && data[i+1]=='^' ){ /* span-bounded inline footnote */
1338
1339
const size_t k = matching_bracket_offset(data+i, data+size);
1340
if( !k ) goto char_link_cleanup;
1341
add_inline_footnote(rndr, data+(i+2), k-2, &fn);
1342
i += k+1;
1343
}else{ /* inline style link */
1344
size_t span_end = i;
1345
while( span_end<size
1346
&& !(data[span_end]==')'
1347
&& (span_end==i || data[span_end-1]!='\\')) ){
1348
span_end++;
1349
}
1350
if( span_end>=size
1351
|| get_link_inline(link, title, data+i+1, span_end-(i+1))<0 ){
1352
goto char_link_cleanup;
1353
}
1354
i = span_end+1;
1355
}
1356
/* reference style link or span-bounded footnote reference */
1357
}else if( i<size && data[i]=='[' ){
1358
char *id_data;
1359
size_t id_size, id_end = i;
1360
int bFootnote;
1361
1362
while( id_end<size && data[id_end]!=']' ){ id_end++; }
1363
if( id_end>=size ) goto char_link_cleanup;
1364
bFootnote = data[i+1]=='^';
1365
if( i+1==id_end || (bFootnote && i+2==id_end) ){
1366
/* implicit id - use the contents */
1367
id_data = data+1;
1368
id_size = txt_e-1;
1369
}else{
1370
/* explicit id - between brackets */
1371
id_data = data+i+1;
1372
id_size = id_end-(i+1);
1373
if( bFootnote ){
1374
id_data++;
1375
id_size--;
1376
}
1377
}
1378
if( bFootnote ){
1379
fn = get_footnote(rndr, id_data, id_size);
1380
}else if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){
1381
goto char_link_cleanup;
1382
}
1383
i = id_end+1;
1384
/* shortcut reference style link */
1385
}else{
1386
if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){
1387
goto char_link_cleanup;
1388
}
1389
/* rewinding an "inter-bracket-whitespace" */
1390
i = txt_e+1;
1391
}
1392
}
1393
/* building content: img alt is escaped, link content is parsed */
1394
if( txt_e>1 && content ){
1395
if( bImg ) blob_append(content, data+1, txt_e-1);
1396
else parse_inline(content, rndr, data+1, txt_e-1);
1397
}
1398
1399
/* calling the relevant rendering function */
1400
if( bImg ){
1401
if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ){
1402
ob->nUsed--;
1403
}
1404
ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
1405
}else if( fn.nUsed ){
1406
if( rndr->make.footnote_ref ){
1407
ret = rndr->make.footnote_ref(ob, content, &fn.upc, fn.iMark,
1408
fn.nUsed, rndr->make.opaque);
1409
}
1410
}else{
1411
ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
1412
}
1413
1414
/* cleanup */
1415
char_link_cleanup:
1416
release_work_buffer(rndr, title);
1417
release_work_buffer(rndr, link);
1418
release_work_buffer(rndr, content);
1419
return ret ? i : 0;
1420
}
1421
1422
1423
/*********************************
1424
* BLOCK-LEVEL PARSING FUNCTIONS *
1425
*********************************/
1426
1427
/* is_empty -- returns the line length when it is empty, 0 otherwise */
1428
static size_t is_empty(const char *data, size_t size){
1429
size_t i;
1430
for(i=0; i<size && data[i]!='\n'; i++){
1431
if( data[i]!=' ' && data[i]!='\t' ) return 0;
1432
}
1433
return i+1;
1434
}
1435
1436
1437
/* is_hrule -- returns whether a line is a horizontal rule */
1438
static int is_hrule(char *data, size_t size){
1439
size_t i = 0, n = 0;
1440
char c;
1441
1442
/* skipping initial spaces */
1443
if( size<3 ) return 0;
1444
if( data[0]==' ' ){
1445
i++;
1446
if( data[1]==' ' ){
1447
i++;
1448
if( data[2]==' ' ){
1449
i++;
1450
}
1451
}
1452
}
1453
1454
/* looking at the hrule char */
1455
if( i+2>=size || (data[i]!='*' && data[i]!='-' && data[i]!='_') ) return 0;
1456
c = data[i];
1457
1458
/* the whole line must be the char or whitespace */
1459
while (i < size && data[i] != '\n') {
1460
if( data[i]==c ){
1461
n += 1;
1462
}else if( data[i]!=' ' && data[i]!='\t' ){
1463
return 0;
1464
}
1465
i++;
1466
}
1467
1468
return n>=3;
1469
}
1470
1471
1472
/* is_headerline -- returns whether the line is a setext-style hdr underline */
1473
static int is_headerline(char *data, size_t size){
1474
size_t i = 0;
1475
1476
/* test of level 1 header */
1477
if( data[i]=='=' ){
1478
for(i=1; i<size && data[i]=='='; i++);
1479
while( i<size && (data[i]==' ' || data[i]=='\t') ){ i++; }
1480
return (i>=size || data[i]=='\n') ? 1 : 0;
1481
}
1482
1483
/* test of level 2 header */
1484
if( data[i]=='-' ){
1485
for(i=1; i<size && data[i]=='-'; i++);
1486
while( i<size && (data[i]==' ' || data[i]=='\t') ){ i++; }
1487
return (i>=size || data[i]=='\n') ? 2 : 0;
1488
}
1489
1490
return 0;
1491
}
1492
1493
1494
/* is_table_sep -- returns whether there is a table separator at pos */
1495
static int is_table_sep(char *data, size_t pos){
1496
return data[pos]=='|' && (pos==0 || data[pos-1]!='\\');
1497
}
1498
1499
1500
/* is_tableline -- returns the number of column tables in the given line */
1501
static int is_tableline(char *data, size_t size){
1502
size_t i = 0;
1503
int n_sep = 0, outer_sep = 0;
1504
1505
/* skip initial blanks */
1506
while( i<size && (data[i]==' ' || data[i]=='\t') ){ i++; }
1507
1508
/* check for initial '|' */
1509
if( i<size && data[i]=='|') outer_sep++;
1510
1511
/* count the number of pipes in the line */
1512
for(n_sep=0; i<size && data[i]!='\n'; i++){
1513
if( is_table_sep(data, i) ) n_sep++;
1514
if( data[i]=='`' ){
1515
skip_codespan(data, size, &i);
1516
i--;
1517
}
1518
}
1519
1520
/* march back to check for optional last '|' before blanks and EOL */
1521
while( i && (data[i-1]==' ' || data[i-1]=='\t' || data[i-1]=='\n') ){ i--; }
1522
if( i && is_table_sep(data, i-1) ) outer_sep += 1;
1523
1524
/* return the number of column or 0 if it's not a table line */
1525
return (n_sep>0) ? (n_sep-outer_sep+1) : 0;
1526
}
1527
1528
1529
/* prefix_quote -- returns blockquote prefix length */
1530
static size_t prefix_quote(char *data, size_t size){
1531
size_t i = 0;
1532
if( i<size && data[i]==' ' ) i++;
1533
if( i<size && data[i]==' ' ) i++;
1534
if( i<size && data[i]==' ' ) i++;
1535
if( i<size && data[i]=='>' ){
1536
if( i+1<size && (data[i+1]==' ' || data[i+1]=='\t') ){
1537
return i + 2;
1538
}else{
1539
return i + 1;
1540
}
1541
}else{
1542
return 0;
1543
}
1544
}
1545
1546
1547
/* prefix_code -- returns prefix length for block code */
1548
static size_t prefix_code(char *data, size_t size){
1549
if( size>0 && data[0]=='\t' ) return 1;
1550
if( size>3 && data[0]==' ' && data[1]==' ' && data[2]==' ' && data[3]==' ' ){
1551
return 4;
1552
}
1553
return 0;
1554
}
1555
1556
/* Return the number of characters in the delimiter of a fenced code
1557
** block. */
1558
static size_t prefix_fencedcode(char *data, size_t size){
1559
char c = data[0];
1560
int nb;
1561
if( c!='`' && c!='~' ) return 0;
1562
for(nb=1; nb<(int)size-3 && data[nb]==c; nb++){}
1563
if( nb<3 ) return 0;
1564
if( nb>=(int)size-nb ) return 0;
1565
return nb;
1566
}
1567
1568
/* prefix_oli -- returns ordered list item prefix */
1569
static size_t prefix_oli(char *data, size_t size){
1570
size_t i = 0;
1571
if( i<size && data[i]==' ') i++;
1572
if( i<size && data[i]==' ') i++;
1573
if( i<size && data[i]==' ') i++;
1574
1575
if( i>=size || data[i]<'0' || data[i]>'9' ) return 0;
1576
while( i<size && data[i]>='0' && data[i]<='9' ){ i++; }
1577
1578
if( i+1>=size
1579
|| (data[i]!='.' && data[i]!=')')
1580
|| (data[i+1]!=' ' && data[i+1]!='\t')
1581
){
1582
return 0;
1583
}
1584
i = i+2;
1585
while( i<size && (data[i]==' ' || data[i]=='\t') ){ i++; }
1586
return i;
1587
}
1588
1589
1590
/* prefix_uli -- returns ordered list item prefix */
1591
static size_t prefix_uli(char *data, size_t size){
1592
size_t i = 0;
1593
if( i<size && data[i]==' ') i++;
1594
if( i<size && data[i]==' ') i++;
1595
if( i<size && data[i]==' ') i++;
1596
if( i+1>=size
1597
|| (data[i]!='*' && data[i]!='+' && data[i]!='-')
1598
|| (data[i+1]!=' ' && data[i+1]!='\t')
1599
){
1600
return 0;
1601
}
1602
i = i+2;
1603
while( i<size && (data[i]==' ' || data[i]=='\t') ){ i++; }
1604
return i;
1605
}
1606
1607
1608
/* parse_block predeclaration */
1609
static void parse_block(
1610
struct Blob *ob,
1611
struct render *rndr,
1612
char *data,
1613
size_t size);
1614
1615
1616
/* parse_blockquote -- handles parsing of a blockquote fragment */
1617
static size_t parse_blockquote(
1618
struct Blob *ob,
1619
struct render *rndr,
1620
char *data,
1621
size_t size
1622
){
1623
size_t beg, end = 0, pre, work_size = 0, nb, endFence = 0;
1624
char *work_data = 0;
1625
struct Blob *out = new_work_buffer(rndr);
1626
1627
/* Check to see if this is a quote of a fenced code block, because
1628
** if it is, then blank lines do not terminated the quoted text. Ex:
1629
**
1630
** > ~~~~
1631
** First line
1632
**
1633
** Line after blank
1634
** ~~~~
1635
**
1636
** If this is a quoted fenced block, then set endFence to be the
1637
** offset of the end of the fenced block.
1638
*/
1639
pre = prefix_quote(data,size);
1640
pre += is_empty(data+pre,size-pre);
1641
nb = prefix_fencedcode(data+pre,size-pre);
1642
if( nb ){
1643
size_t i = 0;
1644
char delim = data[pre];
1645
for(end=pre+nb; end<size && i<nb; end++){
1646
if( data[end]==delim ) i++; else i = 0;
1647
}
1648
if( i>=nb ) endFence = end;
1649
}
1650
1651
beg = 0;
1652
while( beg<size ){
1653
for(end=beg+1; end<size && data[end-1]!='\n'; end++);
1654
pre = prefix_quote(data+beg, end-beg);
1655
if( pre ){
1656
beg += pre; /* skipping prefix */
1657
}else if( is_empty(data+beg, end-beg)
1658
&& (end>=size
1659
|| (end>endFence
1660
&& prefix_quote(data+end, size-end)==0
1661
&& !is_empty(data+end, size-end)))
1662
){
1663
/* empty line followed by non-quote line */
1664
break;
1665
}
1666
if( beg<end ){ /* copy into the in-place working buffer */
1667
if( !work_data ){
1668
work_data = data+beg;
1669
}else if( (data+beg)!=(work_data+work_size) ){
1670
memmove(work_data+work_size, data+beg, end-beg);
1671
}
1672
work_size += end-beg;
1673
}
1674
beg = end;
1675
}
1676
1677
if( rndr->make.blockquote ){
1678
if( !too_deep(rndr) ){
1679
parse_block(out, rndr, work_data, work_size);
1680
}else{
1681
blob_append(out, work_data, work_size);
1682
}
1683
rndr->make.blockquote(ob, out, rndr->make.opaque);
1684
}
1685
release_work_buffer(rndr, out);
1686
return end;
1687
}
1688
1689
1690
/* parse_paragraph -- handles parsing of a regular paragraph */
1691
static size_t parse_paragraph(
1692
struct Blob *ob,
1693
struct render *rndr,
1694
char *data,
1695
size_t size
1696
){
1697
size_t i = 0, end = 0;
1698
int level = 0;
1699
char *work_data = data;
1700
size_t work_size = 0;
1701
1702
while( i<size ){
1703
char *zEnd = memchr(data+i, '\n', size-i-1);
1704
end = zEnd==0 ? size : (size_t)(zEnd - (data-1));
1705
/* The above is the same as:
1706
** for(end=i+1; end<size && data[end-1]!='\n'; end++);
1707
** "end" is left with a value such that data[end] is one byte
1708
** past the first '\n' or one byte past the end of the string */
1709
if( is_empty(data+i, size-i)
1710
|| (level = is_headerline(data+i, size-i))!= 0
1711
){
1712
break;
1713
}
1714
if( (i && data[i]=='#')
1715
|| is_hrule(data+i, size-i)
1716
|| prefix_uli(data+i, size-i)
1717
|| prefix_oli(data+i, size-i)
1718
){
1719
end = i;
1720
break;
1721
}
1722
i = end;
1723
}
1724
1725
work_size = i;
1726
while( work_size && data[work_size-1]=='\n' ){ work_size--; }
1727
1728
if( !level ){
1729
if( rndr->make.paragraph ){
1730
struct Blob *tmp = new_work_buffer(rndr);
1731
parse_inline(tmp, rndr, work_data, work_size);
1732
rndr->make.paragraph(ob, tmp, rndr->make.opaque);
1733
release_work_buffer(rndr, tmp);
1734
}
1735
}else{
1736
if( work_size ){
1737
size_t beg;
1738
i = work_size;
1739
work_size -= 1;
1740
while( work_size && data[work_size]!='\n' ){ work_size--; }
1741
beg = work_size+1;
1742
while( work_size && data[work_size-1]=='\n'){ work_size--; }
1743
if( work_size ){
1744
struct Blob *tmp = new_work_buffer(rndr);
1745
parse_inline(tmp, rndr, work_data, work_size);
1746
if( rndr->make.paragraph ){
1747
rndr->make.paragraph(ob, tmp, rndr->make.opaque);
1748
}
1749
release_work_buffer(rndr, tmp);
1750
work_data += beg;
1751
work_size = i - beg;
1752
}else{
1753
work_size = i;
1754
}
1755
}
1756
1757
if( rndr->make.header ){
1758
struct Blob *span = new_work_buffer(rndr);
1759
parse_inline(span, rndr, work_data, work_size);
1760
rndr->make.header(ob, span, level, rndr->make.opaque);
1761
release_work_buffer(rndr, span);
1762
}
1763
}
1764
return end;
1765
}
1766
1767
1768
/* parse_blockcode -- handles parsing of a block-level code fragment */
1769
static size_t parse_blockcode(
1770
struct Blob *ob,
1771
struct render *rndr,
1772
char *data,
1773
size_t size
1774
){
1775
size_t beg, end, pre;
1776
struct Blob *work = new_work_buffer(rndr);
1777
1778
beg = 0;
1779
while( beg<size ){
1780
char *zEnd = memchr(data+beg, '\n', size-beg-1);
1781
end = zEnd==0 ? size : (size_t)(zEnd - (data-1));
1782
/* The above is the same as:
1783
** for(end=beg+1; end<size && data[end-1]!='\n'; end++);
1784
** "end" is left with a value such that data[end] is one byte
1785
** past the first \n or past then end of the string. */
1786
pre = prefix_code(data+beg, end-beg);
1787
if( pre ){
1788
beg += pre; /* skipping prefix */
1789
}else if( !is_empty(data+beg, end-beg) ){
1790
/* non-empty non-prefixed line breaks the pre */
1791
break;
1792
}
1793
if( beg<end ){
1794
/* verbatim copy to the working buffer, escaping entities */
1795
if( is_empty(data + beg, end - beg) ){
1796
blob_append_char(work, '\n');
1797
}else{
1798
blob_append(work, data+beg, end-beg);
1799
}
1800
}
1801
beg = end;
1802
}
1803
1804
end = blob_size(work);
1805
while( end>0 && blob_buffer(work)[end-1]=='\n' ){ end--; }
1806
work->nUsed = end;
1807
blob_append_char(work, '\n');
1808
1809
if( work!=ob ){
1810
if( rndr->make.blockcode ){
1811
rndr->make.blockcode(ob, work, rndr->make.opaque);
1812
}
1813
release_work_buffer(rndr, work);
1814
}
1815
return beg;
1816
}
1817
1818
1819
/* parse_listitem -- parsing of a single list item */
1820
/* assuming initial prefix is already removed */
1821
static size_t parse_listitem(
1822
struct Blob *ob,
1823
struct render *rndr,
1824
char *data,
1825
size_t size,
1826
int *flags
1827
){
1828
struct Blob *work = 0, *inter = 0;
1829
size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i;
1830
int in_empty = 0, has_inside_empty = 0;
1831
1832
/* keeping track of the first indentation prefix */
1833
if( size>1 && data[0]==' ' ){
1834
orgpre = 1;
1835
if( size>2 && data[1]==' ' ){
1836
orgpre = 2;
1837
if( size>3 && data[2]==' ' ){
1838
orgpre = 3;
1839
}
1840
}
1841
}
1842
beg = prefix_uli(data, size);
1843
if( !beg ) beg = prefix_oli(data, size);
1844
if( !beg ) return 0;
1845
/* skipping to the beginning of the following line */
1846
end = beg;
1847
while( end<size && data[end-1]!='\n' ){ end++; }
1848
1849
/* getting working buffers */
1850
work = new_work_buffer(rndr);
1851
inter = new_work_buffer(rndr);
1852
1853
/* putting the first line into the working buffer */
1854
blob_append(work, data+beg, end-beg);
1855
beg = end;
1856
1857
/* process the following lines */
1858
while( beg<size ){
1859
end++;
1860
while( end<size && data[end-1]!='\n' ){ end++; }
1861
1862
/* process an empty line */
1863
if( is_empty(data+beg, end-beg) ){
1864
in_empty = 1;
1865
beg = end;
1866
continue;
1867
}
1868
1869
/* computing the indentation */
1870
i = 0;
1871
if( end-beg>1 && data[beg]==' ' ){
1872
i = 1;
1873
if( end-beg>2 && data[beg+1]==' ' ){
1874
i = 2;
1875
if( end-beg>3 && data[beg+2]==' ' ){
1876
i = 3;
1877
if( end-beg>3 && data[beg+3]==' ' ){
1878
i = 4;
1879
}
1880
}
1881
}
1882
}
1883
pre = i;
1884
if( data[beg]=='\t' ){ i = 1; pre = 8; }
1885
1886
/* checking for a new item */
1887
if( (prefix_uli(data+beg+i, end-beg-i) && !is_hrule(data+beg+i, end-beg-i))
1888
|| prefix_oli(data+beg+i, end-beg-i)
1889
){
1890
if( in_empty ) has_inside_empty = 1;
1891
if( pre == orgpre ){ /* the following item must have */
1892
break; /* the same indentation */
1893
}
1894
if( !sublist ) sublist = blob_size(work);
1895
1896
/* joining only indented stuff after empty lines */
1897
}else if( in_empty && i<4 && data[beg]!='\t' ){
1898
*flags |= MKD_LI_END;
1899
break;
1900
}else if( in_empty ){
1901
blob_append_char(work, '\n');
1902
has_inside_empty = 1;
1903
}
1904
in_empty = 0;
1905
1906
/* adding the line without prefix into the working buffer */
1907
blob_append(work, data+beg+i, end-beg-i);
1908
beg = end;
1909
}
1910
1911
/* non-recursive fallback when working buffer stack is full */
1912
if( !inter ){
1913
if( rndr->make.listitem ){
1914
rndr->make.listitem(ob, work, *flags, rndr->make.opaque);
1915
}
1916
release_work_buffer(rndr, work);
1917
return beg;
1918
}
1919
1920
/* render of li contents */
1921
if( has_inside_empty ) *flags |= MKD_LI_BLOCK;
1922
if( *flags & MKD_LI_BLOCK ){
1923
/* intermediate render of block li */
1924
if( sublist && sublist<blob_size(work) ){
1925
parse_block(inter, rndr, blob_buffer(work), sublist);
1926
parse_block(inter,
1927
rndr,
1928
blob_buffer(work)+sublist,
1929
blob_size(work)-sublist);
1930
}else{
1931
parse_block(inter, rndr, blob_buffer(work), blob_size(work));
1932
}
1933
}else{
1934
/* intermediate render of inline li */
1935
if( sublist && sublist<blob_size(work) ){
1936
parse_inline(inter, rndr, blob_buffer(work), sublist);
1937
parse_block(inter,
1938
rndr,
1939
blob_buffer(work)+sublist,
1940
blob_size(work)-sublist);
1941
}else{
1942
parse_inline(inter, rndr, blob_buffer(work), blob_size(work));
1943
}
1944
}
1945
1946
/* render of li itself */
1947
if( rndr->make.listitem ){
1948
rndr->make.listitem(ob, inter, *flags, rndr->make.opaque);
1949
}
1950
release_work_buffer(rndr, inter);
1951
release_work_buffer(rndr, work);
1952
return beg;
1953
}
1954
1955
1956
/* parse_list -- parsing ordered or unordered list block */
1957
static size_t parse_list(
1958
struct Blob *ob,
1959
struct render *rndr,
1960
char *data,
1961
size_t size,
1962
int flags
1963
){
1964
struct Blob *work = new_work_buffer(rndr);
1965
size_t i = 0, j;
1966
1967
while( i<size ){
1968
j = parse_listitem(work, rndr, data+i, size-i, &flags);
1969
i += j;
1970
if( !j || (flags & MKD_LI_END) ) break;
1971
}
1972
1973
if( rndr->make.list ) rndr->make.list(ob, work, flags, rndr->make.opaque);
1974
release_work_buffer(rndr, work);
1975
return i;
1976
}
1977
1978
1979
/* parse_atxheader -- parsing of atx-style headers */
1980
static size_t parse_atxheader(
1981
struct Blob *ob,
1982
struct render *rndr,
1983
char *data,
1984
size_t size
1985
){
1986
int level = 0;
1987
size_t i, end, skip, span_beg, span_size;
1988
1989
if( !size || data[0]!='#' ) return 0;
1990
1991
while( level<(int)size && level<6 && data[level]=='#' ){ level++; }
1992
for(i=level; i<size && (data[i]==' ' || data[i]=='\t'); i++);
1993
if ( (int)i == level ) return parse_paragraph(ob, rndr, data, size);
1994
span_beg = i;
1995
1996
for(end=i; end<size && data[end]!='\n'; end++);
1997
skip = end;
1998
if( end<=i ) return parse_paragraph(ob, rndr, data, size);
1999
while( end && data[end-1]=='#' ){ end--; }
2000
while( end && (data[end-1]==' ' || data[end-1]=='\t') ){ end--; }
2001
if( end<=i ) return parse_paragraph(ob, rndr, data, size);
2002
2003
span_size = end-span_beg;
2004
if( rndr->make.header ){
2005
struct Blob *span = new_work_buffer(rndr);
2006
parse_inline(span, rndr, data+span_beg, span_size);
2007
rndr->make.header(ob, span, level, rndr->make.opaque);
2008
release_work_buffer(rndr, span);
2009
}
2010
return skip;
2011
}
2012
2013
2014
/* htmlblock_end -- checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
2015
/* returns the length on match, 0 otherwise */
2016
static size_t htmlblock_end(
2017
const struct html_tag *tag,
2018
const char *data,
2019
size_t size
2020
){
2021
size_t i, w;
2022
2023
/* assuming data[0]=='<' && data[1]=='/' already tested */
2024
2025
/* checking tag is a match */
2026
if( (tag->size+3)>(int)size
2027
|| fossil_strnicmp(data+2, tag->text, tag->size)
2028
|| data[tag->size+2]!='>'
2029
){
2030
return 0;
2031
}
2032
2033
/* checking white lines */
2034
i = tag->size + 3;
2035
w = 0;
2036
if( i<size && (w = is_empty(data+i, size-i))==0 ){
2037
return 0; /* non-blank after tag */
2038
}
2039
i += w;
2040
w = 0;
2041
2042
if( i<size && (w = is_empty(data + i, size - i))==0 ){
2043
return 0; /* non-blank line after tag line */
2044
}
2045
return i+w;
2046
}
2047
2048
2049
/* parse_htmlblock -- parsing of inline HTML block */
2050
static size_t parse_htmlblock(
2051
struct Blob *ob,
2052
struct render *rndr,
2053
char *data,
2054
size_t size
2055
){
2056
size_t i, j = 0;
2057
const struct html_tag *curtag;
2058
int found;
2059
size_t work_size = 0;
2060
struct Blob work = BLOB_INITIALIZER;
2061
2062
/* identification of the opening tag */
2063
if( size<2 || data[0]!='<' ) return 0;
2064
curtag = find_block_tag(data+1, size-1);
2065
2066
/* handling of special cases */
2067
if( !curtag ){
2068
2069
/* HTML comment, laxist form */
2070
if( size>5 && data[1]=='!' && data[2]=='-' && data[3]=='-' ){
2071
i = 5;
2072
while( i<size && !(data[i-2]=='-' && data[i-1]=='-' && data[i]=='>') ){
2073
i++;
2074
}
2075
i++;
2076
if( i<size ){
2077
j = is_empty(data+i, size-i);
2078
if( j ){
2079
work_size = i+j;
2080
if( !rndr->make.blockhtml ) return work_size;
2081
blob_init(&work, data, work_size);
2082
rndr->make.blockhtml(ob, &work, rndr->make.opaque);
2083
return work_size;
2084
}
2085
}
2086
}
2087
2088
/* HR, which is the only self-closing block tag considered */
2089
if( size>4
2090
&& (data[1]=='h' || data[1]=='H')
2091
&& (data[2]=='r' || data[2]=='R')
2092
){
2093
i = 3;
2094
while( i<size && data[i]!='>' ){ i++; }
2095
if( i+1<size ){
2096
i += 1;
2097
j = is_empty(data+i, size-i);
2098
if( j ){
2099
work_size = i+j;
2100
if( !rndr->make.blockhtml ) return work_size;
2101
blob_init(&work, data, work_size);
2102
rndr->make.blockhtml(ob, &work, rndr->make.opaque);
2103
return work_size;
2104
}
2105
}
2106
}
2107
2108
/* no special case recognised */
2109
return 0;
2110
}
2111
2112
/* looking for an matching closing tag */
2113
/* followed by a blank line */
2114
i = 1;
2115
found = 0;
2116
while( i<size ){
2117
i++;
2118
while( i<size && !(data[i-1]=='<' && data[i]=='/') ){ i++; }
2119
if( (i+2+curtag->size)>size ) break;
2120
j = htmlblock_end(curtag, data+i-1, size-i+1);
2121
if (j) {
2122
i += j-1;
2123
found = 1;
2124
break;
2125
}
2126
}
2127
if( !found ) return 0;
2128
2129
/* the end of the block has been found */
2130
if( strcmp(curtag->text,"html")==0 ){
2131
/* Omit <html> tags */
2132
enum mkd_autolink dummy;
2133
int k = tag_length(data, size, &dummy);
2134
int sz = i - (j+k);
2135
if( sz>0 ) blob_init(&work, data+k, sz);
2136
}else{
2137
blob_init(&work, data, i);
2138
}
2139
if( rndr->make.blockhtml ){
2140
rndr->make.blockhtml(ob, &work, rndr->make.opaque);
2141
}
2142
return i;
2143
}
2144
2145
2146
/* parse_table_cell -- parse a cell inside a table */
2147
static void parse_table_cell(
2148
struct Blob *ob, /* output blob */
2149
struct render *rndr, /* renderer description */
2150
char *data, /* input text */
2151
size_t size, /* input text size */
2152
int flags /* table flags */
2153
){
2154
struct Blob *span = new_work_buffer(rndr);
2155
parse_inline(span, rndr, data, size);
2156
rndr->make.table_cell(ob, span, flags, rndr->make.opaque);
2157
release_work_buffer(rndr, span);
2158
}
2159
2160
2161
/* parse_table_row -- parse an input line into a table row */
2162
static size_t parse_table_row(
2163
struct Blob *ob, /* output blob for rendering */
2164
struct render *rndr, /* renderer description */
2165
char *data, /* input text */
2166
size_t size, /* input text size */
2167
int *aligns, /* array of default alignment for columns */
2168
size_t align_size, /* number of columns with default alignment */
2169
int flags /* table flags */
2170
){
2171
size_t i = 0, col = 0;
2172
size_t beg, end, total = 0;
2173
struct Blob *cells = new_work_buffer(rndr);
2174
int align;
2175
2176
/* skip leading blanks and separator */
2177
while( i<size && (data[i]==' ' || data[i]=='\t') ){ i++; }
2178
if( i<size && data[i]=='|' ) i++;
2179
2180
/* go over all the cells */
2181
while( i<size && total==0 ){
2182
/* check optional left/center align marker */
2183
align = 0;
2184
if( data[i]==':' ){
2185
align |= MKD_CELL_ALIGN_LEFT;
2186
i++;
2187
}
2188
2189
/* skip blanks */
2190
while( i<size && (data[i]==' ' || data[i]=='\t') ){ i++; }
2191
beg = i;
2192
2193
/* forward to the next separator or EOL */
2194
while( i<size && !is_table_sep(data, i) && data[i]!='\n' ){
2195
if( data[i]=='`' ){
2196
skip_codespan(data, size, &i);
2197
}else{
2198
i++;
2199
}
2200
}
2201
end = i;
2202
if( i<size ){
2203
i++;
2204
if( data[i-1]=='\n' ) total = i;
2205
}
2206
2207
/* check optional right/center align marker */
2208
if( i>beg && data[end-1]==':' ){
2209
align |= MKD_CELL_ALIGN_RIGHT;
2210
end--;
2211
}
2212
2213
/* remove trailing blanks */
2214
while( end>beg && (data[end-1]==' ' || data[end-1]=='\t') ){ end--; }
2215
2216
/* skip the last cell if it was only blanks */
2217
/* (because it is only the optional end separator) */
2218
if( total && end<=beg ) continue;
2219
2220
/* fallback on default alignment if not explicit */
2221
if( align==0 && aligns && col<align_size ) align = aligns[col];
2222
2223
/* render cells */
2224
if( cells && end>=beg ){
2225
parse_table_cell(cells, rndr, data+beg, end-beg, align|flags);
2226
}
2227
2228
col++;
2229
}
2230
2231
/* render the whole row and clean up */
2232
rndr->make.table_row(ob, cells, flags, rndr->make.opaque);
2233
release_work_buffer(rndr, cells);
2234
return total ? total : size;
2235
}
2236
2237
2238
/* parse_table -- parsing of a whole table */
2239
static size_t parse_table(
2240
struct Blob *ob,
2241
struct render *rndr,
2242
char *data,
2243
size_t size
2244
){
2245
size_t i = 0, head_end, col;
2246
size_t align_size = 0;
2247
int *aligns = 0;
2248
struct Blob *head = 0;
2249
struct Blob *rows = new_work_buffer(rndr);
2250
2251
/* skip the first (presumably header) line */
2252
while( i<size && data[i]!='\n' ){ i++; }
2253
head_end = i;
2254
2255
/* fallback on end of input */
2256
if( i>=size ){
2257
parse_table_row(rows, rndr, data, size, 0, 0, 0);
2258
rndr->make.table(ob, 0, rows, rndr->make.opaque);
2259
release_work_buffer(rndr, rows);
2260
return i;
2261
}
2262
2263
/* attempt to parse a table rule, i.e. blanks, dash, colons and sep */
2264
i++;
2265
col = 0;
2266
while( i<size
2267
&& (data[i]==' '
2268
|| data[i]=='\t'
2269
|| data[i]=='-'
2270
|| data[i] == ':'
2271
|| data[i] =='|')
2272
){
2273
if( data[i] == '|' ) align_size++;
2274
if( data[i] == ':' ) col = 1;
2275
i += 1;
2276
}
2277
2278
if( i<size && data[i]=='\n' ){
2279
align_size++;
2280
2281
/* render the header row */
2282
head = new_work_buffer(rndr);
2283
parse_table_row(head, rndr, data, head_end, 0, 0, MKD_CELL_HEAD);
2284
2285
/* parse alignments if provided */
2286
if( col && (aligns=fossil_malloc(align_size * sizeof *aligns))!=0 ){
2287
for(i=0; i<align_size; i++) aligns[i] = 0;
2288
col = 0;
2289
i = head_end+1;
2290
2291
/* skip initial white space and optional separator */
2292
while( i<size && (data[i]==' ' || data[i]=='\t') ){ i++; }
2293
if( data[i]=='|' ) i++;
2294
2295
/* compute default alignment for each column */
2296
while (i < size && data[i] != '\n') {
2297
if (data[i] == ':')
2298
aligns[col] |= MKD_CELL_ALIGN_LEFT;
2299
while (i < size
2300
&& data[i] != '|' && data[i] != '\n')
2301
i += 1;
2302
if (data[i - 1] == ':')
2303
aligns[col] |= MKD_CELL_ALIGN_RIGHT;
2304
if (i < size && data[i] == '|')
2305
i += 1;
2306
col += 1; }
2307
}
2308
2309
/* point i to the beginning of next line/row */
2310
i++;
2311
2312
}else{
2313
/* there is no valid ruler, continuing without header */
2314
i = 0;
2315
}
2316
2317
/* render the table body lines */
2318
while( i<size && is_tableline(data + i, size - i) ){
2319
i += parse_table_row(rows, rndr, data+i, size-i, aligns, align_size, 0);
2320
}
2321
2322
/* render the full table */
2323
rndr->make.table(ob, head, rows, rndr->make.opaque);
2324
2325
/* cleanup */
2326
release_work_buffer(rndr, head);
2327
release_work_buffer(rndr, rows);
2328
fossil_free(aligns);
2329
return i;
2330
}
2331
2332
2333
/* parse_block -- parsing of one block, returning next char to parse */
2334
static void parse_block(
2335
struct Blob *ob, /* output blob */
2336
struct render *rndr, /* renderer internal state */
2337
char *data, /* input text */
2338
size_t size /* input text size */
2339
){
2340
size_t beg, end, i;
2341
char *txt_data;
2342
int has_table;
2343
if( !size ) return;
2344
has_table = (rndr->make.table
2345
&& rndr->make.table_row
2346
&& rndr->make.table_cell
2347
&& memchr(data, '|', size)!=0);
2348
2349
beg = 0;
2350
while( beg<size ){
2351
txt_data = data+beg;
2352
end = size-beg;
2353
if( data[beg]=='#' ){
2354
beg += parse_atxheader(ob, rndr, txt_data, end);
2355
}else if( data[beg]=='<'
2356
&& rndr->make.blockhtml
2357
&& (i = parse_htmlblock(ob, rndr, txt_data, end))!=0
2358
){
2359
beg += i;
2360
}else if( (i=is_empty(txt_data, end))!=0 ){
2361
beg += i;
2362
}else if( is_hrule(txt_data, end) ){
2363
if( rndr->make.hrule ) rndr->make.hrule(ob, rndr->make.opaque);
2364
while( beg<size && data[beg]!='\n' ){ beg++; }
2365
beg++;
2366
}else if( prefix_quote(txt_data, end) ){
2367
beg += parse_blockquote(ob, rndr, txt_data, end);
2368
}else if( prefix_code(txt_data, end) ){
2369
beg += parse_blockcode(ob, rndr, txt_data, end);
2370
}else if( prefix_uli(txt_data, end) ){
2371
beg += parse_list(ob, rndr, txt_data, end, 0);
2372
}else if( prefix_oli(txt_data, end) ){
2373
beg += parse_list(ob, rndr, txt_data, end, MKD_LIST_ORDERED);
2374
}else if( has_table && is_tableline(txt_data, end) ){
2375
beg += parse_table(ob, rndr, txt_data, end);
2376
}else if( prefix_fencedcode(txt_data, end)
2377
&& (i = char_codespan(ob, rndr, txt_data, 0, end))!=0
2378
){
2379
beg += i;
2380
}else{
2381
beg += parse_paragraph(ob, rndr, txt_data, end);
2382
}
2383
}
2384
}
2385
2386
2387
2388
/*********************
2389
* REFERENCE PARSING *
2390
*********************/
2391
2392
/* is_ref -- returns whether a line is a reference or not */
2393
static int is_ref(
2394
const char *data, /* input text */
2395
size_t beg, /* offset of the beginning of the line */
2396
size_t end, /* offset of the end of the text */
2397
size_t *last, /* last character of the link */
2398
struct Blob *refs /* array of link references */
2399
){
2400
size_t i = 0;
2401
size_t id_offset, id_end;
2402
size_t link_offset, link_end;
2403
size_t title_offset, title_end;
2404
size_t line_end;
2405
struct link_ref lr = {
2406
BLOB_INITIALIZER,
2407
BLOB_INITIALIZER,
2408
BLOB_INITIALIZER
2409
};
2410
2411
/* up to 3 optional leading spaces */
2412
if( beg+3>=end ) return 0;
2413
if( data[beg]==' ' ){
2414
i = 1;
2415
if( data[beg+1]==' ' ){
2416
i = 2;
2417
if( data[beg+2]==' ' ){
2418
i = 3;
2419
if( data[beg+3]==' ' ) return 0;
2420
}
2421
}
2422
}
2423
i += beg;
2424
2425
/* id part: anything but a newline between brackets */
2426
if( data[i]!='[' ) return 0;
2427
i++;
2428
if( i>=end || data[i]=='^' ) return 0; /* see is_footnote() */
2429
2430
id_offset = i;
2431
while( i<end && data[i]!='\n' && data[i]!='\r' && data[i]!=']' ){ i++; }
2432
if( i>=end || data[i]!=']' ) return 0;
2433
id_end = i;
2434
2435
/* spacer: colon (space | tab)* newline? (space | tab)* */
2436
i++;
2437
if( i>=end || data[i]!=':' ) return 0;
2438
i++;
2439
while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2440
if( i<end && (data[i]=='\n' || data[i]=='\r') ){
2441
i++;
2442
if( i<end && data[i]=='\r' && data[i-1] == '\n' ) i++;
2443
}
2444
while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2445
if( i>=end ) return 0;
2446
2447
/* link: whitespace-free sequence, optionally between angle brackets */
2448
if( data[i]=='<' ) i++;
2449
link_offset = i;
2450
while( i<end
2451
&& data[i]!=' '
2452
&& data[i]!='\t'
2453
&& data[i]!='\n'
2454
&& data[i]!='\r'
2455
){
2456
i += 1;
2457
}
2458
/* TODO: maybe require both data[i-1]=='>' && data[link_offset-1]=='<' ? */
2459
if( data[i-1]=='>' ) link_end = i-1; else link_end = i;
2460
2461
/* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */
2462
while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2463
if( i<end
2464
&& data[i]!='\n'
2465
&& data[i]!='\r'
2466
&& data[i]!='\''
2467
&& data[i]!='"'
2468
&& data[i]!='('
2469
){
2470
return 0;
2471
}
2472
line_end = 0;
2473
/* computing end-of-line */
2474
if( i>=end || data[i]=='\r' || data[i]=='\n' ) line_end = i;
2475
if( i+1<end && data[i]=='\n' && data[i+1]=='\r' ) line_end = i+1;
2476
2477
/* optional (space|tab)* spacer after a newline */
2478
if( line_end ){
2479
i = line_end+1;
2480
while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2481
}
2482
2483
/* optional title: any non-newline sequence enclosed in '"()
2484
alone on its line */
2485
title_offset = title_end = 0;
2486
if( i+1<end && (data[i]=='\'' || data[i]=='"' || data[i]=='(') ){
2487
i += 1;
2488
title_offset = i;
2489
/* looking for EOL */
2490
while( i<end && data[i]!='\n' && data[i]!='\r' ){ i++; }
2491
if( i+1<end && data[i]=='\n' && data[i+1]=='\r' ){
2492
title_end = i + 1;
2493
}else{
2494
title_end = i;
2495
}
2496
/* stepping back */
2497
i--;
2498
while( i>title_offset && (data[i]==' ' || data[i]=='\t') ){ i--; }
2499
if( i>title_offset && (data[i]=='\'' || data[i]=='"' || data[i]==')') ){
2500
line_end = title_end;
2501
title_end = i;
2502
}
2503
}
2504
if( !line_end ) return 0; /* garbage after the link */
2505
2506
/* a valid ref has been found, filling-in return structures */
2507
if( last ) *last = line_end;
2508
if( !refs ) return 1;
2509
if( build_ref_id(&lr.id, data+id_offset, id_end-id_offset)<0 ) return 0;
2510
blob_append(&lr.link, data+link_offset, link_end-link_offset);
2511
if( title_end>title_offset ){
2512
blob_append(&lr.title, data+title_offset, title_end-title_offset);
2513
}
2514
blob_append(refs, (char *)&lr, sizeof lr);
2515
return 1;
2516
}
2517
2518
/*********************
2519
* FOOTNOTE PARSING *
2520
*********************/
2521
2522
/* is_footnote -- check if data holds a definition of a labeled footnote.
2523
* If so then append the corresponding element to `footnotes` array */
2524
static int is_footnote(
2525
const char *data, /* input text */
2526
size_t beg, /* offset of the beginning of the line */
2527
size_t end, /* offset of the end of the text */
2528
size_t *last, /* last character of the link */
2529
struct Blob * footnotes
2530
){
2531
size_t i, id_offset, id_end, upc_offset, upc_size;
2532
struct footnote fn = FOOTNOTE_INITIALIZER;
2533
2534
/* failfast if data is too short */
2535
if( beg+5>=end ) return 0;
2536
i = beg;
2537
2538
/* footnote definition must start at the beginning of a line */
2539
if( data[i]!='[' ) return 0;
2540
i++;
2541
if( data[i]!='^' ) return 0;
2542
id_offset = ++i;
2543
2544
/* id part: anything but a newline between brackets */
2545
while( i<end && data[i]!=']' && data[i]!='\n' && data[i]!='\r' ){ i++; }
2546
if( i>=end || data[i]!=']' ) return 0;
2547
id_end = i++;
2548
2549
/* spacer: colon (space | tab)* */
2550
if( i>=end || data[i]!=':' ) return 0;
2551
i++;
2552
while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2553
2554
/* passthrough truncated footnote definition */
2555
if( i>=end ) return 0;
2556
2557
if( build_ref_id(&fn.id, data+id_offset, id_end-id_offset)<0 ) return 0;
2558
2559
/* footnote's text may start on the same line after [^id]: */
2560
upc_offset = upc_size = 0;
2561
if( data[i]!='\n' && data[i]!='\r' ){
2562
size_t j;
2563
upc_size = is_footnote_classlist(data+i, end-i, 1);
2564
upc_offset = i; /* prevent further checks for a classlist */
2565
i += upc_size;
2566
j = i;
2567
while( i<end && data[i]!='\n' && data[i]!='\r' ){ i++; };
2568
if( i!=j )blob_append(&fn.text, data+j, i-j);
2569
if( i<end ){
2570
blob_append_char(&fn.text, data[i]);
2571
i++;
2572
if( i<end && data[i]=='\n' && data[i-1]=='\r' ){
2573
blob_append_char(&fn.text, data[i]);
2574
i++;
2575
}
2576
}
2577
}else{
2578
i++;
2579
if( i<end && data[i]=='\n' && data[i-1]=='\r' ) i++;
2580
}
2581
if( i<end ){
2582
2583
/* compute the indentation from the 2nd line */
2584
size_t indent = i;
2585
const char *spaces = data+i;
2586
while( indent<end && data[indent]==' ' ){ indent++; }
2587
if( indent>=end ) goto footnote_finish;
2588
indent -= i;
2589
if( indent<2 ) goto footnote_finish;
2590
2591
/* process the 2nd and subsequent lines */
2592
while( i+indent<end && memcmp(data+i,spaces,indent)==0 ){
2593
size_t j;
2594
i += indent;
2595
if( !upc_offset ){
2596
/* a classlist must be provided no later than at the 2nd line */
2597
upc_offset = i + sizeof_blank_prefix(data+i, end-i, 1);
2598
upc_size = is_footnote_classlist(data+upc_offset,
2599
end-upc_offset, 1);
2600
if( upc_size ){
2601
i = upc_offset + upc_size;
2602
}
2603
}
2604
j = i;
2605
while( i<end && data[i]!='\n' && data[i]!='\r' ){ i++; }
2606
if( i!=j ) blob_append(&fn.text, data+j, i-j);
2607
if( i>=end ) break;
2608
blob_append_char(&fn.text, data[i]);
2609
i++;
2610
if( i<end && data[i]=='\n' && data[i-1]=='\r' ){
2611
blob_append_char(&fn.text, data[i]);
2612
i++;
2613
}
2614
}
2615
}
2616
footnote_finish:
2617
if( !blob_size(&fn.text) ){
2618
blob_reset(&fn.id);
2619
return 0;
2620
}
2621
if( !blob_trim(&fn.text) ){ /* if the content is all-blank */
2622
if( upc_size ){ /* interpret UPC as plain text */
2623
blob_append(&fn.text, data+upc_offset, upc_size);
2624
upc_size = 0;
2625
}else{
2626
blob_reset(&fn.id); /* or clean up and fail */
2627
blob_reset(&fn.text);
2628
return 0;
2629
}
2630
}
2631
/* a valid note has been found */
2632
if( last ) *last = i;
2633
if( footnotes ){
2634
fn.defno = COUNT_FOOTNOTES( footnotes );
2635
if( upc_size ){
2636
assert( upc_offset && upc_offset+upc_size<end );
2637
blob_append(&fn.upc, data+upc_offset, upc_size);
2638
}
2639
blob_append(footnotes, (char *)&fn, sizeof fn);
2640
}
2641
return 1;
2642
}
2643
2644
/**********************
2645
* EXPORTED FUNCTIONS *
2646
**********************/
2647
2648
/* markdown -- parses the input buffer and renders it into the output buffer */
2649
void markdown(
2650
struct Blob *ob, /* output blob for rendered text */
2651
const struct Blob *ib, /* input blob in markdown */
2652
const struct mkd_renderer *rndrer /* renderer descriptor (callbacks) */
2653
){
2654
struct link_ref *lr;
2655
struct footnote *fn;
2656
int i;
2657
size_t beg, end = 0;
2658
struct render rndr;
2659
size_t size;
2660
Blob text = BLOB_INITIALIZER; /* input after the first pass */
2661
Blob * const allNotes = &rndr.notes.all;
2662
2663
/* filling the render structure */
2664
if( !rndrer ) return;
2665
rndr.make = *rndrer;
2666
rndr.nBlobCache = 0;
2667
rndr.iDepth = 0;
2668
rndr.refs = empty_blob;
2669
rndr.notes.all = empty_blob;
2670
rndr.notes.nMarks = 0;
2671
rndr.notes.misref.id = empty_blob;
2672
rndr.notes.misref.text = empty_blob;
2673
rndr.notes.misref.upc = empty_blob;
2674
rndr.notes.misref.bRndred = 0;
2675
rndr.notes.misref.nUsed = 0;
2676
rndr.notes.misref.iMark = -1;
2677
2678
for(i=0; i<256; i++) rndr.active_char[i] = 0;
2679
if( (rndr.make.emphasis
2680
|| rndr.make.double_emphasis
2681
|| rndr.make.triple_emphasis)
2682
&& rndr.make.emph_chars
2683
){
2684
for(i=0; rndr.make.emph_chars[i]; i++){
2685
rndr.active_char[(unsigned char)rndr.make.emph_chars[i]] = char_emphasis;
2686
}
2687
}
2688
if( rndr.make.codespan ) rndr.active_char['`'] = char_codespan;
2689
if( rndr.make.linebreak ) rndr.active_char['\n'] = char_linebreak;
2690
if( rndr.make.image || rndr.make.link ) rndr.active_char['['] = char_link;
2691
if( rndr.make.footnote_ref ) rndr.active_char['('] = char_footnote;
2692
rndr.active_char['<'] = char_langle_tag;
2693
rndr.active_char['\\'] = char_escape;
2694
rndr.active_char['&'] = char_entity;
2695
2696
/* first pass: iterate over lines looking for references,
2697
* copying everything else into "text" */
2698
beg = 0;
2699
for(size = blob_size(ib); beg<size ;){
2700
const char* const data = blob_buffer(ib);
2701
if( is_ref(data, beg, size, &end, &rndr.refs) ){
2702
beg = end;
2703
}else if(is_footnote(data, beg, size, &end, &rndr.notes.all)){
2704
beg = end;
2705
}else{ /* skipping to the next line */
2706
end = beg;
2707
while( end<size && data[end]!='\n' && data[end]!='\r' ){
2708
end += 1;
2709
}
2710
/* adding the line body if present */
2711
if( end>beg ) blob_append(&text, data + beg, end - beg);
2712
while( end<size && (data[end]=='\n' || data[end]=='\r') ){
2713
/* add one \n per newline */
2714
if( data[end]=='\n' || (end+1<size && data[end+1]!='\n') ){
2715
blob_append_char(&text, '\n');
2716
}
2717
end += 1;
2718
}
2719
beg = end;
2720
}
2721
}
2722
2723
/* sorting the reference array */
2724
if( blob_size(&rndr.refs) ){
2725
qsort(blob_buffer(&rndr.refs),
2726
blob_size(&rndr.refs)/sizeof(struct link_ref),
2727
sizeof(struct link_ref),
2728
cmp_link_ref_sort);
2729
}
2730
rndr.notes.nLbled = COUNT_FOOTNOTES( allNotes );
2731
2732
/* sort footnotes by ID and join duplicates */
2733
if( rndr.notes.nLbled > 1 ){
2734
int nDups = 0;
2735
fn = CAST_AS_FOOTNOTES( allNotes );
2736
qsort(fn, rndr.notes.nLbled, sizeof(struct footnote), cmp_footnote_id);
2737
2738
/* concatenate footnotes with equal labels */
2739
for(i=0; i<rndr.notes.nLbled ;){
2740
struct footnote *x = fn + i;
2741
int j = i+1;
2742
size_t k = blob_size(&x->text) + 64 + blob_size(&x->upc);
2743
while(j<rndr.notes.nLbled && !blob_compare(&x->id, &fn[j].id)){
2744
k += blob_size(&fn[j].text) + 10 + blob_size(&fn[j].upc);
2745
j++;
2746
nDups++;
2747
}
2748
if( i+1<j ){
2749
Blob list = empty_blob;
2750
blob_reserve(&list, k);
2751
/* must match _joined_footnote_indicator in html_footnote_item() */
2752
blob_append_literal(&list, "<ul class='fn-joined'>\n");
2753
for(k=i; (int)k<j; k++){
2754
struct footnote *y = fn + k;
2755
blob_append_literal(&list, "<li>");
2756
if( blob_size(&y->upc) ){
2757
blob_appendb(&list, &y->upc);
2758
blob_reset(&y->upc);
2759
}
2760
blob_appendb(&list, &y->text);
2761
blob_append_literal(&list, "</li>\n");
2762
2763
/* free memory buffer */
2764
blob_reset(&y->text);
2765
if( (int)k!=i ) blob_reset(&y->id);
2766
}
2767
blob_append_literal(&list, "</ul>\n");
2768
x->text = list;
2769
g.ftntsIssues[2]++;
2770
}
2771
i = j;
2772
}
2773
if( nDups ){ /* clean rndr.notes.all from invalidated footnotes */
2774
const int n = rndr.notes.nLbled - nDups;
2775
struct Blob filtered = empty_blob;
2776
blob_reserve(&filtered, n*sizeof(struct footnote));
2777
for(i=0; i<rndr.notes.nLbled; i++){
2778
if( blob_size(&fn[i].id) ){
2779
blob_append(&filtered, (char*)(fn+i), sizeof(struct footnote));
2780
}
2781
}
2782
blob_reset( allNotes );
2783
rndr.notes.all = filtered;
2784
rndr.notes.nLbled = n;
2785
assert( (int)(COUNT_FOOTNOTES(allNotes)) == rndr.notes.nLbled );
2786
}
2787
}
2788
fn = CAST_AS_FOOTNOTES( allNotes );
2789
for(i=0; i<rndr.notes.nLbled; i++){
2790
fn[i].index = i;
2791
}
2792
assert( rndr.notes.nMarks==0 );
2793
2794
/* second pass: actual rendering */
2795
if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
2796
parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
2797
2798
if( blob_size(allNotes) || rndr.notes.misref.nUsed ){
2799
2800
/* Footnotes must be parsed for the correct discovery of (back)links */
2801
Blob *notes = new_work_buffer( &rndr );
2802
if( blob_size(allNotes) ){
2803
Blob *tmp = new_work_buffer( &rndr );
2804
int nMarks = -1, maxDepth = 5;
2805
2806
/* inline notes may get appended to rndr.notes.all while rendering */
2807
while(1){
2808
struct footnote *aNotes;
2809
const int N = COUNT_FOOTNOTES( allNotes );
2810
2811
/* make a shallow copy of `allNotes` */
2812
blob_truncate(notes,0);
2813
blob_appendb(notes, allNotes);
2814
aNotes = CAST_AS_FOOTNOTES(notes);
2815
qsort(aNotes, N, sizeof(struct footnote), cmp_footnote_sort);
2816
2817
if( --maxDepth < 0 || nMarks == rndr.notes.nMarks ) break;
2818
nMarks = rndr.notes.nMarks;
2819
2820
for(i=0; i<N; i++){
2821
const int j = aNotes[i].index;
2822
struct footnote *x = CAST_AS_FOOTNOTES(allNotes) + j;
2823
assert( 0<=j && j<N );
2824
if( x->bRndred || !x->nUsed ) continue;
2825
assert( x->iMark > 0 );
2826
assert( blob_size(&x->text) );
2827
blob_truncate(tmp,0);
2828
2829
/* `allNotes` may be altered and extended through this call */
2830
parse_inline(tmp, &rndr, blob_buffer(&x->text), blob_size(&x->text));
2831
2832
x = CAST_AS_FOOTNOTES(allNotes) + j;
2833
blob_truncate(&x->text,0);
2834
blob_appendb(&x->text, tmp);
2835
x->bRndred = 1;
2836
}
2837
}
2838
release_work_buffer(&rndr,tmp);
2839
}
2840
2841
/* footnotes rendering */
2842
if( rndr.make.footnote_item && rndr.make.footnotes ){
2843
Blob *all_items = new_work_buffer(&rndr);
2844
int j = -1;
2845
2846
/* Assert that the in-memory layout of id, text and upc within
2847
** footnote struct matches the expectations of html_footnote_item()
2848
** If it doesn't then a compiler has done something very weird.
2849
*/
2850
assert( &(rndr.notes.misref.id) == &(rndr.notes.misref.text) - 1 );
2851
assert( &(rndr.notes.misref.upc) == &(rndr.notes.misref.text) + 1 );
2852
2853
for(i=0; i<(int)(COUNT_FOOTNOTES(notes)); i++){
2854
const struct footnote* x = CAST_AS_FOOTNOTES(notes) + i;
2855
const int xUsed = x->bRndred ? x->nUsed : 0;
2856
if( !x->iMark ) break;
2857
assert( x->nUsed );
2858
rndr.make.footnote_item(all_items, &x->text, x->iMark,
2859
xUsed, rndr.make.opaque);
2860
if( !xUsed ) g.ftntsIssues[3]++; /* an overnested footnote */
2861
j = i;
2862
}
2863
if( rndr.notes.misref.nUsed ){
2864
rndr.make.footnote_item(all_items, 0, -1,
2865
rndr.notes.misref.nUsed, rndr.make.opaque);
2866
g.ftntsIssues[0] += rndr.notes.misref.nUsed;
2867
}
2868
while( ++j < (int)(COUNT_FOOTNOTES(notes)) ){
2869
const struct footnote* x = CAST_AS_FOOTNOTES(notes) + j;
2870
assert( !x->iMark );
2871
assert( !x->nUsed );
2872
assert( !x->bRndred );
2873
rndr.make.footnote_item(all_items,&x->text,0,0,rndr.make.opaque);
2874
g.ftntsIssues[1]++;
2875
}
2876
rndr.make.footnotes(ob, all_items, rndr.make.opaque);
2877
release_work_buffer(&rndr, all_items);
2878
}
2879
release_work_buffer(&rndr, notes);
2880
}
2881
if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
2882
2883
/* clean-up */
2884
assert( rndr.iDepth==0 );
2885
blob_reset(&text);
2886
lr = (struct link_ref *)blob_buffer(&rndr.refs);
2887
end = blob_size(&rndr.refs)/sizeof(struct link_ref);
2888
for(i=0; i<(int)end; i++){
2889
blob_reset(&lr[i].id);
2890
blob_reset(&lr[i].link);
2891
blob_reset(&lr[i].title);
2892
}
2893
blob_reset(&rndr.refs);
2894
fn = CAST_AS_FOOTNOTES( allNotes );
2895
end = COUNT_FOOTNOTES( allNotes );
2896
for(i=0; i<(int)end; i++){
2897
if(blob_size(&fn[i].id)) blob_reset(&fn[i].id);
2898
if(blob_size(&fn[i].upc)) blob_reset(&fn[i].upc);
2899
blob_reset(&fn[i].text);
2900
}
2901
blob_reset(&rndr.notes.all);
2902
for(i=0; i<rndr.nBlobCache; i++){
2903
fossil_free(rndr.aBlobCache[i]);
2904
}
2905
}
2906

Keyboard Shortcuts

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