rdiscount 1.3.5 → 1.5.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -5,21 +5,25 @@ Discount is an implementation of John Gruber's Markdown markup language in C. It
5
5
  implements all of the language described in [the markdown syntax document][1] and
6
6
  passes the [Markdown 1.0 test suite][2].
7
7
 
8
- Discount was developed by [David Loren Parsons][3]. The Ruby extension was
9
- developed by [Ryan Tomayko][4].
8
+ CODE: `git clone git://github.com/rtomayko/rdiscount.git`
9
+ HOME: <http://github.com/rtomayko/rdiscount>
10
+ DOCS: <http://rdoc.info/projects/rtomayko/rdiscount>
11
+ BUGS: <http://github.com/rtomayko/rdiscount/issues>
12
+
13
+ Discount was developed by [David Loren Parsons][3]. The Ruby extension
14
+ is maintained by [Ryan Tomayko][4].
10
15
 
11
16
  [1]: http://daringfireball.net/projects/markdown/syntax
12
17
  [2]: http://daringfireball.net/projects/downloads/MarkdownTest_1.0.zip
13
18
  [3]: http://www.pell.portland.or.us/~orc
14
- [4]: http://tomayko.com/
19
+ [4]: http://tomayko.com/about
15
20
 
16
- Installation, Hacking
17
- ---------------------
21
+ INSTALL, HACKING
22
+ ----------------
18
23
 
19
- RDiscount Gem releases are published to RubyForge and can be installed as
20
- follows:
24
+ New releases of RDiscount are published to [gemcutter][]:
21
25
 
22
- $ [sudo] gem install rdiscount
26
+ $ [sudo] gem install rdiscount -s http://gemcutter.org
23
27
 
24
28
  The RDiscount sources are available via Git:
25
29
 
@@ -27,9 +31,9 @@ The RDiscount sources are available via Git:
27
31
  $ cd rdiscount
28
32
  $ rake --tasks
29
33
 
30
- For more information, see [the project page](http://github.com/rtomayko/rdiscount).
34
+ [gemcutter]: http://gemcutter.org/gems/rdiscount
31
35
 
32
- Usage
36
+ USAGE
33
37
  -----
34
38
 
35
39
  RDiscount implements the basic protocol popularized by RedCloth and adopted
@@ -39,6 +43,11 @@ by BlueCloth:
39
43
  markdown = RDiscount.new("Hello World!")
40
44
  puts markdown.to_html
41
45
 
46
+ Additional processing options can be turned on when creating the
47
+ RDiscount object:
48
+
49
+ markdown = RDiscount.new("Hello World!", :smart, :filter_html)
50
+
42
51
  Inject RDiscount into your BlueCloth-using code by replacing your bluecloth
43
52
  require statements with the following:
44
53
 
@@ -56,3 +65,4 @@ Discount is free software; it is released under a BSD-style license
56
65
  that allows you to do as you wish with it as long as you don't attempt
57
66
  to claim it as your own work. RDiscount adopts Discount's license
58
67
  verbatim. See the file `COPYING` for more information.
68
+
data/Rakefile CHANGED
@@ -4,15 +4,8 @@ task :default => :test
4
4
 
5
5
  # PACKAGING =================================================================
6
6
 
7
- # Load the gemspec using the same limitations as github
8
- $spec =
9
- begin
10
- require 'rubygems/specification'
11
- data = File.read('rdiscount.gemspec')
12
- spec = nil
13
- Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
14
- spec
15
- end
7
+ require 'rubygems/specification'
8
+ $spec = eval(File.read('rdiscount.gemspec'))
16
9
 
17
10
  def package(ext='')
18
11
  "pkg/rdiscount-#{$spec.version}" + ext
@@ -91,9 +84,17 @@ task :build => "lib/rdiscount.#{DLEXT}"
91
84
  # Testing
92
85
  # ==========================================================
93
86
 
87
+ def runner
88
+ if system("type turn 2>/dev/null 1>&2")
89
+ "turn"
90
+ else
91
+ "testrb"
92
+ end
93
+ end
94
+
94
95
  desc 'Run unit tests'
95
96
  task 'test:unit' => [:build] do |t|
96
- sh 'testrb test/markdown_test.rb test/rdiscount_test.rb'
97
+ sh "#{runner} test/markdown_test.rb test/rdiscount_test.rb"
97
98
  end
98
99
 
99
100
  desc 'Run conformance tests (MARKDOWN_TEST_VER=1.0)'
@@ -175,7 +176,7 @@ task :gather => 'discount' do |t|
175
176
  files =
176
177
  FileList[
177
178
  'discount/{markdown,mkdio,amalloc,cstring}.h',
178
- 'discount/{markdown,docheader,dumptree,generate,mkdio,resource,toc,Csio}.c'
179
+ 'discount/{markdown,docheader,dumptree,generate,mkdio,resource,toc,Csio,xml,css}.c'
179
180
  ]
180
181
  cp files, 'ext/',
181
182
  :preserve => true,
data/ext/Csio.c CHANGED
@@ -26,15 +26,27 @@ Csprintf(Cstring *iot, char *fmt, ...)
26
26
  do {
27
27
  RESERVE(*iot, siz);
28
28
  va_start(ptr, fmt);
29
- siz = vsnprintf(T(*iot)+S(*iot), ALL(*iot)-S(*iot), fmt, ptr);
29
+ siz = vsnprintf(T(*iot)+S(*iot), ALLOCATED(*iot)-S(*iot), fmt, ptr);
30
30
  va_end(ptr);
31
- } while ( siz > (ALL(*iot)-S(*iot)) );
31
+ } while ( siz > (ALLOCATED(*iot)-S(*iot)) );
32
32
 
33
33
  S(*iot) += siz;
34
34
  return siz;
35
35
  }
36
36
 
37
37
 
38
+ /* write() into a cstring
39
+ */
40
+ int
41
+ Cswrite(Cstring *iot, char *bfr, int size)
42
+ {
43
+ RESERVE(*iot, size);
44
+ memcpy(T(*iot)+S(*iot), bfr, size);
45
+ S(*iot) += size;
46
+ return size;
47
+ }
48
+
49
+
38
50
  /* reparse() into a cstring
39
51
  */
40
52
  void
