rdiscountwl 1.0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/generate.c ADDED
@@ -0,0 +1,1865 @@
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
+ typedef int (*stfu)(const void*,const void*);
21
+ typedef void (*spanhandler)(MMIOT*,int);
22
+
23
+ /* forward declarations */
24
+ static void text(MMIOT *f);
25
+ static Paragraph *display(Paragraph*, MMIOT*);
26
+
27
+ /* externals from markdown.c */
28
+ int __mkd_footsort(Footnote *, Footnote *);
29
+
30
+ /*
31
+ * push text into the generator input buffer
32
+ */
33
+ static void
34
+ push(char *bfr, int size, MMIOT *f)
35
+ {
36
+ while ( size-- > 0 )
37
+ EXPAND(f->in) = *bfr++;
38
+ }
39
+
40
+
41
+ /*
42
+ * push a character into the generator input buffer
43
+ */
44
+ static void
45
+ pushc(char c, MMIOT *f)
46
+ {
47
+ EXPAND(f->in) = c;
48
+ }
49
+
50
+
51
+ /* look <i> characters ahead of the cursor.
52
+ */
53
+ static inline int
54
+ peek(MMIOT *f, int i)
55
+ {
56
+
57
+ i += (f->isp-1);
58
+
59
+ return (i >= 0) && (i < S(f->in)) ? T(f->in)[i] : EOF;
60
+ }
61
+
62
+
63
+ /* pull a byte from the input buffer
64
+ */
65
+ static inline int
66
+ pull(MMIOT *f)
67
+ {
68
+ return ( f->isp < S(f->in) ) ? T(f->in)[f->isp++] : EOF;
69
+ }
70
+
71
+
72
+ /* return a pointer to the current position in the input buffer.
73
+ */
74
+ static inline char*
75
+ cursor(MMIOT *f)
76
+ {
77
+ return T(f->in) + f->isp;
78
+ }
79
+
80
+
81
+ static inline int
82
+ isthisspace(MMIOT *f, int i)
83
+ {
84
+ int c = peek(f, i);
85
+
86
+ if ( c == EOF )
87
+ return 1;
88
+ if ( c & 0x80 )
89
+ return 0;
90
+ return isspace(c) || (c < ' ');
91
+ }
92
+
93
+
94
+ static inline int
95
+ isthisalnum(MMIOT *f, int i)
96
+ {
97
+ int c = peek(f, i);
98
+
99
+ return (c != EOF) && isalnum(c);
100
+ }
101
+
102
+
103
+ static inline int
104
+ isthisnonword(MMIOT *f, int i)
105
+ {
106
+ return isthisspace(f, i) || ispunct(peek(f,i));
107
+ }
108
+
109
+
110
+ /* return/set the current cursor position
111
+ */
112
+ #define mmiotseek(f,x) (f->isp = x)
113
+ #define mmiottell(f) (f->isp)
114
+
115
+
116
+ /* move n characters forward ( or -n characters backward) in the input buffer.
117
+ */
118
+ static void
119
+ shift(MMIOT *f, int i)
120
+ {
121
+ if (f->isp + i >= 0 )
122
+ f->isp += i;
123
+ }
124
+
125
+
126
+ /* Qchar()
127
+ */
128
+ static void
129
+ Qchar(int c, MMIOT *f)
130
+ {
131
+ block *cur;
132
+
133
+ if ( S(f->Q) == 0 ) {
134
+ cur = &EXPAND(f->Q);
135
+ memset(cur, 0, sizeof *cur);
136
+ cur->b_type = bTEXT;
137
+ }
138
+ else
139
+ cur = &T(f->Q)[S(f->Q)-1];
140
+
141
+ EXPAND(cur->b_text) = c;
142
+
143
+ }
144
+
145
+
146
+ /* Qstring()
147
+ */
148
+ static void
149
+ Qstring(char *s, MMIOT *f)
150
+ {
151
+ while (*s)
152
+ Qchar(*s++, f);
153
+ }
154
+
155
+
156
+ /* Qwrite()
157
+ */
158
+ static void
159
+ Qwrite(char *s, int size, MMIOT *f)
160
+ {
161
+ while (size-- > 0)
162
+ Qchar(*s++, f);
163
+ }
164
+
165
+
166
+ /* Qprintf()
167
+ */
168
+ static void
169
+ Qprintf(MMIOT *f, char *fmt, ...)
170
+ {
171
+ char bfr[80];
172
+ va_list ptr;
173
+
174
+ va_start(ptr,fmt);
175
+ vsnprintf(bfr, sizeof bfr, fmt, ptr);
176
+ va_end(ptr);
177
+ Qstring(bfr, f);
178
+ }
179
+
180
+
181
+ /* Qem()
182
+ */
183
+ static void
184
+ Qem(MMIOT *f, char c, int count)
185
+ {
186
+ block *p = &EXPAND(f->Q);
187
+
188
+ memset(p, 0, sizeof *p);
189
+ p->b_type = (c == '*') ? bSTAR : bUNDER;
190
+ p->b_char = c;
191
+ p->b_count = count;
192
+
193
+ memset(&EXPAND(f->Q), 0, sizeof(block));
194
+ }
195
+
196
+
197
+ /* generate html from a markup fragment
198
+ */
199
+ void
200
+ ___mkd_reparse(char *bfr, int size, int flags, MMIOT *f, char *esc)
201
+ {
202
+ MMIOT sub;
203
+ struct escaped e;
204
+
205
+ ___mkd_initmmiot(&sub, f->footnotes);
206
+
207
+ sub.flags = f->flags | flags;
208
+ sub.cb = f->cb;
209
+ sub.ref_prefix = f->ref_prefix;
210
+
211
+ if ( esc ) {
212
+ sub.esc = &e;
213
+ e.up = f->esc;
214
+ e.text = esc;
215
+ }
216
+ else
217
+ sub.esc = f->esc;
218
+
219
+ push(bfr, size, &sub);
220
+ pushc(0, &sub);
221
+ S(sub.in)--;
222
+
223
+ text(&sub);
224
+ ___mkd_emblock(&sub);
225
+
226
+ Qwrite(T(sub.out), S(sub.out), f);
227
+
228
+ ___mkd_freemmiot(&sub, f->footnotes);
229
+ }
230
+
231
+
232
+ /*
233
+ * check the escape list for special cases
234
+ */
235
+ static int
236
+ escaped(MMIOT *f, char c)
237
+ {
238
+ struct escaped *thing = f->esc;
239
+
240
+ while ( thing ) {
241
+ if ( strchr(thing->text, c) )
242
+ return 1;
243
+ thing = thing->up;
244
+ }
245
+ return 0;
246
+ }
247
+
248
+
249
+ /*
250
+ * write out a url, escaping problematic characters
251
+ */
252
+ static void
253
+ puturl(char *s, int size, MMIOT *f, int display)
254
+ {
255
+ unsigned char c;
256
+
257
+ while ( size-- > 0 ) {
258
+ c = *s++;
259
+
260
+ if ( c == '\\' && size-- > 0 ) {
261
+ c = *s++;
262
+
263
+ if ( !( ispunct(c) || isspace(c) ) )
264
+ Qchar('\\', f);
265
+ }
266
+
267
+ if ( c == '&' )
268
+ Qstring("&amp;", f);
269
+ else if ( c == '<' )
270
+ Qstring("&lt;", f);
271
+ else if ( c == '"' )
272
+ Qstring("%22", f);
273
+ else if ( isalnum(c) || ispunct(c) || (display && isspace(c)) )
274
+ Qchar(c, f);
275
+ else if ( c == MKD_EOLN ) /* untokenize hard return */
276
+ Qstring(" ", f);
277
+ else
278
+ Qprintf(f, "%%%02X", c);
279
+ }
280
+ }
281
+
282
+
283
+ /* advance forward until the next character is not whitespace
284
+ */
285
+ static int
286
+ eatspace(MMIOT *f)
287
+ {
288
+ int c;
289
+
290
+ for ( ; ((c=peek(f, 1)) != EOF) && isspace(c); pull(f) )
291
+ ;
292
+ return c;
293
+ }
294
+
295
+
296
+ /* (match (a (nested (parenthetical (string.)))))
297
+ */
298
+ static int
299
+ parenthetical(int in, int out, MMIOT *f)
300
+ {
301
+ int size, indent, c;
302
+
303
+ for ( indent=1,size=0; indent; size++ ) {
304
+ if ( (c = pull(f)) == EOF )
305
+ return EOF;
306
+ else if ( (c == '\\') && (peek(f,1) == out || peek(f,1) == in) ) {
307
+ ++size;
308
+ pull(f);
309
+ }
310
+ else if ( c == in )
311
+ ++indent;
312
+ else if ( c == out )
313
+ --indent;
314
+ }
315
+ return size ? (size-1) : 0;
316
+ }
317
+
318
+
319
+ /* extract a []-delimited label from the input stream.
320
+ */
321
+ static int
322
+ linkylabel(MMIOT *f, Cstring *res)
323
+ {
324
+ char *ptr = cursor(f);
325
+ int size;
326
+
327
+ if ( (size = parenthetical('[',']',f)) != EOF ) {
328
+ T(*res) = ptr;
329
+ S(*res) = size;
330
+ return 1;
331
+ }
332
+ return 0;
333
+ }
334
+
335
+
336
+ /* see if the quote-prefixed linky segment is actually a title.
337
+ */
338
+ static int
339
+ linkytitle(MMIOT *f, char quote, Footnote *ref)
340
+ {
341
+ int whence = mmiottell(f);
342
+ char *title = cursor(f);
343
+ char *e;
344
+ register int c;
345
+
346
+ while ( (c = pull(f)) != EOF ) {
347
+ e = cursor(f);
348
+ if ( c == quote ) {
349
+ if ( (c = eatspace(f)) == ')' ) {
350
+ T(ref->title) = 1+title;
351
+ S(ref->title) = (e-title)-2;
352
+ return 1;
353
+ }
354
+ }
355
+ }
356
+ mmiotseek(f, whence);
357
+ return 0;
358
+ }
359
+
360
+
361
+ /* extract a =HHHxWWW size from the input stream
362
+ */
363
+ static int
364
+ linkysize(MMIOT *f, Footnote *ref)
365
+ {
366
+ int height=0, width=0;
367
+ int whence = mmiottell(f);
368
+ int c;
369
+
370
+ if ( isspace(peek(f,0)) ) {
371
+ pull(f); /* eat '=' */
372
+
373
+ for ( c = pull(f); isdigit(c); c = pull(f))
374
+ width = (width * 10) + (c - '0');
375
+
376
+ if ( c == 'x' ) {
377
+ for ( c = pull(f); isdigit(c); c = pull(f))
378
+ height = (height*10) + (c - '0');
379
+
380
+ if ( isspace(c) )
381
+ c = eatspace(f);
382
+
383
+ if ( (c == ')') || ((c == '\'' || c == '"') && linkytitle(f, c, ref)) ) {
384
+ ref->height = height;
385
+ ref->width = width;
386
+ return 1;
387
+ }
388
+ }
389
+ }
390
+ mmiotseek(f, whence);
391
+ return 0;
392
+ }
393
+
394
+
395
+ /* extract a <...>-encased url from the input stream.
396
+ * (markdown 1.0.2b8 compatibility; older versions
397
+ * of markdown treated the < and > as syntactic
398
+ * sugar that didn't have to be there. 1.0.2b8
399
+ * requires a closing >, and then falls into the
400
+ * title or closing )
401
+ */
402
+ static int
403
+ linkybroket(MMIOT *f, int image, Footnote *p)
404
+ {
405
+ int c;
406
+ int good = 0;
407
+
408
+ T(p->link) = cursor(f);
409
+ for ( S(p->link)=0; (c = pull(f)) != '>'; ++S(p->link) ) {
410
+ /* pull in all input until a '>' is found, or die trying.
411
+ */
412
+ if ( c == EOF )
413
+ return 0;
414
+ else if ( (c == '\\') && ispunct(peek(f,2)) ) {
415
+ ++S(p->link);
416
+ pull(f);
417
+ }
418
+ }
419
+
420
+ c = eatspace(f);
421
+
422
+ /* next nonspace needs to be a title, a size, or )
423
+ */
424
+ if ( ( c == '\'' || c == '"' ) && linkytitle(f,c,p) )
425
+ good=1;
426
+ else if ( image && (c == '=') && linkysize(f,p) )
427
+ good=1;
428
+ else
429
+ good=( c == ')' );
430
+
431
+ if ( good ) {
432
+ if ( peek(f, 1) == ')' )
433
+ pull(f);
434
+
435
+ ___mkd_tidy(&p->link);
436
+ }
437
+
438
+ return good;
439
+ } /* linkybroket */
440
+
441
+
442
+ /* extract a (-prefixed url from the input stream.
443
+ * the label is either of the format `<link>`, where I
444
+ * extract until I find a >, or it is of the format
445
+ * `text`, where I extract until I reach a ')', a quote,
446
+ * or (if image) a '='
447
+ */
448
+ static int
449
+ linkyurl(MMIOT *f, int image, Footnote *p)
450
+ {
451
+ int c;
452
+ int mayneedtotrim=0;
453
+
454
+ if ( (c = eatspace(f)) == EOF )
455
+ return 0;
456
+
457
+ if ( c == '<' ) {
458
+ pull(f);
459
+ if ( !(f->flags & MKD_1_COMPAT) )
460
+ return linkybroket(f,image,p);
461
+ mayneedtotrim=1;
462
+ }
463
+
464
+ T(p->link) = cursor(f);
465
+ for ( S(p->link)=0; (c = peek(f,1)) != ')'; ++S(p->link) ) {
466
+ if ( c == EOF )
467
+ return 0;
468
+ else if ( (c == '"' || c == '\'') && linkytitle(f, c, p) )
469
+ break;
470
+ else if ( image && (c == '=') && linkysize(f, p) )
471
+ break;
472
+ else if ( (c == '\\') && ispunct(peek(f,2)) ) {
473
+ ++S(p->link);
474
+ pull(f);
475
+ }
476
+ pull(f);
477
+ }
478
+ if ( peek(f, 1) == ')' )
479
+ pull(f);
480
+
481
+ ___mkd_tidy(&p->link);
482
+
483
+ if ( mayneedtotrim && (T(p->link)[S(p->link)-1] == '>') )
484
+ --S(p->link);
485
+
486
+ return 1;
487
+ }
488
+
489
+
490
+
491
+ /* prefixes for <automatic links>
492
+ */
493
+ static struct _protocol {
494
+ char *name;
495
+ int nlen;
496
+ } protocol[] = {
497
+ #define _aprotocol(x) { x, (sizeof x)-1 }
498
+ _aprotocol( "https:" ),
499
+ _aprotocol( "http:" ),
500
+ _aprotocol( "news:" ),
501
+ _aprotocol( "ftp:" ),
502
+ #undef _aprotocol
503
+ };
504
+ #define NRPROTOCOLS (sizeof protocol / sizeof protocol[0])
505
+
506
+
507
+ static int
508
+ isautoprefix(char *text, int size)
509
+ {
510
+ int i;
511
+ struct _protocol *p;
512
+
513
+ for (i=0, p=protocol; i < NRPROTOCOLS; i++, p++)
514
+ if ( (size >= p->nlen) && strncasecmp(text, p->name, p->nlen) == 0 )
515
+ return 1;
516
+ return 0;
517
+ }
518
+
519
+
520
+ /*
521
+ * all the tag types that linkylinky can produce are
522
+ * defined by this structure.
523
+ */
524
+ typedef struct linkytype {
525
+ char *pat;
526
+ int szpat;
527
+ char *link_pfx; /* tag prefix and link pointer (eg: "<a href="\"" */
528
+ char *link_sfx; /* link suffix (eg: "\"" */
529
+ int WxH; /* this tag allows width x height arguments */
530
+ char *text_pfx; /* text prefix (eg: ">" */
531
+ char *text_sfx; /* text suffix (eg: "</a>" */
532
+ int flags; /* reparse flags */
533
+ int kind; /* tag is url or something else? */
534
+ #define IS_URL 0x01
535
+ } linkytype;
536
+
537
+ static linkytype imaget = { 0, 0, "<img src=\"", "\"",
538
+ 1, " alt=\"", "\" />", MKD_NOIMAGE|MKD_TAGTEXT, IS_URL };
539
+ static linkytype linkt = { 0, 0, "<a href=\"", "\"",
540
+ 0, ">", "</a>", MKD_NOLINKS, IS_URL };
541
+
542
+ /*
543
+ * pseudo-protocols for [][];
544
+ *
545
+ * id: generates <a id="link">tag</a>
546
+ * class: generates <span class="link">tag</span>
547
+ * raw: just dump the link without any processing
548
+ */
549
+ static linkytype specials[] = {
550
+ { "id:", 3, "<span id=\"", "\"", 0, ">", "</span>", 0, 0 },
551
+ { "raw:", 4, 0, 0, 0, 0, 0, MKD_NOHTML, 0 },
552
+ { "lang:", 5, "<span lang=\"", "\"", 0, ">", "</span>", 0, 0 },
553
+ { "abbr:", 5, "<abbr title=\"", "\"", 0, ">", "</abbr>", 0, 0 },
554
+ { "class:", 6, "<span class=\"", "\"", 0, ">", "</span>", 0, 0 },
555
+ } ;
556
+
557
+ #define NR(x) (sizeof x / sizeof x[0])
558
+
559
+ /* see if t contains one of our pseudo-protocols.
560
+ */
561
+ static linkytype *
562
+ pseudo(Cstring t)
563
+ {
564
+ int i;
565
+ linkytype *r;
566
+
567
+ for ( i=0, r=specials; i < NR(specials); i++,r++ ) {
568
+ if ( (S(t) > r->szpat) && (strncasecmp(T(t), r->pat, r->szpat) == 0) )
569
+ return r;
570
+ }
571
+ return 0;
572
+ }
573
+
574
+
575
+ /* print out the start of an `img' or `a' tag, applying callbacks as needed.
576
+ */
577
+ static void
578
+ printlinkyref(MMIOT *f, linkytype *tag, char *link, int size)
579
+ {
580
+ char *edit;
581
+
582
+ if ( f->flags & IS_LABEL )
583
+ return;
584
+
585
+ Qstring(tag->link_pfx, f);
586
+
587
+ if ( tag->kind & IS_URL ) {
588
+ if ( f->cb && f->cb->e_url && (edit = (*f->cb->e_url)(link, size, f->cb->e_data)) ) {
589
+ puturl(edit, strlen(edit), f, 0);
590
+ if ( f->cb->e_free ) (*f->cb->e_free)(edit, f->cb->e_data);
591
+ }
592
+ else
593
+ puturl(link + tag->szpat, size - tag->szpat, f, 0);
594
+ }
595
+ else
596
+ ___mkd_reparse(link + tag->szpat, size - tag->szpat, MKD_TAGTEXT, f, 0);
597
+
598
+ Qstring(tag->link_sfx, f);
599
+
600
+ if ( f->cb && f->cb->e_flags && (edit = (*f->cb->e_flags)(link, size, f->cb->e_data)) ) {
601
+ Qchar(' ', f);
602
+ Qstring(edit, f);
603
+ if ( f->cb->e_free ) (*f->cb->e_free)(edit, f->cb->e_data);
604
+ }
605
+ } /* printlinkyref */
606
+
607
+
608
+ /* helper function for php markdown extra footnotes; allow the user to
609
+ * define a prefix tag instead of just `fn`
610
+ */
611
+ static char *
612
+ p_or_nothing(p)
613
+ MMIOT *p;
614
+ {
615
+ return p->ref_prefix ? p->ref_prefix : "fn";
616
+ }
617
+
618
+
619
+ /* php markdown extra/daring fireball style print footnotes
620
+ */
621
+ static int
622
+ extra_linky(MMIOT *f, Cstring text, Footnote *ref)
623
+ {
624
+ if ( ref->flags & REFERENCED )
625
+ return 0;
626
+
627
+ if ( f->flags & IS_LABEL )
628
+ ___mkd_reparse(T(text), S(text), linkt.flags, f, 0);
629
+ else {
630
+ ref->flags |= REFERENCED;
631
+ ref->refnumber = ++ f->footnotes->reference;
632
+ Qprintf(f, "<sup id=\"%sref:%d\"><a href=\"#%s:%d\" rel=\"footnote\">%d</a></sup>",
633
+ p_or_nothing(f), ref->refnumber,
634
+ p_or_nothing(f), ref->refnumber, ref->refnumber);
635
+ }
636
+ return 1;
637
+ } /* extra_linky */
638
+
639
+
640
+ /* print out a linky (or fail if it's Not Allowed)
641
+ */
642
+ static int
643
+ linkyformat(MMIOT *f, Cstring text, int image, Footnote *ref)
644
+ {
645
+ linkytype *tag;
646
+
647
+
648
+ if ( image )
649
+ tag = &imaget;
650
+ else if ( tag = pseudo(ref->link) ) {
651
+ if ( f->flags & (MKD_NO_EXT|MKD_SAFELINK) )
652
+ return 0;
653
+ }
654
+ else if ( (f->flags & MKD_SAFELINK) && T(ref->link)
655
+ && (T(ref->link)[0] != '/')
656
+ && !isautoprefix(T(ref->link), S(ref->link)) )
657
+ /* if MKD_SAFELINK, only accept links that are local or
658
+ * a well-known protocol
659
+ */
660
+ return 0;
661
+ else
662
+ tag = &linkt;
663
+
664
+ if ( f->flags & tag->flags )
665
+ return 0;
666
+
667
+ if ( f->flags & IS_LABEL )
668
+ ___mkd_reparse(T(text), S(text), tag->flags, f, 0);
669
+ else if ( tag->link_pfx ) {
670
+ printlinkyref(f, tag, T(ref->link), S(ref->link));
671
+
672
+ if ( tag->WxH ) {
673
+ if ( ref->height ) Qprintf(f," height=\"%d\"", ref->height);
674
+ if ( ref->width ) Qprintf(f, " width=\"%d\"", ref->width);
675
+ }
676
+
677
+ if ( S(ref->title) ) {
678
+ Qstring(" title=\"", f);
679
+ ___mkd_reparse(T(ref->title), S(ref->title), MKD_TAGTEXT, f, 0);
680
+ Qchar('"', f);
681
+ }
682
+
683
+ Qstring(tag->text_pfx, f);
684
+ ___mkd_reparse(T(text), S(text), tag->flags, f, 0);
685
+ Qstring(tag->text_sfx, f);
686
+ }
687
+ else
688
+ Qwrite(T(ref->link) + tag->szpat, S(ref->link) - tag->szpat, f);
689
+
690
+ return 1;
691
+ } /* linkyformat */
692
+
693
+
694
+ /*
695
+ * process embedded links and images
696
+ */
697
+ static int
698
+ linkylinky(int image, MMIOT *f)
699
+ {
700
+ int start = mmiottell(f);
701
+ Cstring name;
702
+ Footnote key, *ref;
703
+
704
+ int status = 0;
705
+ int extra_footnote = 0;
706
+
707
+ CREATE(name);
708
+ memset(&key, 0, sizeof key);
709
+
710
+ if ( linkylabel(f, &name) ) {
711
+ if ( peek(f,1) == '(' ) {
712
+ pull(f);
713
+ if ( linkyurl(f, image, &key) )
714
+ status = linkyformat(f, name, image, &key);
715
+ }
716
+ else {
717
+ int goodlink, implicit_mark = mmiottell(f);
718
+
719
+ if ( isspace(peek(f,1)) )
720
+ pull(f);
721
+
722
+ if ( peek(f,1) == '[' ) {
723
+ pull(f); /* consume leading '[' */
724
+ goodlink = linkylabel(f, &key.tag);
725
+ }
726
+ else {
727
+ /* new markdown implicit name syntax doesn't
728
+ * require a second []
729
+ */
730
+ mmiotseek(f, implicit_mark);
731
+ goodlink = !(f->flags & MKD_1_COMPAT);
732
+
733
+ if ( (f->flags & MKD_EXTRA_FOOTNOTE) && (!image) && S(name) && T(name)[0] == '^' )
734
+ extra_footnote = 1;
735
+ }
736
+
737
+ if ( goodlink ) {
738
+ if ( !S(key.tag) ) {
739
+ DELETE(key.tag);
740
+ T(key.tag) = T(name);
741
+ S(key.tag) = S(name);
742
+ }
743
+
744
+ if ( ref = bsearch(&key, T(f->footnotes->note),
745
+ S(f->footnotes->note),
746
+ sizeof key, (stfu)__mkd_footsort) ) {
747
+ if ( extra_footnote )
748
+ status = extra_linky(f,name,ref);
749
+ else
750
+ status = linkyformat(f, name, image, ref);
751
+ }
752
+ }
753
+ }
754
+ }
755
+
756
+ DELETE(name);
757
+ ___mkd_freefootnote(&key);
758
+
759
+ if ( status == 0 )
760
+ mmiotseek(f, start);
761
+
762
+ return status;
763
+ }
764
+
765
+
766
+ /* write a character to output, doing text escapes ( & -> &amp;,
767
+ * > -> &gt; < -> &lt; )
768
+ */
769
+ static void
770
+ cputc(int c, MMIOT *f)
771
+ {
772
+ switch (c) {
773
+ case '&': Qstring("&amp;", f); break;
774
+ case '>': Qstring("&gt;", f); break;
775
+ case '<': Qstring("&lt;", f); break;
776
+ default : Qchar(c, f); break;
777
+ }
778
+ }
779
+
780
+
781
+ /*
782
+ * convert an email address to a string of nonsense
783
+ */
784
+ static void
785
+ mangle(char *s, int len, MMIOT *f)
786
+ {
787
+ while ( len-- > 0 ) {
788
+ #if DEBIAN_GLITCH
789
+ Qprintf(f, "&#%02d;", *((unsigned char*)(s++)) );
790
+ #else
791
+ Qstring("&#", f);
792
+ Qprintf(f, COINTOSS() ? "x%02x;" : "%02d;", *((unsigned char*)(s++)) );
793
+ #endif
794
+ }
795
+ }
796
+
797
+
798
+ /* nrticks() -- count up a row of tick marks
799
+ */
800
+ static int
801
+ nrticks(int offset, int tickchar, MMIOT *f)
802
+ {
803
+ int tick = 0;
804
+
805
+ while ( peek(f, offset+tick) == tickchar ) tick++;
806
+
807
+ return tick;
808
+ } /* nrticks */
809
+
810
+
811
+ /* matchticks() -- match a certain # of ticks, and if that fails
812
+ * match the largest subset of those ticks.
813
+ *
814
+ * if a subset was matched, return the # of ticks
815
+ * that were matched.
816
+ */
817
+ static int
818
+ matchticks(MMIOT *f, int tickchar, int ticks, int *endticks)
819
+ {
820
+ int size, count, c;
821
+ int subsize=0, subtick=0;
822
+
823
+ *endticks = ticks;
824
+ for (size = 0; (c=peek(f,size+ticks)) != EOF; size ++) {
825
+ if ( (c == tickchar) && ( count = nrticks(size+ticks,tickchar,f)) ) {
826
+ if ( count == ticks )
827
+ return size;
828
+ else if ( count ) {
829
+ if ( (count > subtick) && (count < ticks) ) {
830
+ subsize = size;
831
+ subtick = count;
832
+ }
833
+ size += count;
834
+ }
835
+ }
836
+ }
837
+ if ( subsize ) {
838
+ *endticks = subtick;
839
+ return subsize;
840
+ }
841
+ return 0;
842
+ } /* matchticks */
843
+
844
+
845
+ /* code() -- write a string out as code. The only characters that have
846
+ * special meaning in a code block are * `<' and `&' , which
847
+ * are /always/ expanded to &lt; and &amp;
848
+ */
849
+ static void
850
+ code(MMIOT *f, char *s, int length)
851
+ {
852
+ int i,c;
853
+
854
+ for ( i=0; i < length; i++ )
855
+ if ( (c = s[i]) == MKD_EOLN) /* ^C: expand back to 2 spaces */
856
+ Qstring(" ", f);
857
+ else if ( c == '\\' && (i < length-1) && escaped(f, s[i+1]) )
858
+ cputc(s[++i], f);
859
+ else
860
+ cputc(c, f);
861
+ } /* code */
862
+
863
+ /* delspan() -- write out a chunk of text, blocking with <del>...</del>
864
+ */
865
+ static void
866
+ delspan(MMIOT *f, int size)
867
+ {
868
+ Qstring("<del>", f);
869
+ ___mkd_reparse(cursor(f)-1, size, 0, f, 0);
870
+ Qstring("</del>", f);
871
+ }
872
+
873
+
874
+ /* codespan() -- write out a chunk of text as code, trimming one
875
+ * space off the front and/or back as appropriate.
876
+ */
877
+ static void
878
+ codespan(MMIOT *f, int size)
879
+ {
880
+ int i=0;
881
+
882
+ if ( size > 1 && peek(f, size-1) == ' ' ) --size;
883
+ if ( peek(f,i) == ' ' ) ++i, --size;
884
+
885
+ Qstring("<code>", f);
886
+ code(f, cursor(f)+(i-1), size);
887
+ Qstring("</code>", f);
888
+ } /* codespan */
889
+
890
+
891
+ /* before letting a tag through, validate against
892
+ * MKD_NOLINKS and MKD_NOIMAGE
893
+ */
894
+ static int
895
+ forbidden_tag(MMIOT *f)
896
+ {
897
+ int c = toupper(peek(f, 1));
898
+
899
+ if ( f->flags & MKD_NOHTML )
900
+ return 1;
901
+
902
+ if ( c == 'A' && (f->flags & MKD_NOLINKS) && !isthisalnum(f,2) )
903
+ return 1;
904
+ if ( c == 'I' && (f->flags & MKD_NOIMAGE)
905
+ && strncasecmp(cursor(f)+1, "MG", 2) == 0
906
+ && !isthisalnum(f,4) )
907
+ return 1;
908
+ return 0;
909
+ }
910
+
911
+
912
+ /* Check a string to see if it looks like a mail address
913
+ * "looks like a mail address" means alphanumeric + some
914
+ * specials, then a `@`, then alphanumeric + some specials,
915
+ * but with a `.`
916
+ */
917
+ static int
918
+ maybe_address(char *p, int size)
919
+ {
920
+ int ok = 0;
921
+
922
+ for ( ;size && (isalnum(*p) || strchr("._-+*", *p)); ++p, --size)
923
+ ;
924
+
925
+ if ( ! (size && *p == '@') )
926
+ return 0;
927
+
928
+ --size, ++p;
929
+
930
+ if ( size && *p == '.' ) return 0;
931
+
932
+ for ( ;size && (isalnum(*p) || strchr("._-+", *p)); ++p, --size )
933
+ if ( *p == '.' && size > 1 ) ok = 1;
934
+
935
+ return size ? 0 : ok;
936
+ }
937
+
938
+
939
+ /* The size-length token at cursor(f) is either a mailto:, an
940
+ * implicit mailto:, one of the approved url protocols, or just
941
+ * plain old text. If it's a mailto: or an approved protocol,
942
+ * linkify it, otherwise say "no"
943
+ */
944
+ static int
945
+ process_possible_link(MMIOT *f, int size)
946
+ {
947
+ int address= 0;
948
+ int mailto = 0;
949
+ char *text = cursor(f);
950
+
951
+ if ( f->flags & MKD_NOLINKS ) return 0;
952
+
953
+ if ( (size > 7) && strncasecmp(text, "mailto:", 7) == 0 ) {
954
+ /* if it says it's a mailto, it's a mailto -- who am
955
+ * I to second-guess the user?
956
+ */
957
+ address = 1;
958
+ mailto = 7; /* 7 is the length of "mailto:"; we need this */
959
+ }
960
+ else
961
+ address = maybe_address(text, size);
962
+
963
+ if ( address ) {
964
+ Qstring("<a href=\"", f);
965
+ if ( !mailto ) {
966
+ /* supply a mailto: protocol if one wasn't attached */
967
+ mangle("mailto:", 7, f);
968
+ }
969
+ mangle(text, size, f);
970
+ Qstring("\">", f);
971
+ mangle(text+mailto, size-mailto, f);
972
+ Qstring("</a>", f);
973
+ return 1;
974
+ }
975
+ else if ( isautoprefix(text, size) ) {
976
+ printlinkyref(f, &linkt, text, size);
977
+ Qchar('>', f);
978
+ puturl(text,size,f, 1);
979
+ Qstring("</a>", f);
980
+ return 1;
981
+ }
982
+ return 0;
983
+ } /* process_possible_link */
984
+
985
+
986
+ /* a < may be just a regular character, the start of an embedded html
987
+ * tag, or the start of an <automatic link>. If it's an automatic
988
+ * link, we also need to know if it's an email address because if it
989
+ * is we need to mangle it in our futile attempt to cut down on the
990
+ * spaminess of the rendered page.
991
+ */
992
+ static int
993
+ maybe_tag_or_link(MMIOT *f)
994
+ {
995
+ int c, size;
996
+ int maybetag = 1;
997
+
998
+ if ( f->flags & MKD_TAGTEXT )
999
+ return 0;
1000
+
1001
+ for ( size=0; (c = peek(f, size+1)) != '>'; size++) {
1002
+ if ( c == EOF )
1003
+ return 0;
1004
+ else if ( c == '\\' ) {
1005
+ maybetag=0;
1006
+ if ( peek(f, size+2) != EOF )
1007
+ size++;
1008
+ }
1009
+ else if ( isspace(c) )
1010
+ break;
1011
+ else if ( ! (c == '/'
1012
+ || (f->flags & MKD_GITHUBTAGS && (c == '-' || c == '_'))
1013
+ || isalnum(c) ) )
1014
+ maybetag=0;
1015
+ }
1016
+
1017
+ if ( size ) {
1018
+ if ( maybetag || (size >= 3 && strncmp(cursor(f), "!--", 3) == 0) ) {
1019
+
1020
+ /* It is not a html tag unless we find the closing '>' in
1021
+ * the same block.
1022
+ */
1023
+ while ( (c = peek(f, size+1)) != '>' )
1024
+ if ( c == EOF )
1025
+ return 0;
1026
+ else
1027
+ size++;
1028
+
1029
+ if ( forbidden_tag(f) )
1030
+ return 0;
1031
+
1032
+ Qchar('<', f);
1033
+ while ( ((c = peek(f, 1)) != EOF) && (c != '>') )
1034
+ Qchar(pull(f), f);
1035
+ return 1;
1036
+ }
1037
+ else if ( !isspace(c) && process_possible_link(f, size) ) {
1038
+ shift(f, size+1);
1039
+ return 1;
1040
+ }
1041
+ }
1042
+
1043
+ return 0;
1044
+ }
1045
+
1046
+
1047
+ /* autolinking means that all inline html is <a href'ified>. A
1048
+ * autolink url is alphanumerics, slashes, periods, underscores,
1049
+ * the at sign, colon, and the % character.
1050
+ */
1051
+ static int
1052
+ maybe_autolink(MMIOT *f)
1053
+ {
1054
+ register int c;
1055
+ int size;
1056
+
1057
+ /* greedily scan forward for the end of a legitimate link.
1058
+ */
1059
+ for ( size=0; (c=peek(f, size+1)) != EOF; size++ ) {
1060
+ if ( c == '\\' ) {
1061
+ if ( peek(f, size+2) != EOF )
1062
+ ++size;
1063
+ }
1064
+ else if ( c & 0x80 ) /* HACK: ignore utf-8 extended characters */
1065
+ continue;
1066
+ else if ( isspace(c) || strchr("'\"()[]{}<>`", c) || c == MKD_EOLN )
1067
+ break;
1068
+ }
1069
+
1070
+ if ( (size > 1) && process_possible_link(f, size) ) {
1071
+ shift(f, size);
1072
+ return 1;
1073
+ }
1074
+ return 0;
1075
+ }
1076
+
1077
+
1078
+ /* smartyquote code that's common for single and double quotes
1079
+ */
1080
+ static int
1081
+ smartyquote(int *flags, char typeofquote, MMIOT *f)
1082
+ {
1083
+ int bit = (typeofquote == 's') ? 0x01 : 0x02;
1084
+
1085
+ if ( bit & (*flags) ) {
1086
+ if ( isthisnonword(f,1) ) {
1087
+ Qprintf(f, "&r%cquo;", typeofquote);
1088
+ (*flags) &= ~bit;
1089
+ return 1;
1090
+ }
1091
+ }
1092
+ else if ( isthisnonword(f,-1) && peek(f,1) != EOF ) {
1093
+ Qprintf(f, "&l%cquo;", typeofquote);
1094
+ (*flags) |= bit;
1095
+ return 1;
1096
+ }
1097
+ return 0;
1098
+ }
1099
+
1100
+
1101
+ static int
1102
+ islike(MMIOT *f, char *s)
1103
+ {
1104
+ int len;
1105
+ int i;
1106
+
1107
+ if ( s[0] == '|' ) {
1108
+ if ( !isthisnonword(f, -1) )
1109
+ return 0;
1110
+ ++s;
1111
+ }
1112
+
1113
+ if ( !(len = strlen(s)) )
1114
+ return 0;
1115
+
1116
+ if ( s[len-1] == '|' ) {
1117
+ if ( !isthisnonword(f,len-1) )
1118
+ return 0;
1119
+ len--;
1120
+ }
1121
+
1122
+ for (i=1; i < len; i++)
1123
+ if (tolower(peek(f,i)) != s[i])
1124
+ return 0;
1125
+ return 1;
1126
+ }
1127
+
1128
+
1129
+ static struct smarties {
1130
+ char c0;
1131
+ char *pat;
1132
+ char *entity;
1133
+ int shift;
1134
+ } smarties[] = {
1135
+ { '\'', "'s|", "rsquo", 0 },
1136
+ { '\'', "'t|", "rsquo", 0 },
1137
+ { '\'', "'re|", "rsquo", 0 },
1138
+ { '\'', "'ll|", "rsquo", 0 },
1139
+ { '\'', "'ve|", "rsquo", 0 },
1140
+ { '\'', "'m|", "rsquo", 0 },
1141
+ { '\'', "'d|", "rsquo", 0 },
1142
+ { '-', "---", "mdash", 2 },
1143
+ { '-', "--", "ndash", 1 },
1144
+ { '.', "...", "hellip", 2 },
1145
+ { '.', ". . .", "hellip", 4 },
1146
+ { '(', "(c)", "copy", 2 },
1147
+ { '(', "(r)", "reg", 2 },
1148
+ { '(', "(tm)", "trade", 3 },
1149
+ { '3', "|3/4|", "frac34", 2 },
1150
+ { '3', "|3/4ths|", "frac34", 2 },
1151
+ { '1', "|1/2|", "frac12", 2 },
1152
+ { '1', "|1/4|", "frac14", 2 },
1153
+ { '1', "|1/4th|", "frac14", 2 },
1154
+ { '&', "&#0;", 0, 3 },
1155
+ } ;
1156
+ #define NRSMART ( sizeof smarties / sizeof smarties[0] )
1157
+
1158
+
1159
+ /* Smarty-pants-style chrome for quotes, -, ellipses, and (r)(c)(tm)
1160
+ */
1161
+ static int
1162
+ smartypants(int c, int *flags, MMIOT *f)
1163
+ {
1164
+ int i;
1165
+
1166
+ if ( f->flags & (MKD_NOPANTS|MKD_TAGTEXT|IS_LABEL) )
1167
+ return 0;
1168
+
1169
+ for ( i=0; i < NRSMART; i++)
1170
+ if ( (c == smarties[i].c0) && islike(f, smarties[i].pat) ) {
1171
+ if ( smarties[i].entity )
1172
+ Qprintf(f, "&%s;", smarties[i].entity);
1173
+ shift(f, smarties[i].shift);
1174
+ return 1;
1175
+ }
1176
+
1177
+ switch (c) {
1178
+ case '<' : return 0;
1179
+ case '\'': if ( smartyquote(flags, 's', f) ) return 1;
1180
+ break;
1181
+
1182
+ case '"': if ( smartyquote(flags, 'd', f) ) return 1;
1183
+ break;
1184
+
1185
+ case '`': if ( peek(f, 1) == '`' ) {
1186
+ int j = 2;
1187
+
1188
+ while ( (c=peek(f,j)) != EOF ) {
1189
+ if ( c == '\\' )
1190
+ j += 2;
1191
+ else if ( c == '`' )
1192
+ break;
1193
+ else if ( c == '\'' && peek(f, j+1) == '\'' ) {
1194
+ Qstring("&ldquo;", f);
1195
+ ___mkd_reparse(cursor(f)+1, j-2, 0, f, 0);
1196
+ Qstring("&rdquo;", f);
1197
+ shift(f,j+1);
1198
+ return 1;
1199
+ }
1200
+ else ++j;
1201
+ }
1202
+
1203
+ }
1204
+ break;
1205
+ }
1206
+ return 0;
1207
+ } /* smartypants */
1208
+
1209
+
1210
+ /* process latex with arbitrary 2-character ( $$ .. $$, \[ .. \], \( .. \)
1211
+ * delimiters
1212
+ */
1213
+ static int
1214
+ mathhandler(MMIOT *f, int e1, int e2)
1215
+ {
1216
+ int i = 0;
1217
+
1218
+ while(peek(f, ++i) != EOF) {
1219
+ if (peek(f, i) == e1 && peek(f, i+1) == e2) {
1220
+ cputc(peek(f,-1), f);
1221
+ cputc(peek(f, 0), f);
1222
+ while ( i-- > -1 )
1223
+ cputc(pull(f), f);
1224
+ return 1;
1225
+ }
1226
+ }
1227
+ return 0;
1228
+ }
1229
+
1230
+
1231
+ /* process a body of text encased in some sort of tick marks. If it
1232
+ * works, generate the output and return 1, otherwise just return 0 and
1233
+ * let the caller figure it out.
1234
+ */
1235
+ static int
1236
+ tickhandler(MMIOT *f, int tickchar, int minticks, int allow_space, spanhandler spanner)
1237
+ {
1238
+ int endticks, size;
1239
+ int tick = nrticks(0, tickchar, f);
1240
+
1241
+ if ( !allow_space && isspace(peek(f,tick)) )
1242
+ return 0;
1243
+
1244
+ if ( (tick >= minticks) && (size = matchticks(f,tickchar,tick,&endticks)) ) {
1245
+ if ( endticks < tick ) {
1246
+ size += (tick - endticks);
1247
+ tick = endticks;
1248
+ }
1249
+
1250
+ shift(f, tick);
1251
+ (*spanner)(f,size);
1252
+ shift(f, size+tick-1);
1253
+ return 1;
1254
+ }
1255
+ return 0;
1256
+ }
1257
+
1258
+ #define tag_text(f) (f->flags & MKD_TAGTEXT)
1259
+
1260
+
1261
+ static void
1262
+ text(MMIOT *f)
1263
+ {
1264
+ int c, j;
1265
+ int rep;
1266
+ int smartyflags = 0;
1267
+
1268
+ while (1) {
1269
+ if ( (f->flags & MKD_AUTOLINK) && isalpha(peek(f,1)) && !tag_text(f) )
1270
+ maybe_autolink(f);
1271
+
1272
+ c = pull(f);
1273
+
1274
+ if (c == EOF)
1275
+ break;
1276
+
1277
+ if ( smartypants(c, &smartyflags, f) )
1278
+ continue;
1279
+ switch (c) {
1280
+ case 0: break;
1281
+
1282
+ case MKD_EOLN:
1283
+ Qstring(tag_text(f) ? " " : "<br/>", f);
1284
+ break;
1285
+
1286
+ case '>': if ( tag_text(f) )
1287
+ Qstring("&gt;", f);
1288
+ else
1289
+ Qchar(c, f);
1290
+ break;
1291
+
1292
+ case '"': if ( tag_text(f) )
1293
+ Qstring("&quot;", f);
1294
+ else
1295
+ Qchar(c, f);
1296
+ break;
1297
+
1298
+ case '!': if ( peek(f,1) == '[' ) {
1299
+ pull(f);
1300
+ if ( tag_text(f) || !linkylinky(1, f) )
1301
+ Qstring("![", f);
1302
+ }
1303
+ else
1304
+ Qchar(c, f);
1305
+ break;
1306
+
1307
+ case '[': if ( tag_text(f) || !linkylinky(0, f) )
1308
+ Qchar(c, f);
1309
+ break;
1310
+ /* A^B -> A<sup>B</sup> */
1311
+ case '^': if ( (f->flags & (MKD_NOSUPERSCRIPT|MKD_STRICT|MKD_TAGTEXT))
1312
+ || (isthisnonword(f,-1) && peek(f,-1) != ')')
1313
+ || isthisspace(f,1) )
1314
+ Qchar(c,f);
1315
+ else {
1316
+ char *sup = cursor(f);
1317
+ int len = 0;
1318
+
1319
+ if ( peek(f,1) == '(' ) {
1320
+ int here = mmiottell(f);
1321
+ pull(f);
1322
+
1323
+ if ( (len = parenthetical('(',')',f)) <= 0 ) {
1324
+ mmiotseek(f,here);
1325
+ Qchar(c, f);
1326
+ break;
1327
+ }
1328
+ sup++;
1329
+ }
1330
+ else {
1331
+ while ( isthisalnum(f,1+len) )
1332
+ ++len;
1333
+ if ( !len ) {
1334
+ Qchar(c,f);
1335
+ break;
1336
+ }
1337
+ shift(f,len);
1338
+ }
1339
+ Qstring("<sup>",f);
1340
+ ___mkd_reparse(sup, len, 0, f, "()");
1341
+ Qstring("</sup>", f);
1342
+ }
1343
+ break;
1344
+ case '_':
1345
+ /* Underscores don't count if they're in the middle of a word */
1346
+ if ( !(f->flags & (MKD_NORELAXED|MKD_STRICT))
1347
+ && isthisalnum(f,-1)
1348
+ && isthisalnum(f,1) ) {
1349
+ Qchar(c, f);
1350
+ break;
1351
+ }
1352
+ case '*':
1353
+ /* Underscores & stars don't count if they're out in the middle
1354
+ * of whitespace */
1355
+ if ( isthisspace(f,-1) && isthisspace(f,1) ) {
1356
+ Qchar(c, f);
1357
+ break;
1358
+ }
1359
+ /* else fall into the regular old emphasis case */
1360
+ if ( tag_text(f) )
1361
+ Qchar(c, f);
1362
+ else {
1363
+ for (rep = 1; peek(f,1) == c; pull(f) )
1364
+ ++rep;
1365
+ Qem(f,c,rep);
1366
+ }
1367
+ break;
1368
+
1369
+ case '~': if ( (f->flags & (MKD_NOSTRIKETHROUGH|MKD_TAGTEXT|MKD_STRICT)) || ! tickhandler(f,c,2,0, delspan) )
1370
+ Qchar(c, f);
1371
+ break;
1372
+
1373
+ case '`': if ( tag_text(f) || !tickhandler(f,c,1,1,codespan) )
1374
+ Qchar(c, f);
1375
+ break;
1376
+
1377
+ case '\\': switch ( c = pull(f) ) {
1378
+ case '&': Qstring("&amp;", f);
1379
+ break;
1380
+ case '<': c = peek(f,1);
1381
+ if ( (c == EOF) || isspace(c) )
1382
+ Qstring("&lt;", f);
1383
+ else {
1384
+ /* Markdown.pl does not escape <[nonwhite]
1385
+ * sequences */
1386
+ Qchar('\\', f);
1387
+ shift(f, -1);
1388
+ }
1389
+
1390
+ break;
1391
+ case '^': if ( f->flags & (MKD_STRICT|MKD_NOSUPERSCRIPT) ) {
1392
+ Qchar('\\', f);
1393
+ shift(f,-1);
1394
+ break;
1395
+ }
1396
+ Qchar(c, f);
1397
+ break;
1398
+
1399
+ case ':': case '|':
1400
+ if ( f->flags & MKD_NOTABLES ) {
1401
+ Qchar('\\', f);
1402
+ shift(f,-1);
1403
+ break;
1404
+ }
1405
+ Qchar(c, f);
1406
+ break;
1407
+
1408
+ case EOF: Qchar('\\', f);
1409
+ break;
1410
+
1411
+ case '[':
1412
+ case '(': if ( mathhandler(f, '\\', (c =='(')?')':']') )
1413
+ break;
1414
+ /* else fall through to default */
1415
+
1416
+ default: if ( escaped(f,c) ||
1417
+ strchr(">#.-+{}]![*_\\()`", c) )
1418
+ Qchar(c, f);
1419
+ else {
1420
+ Qchar('\\', f);
1421
+ shift(f, -1);
1422
+ }
1423
+ break;
1424
+ }
1425
+ break;
1426
+
1427
+ case '<': if ( !maybe_tag_or_link(f) )
1428
+ Qstring("&lt;", f);
1429
+ break;
1430
+
1431
+ case '&': j = (peek(f,1) == '#' ) ? 2 : 1;
1432
+ while ( isthisalnum(f,j) )
1433
+ ++j;
1434
+
1435
+ if ( peek(f,j) != ';' )
1436
+ Qstring("&amp;", f);
1437
+ else
1438
+ Qchar(c, f);
1439
+ break;
1440
+
1441
+ case '$': if ( peek(f, 1) == '$' ) {
1442
+ pull(f);
1443
+ if ( mathhandler(f, '$', '$') )
1444
+ break;
1445
+ Qchar('$', f);
1446
+ }
1447
+ /* fall through to default */
1448
+
1449
+ default: Qchar(c, f);
1450
+ break;
1451
+ }
1452
+ }
1453
+ /* truncate the input string after we've finished processing it */
1454
+ S(f->in) = f->isp = 0;
1455
+ } /* text */
1456
+
1457
+
1458
+ /* print a header block
1459
+ */
1460
+ static void
1461
+ printheader(Paragraph *pp, MMIOT *f)
1462
+ {
1463
+ if ( f->flags & MKD_IDANCHOR ) {
1464
+ Qprintf(f, "<h%d", pp->hnumber);
1465
+ if ( f->flags & MKD_TOC ) {
1466
+ Qstring(" id=\"", f);
1467
+ mkd_string_to_anchor(T(pp->text->text),
1468
+ S(pp->text->text),
1469
+ (mkd_sta_function_t)Qchar, f, 1, f->flags);
1470
+ Qchar('"', f);
1471
+ }
1472
+ Qchar('>', f);
1473
+ } else {
1474
+ if ( f->flags & MKD_TOC ) {
1475
+ Qstring("<a name=\"", f);
1476
+ mkd_string_to_anchor(T(pp->text->text),
1477
+ S(pp->text->text),
1478
+ (mkd_sta_function_t)Qchar, f, 1, f->flags);
1479
+ Qstring("\"></a>\n", f);
1480
+ }
1481
+ Qprintf(f, "<h%d>", pp->hnumber);
1482
+ }
1483
+ push(T(pp->text->text), S(pp->text->text), f);
1484
+ text(f);
1485
+ Qprintf(f, "</h%d>", pp->hnumber);
1486
+ }
1487
+
1488
+
1489
+ enum e_alignments { a_NONE, a_CENTER, a_LEFT, a_RIGHT };
1490
+
1491
+ static char* alignments[] = { "", " style=\"text-align:center;\"",
1492
+ " style=\"text-align:left;\"",
1493
+ " style=\"text-align:right;\"" };
1494
+
1495
+ typedef STRING(int) Istring;
1496
+
1497
+ static int
1498
+ splat(Line *p, char *block, Istring align, int force, MMIOT *f)
1499
+ {
1500
+ int first,
1501
+ idx = p->dle,
1502
+ colno = 0;
1503
+
1504
+
1505
+ ___mkd_tidy(&p->text);
1506
+ if ( T(p->text)[S(p->text)-1] == '|' )
1507
+ --S(p->text);
1508
+
1509
+ Qstring("<tr>\n", f);
1510
+ while ( idx < S(p->text) ) {
1511
+ first = idx;
1512
+ if ( force && (colno >= S(align)-1) )
1513
+ idx = S(p->text);
1514
+ else
1515
+ while ( (idx < S(p->text)) && (T(p->text)[idx] != '|') ) {
1516
+ if ( T(p->text)[idx] == '\\' )
1517
+ ++idx;
1518
+ ++idx;
1519
+ }
1520
+
1521
+ Qprintf(f, "<%s%s>",
1522
+ block,
1523
+ alignments[ (colno < S(align)) ? T(align)[colno] : a_NONE ]);
1524
+ ___mkd_reparse(T(p->text)+first, idx-first, 0, f, "|");
1525
+ Qprintf(f, "</%s>\n", block);
1526
+ idx++;
1527
+ colno++;
1528
+ }
1529
+ if ( force )
1530
+ while (colno < S(align) ) {
1531
+ Qprintf(f, "<%s></%s>\n", block, block);
1532
+ ++colno;
1533
+ }
1534
+ Qstring("</tr>\n", f);
1535
+ return colno;
1536
+ }
1537
+
1538
+
1539
+ static int
1540
+ printtable(Paragraph *pp, MMIOT *f)
1541
+ {
1542
+ /* header, dashes, then lines of content */
1543
+
1544
+ Line *hdr, *dash, *body;
1545
+ Istring align;
1546
+ int hcols,start;
1547
+ char *p;
1548
+ enum e_alignments it;
1549
+
1550
+ hdr = pp->text;
1551
+ dash= hdr->next;
1552
+ body= dash->next;
1553
+
1554
+ if ( T(hdr->text)[hdr->dle] == '|' ) {
1555
+ /* trim leading pipe off all lines
1556
+ */
1557
+ Line *r;
1558
+ for ( r = pp->text; r; r = r->next )
1559
+ r->dle ++;
1560
+ }
1561
+
1562
+ /* figure out cell alignments */
1563
+
1564
+ CREATE(align);
1565
+
1566
+ for (p=T(dash->text), start=dash->dle; start < S(dash->text); ) {
1567
+ char first, last;
1568
+ int end;
1569
+
1570
+ last=first=0;
1571
+ for (end=start ; (end < S(dash->text)) && p[end] != '|'; ++ end ) {
1572
+ if ( p[end] == '\\' )
1573
+ ++ end;
1574
+ else if ( !isspace(p[end]) ) {
1575
+ if ( !first) first = p[end];
1576
+ last = p[end];
1577
+ }
1578
+ }
1579
+ it = ( first == ':' ) ? (( last == ':') ? a_CENTER : a_LEFT)
1580
+ : (( last == ':') ? a_RIGHT : a_NONE );
1581
+
1582
+ EXPAND(align) = it;
1583
+ start = 1+end;
1584
+ }
1585
+
1586
+ Qstring("<table>\n", f);
1587
+ Qstring("<thead>\n", f);
1588
+ hcols = splat(hdr, "th", align, 0, f);
1589
+ Qstring("</thead>\n", f);
1590
+
1591
+ if ( hcols < S(align) )
1592
+ S(align) = hcols;
1593
+ else
1594
+ while ( hcols > S(align) )
1595
+ EXPAND(align) = a_NONE;
1596
+
1597
+ Qstring("<tbody>\n", f);
1598
+ for ( ; body; body = body->next)
1599
+ splat(body, "td", align, 1, f);
1600
+ Qstring("</tbody>\n", f);
1601
+ Qstring("</table>\n", f);
1602
+
1603
+ DELETE(align);
1604
+ return 1;
1605
+ }
1606
+
1607
+
1608
+ static int
1609
+ printblock(Paragraph *pp, MMIOT *f)
1610
+ {
1611
+ Line *t = pp->text;
1612
+ static char *Begin[] = { "", "<p>", "<p style=\"text-align:center;\">" };
1613
+ static char *End[] = { "", "</p>","</p>" };
1614
+
1615
+ while (t) {
1616
+ if ( S(t->text) ) {
1617
+ if ( t->next && S(t->text) > 2
1618
+ && T(t->text)[S(t->text)-2] == ' '
1619
+ && T(t->text)[S(t->text)-1] == ' ' ) {
1620
+ push(T(t->text), S(t->text)-2, f);
1621
+ pushc(MKD_EOLN, f);
1622
+ pushc('\n', f);
1623
+ }
1624
+ else {
1625
+ ___mkd_tidy(&t->text);
1626
+ push(T(t->text), S(t->text), f);
1627
+ if ( t->next )
1628
+ pushc('\n', f);
1629
+ }
1630
+ }
1631
+ t = t->next;
1632
+ }
1633
+ Qstring(Begin[pp->align], f);
1634
+ text(f);
1635
+ Qstring(End[pp->align], f);
1636
+ return 1;
1637
+ }
1638
+
1639
+
1640
+ static void
1641
+ printcode(Line *t, char *lang, MMIOT *f)
1642
+ {
1643
+ int blanks;
1644
+
1645
+ Qstring("<pre><code", f);
1646
+ if (lang) {
1647
+ Qstring(" class=\"", f);
1648
+ Qstring(lang, f);
1649
+ Qstring("\"", f);
1650
+ }
1651
+ Qstring(">", f);
1652
+ for ( blanks = 0; t ; t = t->next ) {
1653
+ if ( S(t->text) > t->dle ) {
1654
+ while ( blanks ) {
1655
+ Qchar('\n', f);
1656
+ --blanks;
1657
+ }
1658
+ code(f, T(t->text), S(t->text));
1659
+ Qchar('\n', f);
1660
+ }
1661
+ else blanks++;
1662
+ }
1663
+ Qstring("</code></pre>", f);
1664
+ }
1665
+
1666
+
1667
+ static void
1668
+ printhtml(Line *t, MMIOT *f)
1669
+ {
1670
+ int blanks;
1671
+
1672
+ for ( blanks=0; t ; t = t->next )
1673
+ if ( S(t->text) ) {
1674
+ for ( ; blanks; --blanks )
1675
+ Qchar('\n', f);
1676
+
1677
+ Qwrite(T(t->text), S(t->text), f);
1678
+ Qchar('\n', f);
1679
+ }
1680
+ else
1681
+ blanks++;
1682
+ }
1683
+
1684
+
1685
+ static void
1686
+ htmlify(Paragraph *p, char *block, char *arguments, MMIOT *f)
1687
+ {
1688
+ ___mkd_emblock(f);
1689
+ if ( block )
1690
+ Qprintf(f, arguments ? "<%s %s>" : "<%s>", block, arguments);
1691
+ ___mkd_emblock(f);
1692
+
1693
+ while (( p = display(p, f) )) {
1694
+ ___mkd_emblock(f);
1695
+ Qstring("\n\n", f);
1696
+ }
1697
+
1698
+ if ( block )
1699
+ Qprintf(f, "</%s>", block);
1700
+ ___mkd_emblock(f);
1701
+ }
1702
+
1703
+
1704
+ static void
1705
+ definitionlist(Paragraph *p, MMIOT *f)
1706
+ {
1707
+ Line *tag;
1708
+
1709
+ if ( p ) {
1710
+ Qstring("<dl>\n", f);
1711
+
1712
+ for ( ; p ; p = p->next) {
1713
+ for ( tag = p->text; tag; tag = tag->next ) {
1714
+ Qstring("<dt>", f);
1715
+ ___mkd_reparse(T(tag->text), S(tag->text), 0, f, 0);
1716
+ Qstring("</dt>\n", f);
1717
+ }
1718
+
1719
+ htmlify(p->down, "dd", p->ident, f);
1720
+ Qchar('\n', f);
1721
+ }
1722
+
1723
+ Qstring("</dl>", f);
1724
+ }
1725
+ }
1726
+
1727
+
1728
+ static void
1729
+ listdisplay(int typ, Paragraph *p, MMIOT* f)
1730
+ {
1731
+ if ( p ) {
1732
+ Qprintf(f, "<%cl", (typ==UL)?'u':'o');
1733
+ if ( typ == AL )
1734
+ Qprintf(f, " type=\"a\"");
1735
+ Qprintf(f, ">\n");
1736
+
1737
+ for ( ; p ; p = p->next ) {
1738
+ htmlify(p->down, "li", p->ident, f);
1739
+ Qchar('\n', f);
1740
+ }
1741
+
1742
+ Qprintf(f, "</%cl>\n", (typ==UL)?'u':'o');
1743
+ }
1744
+ }
1745
+
1746
+
1747
+ /* dump out a Paragraph in the desired manner
1748
+ */
1749
+ static Paragraph*
1750
+ display(Paragraph *p, MMIOT *f)
1751
+ {
1752
+ if ( !p ) return 0;
1753
+
1754
+ switch ( p->typ ) {
1755
+ case STYLE:
1756
+ case WHITESPACE:
1757
+ break;
1758
+
1759
+ case HTML:
1760
+ printhtml(p->text, f);
1761
+ break;
1762
+
1763
+ case CODE:
1764
+ printcode(p->text, p->lang, f);
1765
+ break;
1766
+
1767
+ case QUOTE:
1768
+ htmlify(p->down, p->ident ? "div" : "blockquote", p->ident, f);
1769
+ break;
1770
+
1771
+ case UL:
1772
+ case OL:
1773
+ case AL:
1774
+ listdisplay(p->typ, p->down, f);
1775
+ break;
1776
+
1777
+ case DL:
1778
+ definitionlist(p->down, f);
1779
+ break;
1780
+
1781
+ case HR:
1782
+ Qstring("<hr />", f);
1783
+ break;
1784
+
1785
+ case HDR:
1786
+ printheader(p, f);
1787
+ break;
1788
+
1789
+ case TABLE:
1790
+ printtable(p, f);
1791
+ break;
1792
+
1793
+ case SOURCE:
1794
+ htmlify(p->down, 0, 0, f);
1795
+ break;
1796
+
1797
+ default:
1798
+ printblock(p, f);
1799
+ break;
1800
+ }
1801
+ return p->next;
1802
+ }
1803
+
1804
+
1805
+ /* dump out a list of footnotes
1806
+ */
1807
+ static void
1808
+ mkd_extra_footnotes(MMIOT *m)
1809
+ {
1810
+ int j, i;
1811
+ Footnote *t;
1812
+
1813
+ if ( m->footnotes->reference == 0 )
1814
+ return;
1815
+
1816
+ Csprintf(&m->out, "\n<div class=\"footnotes\">\n<hr/>\n<ol>\n");
1817
+
1818
+ for ( i=1; i <= m->footnotes->reference; i++ ) {
1819
+ for ( j=0; j < S(m->footnotes->note); j++ ) {
1820
+ t = &T(m->footnotes->note)[j];
1821
+ if ( (t->refnumber == i) && (t->flags & REFERENCED) ) {
1822
+ Csprintf(&m->out, "<li id=\"%s:%d\">\n<p>",
1823
+ p_or_nothing(m), t->refnumber);
1824
+ Csreparse(&m->out, T(t->title), S(t->title), 0);
1825
+ Csprintf(&m->out, "<a href=\"#%sref:%d\" rev=\"footnote\">&#8617;</a>",
1826
+ p_or_nothing(m), t->refnumber);
1827
+ Csprintf(&m->out, "</p></li>\n");
1828
+ }
1829
+ }
1830
+ }
1831
+ Csprintf(&m->out, "</ol>\n</div>\n");
1832
+ }
1833
+
1834
+
1835
+ /* return a pointer to the compiled markdown
1836
+ * document.
1837
+ */
1838
+ int
1839
+ mkd_document(Document *p, char **res)
1840
+ {
1841
+ int size;
1842
+
1843
+ if ( p && p->compiled ) {
1844
+ if ( ! p->html ) {
1845
+ htmlify(p->code, 0, 0, p->ctx);
1846
+ if ( p->ctx->flags & MKD_EXTRA_FOOTNOTE )
1847
+ mkd_extra_footnotes(p->ctx);
1848
+ p->html = 1;
1849
+ size = S(p->ctx->out);
1850
+
1851
+ if ( (size == 0) || T(p->ctx->out)[size-1] ) {
1852
+ /* Add a null byte at the end of the generated html,
1853
+ * but pretend it doesn't exist.
1854
+ */
1855
+ EXPAND(p->ctx->out) = 0;
1856
+ --S(p->ctx->out);
1857
+ }
1858
+ }
1859
+
1860
+ *res = T(p->ctx->out);
1861
+ return S(p->ctx->out);
1862
+ }
1863
+ return EOF;
1864
+ }
1865
+