moredown 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,962 @@
1
+ /* markdown: a C implementation of John Gruber's Markdown markup language.
2
+ *
3
+ * Copyright (C) 2007 David L Parsons.
4
+ * The redistribution terms are provided in the COPYRIGHT file that must
5
+ * be distributed with this source code.
6
+ */
7
+ #include <stdio.h>
8
+ #include <string.h>
9
+ #include <stdarg.h>
10
+ #include <stdlib.h>
11
+ #include <time.h>
12
+ #include <ctype.h>
13
+
14
+ #include "config.h"
15
+
16
+ #include "cstring.h"
17
+ #include "markdown.h"
18
+ #include "amalloc.h"
19
+
20
+ /* block-level tags for passing html blocks through the blender
21
+ */
22
+ struct kw {
23
+ char *id;
24
+ int siz;
25
+ } ;
26
+
27
+ #define KW(x) { x, sizeof(x)-1 }
28
+
29
+ static struct kw blocktags[] = { KW("!--"), KW("STYLE"), KW("SCRIPT"),
30
+ KW("ADDRESS"), KW("BDO"), KW("BLOCKQUOTE"),
31
+ KW("CENTER"), KW("DFN"), KW("DIV"), KW("H1"),
32
+ KW("H2"), KW("H3"), KW("H4"), KW("H5"),
33
+ KW("H6"), KW("LISTING"), KW("NOBR"),
34
+ KW("UL"), KW("P"), KW("OL"), KW("DL"),
35
+ KW("PLAINTEXT"), KW("PRE"), KW("TABLE"),
36
+ KW("WBR"), KW("XMP"), KW("HR"), KW("BR") };
37
+ #define SZTAGS (sizeof blocktags / sizeof blocktags[0])
38
+ #define MAXTAG 11 /* sizeof "BLOCKQUOTE" */
39
+
40
+ typedef int (*stfu)(const void*,const void*);
41
+
42
+ typedef ANCHOR(Paragraph) ParagraphRoot;
43
+
44
+
45
+ /* case insensitive string sort (for qsort() and bsearch() of block tags)
46
+ */
47
+ static int
48
+ casort(struct kw *a, struct kw *b)
49
+ {
50
+ if ( a->siz != b->siz )
51
+ return a->siz - b->siz;
52
+ return strncasecmp(a->id, b->id, b->siz);
53
+ }
54
+
55
+
56
+ /* case insensitive string sort for Footnote tags.
57
+ */
58
+ int
59
+ __mkd_footsort(Footnote *a, Footnote *b)
60
+ {
61
+ int i;
62
+ char ac, bc;
63
+
64
+ if ( S(a->tag) != S(b->tag) )
65
+ return S(a->tag) - S(b->tag);
66
+
67
+ for ( i=0; i < S(a->tag); i++) {
68
+ ac = tolower(T(a->tag)[i]);
69
+ bc = tolower(T(b->tag)[i]);
70
+
71
+ if ( isspace(ac) && isspace(bc) )
72
+ continue;
73
+ if ( ac != bc )
74
+ return ac - bc;
75
+ }
76
+ return 0;
77
+ }
78
+
79
+
80
+ /* find the first blank character after position <i>
81
+ */
82
+ static int
83
+ nextblank(Line *t, int i)
84
+ {
85
+ while ( (i < S(t->text)) && !isspace(T(t->text)[i]) )
86
+ ++i;
87
+ return i;
88
+ }
89
+
90
+
91
+ /* find the next nonblank character after position <i>
92
+ */
93
+ static int
94
+ nextnonblank(Line *t, int i)
95
+ {
96
+ while ( (i < S(t->text)) && isspace(T(t->text)[i]) )
97
+ ++i;
98
+ return i;
99
+ }
100
+
101
+
102
+ /* find the first nonblank character on the Line.
103
+ */
104
+ int
105
+ mkd_firstnonblank(Line *p)
106
+ {
107
+ return nextnonblank(p,0);
108
+ }
109
+
110
+
111
+ static int
112
+ blankline(Line *p)
113
+ {
114
+ return ! (p && (S(p->text) > p->dle) );
115
+ }
116
+
117
+
118
+ static Line *
119
+ skipempty(Line *p)
120
+ {
121
+ while ( p && (p->dle == S(p->text)) )
122
+ p = p->next;
123
+ return p;
124
+ }
125
+
126
+
127
+ void
128
+ ___mkd_tidy(Line *t)
129
+ {
130
+ while ( S(t->text) && isspace(T(t->text)[S(t->text)-1]) )
131
+ --S(t->text);
132
+ }
133
+
134
+
135
+ static char *
136
+ isopentag(Line *p)
137
+ {
138
+ int i=0, len;
139
+ struct kw key, *ret;
140
+
141
+ if ( !p ) return 0;
142
+
143
+ len = S(p->text);
144
+
145
+ if ( len < 3 || T(p->text)[0] != '<' )
146
+ return 0;
147
+
148
+ /* find how long the tag is so we can check to see if
149
+ * it's a block-level tag
150
+ */
151
+ for ( i=1; i < len && T(p->text)[i] != '>'
152
+ && T(p->text)[i] != '/'
153
+ && !isspace(T(p->text)[i]); ++i )
154
+ ;
155
+
156
+ key.id = T(p->text)+1;
157
+ key.siz = i-1;
158
+
159
+ if ( ret = bsearch(&key,blocktags,SZTAGS,sizeof key, (stfu)casort))
160
+ return ret->id;
161
+
162
+ return 0;
163
+ }
164
+
165
+
166
+ static int
167
+ selfclose(Line *t, char *tag)
168
+ {
169
+ char *q = T(t->text);
170
+ int siz = strlen(tag);
171
+ int i;
172
+
173
+ if ( strcasecmp(tag, "HR") == 0 || strcasecmp(tag, "BR") == 0 )
174
+ /* <HR> and <BR> are self-closing block-level tags,
175
+ */
176
+ return 1;
177
+
178
+ i = S(t->text) - (siz + 3);
179
+
180
+ /* we specialcase start and end tags on the same line.
181
+ */
182
+ return ( i > 0 ) && (q[i] == '<') && (q[i+1] == '/')
183
+ && (q[i+2+siz] == '>')
184
+ && (strncasecmp(&q[i+2], tag, siz) == 0);
185
+ }
186
+
187
+
188
+ static Line *
189
+ htmlblock(Paragraph *p, char *tag)
190
+ {
191
+ Line *t = p->text, *ret;
192
+ int closesize, tagsize;
193
+ char close[MAXTAG+4];
194
+
195
+ char *ps, *pse;
196
+ int depth;
197
+
198
+ tagsize = strlen(tag);
199
+
200
+ if ( selfclose(t, tag) || (tagsize >= MAXTAG) ) {
201
+ ret = t->next;
202
+ t->next = 0;
203
+ return ret;
204
+ }
205
+
206
+ closesize = sprintf(close, "</%s>", tag);
207
+ depth = 0;
208
+
209
+ for ( ; t ; t = t->next) {
210
+ ps = T(t->text);
211
+ pse = ps + (S(t->text) - (tagsize + 1));
212
+ for ( ; ps < pse; ps++ ) {
213
+ if ( *ps == '<' ) {
214
+ /* check for close tag */
215
+ if ( strncasecmp(ps, close, closesize) == 0 ) {
216
+ depth--;
217
+ if ( depth == 0 ) {
218
+ ret = t->next;
219
+ t->next = 0;
220
+ return ret;
221
+ }
222
+ continue;
223
+ }
224
+
225
+ /* check for nested open tag */
226
+ if ( (strncasecmp(ps + 1, tag, tagsize) == 0) &&
227
+ (ps[tagsize + 1] == '>' || ps[tagsize + 1] == ' ') ) {
228
+ depth++;
229
+ }
230
+ }
231
+ }
232
+ }
233
+ return 0;
234
+ }
235
+
236
+
237
+ static Line *
238
+ comment(Paragraph *p, char *key)
239
+ {
240
+ Line *t, *ret;
241
+
242
+ for ( t = p->text; t ; t = t->next) {
243
+ if ( strstr(T(t->text), "-->") ) {
244
+ ret = t->next;
245
+ t->next = 0;
246
+ return ret;
247
+ }
248
+ }
249
+ return t;
250
+
251
+ }
252
+
253
+
254
+ /* footnotes look like ^<whitespace>{0,3}[stuff]: <content>$
255
+ */
256
+ static int
257
+ isfootnote(Line *t)
258
+ {
259
+ int i;
260
+
261
+ if ( ( (i = t->dle) > 3) || (T(t->text)[i] != '[') )
262
+ return 0;
263
+
264
+ for ( ++i; i < S(t->text) ; ++i ) {
265
+ if ( T(t->text)[i] == '[' )
266
+ return 0;
267
+ else if ( T(t->text)[i] == ']' && T(t->text)[i+1] == ':' )
268
+ return 1;
269
+ }
270
+ return 0;
271
+ }
272
+
273
+
274
+ static int
275
+ isquote(Line *t)
276
+ {
277
+ return ( T(t->text)[0] == '>' );
278
+ }
279
+
280
+
281
+ static int
282
+ dashchar(char c)
283
+ {
284
+ return (c == '*') || (c == '-') || (c == '_');
285
+ }
286
+
287
+
288
+ static int
289
+ iscode(Line *t)
290
+ {
291
+ return (t->dle >= 4);
292
+ }
293
+
294
+
295
+ static int
296
+ ishr(Line *t)
297
+ {
298
+ int i, count=0;
299
+ char dash = 0;
300
+ char c;
301
+
302
+ if ( iscode(t) ) return 0;
303
+
304
+ for ( i = 0; i < S(t->text); i++) {
305
+ c = T(t->text)[i];
306
+ if ( (dash == 0) && dashchar(c) )
307
+ dash = c;
308
+
309
+ if ( c == dash ) ++count;
310
+ else if ( !isspace(c) )
311
+ return 0;
312
+ }
313
+ return (count >= 3);
314
+ }
315
+
316
+
317
+ static int
318
+ ishdr(Line *t, int *htyp)
319
+ {
320
+ int i;
321
+
322
+
323
+ /* first check for etx-style ###HEADER###
324
+ */
325
+
326
+ /* leading run of `#`'s ?
327
+ */
328
+ for ( i=0; T(t->text)[i] == '#'; ++i)
329
+ ;
330
+
331
+ /* ANY leading `#`'s make this into an ETX header
332
+ */
333
+ if ( i ) {
334
+ *htyp = ETX;
335
+ return 1;
336
+ }
337
+
338
+ /* then check for setext-style HEADER
339
+ * ======
340
+ */
341
+
342
+ if ( t->next ) {
343
+ char *q = T(t->next->text);
344
+
345
+ if ( (*q == '=') || (*q == '-') ) {
346
+ for (i=1; i < S(t->next->text); i++)
347
+ if ( q[0] != q[i] )
348
+ return 0;
349
+ *htyp = SETEXT;
350
+ return 1;
351
+ }
352
+ }
353
+ return 0;
354
+ }
355
+
356
+
357
+ static int
358
+ isdefinition(Line *t)
359
+ {
360
+ #if DL_TAG_EXTENSION
361
+ return t && t->next
362
+ && (S(t->text) > 2)
363
+ && (t->dle == 0)
364
+ && (T(t->text)[0] == '=')
365
+ && (T(t->text)[S(t->text)-1] == '=')
366
+ && ( (t->next->dle >= 4) || isdefinition(t->next) );
367
+ #else
368
+ return 0;
369
+ #endif
370
+ }
371
+
372
+
373
+ static int
374
+ islist(Line *t, int *trim)
375
+ {
376
+ int i, j;
377
+ char *q;
378
+
379
+ if ( iscode(t) || blankline(t) || ishdr(t,&i) || ishr(t) )
380
+ return 0;
381
+
382
+ if ( isdefinition(t) ) {
383
+ *trim = 4;
384
+ return DL;
385
+ }
386
+
387
+ if ( strchr("*-+", T(t->text)[t->dle]) && isspace(T(t->text)[t->dle+1]) ) {
388
+ i = nextnonblank(t, t->dle+1);
389
+ *trim = (i > 4) ? 4 : i;
390
+ return UL;
391
+ }
392
+
393
+ if ( (j = nextblank(t,t->dle)) > t->dle ) {
394
+ if ( T(t->text)[j-1] == '.' ) {
395
+ #if ALPHA_LIST
396
+ if ( (j == t->dle + 2) && isalpha(T(t->text)[t->dle]) ) {
397
+ j = nextnonblank(t,j);
398
+ *trim = j;
399
+ return AL;
400
+ }
401
+ #endif
402
+ strtoul(T(t->text)+t->dle, &q, 10);
403
+ if ( (q > T(t->text)+t->dle) && (q == T(t->text) + (j-1)) ) {
404
+ j = nextnonblank(t,j);
405
+ *trim = j;
406
+ return OL;
407
+ }
408
+ }
409
+ }
410
+ return 0;
411
+ }
412
+
413
+
414
+ static Line *
415
+ headerblock(Paragraph *pp, int htyp)
416
+ {
417
+ Line *ret = 0;
418
+ Line *p = pp->text;
419
+ int i, j;
420
+
421
+ switch (htyp) {
422
+ case SETEXT:
423
+ /* p->text is header, p->next->text is -'s or ='s
424
+ */
425
+ pp->hnumber = (T(p->next->text)[0] == '=') ? 1 : 2;
426
+
427
+ ret = p->next->next;
428
+ ___mkd_freeLine(p->next);
429
+ p->next = 0;
430
+ break;
431
+
432
+ case ETX:
433
+ /* p->text is ###header###, so we need to trim off
434
+ * the leading and trailing `#`'s
435
+ */
436
+
437
+ for (i=0; (T(p->text)[i] == T(p->text)[0]) && (i < S(p->text)-1); i++)
438
+ ;
439
+
440
+ pp->hnumber = i;
441
+
442
+ while ( (i < S(p->text)) && isspace(T(p->text)[i]) )
443
+ ++i;
444
+
445
+ CLIP(p->text, 0, i);
446
+
447
+ for (j=S(p->text); (j > 1) && (T(p->text)[j-1] == '#'); --j)
448
+ ;
449
+
450
+ while ( j && isspace(T(p->text)[j-1]) )
451
+ --j;
452
+
453
+ S(p->text) = j;
454
+
455
+ ret = p->next;
456
+ p->next = 0;
457
+ break;
458
+ }
459
+ return ret;
460
+ }
461
+
462
+
463
+ static Line *
464
+ codeblock(Paragraph *p)
465
+ {
466
+ Line *t = p->text, *r;
467
+
468
+ for ( ; t; t = r ) {
469
+ CLIP(t->text,0,4);
470
+ t->dle = mkd_firstnonblank(t);
471
+
472
+ if ( !( (r = skipempty(t->next)) && iscode(r)) ) {
473
+ ___mkd_freeLineRange(t,r);
474
+ t->next = 0;
475
+ return r;
476
+ }
477
+ }
478
+ return t;
479
+ }
480
+
481
+
482
+ static int
483
+ centered(Line *first, Line *last)
484
+ {
485
+
486
+ if ( first&&last ) {
487
+ int len = S(last->text);
488
+
489
+ if ( (len > 2) && (strncmp(T(first->text), "->", 2) == 0)
490
+ && (strncmp(T(last->text)+len-2, "<-", 2) == 0) ) {
491
+ CLIP(first->text, 0, 2);
492
+ S(last->text) -= 2;
493
+ return CENTER;
494
+ }
495
+ }
496
+ return 0;
497
+ }
498
+
499
+
500
+ static int
501
+ endoftextblock(Line *t, int toplevelblock)
502
+ {
503
+ int z;
504
+
505
+ if ( blankline(t)||isquote(t)||iscode(t)||ishdr(t,&z)||ishr(t) )
506
+ return 1;
507
+
508
+ /* HORRIBLE STANDARDS KLUDGE: Toplevel paragraphs eat absorb adjacent
509
+ * list items, but sublevel blocks behave properly.
510
+ */
511
+ return toplevelblock ? 0 : islist(t,&z);
512
+ }
513
+
514
+
515
+ static Line *
516
+ textblock(Paragraph *p, int toplevel)
517
+ {
518
+ Line *t, *next;
519
+
520
+ for ( t = p->text; t ; t = next ) {
521
+ if ( ((next = t->next) == 0) || endoftextblock(next, toplevel) ) {
522
+ p->align = centered(p->text, t);
523
+ t->next = 0;
524
+ return next;
525
+ }
526
+ }
527
+ return t;
528
+ }
529
+
530
+
531
+ /* length of the id: or class: kind in a special div-not-quote block
532
+ */
533
+ static int
534
+ szmarkerclass(char *p)
535
+ {
536
+ if ( strncasecmp(p, "id:", 3) == 0 )
537
+ return 3;
538
+ if ( strncasecmp(p, "class:", 6) == 0 )
539
+ return 6;
540
+ return 0;
541
+ }
542
+
543
+
544
+ /*
545
+ * check if the first line of a quoted block is the special div-not-quote
546
+ * marker %[kind:]name%
547
+ */
548
+ static int
549
+ isdivmarker(Line *p)
550
+ {
551
+ #if DIV_QUOTE
552
+ char *s = T(p->text);
553
+ int len = S(p->text);
554
+ int i;
555
+
556
+ if ( !(len && s[0] == '%' && s[len-1] == '%') ) return 0;
557
+
558
+ i = szmarkerclass(s+1);
559
+ --len;
560
+
561
+ while ( ++i < len )
562
+ if ( !isalnum(s[i]) )
563
+ return 0;
564
+
565
+ return 1;
566
+ #else
567
+ return 0;
568
+ #endif
569
+ }
570
+
571
+
572
+ /*
573
+ * accumulate a blockquote.
574
+ *
575
+ * one sick horrible thing about blockquotes is that even though
576
+ * it just takes ^> to start a quote, following lines, if quoted,
577
+ * assume that the prefix is ``>''. This means that code needs
578
+ * to be indented *5* spaces from the leading '>', but *4* spaces
579
+ * from the start of the line. This does not appear to be
580
+ * documented in the reference implementation, but it's the
581
+ * way the markdown sample web form at Daring Fireball works.
582
+ */
583
+ static Line *
584
+ quoteblock(Paragraph *p)
585
+ {
586
+ Line *t, *q;
587
+ int qp;
588
+
589
+ for ( t = p->text; t ; t = q ) {
590
+ if ( isquote(t) ) {
591
+ qp = (T(t->text)[1] == ' ') ? 2 : 1;
592
+ CLIP(t->text, 0, qp);
593
+ t->dle = mkd_firstnonblank(t);
594
+ }
595
+
596
+ if ( !(q = skipempty(t->next)) || ((q != t->next) && !isquote(q)) ) {
597
+ ___mkd_freeLineRange(t, q);
598
+ t = q;
599
+ break;
600
+ }
601
+ }
602
+ if ( isdivmarker(p->text) ) {
603
+ char *prefix = "class";
604
+ int i;
605
+
606
+ q = p->text;
607
+ p->text = p->text->next;
608
+
609
+ if ( (i = szmarkerclass(1+T(q->text))) == 3 )
610
+ /* and this would be an "%id:" prefix */
611
+ prefix="id";
612
+
613
+ if ( p->ident = malloc(4+i+S(q->text)) )
614
+ sprintf(p->ident, "%s=\"%.*s\"", prefix, S(q->text)-(i+2),
615
+ T(q->text)+(i+1) );
616
+
617
+ ___mkd_freeLine(q);
618
+ }
619
+ return t;
620
+ }
621
+
622
+
623
+ static Paragraph *Pp(ParagraphRoot *, Line *, int);
624
+ static Paragraph *compile(Line *, int, MMIOT *);
625
+
626
+
627
+ /*
628
+ * pull in a list block. A list block starts with a list marker and
629
+ * runs until the next list marker, the next non-indented paragraph,
630
+ * or EOF. You do not have to indent nonblank lines after the list
631
+ * marker, but multiple paragraphs need to start with a 4-space indent.
632
+ */
633
+ static Line *
634
+ listitem(Paragraph *p, int indent)
635
+ {
636
+ Line *t, *q;
637
+ int clip = indent;
638
+ int z;
639
+
640
+ for ( t = p->text; t ; t = q) {
641
+ CLIP(t->text, 0, clip);
642
+ t->dle = mkd_firstnonblank(t);
643
+
644
+ if ( (q = skipempty(t->next)) == 0 ) {
645
+ ___mkd_freeLineRange(t,q);
646
+ return 0;
647
+ }
648
+
649
+ /* after a blank line, the next block needs to start with a line
650
+ * that's indented 4 spaces, but after that the line doesn't
651
+ * need any indentation
652
+ */
653
+ if ( q != t->next ) {
654
+ if (q->dle < 4) {
655
+ q = t->next;
656
+ t->next = 0;
657
+ return q;
658
+ }
659
+ indent = 4;
660
+ }
661
+
662
+ if ( (q->dle < indent) && (ishr(q) || islist(q,&z)) && !ishdr(q,&z) ) {
663
+ q = t->next;
664
+ t->next = 0;
665
+ return q;
666
+ }
667
+
668
+ clip = (q->dle > indent) ? indent : q->dle;
669
+ }
670
+ return t;
671
+ }
672
+
673
+
674
+ static Line *
675
+ listblock(Paragraph *top, int trim, MMIOT *f)
676
+ {
677
+ ParagraphRoot d = { 0, 0 };
678
+ Paragraph *p;
679
+ Line *q = top->text, *text;
680
+ Line *label;
681
+ int para = 0;
682
+
683
+ while (( text = q )) {
684
+ if ( top->typ == DL ) {
685
+ Line *lp;
686
+
687
+ for ( lp = label = text; lp ; lp = lp->next ) {
688
+ text = lp->next;
689
+ CLIP(lp->text, 0, 1);
690
+ S(lp->text)--;
691
+ if ( !isdefinition(lp->next) )
692
+ lp->next = 0;
693
+ }
694
+ }
695
+ else label = 0;
696
+
697
+ p = Pp(&d, text, LISTITEM);
698
+ text = listitem(p, trim);
699
+
700
+ p->down = compile(p->text, 0, f);
701
+ p->text = label;
702
+
703
+ if ( para && (top->typ != DL) && p->down ) p->down->align = PARA;
704
+
705
+ if ( !(q = skipempty(text)) || (islist(q, &trim) == 0) )
706
+ break;
707
+
708
+ if ( para = (q != text) ) {
709
+ Line anchor;
710
+
711
+ anchor.next = text;
712
+ ___mkd_freeLineRange(&anchor, q);
713
+ }
714
+
715
+ if ( para && (top->typ != DL) && p->down ) p->down->align = PARA;
716
+ }
717
+ top->text = 0;
718
+ top->down = T(d);
719
+ return text;
720
+ }
721
+
722
+
723
+ static int
724
+ tgood(char c)
725
+ {
726
+ switch (c) {
727
+ case '\'':
728
+ case '"': return c;
729
+ case '(': return ')';
730
+ }
731
+ return 0;
732
+ }
733
+
734
+
735
+ /*
736
+ * add a new (image or link) footnote to the footnote table
737
+ */
738
+ static Line*
739
+ addfootnote(Line *p, MMIOT* f)
740
+ {
741
+ int j, i;
742
+ int c;
743
+ Line *np = p->next;
744
+
745
+ Footnote *foot = &EXPAND(*f->footnotes);
746
+
747
+ CREATE(foot->tag);
748
+ CREATE(foot->link);
749
+ CREATE(foot->title);
750
+ foot->height = foot->width = 0;
751
+
752
+ for (j=i=p->dle+1; T(p->text)[j] != ']'; j++)
753
+ EXPAND(foot->tag) = T(p->text)[j];
754
+
755
+ EXPAND(foot->tag) = 0;
756
+ S(foot->tag)--;
757
+ j = nextnonblank(p, j+2);
758
+
759
+ while ( (j < S(p->text)) && !isspace(T(p->text)[j]) )
760
+ EXPAND(foot->link) = T(p->text)[j++];
761
+ EXPAND(foot->link) = 0;
762
+ S(foot->link)--;
763
+ j = nextnonblank(p,j);
764
+
765
+ if ( T(p->text)[j] == '=' ) {
766
+ sscanf(T(p->text)+j, "=%dx%d", &foot->width, &foot->height);
767
+ while ( (j < S(p->text)) && !isspace(T(p->text)[j]) )
768
+ ++j;
769
+ j = nextnonblank(p,j);
770
+ }
771
+
772
+
773
+ if ( (j >= S(p->text)) && np && np->dle && tgood(T(np->text)[np->dle]) ) {
774
+ ___mkd_freeLine(p);
775
+ p = np;
776
+ np = p->next;
777
+ j = p->dle;
778
+ }
779
+
780
+ if ( (c = tgood(T(p->text)[j])) ) {
781
+ /* Try to take the rest of the line as a comment; read to
782
+ * EOL, then shrink the string back to before the final
783
+ * quote.
784
+ */
785
+ ++j; /* skip leading quote */
786
+
787
+ while ( j < S(p->text) )
788
+ EXPAND(foot->title) = T(p->text)[j++];
789
+
790
+ while ( S(foot->title) && T(foot->title)[S(foot->title)-1] != c )
791
+ --S(foot->title);
792
+ if ( S(foot->title) ) /* skip trailing quote */
793
+ --S(foot->title);
794
+ EXPAND(foot->title) = 0;
795
+ --S(foot->title);
796
+ }
797
+
798
+ ___mkd_freeLine(p);
799
+ return np;
800
+ }
801
+
802
+
803
+ /*
804
+ * allocate a paragraph header, link it to the
805
+ * tail of the current document
806
+ */
807
+ static Paragraph *
808
+ Pp(ParagraphRoot *d, Line *ptr, int typ)
809
+ {
810
+ Paragraph *ret = calloc(sizeof *ret, 1);
811
+
812
+ ret->text = ptr;
813
+ ret->typ = typ;
814
+
815
+ return ATTACH(*d, ret);
816
+ }
817
+
818
+
819
+
820
+ static Line*
821
+ consume(Line *ptr, int *eaten)
822
+ {
823
+ Line *next;
824
+ int blanks=0;
825
+
826
+ for (; ptr && blankline(ptr); ptr = next, blanks++ ) {
827
+ next = ptr->next;
828
+ ___mkd_freeLine(ptr);
829
+ }
830
+ if ( ptr ) *eaten = blanks;
831
+ return ptr;
832
+ }
833
+
834
+
835
+ /*
836
+ * break a collection of markdown input into
837
+ * blocks of lists, code, html, and text to
838
+ * be marked up.
839
+ */
840
+ static Paragraph *
841
+ compile(Line *ptr, int toplevel, MMIOT *f)
842
+ {
843
+ ParagraphRoot d = { 0, 0 };
844
+ Paragraph *p = 0;
845
+ char *key;
846
+ Line *r;
847
+ int para = toplevel;
848
+ int hdr_type, list_type, indent;
849
+
850
+ ptr = consume(ptr, &para);
851
+
852
+ while ( ptr ) {
853
+ if ( toplevel && !(f->flags & DENY_HTML) && (key = isopentag(ptr)) ) {
854
+ p = Pp(&d, ptr, strcmp(key, "STYLE") == 0 ? STYLE : HTML);
855
+ if ( strcmp(key, "!--") == 0 )
856
+ ptr = comment(p, key);
857
+ else
858
+ ptr = htmlblock(p, key);
859
+ }
860
+ else if ( iscode(ptr) ) {
861
+ p = Pp(&d, ptr, CODE);
862
+
863
+ if ( f->flags & MKD_1_COMPAT) {
864
+ /* HORRIBLE STANDARDS KLUDGE: the first line of every block
865
+ * has trailing whitespace trimmed off.
866
+ */
867
+ ___mkd_tidy(p->text);
868
+ }
869
+
870
+ ptr = codeblock(p);
871
+ }
872
+ else if ( ishr(ptr) ) {
873
+ p = Pp(&d, 0, HR);
874
+ r = ptr;
875
+ ptr = ptr->next;
876
+ ___mkd_freeLine(r);
877
+ }
878
+ else if (( list_type = islist(ptr, &indent) )) {
879
+ p = Pp(&d, ptr, list_type);
880
+ ptr = listblock(p, indent, f);
881
+ }
882
+ else if ( isquote(ptr) ) {
883
+ p = Pp(&d, ptr, QUOTE);
884
+ ptr = quoteblock(p);
885
+ p->down = compile(p->text, 1, f);
886
+ p->text = 0;
887
+ }
888
+ else if ( ishdr(ptr, &hdr_type) ) {
889
+ p = Pp(&d, ptr, HDR);
890
+ ptr = headerblock(p, hdr_type);
891
+ }
892
+ else if ( toplevel && (isfootnote(ptr)) ) {
893
+ ptr = consume(addfootnote(ptr, f), &para);
894
+ continue;
895
+ }
896
+ else {
897
+ p = Pp(&d, ptr, MARKUP);
898
+ ptr = textblock(p, toplevel);
899
+ }
900
+
901
+ if ( (para||toplevel) && !p->align )
902
+ p->align = PARA;
903
+
904
+ para = toplevel;
905
+ ptr = consume(ptr, &para);
906
+
907
+ if ( para && !p->align )
908
+ p->align = PARA;
909
+
910
+ }
911
+ return T(d);
912
+ }
913
+
914
+
915
+ static void
916
+ initialize()
917
+ {
918
+ static int first = 1;
919
+
920
+ if ( first-- > 0 ) {
921
+ first = 0;
922
+ INITRNG(time(0));
923
+ qsort(blocktags, SZTAGS, sizeof blocktags[0], (stfu)casort);
924
+ }
925
+ }
926
+
927
+
928
+ /*
929
+ * the guts of the markdown() function, ripped out so I can do
930
+ * debugging.
931
+ */
932
+
933
+ /*
934
+ * prepare and compile `text`, returning a Paragraph tree.
935
+ */
936
+ int
937
+ mkd_compile(Document *doc, int flags)
938
+ {
939
+ if ( !doc )
940
+ return 0;
941
+
942
+ if ( doc->compiled )
943
+ return 1;
944
+
945
+ doc->compiled = 1;
946
+ memset(doc->ctx, 0, sizeof(MMIOT) );
947
+ doc->ctx->flags = flags & USER_FLAGS;
948
+ doc->ctx->base = doc->base;
949
+ CREATE(doc->ctx->in);
950
+ doc->ctx->footnotes = malloc(sizeof doc->ctx->footnotes[0]);
951
+ CREATE(*doc->ctx->footnotes);
952
+
953
+ initialize();
954
+
955
+ doc->code = compile(T(doc->content), 1, doc->ctx);
956
+ qsort(T(*doc->ctx->footnotes), S(*doc->ctx->footnotes),
957
+ sizeof T(*doc->ctx->footnotes)[0],
958
+ (stfu)__mkd_footsort);
959
+ memset(&doc->content, 0, sizeof doc->content);
960
+ return 1;
961
+ }
962
+