data/ext/css.c ADDED
@@ -0,0 +1,76 @@
1
+ /* markdown: a C implementation of John Gruber's Markdown markup language.
2
+ *
3
+ * Copyright (C) 2009 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
+
21
+ /*
22
+ * dump out stylesheet sections.
23
+ */
24
+ static void
25
+ stylesheets(Paragraph *p, Cstring *f)
26
+ {
27
+ Line* q;
28
+
29
+ for ( ; p ; p = p->next ) {
30
+ if ( p->typ == STYLE ) {
31
+ for ( q = p->text; q ; q = q->next )
32
+ Cswrite(f, T(q->text), S(q->text));
33
+ Csputc('\n', f);
34
+ }
35
+ if ( p->down )
36
+ stylesheets(p->down, f);
37
+ }
38
+ }
39
+
40
+
41
+ /* dump any embedded styles to a string
42
+ */
43
+ int
44
+ mkd_css(Document *d, char **res)
45
+ {
46
+ Cstring f;
47
+
48
+ if ( res && *res && d && d->compiled ) {
49
+ CREATE(f);
50
+ RESERVE(f, 100);
51
+ stylesheets(d->code, &f);
52
+
53
+ /* HACK ALERT! HACK ALERT! HACK ALERT! */
54
+ *res = T(f); /* we know that a T(Cstring) is a character pointer */
55
+ /* so we can simply pick it up and carry it away, */
56
+ return S(f); /* leaving the husk of the Ctring on the stack */
57
+ /* END HACK ALERT */
58
+ }
59
+ return EOF;
60
+ }
61
+
62
+
63
+ /* dump any embedded styles to a file
64
+ */
65
+ int
66
+ mkd_generatecss(Document *d, FILE *f)
67
+ {
68
+ char *res;
69
+ int written = EOF, size = mkd_css(d, &res);
70
+
71
+ if ( size > 0 )
72
+ written = fwrite(res, size, 1, f);
73
+ if ( res )
74
+ free(res);
75
+ return (written == size) ? size : EOF;
76
+ }
data/ext/cstring.h CHANGED
@@ -22,8 +22,8 @@
22
22
  : (T(x) = T(x) ? realloc(T(x), sizeof T(x)[0] * ((x).alloc += 100)) \
23
23
  : malloc(sizeof T(x)[0] * ((x).alloc += 100)) )]
24
24
 
25
- #define DELETE(x) (x).alloc ? (free(T(x)), S(x) = (x).alloc = 0) \
26
- : ( S(x) = 0 )
25
+ #define DELETE(x) ALLOCATED(x) ? (free(T(x)), S(x) = (x).alloc = 0) \
26
+ : ( S(x) = 0 )
27
27
  #define CLIP(t,i,sz) \
28
28
  ( ((i) >= 0) && ((sz) > 0) && (((i)+(sz)) <= S(t)) ) ? \
29
29
  (memmove(&T(t)[i], &T(t)[i+sz], (S(t)-(i+sz)+1)*sizeof(T(t)[0])), \
@@ -50,7 +50,7 @@
50
50
  */
51
51
  #define T(x) (x).text
52
52
  #define S(x) (x).size
53
- #define ALL(x) (x).alloc
53
+ #define ALLOCATED(x) (x).alloc
54
54
 
55
55
  /* abstract anchor type that defines a list base
56
56
  * with a function that attaches an element to
@@ -60,14 +60,16 @@
60
60
  * macro will work with it.
61
61
  */
62
62
  #define ANCHOR(t) struct { t *text, *end; }
63
+ #define E(t) ((t).end)
63
64
 
64
- #define ATTACH(t, p) ( (t).text ?( ((t).end->next = (p)), ((t).end = (p)) ) \
65
- :( ((t).text = (t).end = (p)) ) )
65
+ #define ATTACH(t, p) ( T(t) ? ( (E(t)->next = (p)), (E(t) = (p)) ) \
66
+ : ( (T(t) = E(t) = (p)) ) )
66
67
 
67
68
  typedef STRING(char) Cstring;
68
69
 
69
70
  extern void Csputc(int, Cstring *);
70
71
  extern int Csprintf(Cstring *, char *, ...);
72
+ extern int Cswrite(Cstring *, char *, int);
71
73
  extern void Csreparse(Cstring *, char *, int, int);
72
74
 
73
75
  #endif/*_CSTRING_D*/
data/ext/dumptree.c CHANGED
@@ -30,7 +30,9 @@ Pptype(int typ)
30
30
  case OL : return "ol";
31
31
  case LISTITEM : return "item";
32
32
  case HDR : return "header";
33
- case HR : return "HR";
33
+ case HR : return "hr";
34
+ case TABLE : return "table";
35
+ case SOURCE : return "source";
34
36
  default : return "mystery node!";
35
37
  }
36
38
  }
@@ -106,6 +108,8 @@ dumptree(Paragraph *pp, Stack *sp, FILE *f)
106
108
  printpfx(sp, f);
107
109
 
108
110
  d = fprintf(f, "[%s", Pptype(pp->typ));
111
+ if ( pp->ident )
112
+ d += fprintf(f, " %s", pp->ident);
109
113
  if ( pp->align )
110
114
  d += fprintf(f, ", <%s>", Begin[pp->align]);
111
115
 
data/ext/generate.c CHANGED
@@ -17,11 +17,6 @@
17
17
  #include "markdown.h"
18
18
  #include "amalloc.h"
19
19
 
20
- /* prefixes for <automatic links>
21
- */
22
- static char *autoprefix[] = { "http://", "https://", "ftp://", "news://" };
23
- #define SZAUTOPREFIX (sizeof autoprefix / sizeof autoprefix[0])
24
-
25
20
  typedef int (*stfu)(const void*,const void*);
26
21
 
27
22
 
@@ -119,7 +114,7 @@ shift(MMIOT *f, int i)
119
114
  /* Qchar()
120
115
  */
121
116
  static void
122
- Qchar(char c, MMIOT *f)
117
+ Qchar(int c, MMIOT *f)
123
118
  {
124
119
  block *cur;
125
120
 
@@ -328,18 +323,27 @@ ___mkd_reparse(char *bfr, int size, int flags, MMIOT *f)
328
323
  * write out a url, escaping problematic characters
329
324
  */
330
325
  static void
331
- puturl(char *s, int size, MMIOT *f)
326
+ puturl(char *s, int size, MMIOT *f, int display)
332
327
  {
333
328
  unsigned char c;
334
329
 
335
330
  while ( size-- > 0 ) {
336
331
  c = *s++;
337
332
 
333
+ if ( c == '\\' && size-- > 0 ) {
334
+ c = *s++;
335
+
336
+ if ( !( ispunct(c) || isspace(c) ) )
337
+ Qchar('\\', f);
338
+ }
339
+
338
340
  if ( c == '&' )
339
341
  Qstring("&amp;", f);
340
342
  else if ( c == '<' )
341
343
  Qstring("&lt;", f);
342
- else if ( isalnum(c) || ispunct(c) )
344
+ else if ( c == '"' )
345
+ Qstring("%22", f);
346
+ else if ( isalnum(c) || ispunct(c) || (display && isspace(c)) )
343
347
  Qchar(c, f);
344
348
  else
345
349
  Qprintf(f, "%%%02X", c);
@@ -372,181 +376,164 @@ parenthetical(int in, int out, MMIOT *f)
372
376
  return EOF;
373
377
  else if ( c == in )
374
378
  ++indent;
379
+ else if ( (c == '\\') && (peek(f,1) == out) ) {
380
+ ++size;
381
+ pull(f);
382
+ }
375
383
  else if ( c == out )
376
384
  --indent;
377
385
  }
378
- return size-1;
386
+ return size ? (size-1) : 0;
379
387
  }
380
388
 
381
389
 
382
390
  /* extract a []-delimited label from the input stream.
383
391
  */
384
- static char *
385
- linkylabel(MMIOT *f, int *sizep)
392
+ static int
393
+ linkylabel(MMIOT *f, Cstring *res)
386
394
  {
387
395
  char *ptr = cursor(f);
396
+ int size;
388
397
 
389
- if ( (*sizep = parenthetical('[',']',f)) != EOF )
390
- return ptr;
398
+ if ( (size = parenthetical('[',']',f)) != EOF ) {
399
+ T(*res) = ptr;
400
+ S(*res) = size;
401
+ return 1;
402
+ }
391
403
  return 0;
392
404
  }
393
405
 
394
406
 
395
- /* extract a (-prefixed url from the input stream.
396
- * the label is either of the format `<link>`, where I
397
- * extract until I find a >, or it is of the format
398
- * `text`, where I extract until I reach a ')' or
399
- * whitespace.
407
+ /* see if the quote-prefixed linky segment is actually a title.
400
408
  */
401
- static char*
402
- linkyurl(MMIOT *f, int *sizep)
409
+ static int
410
+ linkytitle(MMIOT *f, char quote, Footnote *ref)
403
411
  {
404
- int size = 0;
405
- char *ptr;
406
- int c;
412
+ int whence = mmiottell(f);
413
+ char *title = cursor(f);
414
+ char *e;
415
+ register int c;
407
416
 
408
- if ( (c = eatspace(f)) == EOF )
409
- return 0;
410
-
411
- ptr = cursor(f);
412
-
413
- if ( c == '<' ) {
414
- pull(f);
415
- ptr++;
416
- if ( (size = parenthetical('<', '>', f)) == EOF )
417
- return 0;
418
- }
419
- else {
420
- for ( ; ((c=pull(f)) != ')') && !isspace(c); size++)
421
- if ( c == EOF ) return 0;
422
- if ( c == ')' )
423
- shift(f, -1);
417
+ while ( (c = pull(f)) != EOF ) {
418
+ e = cursor(f);
419
+ if ( c == quote ) {
420
+ if ( (c = eatspace(f)) == ')' ) {
421
+ T(ref->title) = 1+title;
422
+ S(ref->title) = (e-title)-2;
423
+ return 1;
424
+ }
425
+ }
424
426
  }
425
- *sizep = size;
426
- return ptr;
427
+ mmiotseek(f, whence);
428
+ return 0;
427
429
  }
428
430
 
429
431
 
430
432
  /* extract a =HHHxWWW size from the input stream
431
433
  */
432
434
  static int
433
- linkysize(MMIOT *f, int *heightp, int *widthp)
435
+ linkysize(MMIOT *f, Footnote *ref)
434
436
  {
435
437
  int height=0, width=0;
438
+ int whence = mmiottell(f);
436
439
  int c;
437
440
 
438
- *heightp = 0;
439
- *widthp = 0;
441
+ if ( isspace(peek(f,0)) ) {
442
+ pull(f); /* eat '=' */
440
443
 
441
- if ( (c = eatspace(f)) != '=' )
442
- return (c != EOF);
443
- pull(f); /* eat '=' */
444
-
445
- for ( c = pull(f); isdigit(c); c = pull(f))
446
- width = (width * 10) + (c - '0');
447
-
448
- if ( c == 'x' ) {
449
444
  for ( c = pull(f); isdigit(c); c = pull(f))
450
- height = (height*10) + (c - '0');
451
-
452
- if ( c != EOF ) {
453
- if ( !isspace(c) ) shift(f, -1);
454
- *heightp = height;
455
- *widthp = width;
456
- return 1;
457
- }
458
- }
459
- return 0;
460
- }
445
+ width = (width * 10) + (c - '0');
461
446
 
447
+ if ( c == 'x' ) {
448
+ for ( c = pull(f); isdigit(c); c = pull(f))
449
+ height = (height*10) + (c - '0');
462
450
 
463
- /* extract a )-terminated title from the input stream.
464
- */
465
- static char*
466
- linkytitle(MMIOT *f, int *sizep)
467
- {
468
- int countq=0, qc, c, size;
469
- char *ret, *lastqc = 0;
470
-
471
- eatspace(f);
472
- if ( (qc=pull(f)) != '"' && qc != '\'' && qc != '(' )
473
- return 0;
451
+ if ( isspace(c) )
452
+ c = eatspace(f);
474
453
 
475
- if ( qc == '(' ) qc = ')';
476
-
477
- for ( ret = cursor(f); (c = pull(f)) != EOF; ) {
478
- if ( (c == ')') && countq ) {
479
- size = (lastqc ? lastqc : cursor(f)) - ret;
480
- *sizep = size-1;
481
- return ret;
482
- }
483
- else if ( c == qc ) {
484
- lastqc = cursor(f);
485
- countq++;
454
+ if ( (c == ')') || ((c == '\'' || c == '"') && linkytitle(f, c, ref)) ) {
455
+ ref->height = height;
456
+ ref->width = width;
457
+ return 1;
458
+ }
486
459
  }
487
460
  }
461
+ mmiotseek(f, whence);
488
462
  return 0;
489
463
  }
490
464
 
491
465
 
492
- /* look up (or construct) a footnote from the [xxx] link
493
- * at the head of the stream.
466
+ /* extract a (-prefixed url from the input stream.
467
+ * the label is either of the format `<link>`, where I
468
+ * extract until I find a >, or it is of the format
469
+ * `text`, where I extract until I reach a ')', a quote,
470
+ * or (if image) a '='
494
471
  */
495
472
  static int
496
- linkykey(int image, Footnote *val, MMIOT *f)
473
+ linkyurl(MMIOT *f, int image, Footnote *p)
497
474
  {
498
- Footnote *ret;
499
- Cstring mylabel;
500
- int here;
501
-
502
- memset(val, 0, sizeof *val);
475
+ int c;
476
+ int mayneedtotrim=0;
503
477
 
504
- if ( (T(val->tag) = linkylabel(f, &S(val->tag))) == 0 )
478
+ if ( (c = eatspace(f)) == EOF )
505
479
  return 0;
506
480
 
507
- here = mmiottell(f);
508
- eatspace(f);
509
- switch ( pull(f) ) {
510
- case '(':
511
- /* embedded link */
512
- if ( (T(val->link) = linkyurl(f,&S(val->link))) == 0 )
513
- return 0;
481
+ if ( c == '<' ) {
482
+ pull(f);
483
+ mayneedtotrim=1;
484
+ }
514
485
 
515
- if ( image && !linkysize(f, &val->height, &val->width) )
486
+ T(p->link) = cursor(f);
487
+ for ( S(p->link)=0; (c = peek(f,1)) != ')'; ++S(p->link) ) {
488
+ if ( c == EOF )
516
489
  return 0;
490
+ else if ( (c == '"' || c == '\'') && linkytitle(f, c, p) )
491
+ break;
492
+ else if ( image && (c == '=') && linkysize(f, p) )
493
+ break;
494
+ else if ( (c == '\\') && ispunct(peek(f,2)) ) {
495
+ ++S(p->link);
496
+ pull(f);
497
+ }
498
+ pull(f);
499
+ }
500
+ if ( peek(f, 1) == ')' )
501
+ pull(f);
502
+
503
+ ___mkd_tidy(&p->link);
504
+
505
+ if ( mayneedtotrim && (T(p->link)[S(p->link)-1] == '>') )
506
+ --S(p->link);
507
+
508
+ return 1;
509
+ }
517
510
 
518
- T(val->title) = linkytitle(f, &S(val->title));
519
511
 
520
- return peek(f,0) == ')';
521
512
 
522
- case '[': /* footnote links /as defined in the standard/ */
523
- default: /* footnote links -- undocumented extension */
524
- /* footnote link */
525
- mylabel = val->tag;
526
- if ( peek(f,0) == '[' ) {
527
- if ( (T(val->tag) = linkylabel(f, &S(val->tag))) == 0 )
528
- return 0;
513
+ /* prefixes for <automatic links>
514
+ */
515
+ static struct {
516
+ char *name;
517
+ int nlen;
518
+ } protocol[] = {
519
+ #define _aprotocol(x) { x, (sizeof x)-1 }
520
+ _aprotocol( "http://" ),
521
+ _aprotocol( "https://" ),
522
+ _aprotocol( "ftp://" ),
523
+ _aprotocol( "news://" ),
524
+ #undef _aprotocol
525
+ };
526
+ #define NRPROTOCOLS (sizeof protocol / sizeof protocol[0])
529
527
 
530
- if ( !S(val->tag) )
531
- val->tag = mylabel;
532
- }
533
- else if ( f->flags & MKD_1_COMPAT )
534
- break;
535
- else
536
- mmiotseek(f,here);
537
528
 
538
- ret = bsearch(val, T(*f->footnotes), S(*f->footnotes),
539
- sizeof *val, (stfu)__mkd_footsort);
529
+ static int
530
+ isautoprefix(char *text)
531
+ {
532
+ int i;
540
533
 
541
- if ( ret ) {
542
- val->tag = mylabel;
543
- val->link = ret->link;
544
- val->title = ret->title;
545
- val->height = ret->height;
546
- val->width = ret->width;
534
+ for (i=0; i < NRPROTOCOLS; i++)
535
+ if ( strncasecmp(text, protocol[i].name, protocol[i].nlen) == 0 )
547
536
  return 1;
548
- }
549
- }
550
537
  return 0;
551
538
  }
552
539
 
@@ -564,12 +551,14 @@ typedef struct linkytype {
564
551
  char *text_pfx; /* text prefix (eg: ">" */
565
552
  char *text_sfx; /* text suffix (eg: "</a>" */
566
553
  int flags; /* reparse flags */
554
+ int kind; /* tag is url or something else? */
555
+ #define IS_URL 0x01
567
556
  } linkytype;
568
557
 
569
558
  static linkytype imaget = { 0, 0, "<img src=\"", "\"",
570
- 1, " alt=\"", "\" />", DENY_IMG|INSIDE_TAG };
559
+ 1, " alt=\"", "\" />", DENY_IMG|INSIDE_TAG, IS_URL };
571
560
  static linkytype linkt = { 0, 0, "<a href=\"", "\"",
572
- 0, ">", "</a>", DENY_A };
561
+ 0, ">", "</a>", DENY_A, IS_URL };
573
562
 
574
563
  /*
575
564
  * pseudo-protocols for [][];
@@ -579,9 +568,10 @@ static linkytype linkt = { 0, 0, "<a href=\"", "\"",
579
568
  * raw: just dump the link without any processing
580
569
  */
581
570
  static linkytype specials[] = {
582
- { "id:", 3, "<a id=\"", "\"", 0, ">", "</a>", 0 },
583
- { "class:", 6, "<span class=\"", "\"", 0, ">", "</span>", 0 },
584
- { "raw:", 4, 0, 0, 0, 0, 0, 0 },
571
+ { "id:", 3, "<a id=\"", "\"", 0, ">", "</a>", 0, IS_URL },
572
+ { "class:", 6, "<span class=\"", "\"", 0, ">", "</span>", 0, 0 },
573
+ { "raw:", 4, 0, 0, 0, 0, 0, DENY_HTML, 0 },
574
+ { "abbr:", 5, "<abbr title=\"", "\"", 0, ">", "</abbr>", 0, 0 },
585
575
  } ;
586
576
 
587
577
  #define NR(x) (sizeof x / sizeof x[0])
@@ -589,7 +579,7 @@ static linkytype specials[] = {
589
579
  /* see if t contains one of our pseudo-protocols.
590
580
  */
591
581
  static linkytype *
592
- extratag(Cstring t)
582
+ pseudo(Cstring t)
593
583
  {
594
584
  int i;
595
585
  linkytype *r;
@@ -603,57 +593,124 @@ extratag(Cstring t)
603
593
  }
604
594
 
605
595
 
606
- /*
607
- * process embedded links and images
596
+ /* print out a linky (or fail if it's Not Allowed)
608
597
  */
609
598
  static int
610
- linkylinky(int image, MMIOT *f)
599
+ linkyformat(MMIOT *f, Cstring text, int image, Footnote *ref)
611
600
  {
612
- int start = mmiottell(f);
613
- Footnote link;
614
601
  linkytype *tag;
615
602
 
616
- if ( !linkykey(image, &link, f) ) {
617
- mmiotseek(f, start);
618
- return 0;
619
- }
620
-
621
603
  if ( image )
622
604
  tag = &imaget;
623
- else if ( (f->flags & NO_PSEUDO_PROTO) || (tag = extratag(link.link)) == 0 )
605
+ else if ( tag = pseudo(ref->link) ) {
606
+ if ( f->flags & (NO_PSEUDO_PROTO|SAFELINK) )
607
+ return 0;
608
+ }
609
+ else if ( (f->flags & SAFELINK) && T(ref->link)
610
+ && (T(ref->link)[0] != '/')
611
+ && !isautoprefix(T(ref->link)) )
612
+ /* if SAFELINK, only accept links that are local or
613
+ * a well-known protocol
614
+ */
615
+ return 0;
616
+ else
624
617
  tag = &linkt;
625
618
 
626
- if ( f->flags & tag-> flags ) {
627
- mmiotseek(f, start);
619
+ if ( f->flags & tag->flags )
628
620
  return 0;
629
- }
630
621
 
631
622
  if ( tag->link_pfx ) {
632
623
  Qstring(tag->link_pfx, f);
633
- if ( f->base && (T(link.link)[tag->szpat] == '/') )
634
- puturl(f->base, strlen(f->base), f);
635
- puturl(T(link.link) + tag->szpat, S(link.link) - tag->szpat, f);
624
+
625
+ if ( tag->kind & IS_URL ) {
626
+ if ( f->base && T(ref->link) && (T(ref->link)[tag->szpat] == '/') )
627
+ puturl(f->base, strlen(f->base), f, 0);
628
+ puturl(T(ref->link) + tag->szpat, S(ref->link) - tag->szpat, f, 0);
629
+ }
630
+ else
631
+ ___mkd_reparse(T(ref->link) + tag->szpat, S(ref->link) - tag->szpat, INSIDE_TAG, f);
632
+
636
633
  Qstring(tag->link_sfx, f);
637
634
 
638
- if ( tag->WxH && link.height && link.width ) {
639
- Qprintf(f," height=\"%d\"", link.height);
640
- Qprintf(f, " width=\"%d\"", link.width);
635
+ if ( tag->WxH && ref->height && ref->width ) {
636
+ Qprintf(f," height=\"%d\"", ref->height);
637
+ Qprintf(f, " width=\"%d\"", ref->width);
641
638
  }
642
639
 
643
- if ( S(link.title) ) {
640
+ if ( S(ref->title) ) {
644
641
  Qstring(" title=\"", f);
645
- ___mkd_reparse(T(link.title), S(link.title), INSIDE_TAG, f);
642
+ ___mkd_reparse(T(ref->title), S(ref->title), INSIDE_TAG, f);
646
643
  Qchar('"', f);
647
644
  }
648
645
 
649
646
  Qstring(tag->text_pfx, f);
650
- ___mkd_reparse(T(link.tag), S(link.tag), tag->flags, f);
647
+ ___mkd_reparse(T(text), S(text), tag->flags, f);
651
648
  Qstring(tag->text_sfx, f);
652
649
  }
653
650
  else
654
- Qwrite(T(link.link) + tag->szpat, S(link.link) - tag->szpat, f);
651
+ Qwrite(T(ref->link) + tag->szpat, S(ref->link) - tag->szpat, f);
655
652
 
656
653
  return 1;
654
+ } /* linkyformat */
655
+
656
+
657
+ /*
658
+ * process embedded links and images
659
+ */
660
+ static int
661
+ linkylinky(int image, MMIOT *f)
662
+ {
663
+ int start = mmiottell(f);
664
+ Cstring name;
665
+ Footnote key, *ref;
666
+
667
+ int status = 0;
668
+
669
+ CREATE(name);
670
+ bzero(&key, sizeof key);
671
+
672
+ if ( linkylabel(f, &name) ) {
673
+ if ( peek(f,1) == '(' ) {
674
+ pull(f);
675
+ if ( linkyurl(f, image, &key) )
676
+ status = linkyformat(f, name, image, &key);
677
+ }
678
+ else {
679
+ int goodlink, implicit_mark = mmiottell(f);
680
+
681
+ if ( eatspace(f) == '[' ) {
682
+ pull(f); /* consume leading '[' */
683
+ goodlink = linkylabel(f, &key.tag);
684
+ }
685
+ else {
686
+ /* new markdown implicit name syntax doesn't
687
+ * require a second []
688
+ */
689
+ mmiotseek(f, implicit_mark);
690
+ goodlink = !(f->flags & MKD_1_COMPAT);
691
+ }
692
+
693
+ if ( goodlink ) {
694
+ if ( !S(key.tag) ) {
695
+ DELETE(key.tag);
696
+ T(key.tag) = T(name);
697
+ S(key.tag) = S(name);
698
+ }
699
+
700
+ if ( ref = bsearch(&key, T(*f->footnotes), S(*f->footnotes),
701
+ sizeof key, (stfu)__mkd_footsort) )
702
+ status = linkyformat(f, name, image, ref);
703
+ }
704
+ }
705
+ }
706
+
707
+ DELETE(name);
708
+ ___mkd_freefootnote(&key);
709
+
710
+ if ( status == 0 )
711
+ mmiotseek(f, start);
712
+
713
+ return status;
657
714
  }
658
715
 
659
716
 
@@ -706,6 +763,80 @@ forbidden_tag(MMIOT *f)
706
763
  }
707
764
 
708
765
 
766
+ /* Check a string to see if it looks like a mail address
767
+ * "looks like a mail address" means alphanumeric + some
768
+ * specials, then a `@`, then alphanumeric + some specials,
769
+ * but with a `.`
770
+ */
771
+ static int
772
+ maybe_address(char *p, int size)
773
+ {
774
+ int ok = 0;
775
+
776
+ for ( ;size && (isalnum(*p) || strchr("._-+*", *p)); ++p, --size)
777
+ ;
778
+
779
+ if ( ! (size && *p == '@') )
780
+ return 0;
781
+
782
+ --size, ++p;
783
+
784
+ if ( size && *p == '.' ) return 0;
785
+
786
+ for ( ;size && (isalnum(*p) || strchr("._-+", *p)); ++p, --size )
787
+ if ( *p == '.' && size > 1 ) ok = 1;
788
+
789
+ return size ? 0 : ok;
790
+ }
791
+
792
+
793
+ /* The size-length token at cursor(f) is either a mailto:, an
794
+ * implicit mailto:, one of the approved url protocols, or just
795
+ * plain old text. If it's a mailto: or an approved protocol,
796
+ * linkify it, otherwise say "no"
797
+ */
798
+ static int
799
+ process_possible_link(MMIOT *f, int size)
800
+ {
801
+ int address= 0;
802
+ int mailto = 0;
803
+ char *text = cursor(f);
804
+
805
+ if ( f->flags & DENY_A ) return 0;
806
+
807
+ if ( (size > 7) && strncasecmp(text, "mailto:", 7) == 0 ) {
808
+ /* if it says it's a mailto, it's a mailto -- who am
809
+ * I to second-guess the user?
810
+ */
811
+ address = 1;
812
+ mailto = 7; /* 7 is the length of "mailto:"; we need this */
813
+ }
814
+ else
815
+ address = maybe_address(text, size);
816
+
817
+ if ( address ) {
818
+ Qstring("<a href=\"", f);
819
+ if ( !mailto ) {
820
+ /* supply a mailto: protocol if one wasn't attached */
821
+ mangle("mailto:", 7, f);
822
+ }
823
+ mangle(text, size, f);
824
+ Qstring("\">", f);
825
+ mangle(text+mailto, size-mailto, f);
826
+ Qstring("</a>", f);
827
+ return 1;
828
+ }
829
+ else if ( isautoprefix(text) ) {
830
+ Qstring("<a href=\"", f);
831
+ puturl(text,size,f, 0);
832
+ Qstring("\">", f);
833
+ puturl(text,size,f, 1);
834
+ Qstring("</a>", f);
835
+ return 1;
836
+ }
837
+ return 0;
838
+ } /* process_possible_link */
839
+
709
840
 
710
841
  /* a < may be just a regular character, the start of an embedded html
711
842
  * tag, or the start of an <automatic link>. If it's an automatic
@@ -716,68 +847,69 @@ forbidden_tag(MMIOT *f)
716
847
  static int
717
848
  maybe_tag_or_link(MMIOT *f)
718
849
  {
719
- char *text;
720
- int c, size, i;
721
- int maybetag=1, maybeaddress=0;
722
- int mailto;
850
+ int c, size;
851
+ int maybetag = 1;
723
852
 
724
853
  if ( f->flags & INSIDE_TAG )
725
854
  return 0;
726
855
 
727
- for ( size=0; ((c = peek(f,size+1)) != '>') && !isspace(c); size++ ) {
728
- if ( ! (c == '/' || isalnum(c) || c == '~') )
729
- maybetag=0;
730
- if ( c == '@' )
731
- maybeaddress=1;
732
- else if ( c == EOF )
856
+ for ( size=0; (c = peek(f, size+1)) != '>'; size++) {
857
+ if ( c == EOF )
733
858
  return 0;
859
+ else if ( c == '\\' ) {
860
+ maybetag=0;
861
+ if ( peek(f, size+2) != EOF )
862
+ size++;
863
+ }
864
+ else if ( isspace(c) )
865
+ break;
866
+ else if ( ! (c == '/' || isalnum(c) ) )
867
+ maybetag=0;
734
868
  }
735
869
 
736
- if ( size == 0 )
737
- return 0;
738
-
739
- if ( maybetag || (size >= 3 && strncmp(cursor(f), "!--", 3) == 0) ) {
740
- Qstring(forbidden_tag(f) ? "&lt;" : "<", f);
741
- while ( ((c = peek(f, 1)) != EOF) && (c != '>') )
742
- cputc(pull(f), f);
743
- return 1;
870
+ if ( size ) {
871
+ if ( maybetag || (size >= 3 && strncmp(cursor(f), "!--", 3) == 0) ) {
872
+ Qstring(forbidden_tag(f) ? "&lt;" : "<", f);
873
+ while ( ((c = peek(f, 1)) != EOF) && (c != '>') )
874
+ cputc(pull(f), f);
875
+ return 1;
876
+ }
877
+ else if ( !isspace(c) && process_possible_link(f, size) ) {
878
+ shift(f, size+1);
879
+ return 1;
880
+ }
744
881
  }
882
+
883
+ return 0;
884
+ }
745
885
 
746
- if ( f->flags & DENY_A ) return 0;
747
-
748
- text = cursor(f);
749
- shift(f, size+1);
750
886
 
751
- for ( i=0; i < SZAUTOPREFIX; i++ )
752
- if ( strncasecmp(text, autoprefix[i], strlen(autoprefix[i])) == 0 ) {
753
- Qstring("<a href=\"", f);
754
- puturl(text,size,f);
755
- Qstring("\">", f);
756
- puturl(text,size,f);
757
- Qstring("</a>", f);
758
- return 1;
759
- }
760
- if ( maybeaddress ) {
887
+ /* autolinking means that all inline html is <a href'ified>. A
888
+ * autolink url is alphanumerics, slashes, periods, underscores,
889
+ * the at sign, colon, and the % character.
890
+ */
891
+ static int
892
+ maybe_autolink(MMIOT *f)
893
+ {
894
+ register int c;
895
+ int size;
761
896
 
762
- Qstring("<a href=\"", f);
763
- if ( (size > 7) && strncasecmp(text, "mailto:", 7) == 0 )
764
- mailto = 7;
765
- else {
766
- mailto = 0;
767
- /* supply a mailto: protocol if one wasn't attached */
768
- mangle("mailto:", 7, f);
897
+ /* greedily scan forward for the end of a legitimate link.
898
+ */
899
+ for ( size=0; (c=peek(f, size+1)) != EOF; size++ )
900
+ if ( c == '\\' ) {
901
+ if ( peek(f, size+2) != EOF )
902
+ ++size;
769
903
  }
904
+ else if ( isspace(c) || strchr("'\"()[]{}<>`", c) )
905
+ break;
770
906
 
771
- mangle(text, size, f);
772
- Qstring("\">", f);
773
- mangle(text+mailto, size-mailto, f);
774
- Qstring("</a>", f);
907
+ if ( (size > 1) && process_possible_link(f, size) ) {
908
+ shift(f, size);
775
909
  return 1;
776
910
  }
777
-
778
- shift(f, -(size+1));
779
911
  return 0;
780
- } /* maybe_tag_or_link */
912
+ }
781
913
 
782
914
 
783
915
  /* smartyquote code that's common for single and double quotes
@@ -865,7 +997,7 @@ smartypants(int c, int *flags, MMIOT *f)
865
997
  {
866
998
  int i;
867
999
 
868
- if ( f->flags & DENY_SMARTY )
1000
+ if ( f->flags & (DENY_SMARTY|INSIDE_TAG) )
869
1001
  return 0;
870
1002
 
871
1003
  for ( i=0; i < NRSMART; i++)
@@ -919,12 +1051,23 @@ text(MMIOT *f)
919
1051
  int rep;
920
1052
  int smartyflags = 0;
921
1053
 
922
- while ( (c = pull(f)) != EOF ) {
1054
+ while (1) {
1055
+ if ( (f->flags & AUTOLINK) && isalpha(peek(f,1)) )
1056
+ maybe_autolink(f);
1057
+
1058
+ c = pull(f);
1059
+
1060
+ if (c == EOF)
1061
+ break;
1062
+
923
1063
  if ( smartypants(c, &smartyflags, f) )
924
1064
  continue;
925
1065
  switch (c) {
926
1066
  case 0: break;
927
1067
 
1068
+ case 3: Qstring("<br/>", f);
1069
+ break;
1070
+
928
1071
  case '>': if ( tag_text(f) )
929
1072
  Qstring("&gt;", f);
930
1073
  else
@@ -968,15 +1111,24 @@ text(MMIOT *f)
968
1111
  case '_':
969
1112
  #if RELAXED_EMPHASIS
970
1113
  /* Underscores don't count if they're in the middle of a word */
971
- if ( (!(f->flags & STRICT))
972
- && ((isthisspace(f,-1) && isthisspace(f,1))
973
- || (isthisalnum(f,-1) && isthisalnum(f,1))) ){
1114
+ if ( !(f->flags & STRICT) && isthisalnum(f,-1)
1115
+ && isthisalnum(f,1) ) {
1116
+ Qchar(c, f);
1117
+ break;
1118
+ }
1119
+ #endif
1120
+ case '*':
1121
+ #if RELAXED_EMPHASIS
1122
+ /* Underscores & stars don't count if they're out in the middle
1123
+ * of whitespace */
1124
+ if ( !(f->flags & STRICT) && isthisspace(f,-1)
1125
+ && isthisspace(f,1) ) {
974
1126
  Qchar(c, f);
975
1127
  break;
976
1128
  }
977
1129
  /* else fall into the regular old emphasis case */
978
1130
  #endif
979
- case '*': if ( tag_text(f) )
1131
+ if ( tag_text(f) )
980
1132
  Qchar(c, f);
981
1133
  else {
982
1134
  for (rep = 1; peek(f,1) == c; pull(f) )
@@ -1128,6 +1280,106 @@ printheader(Paragraph *pp, MMIOT *f)
1128
1280
  }
1129
1281
 
1130
1282
 
1283
+ enum e_alignments { a_NONE, a_CENTER, a_LEFT, a_RIGHT };
1284
+
1285
+ static char* alignments[] = { "", " align=\"center\"", " align=\"left\"",
1286
+ " align=\"right\"" };
1287
+
1288
+ typedef STRING(int) Istring;
1289
+
1290
+ static int
1291
+ splat(Line *p, char *block, Istring align, int force, MMIOT *f)
1292
+ {
1293
+ int first,
1294
+ idx = 0,
1295
+ colno = 0;
1296
+
1297
+ Qstring("<tr>\n", f);
1298
+ while ( idx < S(p->text) ) {
1299
+ first = idx;
1300
+ if ( force && (colno >= S(align)-1) )
1301
+ idx = S(p->text);
1302
+ else
1303
+ while ( (idx < S(p->text)) && (T(p->text)[idx] != '|') )
1304
+ ++idx;
1305
+
1306
+ Qprintf(f, "<%s%s>",
1307
+ block,
1308
+ alignments[ (colno < S(align)) ? T(align)[colno] : a_NONE ]);
1309
+ ___mkd_reparse(T(p->text)+first, idx-first, 0, f);
1310
+ Qprintf(f, "</%s>\n", block);
1311
+ idx++;
1312
+ colno++;
1313
+ }
1314
+ if ( force )
1315
+ while (colno < S(align) ) {
1316
+ Qprintf(f, "<%s></%s>\n", block, block);
1317
+ ++colno;
1318
+ }
1319
+ Qstring("</tr>\n", f);
1320
+ return colno;
1321
+ }
1322
+
1323
+ static int
1324
+ printtable(Paragraph *pp, MMIOT *f)
1325
+ {
1326
+ /* header, dashes, then lines of content */
1327
+
1328
+ Line *hdr, *dash, *body;
1329
+ Istring align;
1330
+ int start;
1331
+ int hcols;
1332
+ char *p;
1333
+
1334
+ if ( !(pp->text && pp->text->next && pp->text->next->next) )
1335
+ return 0;
1336
+
1337
+ hdr = pp->text;
1338
+ dash= hdr->next;
1339
+ body= dash->next;
1340
+
1341
+ /* first figure out cell alignments */
1342
+
1343
+ CREATE(align);
1344
+
1345
+ for (p=T(dash->text), start=0; start < S(dash->text); ) {
1346
+ char first, last;
1347
+ int end;
1348
+
1349
+ last=first=0;
1350
+ for (end=start ; (end < S(dash->text)) && p[end] != '|'; ++ end ) {
1351
+ if ( !isspace(p[end]) ) {
1352
+ if ( !first) first = p[end];
1353
+ last = p[end];
1354
+ }
1355
+ }
1356
+ EXPAND(align) = ( first == ':' ) ? (( last == ':') ? a_CENTER : a_LEFT)
1357
+ : (( last == ':') ? a_RIGHT : a_NONE );
1358
+ start = 1+end;
1359
+ }
1360
+
1361
+ Qstring("<table>\n", f);
1362
+ Qstring("<thead>\n", f);
1363
+ hcols = splat(hdr, "th", align, 0, f);
1364
+ Qstring("</thead>\n", f);
1365
+
1366
+ if ( hcols < S(align) )
1367
+ S(align) = hcols;
1368
+ else
1369
+ while ( hcols > S(align) )
1370
+ EXPAND(align) = a_NONE;
1371
+
1372
+ Qstring("<tbody>\n", f);
1373
+ for ( ; body; body = body->next)
1374
+ splat(body, "td", align, 1, f);
1375
+ Qstring("</tbody>\n", f);
1376
+ Qstring("</table>\n", f);
1377
+
1378
+ DELETE(align);
1379
+ return 1;
1380
+ }
1381
+
1382
+
1131
1383
  static int
1132
1384
  printblock(Paragraph *pp, MMIOT *f)
1133
1385
  {
@@ -1140,10 +1392,10 @@ printblock(Paragraph *pp, MMIOT *f)
1140
1392
  if ( S(t->text) > 2 && T(t->text)[S(t->text)-2] == ' '
1141
1393
  && T(t->text)[S(t->text)-1] == ' ') {
1142
1394
  push(T(t->text), S(t->text)-2, f);
1143
- push("<br/>\n", 6, f);
1395
+ push("\003\n", 2, f);
1144
1396
  }
1145
1397
  else {
1146
- ___mkd_tidy(t);
1398
+ ___mkd_tidy(&t->text);
1147
1399
  push(T(t->text), S(t->text), f);
1148
1400
  if ( t->next )
1149
1401
  push("\n", 1, f);
@@ -1234,6 +1486,7 @@ definitionlist(Paragraph *p, MMIOT *f)
1234
1486
  }
1235
1487
 
1236
1488
  htmlify(p->down, "dd", p->ident, f);
1489
+ Qchar('\n', f);
1237
1490
  }
1238
1491
 
1239
1492
  Qstring("</dl>", f);
@@ -1305,6 +1558,14 @@ display(Paragraph *p, MMIOT *f)
1305
1558
  printheader(p, f);
1306
1559
  break;
1307
1560
 
1561
+ case TABLE:
1562
+ printtable(p, f);
1563
+ break;
1564
+
1565
+ case SOURCE:
1566
+ htmlify(p->down, 0, 0, f);
1567
+ break;
1568
+
1308
1569
  default:
1309
1570
  printblock(p, f);
1310
1571
  break;
@@ -1313,29 +1574,6 @@ display(Paragraph *p, MMIOT *f)
1313
1574
  }
1314
1575
 
1315
1576
 
1316
- /*
1317
- * dump out stylesheet sections.
1318
- */
1319
- static int
1320
- stylesheets(Paragraph *p, FILE *f)
1321
- {
1322
- Line* q;
1323
-
1324
- for ( ; p ; p = p->next ) {
1325
- if ( p->typ == STYLE ) {
1326
- for ( q = p->text; q ; q = q->next )
1327
- if ( fwrite(T(q->text), S(q->text), 1, f) == 1 )
1328
- putc('\n', f);
1329
- else
1330
- return EOF;
1331
- }
1332
- if ( p->down && (stylesheets(p->down, f) == EOF) )
1333
- return EOF;
1334
- }
1335
- return 0;
1336
- }
1337
-
1338
-
1339
1577
  /* return a pointer to the compiled markdown
1340
1578
  * document.
1341
1579
  */
@@ -1354,36 +1592,3 @@ mkd_document(Document *p, char **res)
1354
1592
  return EOF;
1355
1593
  }
1356
1594
 
1357
-
1358
- /* public interface for ___mkd_reparse()
1359
- */
1360
- int
1361
- mkd_text(char *bfr, int size, FILE *output, int flags)
1362
- {
1363
- MMIOT f;
1364
-
1365
- ___mkd_initmmiot(&f, 0);
1366
- f.flags = flags & USER_FLAGS;
1367
-
1368
- ___mkd_reparse(bfr, size, 0, &f);
1369
- ___mkd_emblock(&f);
1370
- if ( flags & CDATA_OUTPUT )
1371
- ___mkd_xml(T(f.out), S(f.out), output);
1372
- else
1373
- fwrite(T(f.out), S(f.out), 1, output);
1374
-
1375
- ___mkd_freemmiot(&f, 0);
1376
- return 0;
1377
- }
1378
-
1379
-
1380
- /* dump any embedded styles
1381
- */
1382
- int
1383
- mkd_style(Document *d, FILE *f)
1384
- {
1385
- if ( d && d->compiled )
1386
- return stylesheets(d->code, f);
1387
- return EOF;
1388
- }
1389
-