bluecloth 2.0.6.pre120-x86-mswin32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. data/ChangeLog +363 -0
  2. data/LICENSE +27 -0
  3. data/LICENSE.discount +47 -0
  4. data/README +81 -0
  5. data/Rakefile +349 -0
  6. data/Rakefile.local +43 -0
  7. data/bin/bluecloth +84 -0
  8. data/ext/Csio.c +61 -0
  9. data/ext/VERSION +1 -0
  10. data/ext/amalloc.h +29 -0
  11. data/ext/bluecloth.c +391 -0
  12. data/ext/bluecloth.h +19 -0
  13. data/ext/config.h +55 -0
  14. data/ext/css.c +76 -0
  15. data/ext/cstring.h +75 -0
  16. data/ext/docheader.c +43 -0
  17. data/ext/extconf.rb +52 -0
  18. data/ext/generate.c +1602 -0
  19. data/ext/markdown.c +1078 -0
  20. data/ext/markdown.h +146 -0
  21. data/ext/mkdio.c +303 -0
  22. data/ext/mkdio.h +79 -0
  23. data/ext/resource.c +155 -0
  24. data/ext/version.c +28 -0
  25. data/ext/xml.c +82 -0
  26. data/ext/xmlpage.c +48 -0
  27. data/lib/1.8/bluecloth_ext.so +0 -0
  28. data/lib/1.9/bluecloth_ext.so +0 -0
  29. data/lib/bluecloth.rb +164 -0
  30. data/rake/191_compat.rb +26 -0
  31. data/rake/dependencies.rb +76 -0
  32. data/rake/helpers.rb +434 -0
  33. data/rake/hg.rb +273 -0
  34. data/rake/manual.rb +782 -0
  35. data/rake/packaging.rb +126 -0
  36. data/rake/publishing.rb +269 -0
  37. data/rake/rdoc.rb +30 -0
  38. data/rake/style.rb +62 -0
  39. data/rake/svn.rb +668 -0
  40. data/rake/testing.rb +187 -0
  41. data/rake/verifytask.rb +64 -0
  42. data/rake/win32.rb +190 -0
  43. data/spec/bluecloth/101_changes_spec.rb +141 -0
  44. data/spec/bluecloth/autolinks_spec.rb +49 -0
  45. data/spec/bluecloth/blockquotes_spec.rb +145 -0
  46. data/spec/bluecloth/code_spans_spec.rb +164 -0
  47. data/spec/bluecloth/emphasis_spec.rb +164 -0
  48. data/spec/bluecloth/entities_spec.rb +65 -0
  49. data/spec/bluecloth/hrules_spec.rb +90 -0
  50. data/spec/bluecloth/images_spec.rb +92 -0
  51. data/spec/bluecloth/inline_html_spec.rb +238 -0
  52. data/spec/bluecloth/links_spec.rb +171 -0
  53. data/spec/bluecloth/lists_spec.rb +294 -0
  54. data/spec/bluecloth/paragraphs_spec.rb +75 -0
  55. data/spec/bluecloth/titles_spec.rb +305 -0
  56. data/spec/bluecloth_spec.rb +281 -0
  57. data/spec/bugfix_spec.rb +172 -0
  58. data/spec/contributions_spec.rb +85 -0
  59. data/spec/data/antsugar.txt +34 -0
  60. data/spec/data/markdowntest/Amps and angle encoding.html +17 -0
  61. data/spec/data/markdowntest/Amps and angle encoding.text +21 -0
  62. data/spec/data/markdowntest/Auto links.html +18 -0
  63. data/spec/data/markdowntest/Auto links.text +13 -0
  64. data/spec/data/markdowntest/Backslash escapes.html +118 -0
  65. data/spec/data/markdowntest/Backslash escapes.text +120 -0
  66. data/spec/data/markdowntest/Blockquotes with code blocks.html +15 -0
  67. data/spec/data/markdowntest/Blockquotes with code blocks.text +11 -0
  68. data/spec/data/markdowntest/Code Blocks.html +18 -0
  69. data/spec/data/markdowntest/Code Blocks.text +14 -0
  70. data/spec/data/markdowntest/Code Spans.html +5 -0
  71. data/spec/data/markdowntest/Code Spans.text +5 -0
  72. data/spec/data/markdowntest/Hard-wrapped paragraphs with list-like lines.html +8 -0
  73. data/spec/data/markdowntest/Hard-wrapped paragraphs with list-like lines.text +8 -0
  74. data/spec/data/markdowntest/Horizontal rules.html +71 -0
  75. data/spec/data/markdowntest/Horizontal rules.text +67 -0
  76. data/spec/data/markdowntest/Inline HTML (Advanced).html +15 -0
  77. data/spec/data/markdowntest/Inline HTML (Advanced).text +15 -0
  78. data/spec/data/markdowntest/Inline HTML (Simple).html +72 -0
  79. data/spec/data/markdowntest/Inline HTML (Simple).text +69 -0
  80. data/spec/data/markdowntest/Inline HTML comments.html +13 -0
  81. data/spec/data/markdowntest/Inline HTML comments.text +13 -0
  82. data/spec/data/markdowntest/Links, inline style.html +11 -0
  83. data/spec/data/markdowntest/Links, inline style.text +12 -0
  84. data/spec/data/markdowntest/Links, reference style.html +52 -0
  85. data/spec/data/markdowntest/Links, reference style.text +71 -0
  86. data/spec/data/markdowntest/Links, shortcut references.html +9 -0
  87. data/spec/data/markdowntest/Links, shortcut references.text +20 -0
  88. data/spec/data/markdowntest/Literal quotes in titles.html +3 -0
  89. data/spec/data/markdowntest/Literal quotes in titles.text +7 -0
  90. data/spec/data/markdowntest/Markdown Documentation - Basics.html +314 -0
  91. data/spec/data/markdowntest/Markdown Documentation - Basics.text +306 -0
  92. data/spec/data/markdowntest/Markdown Documentation - Syntax.html +942 -0
  93. data/spec/data/markdowntest/Markdown Documentation - Syntax.text +888 -0
  94. data/spec/data/markdowntest/Nested blockquotes.html +9 -0
  95. data/spec/data/markdowntest/Nested blockquotes.text +5 -0
  96. data/spec/data/markdowntest/Ordered and unordered lists.html +148 -0
  97. data/spec/data/markdowntest/Ordered and unordered lists.text +131 -0
  98. data/spec/data/markdowntest/Strong and em together.html +7 -0
  99. data/spec/data/markdowntest/Strong and em together.text +7 -0
  100. data/spec/data/markdowntest/Tabs.html +25 -0
  101. data/spec/data/markdowntest/Tabs.text +21 -0
  102. data/spec/data/markdowntest/Tidyness.html +8 -0
  103. data/spec/data/markdowntest/Tidyness.text +5 -0
  104. data/spec/data/ml-announce.txt +17 -0
  105. data/spec/data/re-overflow.txt +67 -0
  106. data/spec/data/re-overflow2.txt +281 -0
  107. data/spec/discount_spec.rb +184 -0
  108. data/spec/lib/constants.rb +5 -0
  109. data/spec/lib/helpers.rb +137 -0
  110. data/spec/lib/matchers.rb +235 -0
  111. data/spec/markdowntest_spec.rb +79 -0
  112. metadata +205 -0
data/ext/bluecloth.h ADDED
@@ -0,0 +1,19 @@
1
+ /*
2
+ * BlueCloth -- a Ruby implementation of Markdown
3
+ * $Id$
4
+ *
5
+ */
6
+
7
+ #ifndef BLUECLOTH_H
8
+ #define BLUECLOTH_H
9
+
10
+ #include "config.h"
11
+
12
+ #include "mkdio.h"
13
+ #include "ruby.h"
14
+
15
+ #ifdef HAVE_RUBY_ENCODING_H
16
+ # include "ruby/encoding.h"
17
+ #endif
18
+
19
+ #endif
data/ext/config.h ADDED
@@ -0,0 +1,55 @@
1
+ /*
2
+ * configuration for markdown, generated Tue Dec 23 06:19:25 PST 2008
3
+ * by ged@tenar.home.faeriemud.org
4
+ */
5
+
6
+ #ifndef CONFIG_H_RZLE3ADO
7
+ #define CONFIG_H_RZLE3ADO
8
+
9
+ #ifdef RUBY_EXTCONF_H
10
+ # include RUBY_EXTCONF_H
11
+ #endif
12
+
13
+ #ifdef HAVE_SRANDOM
14
+ # define INITRNG(x) srandom((unsigned int)x)
15
+ #elif HAVE_SRAND
16
+ # define INITRNG(x) srand((unsigned int)x)
17
+ #else
18
+ # define INITRNG(x) (void)1
19
+ #endif
20
+
21
+ #ifndef HAVE_BZERO
22
+ # define bzero(s, n) (memset((void *)s, 0, (size_t)n))
23
+ #endif
24
+
25
+ #ifdef HAVE_STRCASECMP
26
+ #elif HAVE_STRICMP
27
+ # define strcasecmp stricmp
28
+ #endif
29
+
30
+ #ifdef HAVE_STRNCASECMP
31
+ #elif HAVE_STRNICMP
32
+ # define strncasecmp strnicmp
33
+ #endif
34
+
35
+ #ifdef HAVE_RANDOM
36
+ # define COINTOSS() (random()&1)
37
+ #elif HAVE_RAND
38
+ # define COINTOSS() (rand()&1)
39
+ #else
40
+ # define COINTOSS() 1
41
+ #endif
42
+
43
+ #define TABSTOP 4
44
+ #undef USE_AMALLOC
45
+
46
+ /* Extensions */
47
+ #define SUPERSCRIPT 1
48
+ #define RELAXED_EMPHASIS 1
49
+ #define DIV_QUOTE 1
50
+ #define DL_TAG_EXTENSION 1
51
+ #define PANDOC_HEADER 1
52
+ #define ALPHA_LIST 1
53
+
54
+ #endif /* end of include guard: CONFIG_H_RZLE3ADO */
55
+
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 ADDED
@@ -0,0 +1,75 @@
1
+ /* two template types: STRING(t) which defines a pascal-style string
2
+ * of element (t) [STRING(char) is the closest to the pascal string],
3
+ * and ANCHOR(t) which defines a baseplate that a linked list can be
4
+ * built up from. [The linked list /must/ contain a ->next pointer
5
+ * for linking the list together with.]
6
+ */
7
+ #ifndef _CSTRING_D
8
+ #define _CSTRING_D
9
+
10
+ #include <string.h>
11
+ #include <stdlib.h>
12
+
13
+ #include "amalloc.h"
14
+
15
+ /* expandable Pascal-style string.
16
+ */
17
+ #define STRING(type) struct { type *text; int size, alloc; }
18
+
19
+ #define CREATE(x) T(x) = (void*)(S(x) = (x).alloc = 0)
20
+ #define EXPAND(x) (S(x)++)[(S(x) < (x).alloc) \
21
+ ? (T(x)) \
22
+ : (T(x) = T(x) ? realloc(T(x), sizeof T(x)[0] * ((x).alloc += 100)) \
23
+ : malloc(sizeof T(x)[0] * ((x).alloc += 100)) )]
24
+
25
+ #define DELETE(x) ALLOCATED(x) ? (free(T(x)), S(x) = (x).alloc = 0) \
26
+ : ( S(x) = 0 )
27
+ #define CLIP(t,i,sz) \
28
+ ( ((i) >= 0) && ((sz) > 0) && (((i)+(sz)) <= S(t)) ) ? \
29
+ (memmove(&T(t)[i], &T(t)[i+sz], (S(t)-(i+sz)+1)*sizeof(T(t)[0])), \
30
+ S(t) -= (sz)) : -1
31
+
32
+ #define RESERVE(x, sz) T(x) = ((x).alloc > S(x) + (sz) \
33
+ ? T(x) \
34
+ : T(x) \
35
+ ? realloc(T(x), sizeof T(x)[0] * ((x).alloc = 100+(sz)+S(x))) \
36
+ : malloc(sizeof T(x)[0] * ((x).alloc = 100+(sz)+S(x))))
37
+ #define SUFFIX(t,p,sz) \
38
+ memcpy(((S(t) += (sz)) - (sz)) + \
39
+ (T(t) = T(t) ? realloc(T(t), sizeof T(t)[0] * ((t).alloc += sz)) \
40
+ : malloc(sizeof T(t)[0] * ((t).alloc += sz))), \
41
+ (p), sizeof(T(t)[0])*(sz))
42
+
43
+ #define PREFIX(t,p,sz) \
44
+ RESERVE( (t), (sz) ); \
45
+ if ( S(t) ) { memmove(T(t)+(sz), T(t), S(t)); } \
46
+ memcpy( T(t), (p), (sz) ); \
47
+ S(t) += (sz)
48
+
49
+ /* reference-style links (and images) are stored in an array
50
+ */
51
+ #define T(x) (x).text
52
+ #define S(x) (x).size
53
+ #define ALLOCATED(x) (x).alloc
54
+
55
+ /* abstract anchor type that defines a list base
56
+ * with a function that attaches an element to
57
+ * the end of the list.
58
+ *
59
+ * the list base field is named .text so that the T()
60
+ * macro will work with it.
61
+ */
62
+ #define ANCHOR(t) struct { t *text, *end; }
63
+ #define E(t) ((t).end)
64
+
65
+ #define ATTACH(t, p) ( T(t) ? ( (E(t)->next = (p)), (E(t) = (p)) ) \
66
+ : ( (T(t) = E(t) = (p)) ) )
67
+
68
+ typedef STRING(char) Cstring;
69
+
70
+ extern void Csputc(int, Cstring *);
71
+ extern int Csprintf(Cstring *, char *, ...);
72
+ extern int Cswrite(Cstring *, char *, int);
73
+ extern void Csreparse(Cstring *, char *, int, int);
74
+
75
+ #endif/*_CSTRING_D*/
data/ext/docheader.c ADDED
@@ -0,0 +1,43 @@
1
+ /*
2
+ * docheader -- get values from the document header
3
+ *
4
+ * Copyright (C) 2007 David L Parsons.
5
+ * The redistribution terms are provided in the COPYRIGHT file that must
6
+ * be distributed with this source code.
7
+ */
8
+ #include "config.h"
9
+ #include <stdio.h>
10
+ #include <stdlib.h>
11
+ #include <ctype.h>
12
+
13
+ #include "cstring.h"
14
+ #include "markdown.h"
15
+ #include "amalloc.h"
16
+
17
+ #define afterdle(t) (T((t)->text) + (t)->dle)
18
+
19
+ char *
20
+ mkd_doc_title(Document *doc)
21
+ {
22
+ if ( doc && doc->headers )
23
+ return afterdle(doc->headers);
24
+ return 0;
25
+ }
26
+
27
+
28
+ char *
29
+ mkd_doc_author(Document *doc)
30
+ {
31
+ if ( doc && doc->headers && doc->headers->next )
32
+ return afterdle(doc->headers->next);
33
+ return 0;
34
+ }
35
+
36
+
37
+ char *
38
+ mkd_doc_date(Document *doc)
39
+ {
40
+ if ( doc && doc->headers && doc->headers->next && doc->headers->next->next )
41
+ return afterdle(doc->headers->next->next);
42
+ return 0;
43
+ }
data/ext/extconf.rb ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mkmf'
4
+ require 'fileutils'
5
+ require 'pathname'
6
+ require 'rbconfig'
7
+ include Config
8
+
9
+ versionfile = Pathname.new( __FILE__ ).dirname + 'VERSION'
10
+ version = versionfile.read.chomp
11
+
12
+ # Thanks to Daniel Berger for helping me out with this. :)
13
+ if CONFIG['host_os'].match( 'mswin' )
14
+ $CFLAGS << ' -I.' << ' -W3' << ' -Zi'
15
+ else
16
+ $CFLAGS << ' -I.' << ' -Wall'
17
+ end
18
+ $CPPFLAGS << %Q{ -DVERSION=\\"#{version}\\"}
19
+
20
+ # Add my own debugging hooks if building for me
21
+ if ENV['DEBUGGING_BUILD']
22
+ $CFLAGS << ' -ggdb' << ' -DDEBUG'
23
+ end
24
+
25
+ def fail( *messages )
26
+ $stderr.puts( *messages )
27
+ exit( 1 )
28
+ end
29
+
30
+ # Stuff from configure.sh
31
+ have_func( "srand" ) || have_func( "srandom" )
32
+ have_func( "random" ) || have_func( "rand" )
33
+
34
+ # bzero() isn't ANSI C, so use memset() if it isn't defined
35
+ have_func( "bzero", %w[string.h strings.h] )
36
+
37
+ unless have_func( "strcasecmp" ) || have_func( "stricmp" )
38
+ fail( "This extension requires either strcasecmp() or stricmp()" )
39
+ end
40
+ unless have_func( "strncasecmp" ) || have_func( "strnicmp" )
41
+ fail( "This extensions requires either strncasecmp() or strnicmp()" )
42
+ end
43
+
44
+ have_header( 'mkdio.h' ) or fail( "missing mkdio.h" )
45
+
46
+ # Check for 1.9.xish encoding header
47
+ have_header( 'ruby/encoding.h' )
48
+
49
+ create_header()
50
+ create_makefile( 'bluecloth_ext' )
51
+
52
+ FileUtils.rm_rf( 'conftest.dSYM' ) # MacOS X cleanup
data/ext/generate.c ADDED
@@ -0,0 +1,1602 @@
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
+
22
+
23
+ /* forward declarations */
24
+ static int iscodeblock(MMIOT*);
25
+ static void code(int, MMIOT*);
26
+ static void text(MMIOT *f);
27
+ static Paragraph *display(Paragraph*, MMIOT*);
28
+
29
+ /* externals from markdown.c */
30
+ int __mkd_footsort(Footnote *, Footnote *);
31
+
32
+ /*
33
+ * push text into the generator input buffer
34
+ */
35
+ static void
36
+ push(char *bfr, int size, MMIOT *f)
37
+ {
38
+ while ( size-- > 0 )
39
+ EXPAND(f->in) = *bfr++;
40
+ }
41
+
42
+
43
+ /* look <i> characters ahead of the cursor.
44
+ */
45
+ static int
46
+ peek(MMIOT *f, int i)
47
+ {
48
+
49
+ i += (f->isp-1);
50
+
51
+ return (i >= 0) && (i < S(f->in)) ? T(f->in)[i] : EOF;
52
+ }
53
+
54
+
55
+ /* pull a byte from the input buffer
56
+ */
57
+ static int
58
+ pull(MMIOT *f)
59
+ {
60
+ return ( f->isp < S(f->in) ) ? T(f->in)[f->isp++] : EOF;
61
+ }
62
+
63
+
64
+ /* return a pointer to the current position in the input buffer.
65
+ */
66
+ static char*
67
+ cursor(MMIOT *f)
68
+ {
69
+ return T(f->in) + f->isp;
70
+ }
71
+
72
+
73
+ static int
74
+ isthisspace(MMIOT *f, int i)
75
+ {
76
+ int c = peek(f, i);
77
+
78
+ return isspace(c) || (c == EOF);
79
+ }
80
+
81
+
82
+ static int
83
+ isthisalnum(MMIOT *f, int i)
84
+ {
85
+ int c = peek(f, i);
86
+
87
+ return (c != EOF) && isalnum(c);
88
+ }
89
+
90
+
91
+ static int
92
+ isthisnonword(MMIOT *f, int i)
93
+ {
94
+ return isthisspace(f, i) || ispunct(peek(f,i));
95
+ }
96
+
97
+
98
+ /* return/set the current cursor position
99
+ */
100
+ #define mmiotseek(f,x) (f->isp = x)
101
+ #define mmiottell(f) (f->isp)
102
+
103
+
104
+ /* move n characters forward ( or -n characters backward) in the input buffer.
105
+ */
106
+ static void
107
+ shift(MMIOT *f, int i)
108
+ {
109
+ if (f->isp + i >= 0 )
110
+ f->isp += i;
111
+ }
112
+
113
+
114
+ /* Qchar()
115
+ */
116
+ static void
117
+ Qchar(int c, MMIOT *f)
118
+ {
119
+ block *cur;
120
+
121
+ if ( S(f->Q) == 0 ) {
122
+ cur = &EXPAND(f->Q);
123
+ memset(cur, 0, sizeof *cur);
124
+ cur->b_type = bTEXT;
125
+ }
126
+ else
127
+ cur = &T(f->Q)[S(f->Q)-1];
128
+
129
+ EXPAND(cur->b_text) = c;
130
+
131
+ }
132
+
133
+
134
+ /* Qstring()
135
+ */
136
+ static void
137
+ Qstring(char *s, MMIOT *f)
138
+ {
139
+ while (*s)
140
+ Qchar(*s++, f);
141
+ }
142
+
143
+
144
+ /* Qwrite()
145
+ */
146
+ static void
147
+ Qwrite(char *s, int size, MMIOT *f)
148
+ {
149
+ while (size-- > 0)
150
+ Qchar(*s++, f);
151
+ }
152
+
153
+
154
+ /* Qprintf()
155
+ */
156
+ static void
157
+ Qprintf(MMIOT *f, char *fmt, ...)
158
+ {
159
+ char bfr[80];
160
+ va_list ptr;
161
+
162
+ va_start(ptr,fmt);
163
+ vsnprintf(bfr, sizeof bfr, fmt, ptr);
164
+ va_end(ptr);
165
+ Qstring(bfr, f);
166
+ }
167
+
168
+
169
+ /* Qem()
170
+ */
171
+ static void
172
+ Qem(MMIOT *f, char c, int count)
173
+ {
174
+ block *p = &EXPAND(f->Q);
175
+
176
+ memset(p, 0, sizeof *p);
177
+ p->b_type = (c == '*') ? bSTAR : bUNDER;
178
+ p->b_char = c;
179
+ p->b_count = count;
180
+
181
+ memset(&EXPAND(f->Q), 0, sizeof(block));
182
+ }
183
+
184
+
185
+ /* empair()
186
+ */
187
+ static int
188
+ empair(MMIOT *f, int go, int level)
189
+ {
190
+
191
+ int i;
192
+ block *begin, *p;
193
+
194
+ begin = &T(f->Q)[go];
195
+ for (i=go+1; i < S(f->Q); i++) {
196
+ p = &T(f->Q)[i];
197
+
198
+ if ( (p->b_type != bTEXT) && (p->b_count <= 0) )
199
+ break;
200
+
201
+ if ( p->b_type == begin->b_type ) {
202
+ if ( p->b_count == level ) /* exact match */
203
+ return i-go;
204
+
205
+ if ( p->b_count > 2 ) /* fuzzy match */
206
+ return i-go;
207
+ }
208
+ }
209
+ return EOF;
210
+ }
211
+
212
+
213
+
214
+ static struct emtags {
215
+ char open[10];
216
+ char close[10];
217
+ int size;
218
+ } emtags[] = { { "<em>" , "</em>", 5 }, { "<strong>", "</strong>", 9 } };
219
+
220
+
221
+ static void
222
+ emclose(Cstring *s, int level)
223
+ {
224
+ PREFIX(*s, emtags[level-1].close, emtags[level-1].size);
225
+ }
226
+
227
+
228
+ static void
229
+ emopen(Cstring *s, int level)
230
+ {
231
+ SUFFIX(*s, emtags[level-1].open, emtags[level-1].size-1);
232
+ }
233
+
234
+
235
+ /* emmatch()
236
+ */
237
+ static void
238
+ emmatch(MMIOT *f, int go)
239
+ {
240
+ block *start = &T(f->Q)[go], *end;
241
+ int e, e2, i, match;
242
+
243
+ while ( start->b_count ) {
244
+ switch (start->b_count) {
245
+ case 2: e = empair(f,go,match=2);
246
+ if ( e != EOF ) break;
247
+ case 1: e = empair(f,go,match=1); break;
248
+ default:
249
+ e = empair(f,go,1);
250
+ e2= empair(f,go,2);
251
+
252
+ if ( e == EOF || ((e2 != EOF) && (e2 >= e)) ) {
253
+ e = e2;
254
+ match = 2;
255
+ }
256
+ else
257
+ match = 1;
258
+ }
259
+ if ( e != EOF ) {
260
+ end = &T(f->Q)[go+e];
261
+ emclose(&end->b_post, match);
262
+ emopen(&start->b_text, match);
263
+ end->b_count -= match;
264
+ }
265
+ else {
266
+ for (i=0; i < match; i++)
267
+ EXPAND(start->b_text) = start->b_char;
268
+ }
269
+
270
+ start->b_count -= match;
271
+ }
272
+ }
273
+
274
+
275
+ /* ___mkd_emblock()
276
+ */
277
+ void
278
+ ___mkd_emblock(MMIOT *f)
279
+ {
280
+ int i;
281
+ block *p;
282
+
283
+ for (i=0; i < S(f->Q); i++) {
284
+ p = &T(f->Q)[i];
285
+
286
+ if ( p->b_type != bTEXT ) emmatch(f, i);
287
+
288
+ if ( S(p->b_post) ) { SUFFIX(f->out, T(p->b_post), S(p->b_post));
289
+ DELETE(p->b_post); }
290
+ if ( S(p->b_text) ) { SUFFIX(f->out, T(p->b_text), S(p->b_text));
291
+ DELETE(p->b_text); }
292
+ }
293
+ S(f->Q) = 0;
294
+ }
295
+
296
+
297
+ /* generate html from a markup fragment
298
+ */
299
+ void
300
+ ___mkd_reparse(char *bfr, int size, int flags, MMIOT *f)
301
+ {
302
+ MMIOT sub;
303
+
304
+ ___mkd_initmmiot(&sub, f->footnotes);
305
+
306
+ sub.flags = f->flags | flags;
307
+ sub.base = f->base;
308
+
309
+ push(bfr, size, &sub);
310
+ EXPAND(sub.in) = 0;
311
+ S(sub.in)--;
312
+
313
+ text(&sub);
314
+ ___mkd_emblock(&sub);
315
+
316
+ Qwrite(T(sub.out), S(sub.out), f);
317
+
318
+ ___mkd_freemmiot(&sub, f->footnotes);
319
+ }
320
+
321
+
322
+ /*
323
+ * write out a url, escaping problematic characters
324
+ */
325
+ static void
326
+ puturl(char *s, int size, MMIOT *f, int display)
327
+ {
328
+ unsigned char c;
329
+
330
+ while ( size-- > 0 ) {
331
+ c = *s++;
332
+
333
+ if ( c == '\\' && size-- > 0 ) {
334
+ c = *s++;
335
+
336
+ if ( !( ispunct(c) || isspace(c) ) )
337
+ Qchar('\\', f);
338
+ }
339
+
340
+ if ( c == '&' )
341
+ Qstring("&amp;", f);
342
+ else if ( c == '<' )
343
+ Qstring("&lt;", f);
344
+ else if ( c == '"' )
345
+ Qstring("%22", f);
346
+ else if ( isalnum(c) || ispunct(c) || (display && isspace(c)) )
347
+ Qchar(c, f);
348
+ else
349
+ Qprintf(f, "%%%02X", c);
350
+ }
351
+ }
352
+
353
+
354
+ /* advance forward until the next character is not whitespace
355
+ */
356
+ static int
357
+ eatspace(MMIOT *f)
358
+ {
359
+ int c;
360
+
361
+ for ( ; ((c=peek(f, 1)) != EOF) && isspace(c); pull(f) )
362
+ ;
363
+ return c;
364
+ }
365
+
366
+
367
+ /* (match (a (nested (parenthetical (string.)))))
368
+ */
369
+ static int
370
+ parenthetical(int in, int out, MMIOT *f)
371
+ {
372
+ int size, indent, c;
373
+
374
+ for ( indent=1,size=0; indent; size++ ) {
375
+ if ( (c = pull(f)) == EOF )
376
+ return EOF;
377
+ else if ( c == in )
378
+ ++indent;
379
+ else if ( (c == '\\') && (peek(f,1) == out) ) {
380
+ ++size;
381
+ pull(f);
382
+ }
383
+ else if ( c == out )
384
+ --indent;
385
+ }
386
+ return size ? (size-1) : 0;
387
+ }
388
+
389
+
390
+ /* extract a []-delimited label from the input stream.
391
+ */
392
+ static int
393
+ linkylabel(MMIOT *f, Cstring *res)
394
+ {
395
+ char *ptr = cursor(f);
396
+ int size;
397
+
398
+ if ( (size = parenthetical('[',']',f)) != EOF ) {
399
+ T(*res) = ptr;
400
+ S(*res) = size;
401
+ return 1;
402
+ }
403
+ return 0;
404
+ }
405
+
406
+
407
+ /* see if the quote-prefixed linky segment is actually a title.
408
+ */
409
+ static int
410
+ linkytitle(MMIOT *f, char quote, Footnote *ref)
411
+ {
412
+ int whence = mmiottell(f);
413
+ char *title = cursor(f);
414
+ char *e;
415
+ register int c;
416
+
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
+ }
426
+ }
427
+ mmiotseek(f, whence);
428
+ return 0;
429
+ }
430
+
431
+
432
+ /* extract a =HHHxWWW size from the input stream
433
+ */
434
+ static int
435
+ linkysize(MMIOT *f, Footnote *ref)
436
+ {
437
+ int height=0, width=0;
438
+ int whence = mmiottell(f);
439
+ int c;
440
+
441
+ if ( isspace(peek(f,0)) ) {
442
+ pull(f); /* eat '=' */
443
+
444
+ for ( c = pull(f); isdigit(c); c = pull(f))
445
+ width = (width * 10) + (c - '0');
446
+
447
+ if ( c == 'x' ) {
448
+ for ( c = pull(f); isdigit(c); c = pull(f))
449
+ height = (height*10) + (c - '0');
450
+
451
+ if ( isspace(c) )
452
+ c = eatspace(f);
453
+
454
+ if ( (c == ')') || ((c == '\'' || c == '"') && linkytitle(f, c, ref)) ) {
455
+ ref->height = height;
456
+ ref->width = width;
457
+ return 1;
458
+ }
459
+ }
460
+ }
461
+ mmiotseek(f, whence);
462
+ return 0;
463
+ }
464
+
465
+
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 '='
471
+ */
472
+ static int
473
+ linkyurl(MMIOT *f, int image, Footnote *p)
474
+ {
475
+ int c;
476
+ int mayneedtotrim=0;
477
+
478
+ if ( (c = eatspace(f)) == EOF )
479
+ return 0;
480
+
481
+ if ( c == '<' ) {
482
+ pull(f);
483
+ mayneedtotrim=1;
484
+ }
485
+
486
+ T(p->link) = cursor(f);
487
+ for ( S(p->link)=0; (c = peek(f,1)) != ')'; ++S(p->link) ) {
488
+ if ( c == EOF )
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
+ }
510
+
511
+
512
+
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])
527
+
528
+
529
+ static int
530
+ isautoprefix(char *text)
531
+ {
532
+ int i;
533
+
534
+ for (i=0; i < NRPROTOCOLS; i++)
535
+ if ( strncasecmp(text, protocol[i].name, protocol[i].nlen) == 0 )
536
+ return 1;
537
+ return 0;
538
+ }
539
+
540
+
541
+ /*
542
+ * all the tag types that linkylinky can produce are
543
+ * defined by this structure.
544
+ */
545
+ typedef struct linkytype {
546
+ char *pat;
547
+ int szpat;
548
+ char *link_pfx; /* tag prefix and link pointer (eg: "<a href="\"" */
549
+ char *link_sfx; /* link suffix (eg: "\"" */
550
+ int WxH; /* this tag allows width x height arguments */
551
+ char *text_pfx; /* text prefix (eg: ">" */
552
+ char *text_sfx; /* text suffix (eg: "</a>" */
553
+ int flags; /* reparse flags */
554
+ int kind; /* tag is url or something else? */
555
+ #define IS_URL 0x01
556
+ } linkytype;
557
+
558
+ static linkytype imaget = { 0, 0, "<img src=\"", "\"",
559
+ 1, " alt=\"", "\" />", DENY_IMG|INSIDE_TAG, IS_URL };
560
+ static linkytype linkt = { 0, 0, "<a href=\"", "\"",
561
+ 0, ">", "</a>", DENY_A, IS_URL };
562
+
563
+ /*
564
+ * pseudo-protocols for [][];
565
+ *
566
+ * id: generates <a id="link">tag</a>
567
+ * class: generates <span class="link">tag</span>
568
+ * raw: just dump the link without any processing
569
+ */
570
+ static linkytype specials[] = {
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 },
575
+ } ;
576
+
577
+ #define NR(x) (sizeof x / sizeof x[0])
578
+
579
+ /* see if t contains one of our pseudo-protocols.
580
+ */
581
+ static linkytype *
582
+ pseudo(Cstring t)
583
+ {
584
+ int i;
585
+ linkytype *r;
586
+
587
+ for ( i=0; i < NR(specials); i++ ) {
588
+ r = &specials[i];
589
+ if ( (S(t) > r->szpat) && (strncasecmp(T(t), r->pat, r->szpat) == 0) )
590
+ return r;
591
+ }
592
+ return 0;
593
+ }
594
+
595
+
596
+ /* print out a linky (or fail if it's Not Allowed)
597
+ */
598
+ static int
599
+ linkyformat(MMIOT *f, Cstring text, int image, Footnote *ref)
600
+ {
601
+ linkytype *tag;
602
+
603
+ if ( image )
604
+ tag = &imaget;
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
617
+ tag = &linkt;
618
+
619
+ if ( f->flags & tag->flags )
620
+ return 0;
621
+
622
+ if ( tag->link_pfx ) {
623
+ Qstring(tag->link_pfx, 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
+
633
+ Qstring(tag->link_sfx, f);
634
+
635
+ if ( tag->WxH) {
636
+ if ( ref->height) Qprintf(f," height=\"%d\"", ref->height);
637
+ if ( ref->width) Qprintf(f, " width=\"%d\"", ref->width);
638
+ }
639
+
640
+ if ( S(ref->title) ) {
641
+ Qstring(" title=\"", f);
642
+ ___mkd_reparse(T(ref->title), S(ref->title), INSIDE_TAG, f);
643
+ Qchar('"', f);
644
+ }
645
+
646
+ Qstring(tag->text_pfx, f);
647
+ ___mkd_reparse(T(text), S(text), tag->flags, f);
648
+ Qstring(tag->text_sfx, f);
649
+ }
650
+ else
651
+ Qwrite(T(ref->link) + tag->szpat, S(ref->link) - tag->szpat, f);
652
+
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
+ memset(&key, 0, 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;
714
+ }
715
+
716
+
717
+ /* write a character to output, doing text escapes ( & -> &amp;,
718
+ * > -> &gt; < -> &lt; )
719
+ */
720
+ static void
721
+ cputc(int c, MMIOT *f)
722
+ {
723
+ switch (c) {
724
+ case '&': Qstring("&amp;", f); break;
725
+ case '>': Qstring("&gt;", f); break;
726
+ case '<': Qstring("&lt;", f); break;
727
+ default : Qchar(c, f); break;
728
+ }
729
+ }
730
+
731
+
732
+ /*
733
+ * convert an email address to a string of nonsense
734
+ */
735
+ static void
736
+ mangle(char *s, int len, MMIOT *f)
737
+ {
738
+ while ( len-- > 0 ) {
739
+ Qstring("&#", f);
740
+ Qprintf(f, COINTOSS() ? "x%02x;" : "%02d;", *((unsigned char*)(s++)) );
741
+ }
742
+ }
743
+
744
+
745
+ /* before letting a tag through, validate against
746
+ * DENY_A and DENY_IMG
747
+ */
748
+ static int
749
+ forbidden_tag(MMIOT *f)
750
+ {
751
+ int c = toupper(peek(f, 1));
752
+
753
+ if ( f->flags & DENY_HTML )
754
+ return 1;
755
+
756
+ if ( c == 'A' && (f->flags & DENY_A) && !isthisalnum(f,2) )
757
+ return 1;
758
+ if ( c == 'I' && (f->flags & DENY_IMG)
759
+ && strncasecmp(cursor(f)+1, "MG", 2) == 0
760
+ && !isthisalnum(f,4) )
761
+ return 1;
762
+ return 0;
763
+ }
764
+
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
+
840
+
841
+ /* a < may be just a regular character, the start of an embedded html
842
+ * tag, or the start of an <automatic link>. If it's an automatic
843
+ * link, we also need to know if it's an email address because if it
844
+ * is we need to mangle it in our futile attempt to cut down on the
845
+ * spaminess of the rendered page.
846
+ */
847
+ static int
848
+ maybe_tag_or_link(MMIOT *f)
849
+ {
850
+ int c, size;
851
+ int maybetag = 1;
852
+
853
+ if ( f->flags & INSIDE_TAG )
854
+ return 0;
855
+
856
+ for ( size=0; (c = peek(f, size+1)) != '>'; size++) {
857
+ if ( c == EOF )
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;
868
+ }
869
+
870
+ if ( size ) {
871
+ if ( maybetag || (size >= 3 && strncmp(cursor(f), "!--", 3) == 0) ) {
872
+
873
+ /* It is not a html tag unless we find the closing '>' in
874
+ * the same block.
875
+ */
876
+ while ( (c = peek(f, size+1)) != '>' )
877
+ if ( c == EOF )
878
+ return 0;
879
+ else
880
+ size++;
881
+
882
+ Qstring(forbidden_tag(f) ? "&lt;" : "<", f);
883
+ while ( ((c = peek(f, 1)) != EOF) && (c != '>') )
884
+ Qchar(pull(f), f);
885
+ return 1;
886
+ }
887
+ else if ( !isspace(c) && process_possible_link(f, size) ) {
888
+ shift(f, size+1);
889
+ return 1;
890
+ }
891
+ }
892
+
893
+ return 0;
894
+ }
895
+
896
+
897
+ /* autolinking means that all inline html is <a href'ified>. A
898
+ * autolink url is alphanumerics, slashes, periods, underscores,
899
+ * the at sign, colon, and the % character.
900
+ */
901
+ static int
902
+ maybe_autolink(MMIOT *f)
903
+ {
904
+ register int c;
905
+ int size;
906
+
907
+ /* greedily scan forward for the end of a legitimate link.
908
+ */
909
+ for ( size=0; (c=peek(f, size+1)) != EOF; size++ )
910
+ if ( c == '\\' ) {
911
+ if ( peek(f, size+2) != EOF )
912
+ ++size;
913
+ }
914
+ else if ( isspace(c) || strchr("'\"()[]{}<>`", c) )
915
+ break;
916
+
917
+ if ( (size > 1) && process_possible_link(f, size) ) {
918
+ shift(f, size);
919
+ return 1;
920
+ }
921
+ return 0;
922
+ }
923
+
924
+
925
+ /* smartyquote code that's common for single and double quotes
926
+ */
927
+ static int
928
+ smartyquote(int *flags, char typeofquote, MMIOT *f)
929
+ {
930
+ int bit = (typeofquote == 's') ? 0x01 : 0x02;
931
+
932
+ if ( bit & (*flags) ) {
933
+ if ( isthisnonword(f,1) ) {
934
+ Qprintf(f, "&r%cquo;", typeofquote);
935
+ (*flags) &= ~bit;
936
+ return 1;
937
+ }
938
+ }
939
+ else if ( isthisnonword(f,-1) && peek(f,1) != EOF ) {
940
+ Qprintf(f, "&l%cquo;", typeofquote);
941
+ (*flags) |= bit;
942
+ return 1;
943
+ }
944
+ return 0;
945
+ }
946
+
947
+
948
+ static int
949
+ islike(MMIOT *f, char *s)
950
+ {
951
+ int len;
952
+ int i;
953
+
954
+ if ( s[0] == '<' ) {
955
+ if ( !isthisnonword(f, -1) )
956
+ return 0;
957
+ ++s;
958
+ }
959
+
960
+ if ( !(len = strlen(s)) )
961
+ return 0;
962
+
963
+ if ( s[len-1] == '>' ) {
964
+ if ( !isthisnonword(f,len-1) )
965
+ return 0;
966
+ len--;
967
+ }
968
+
969
+ for (i=1; i < len; i++)
970
+ if (tolower(peek(f,i)) != s[i])
971
+ return 0;
972
+ return 1;
973
+ }
974
+
975
+
976
+ static struct smarties {
977
+ char c0;
978
+ char *pat;
979
+ char *entity;
980
+ int shift;
981
+ } smarties[] = {
982
+ { '\'', "'s>", "rsquo", 0 },
983
+ { '\'', "'t>", "rsquo", 0 },
984
+ { '-', "--", "mdash", 1 },
985
+ { '-', "<->", "ndash", 0 },
986
+ { '.', "...", "hellip", 2 },
987
+ { '.', ". . .", "hellip", 4 },
988
+ { '(', "(c)", "copy", 2 },
989
+ { '(', "(r)", "reg", 2 },
990
+ { '(', "(tm)", "trade", 3 },
991
+ { '3', "<3/4>", "frac34", 2 },
992
+ { '3', "<3/4ths>", "frac34", 2 },
993
+ { '1', "<1/2>", "frac12", 2 },
994
+ { '1', "<1/4>", "frac14", 2 },
995
+ { '1', "<1/4th>", "frac14", 2 },
996
+ { '&', "&#0;", 0, 3 },
997
+ } ;
998
+ #define NRSMART ( sizeof smarties / sizeof smarties[0] )
999
+
1000
+
1001
+ /* Smarty-pants-style chrome for quotes, -, ellipses, and (r)(c)(tm)
1002
+ */
1003
+ static int
1004
+ smartypants(int c, int *flags, MMIOT *f)
1005
+ {
1006
+ int i;
1007
+
1008
+ if ( f->flags & (DENY_SMARTY|INSIDE_TAG) )
1009
+ return 0;
1010
+
1011
+ for ( i=0; i < NRSMART; i++)
1012
+ if ( (c == smarties[i].c0) && islike(f, smarties[i].pat) ) {
1013
+ if ( smarties[i].entity )
1014
+ Qprintf(f, "&%s;", smarties[i].entity);
1015
+ shift(f, smarties[i].shift);
1016
+ return 1;
1017
+ }
1018
+
1019
+ switch (c) {
1020
+ case '<' : return 0;
1021
+ case '\'': if ( smartyquote(flags, 's', f) ) return 1;
1022
+ break;
1023
+
1024
+ case '"': if ( smartyquote(flags, 'd', f) ) return 1;
1025
+ break;
1026
+
1027
+ case '`': if ( peek(f, 1) == '`' ) {
1028
+ int j = 2;
1029
+
1030
+ while ( (c=peek(f,j)) != EOF ) {
1031
+ if ( c == '\\' )
1032
+ j += 2;
1033
+ else if ( c == '`' )
1034
+ break;
1035
+ else if ( c == '\'' && peek(f, j+1) == '\'' ) {
1036
+ Qstring("&ldquo;", f);
1037
+ ___mkd_reparse(cursor(f)+1, j-2, 0, f);
1038
+ Qstring("&rdquo;", f);
1039
+ shift(f,j+1);
1040
+ return 1;
1041
+ }
1042
+ else ++j;
1043
+ }
1044
+
1045
+ }
1046
+ break;
1047
+ }
1048
+ return 0;
1049
+ } /* smartypants */
1050
+
1051
+
1052
+ #define tag_text(f) (f->flags & INSIDE_TAG)
1053
+
1054
+
1055
+ static void
1056
+ text(MMIOT *f)
1057
+ {
1058
+ int c, j;
1059
+ int rep;
1060
+ int smartyflags = 0;
1061
+
1062
+ while (1) {
1063
+ if ( (f->flags & AUTOLINK) && isalpha(peek(f,1)) )
1064
+ maybe_autolink(f);
1065
+
1066
+ c = pull(f);
1067
+
1068
+ if (c == EOF)
1069
+ break;
1070
+
1071
+ if ( smartypants(c, &smartyflags, f) )
1072
+ continue;
1073
+ switch (c) {
1074
+ case 0: break;
1075
+
1076
+ case 3: Qstring("<br/>", f);
1077
+ break;
1078
+
1079
+ case '>': if ( tag_text(f) )
1080
+ Qstring("&gt;", f);
1081
+ else
1082
+ Qchar(c, f);
1083
+ break;
1084
+
1085
+ case '"': if ( tag_text(f) )
1086
+ Qstring("&quot;", f);
1087
+ else
1088
+ Qchar(c, f);
1089
+ break;
1090
+
1091
+ case '!': if ( peek(f,1) == '[' ) {
1092
+ pull(f);
1093
+ if ( tag_text(f) || !linkylinky(1, f) )
1094
+ Qstring("![", f);
1095
+ }
1096
+ else
1097
+ Qchar(c, f);
1098
+ break;
1099
+ case '[': if ( tag_text(f) || !linkylinky(0, f) )
1100
+ Qchar(c, f);
1101
+ break;
1102
+ #if SUPERSCRIPT
1103
+ /* A^B -> A<sup>B</sup> */
1104
+ case '^': if ( (f->flags & (STRICT|INSIDE_TAG)) || isthisspace(f,-1) || isthisspace(f,1) )
1105
+ Qchar(c,f);
1106
+ else {
1107
+ char *sup = cursor(f);
1108
+ int len = 0;
1109
+ Qstring("<sup>",f);
1110
+ while ( !isthisspace(f,1+len) ) {
1111
+ ++len;
1112
+ }
1113
+ shift(f,len);
1114
+ ___mkd_reparse(sup, len, 0, f);
1115
+ Qstring("</sup>", f);
1116
+ }
1117
+ break;
1118
+ #endif
1119
+ case '_':
1120
+ #if RELAXED_EMPHASIS
1121
+ /* Underscores don't count if they're in the middle of a word */
1122
+ if ( !(f->flags & STRICT) && isthisalnum(f,-1)
1123
+ && isthisalnum(f,1) ) {
1124
+ Qchar(c, f);
1125
+ break;
1126
+ }
1127
+ #endif
1128
+ case '*':
1129
+ #if RELAXED_EMPHASIS
1130
+ /* Underscores & stars don't count if they're out in the middle
1131
+ * of whitespace */
1132
+ if ( !(f->flags & STRICT) && isthisspace(f,-1)
1133
+ && isthisspace(f,1) ) {
1134
+ Qchar(c, f);
1135
+ break;
1136
+ }
1137
+ /* else fall into the regular old emphasis case */
1138
+ #endif
1139
+ if ( tag_text(f) )
1140
+ Qchar(c, f);
1141
+ else {
1142
+ for (rep = 1; peek(f,1) == c; pull(f) )
1143
+ ++rep;
1144
+ Qem(f,c,rep);
1145
+ }
1146
+ break;
1147
+
1148
+ case '`': if ( tag_text(f) || !iscodeblock(f) )
1149
+ Qchar(c, f);
1150
+ else {
1151
+ Qstring("<code>", f);
1152
+ if ( peek(f, 1) == '`' ) {
1153
+ pull(f);
1154
+ code(2, f);
1155
+ }
1156
+ else
1157
+ code(1, f);
1158
+ Qstring("</code>", f);
1159
+ }
1160
+ break;
1161
+
1162
+ case '\\': switch ( c = pull(f) ) {
1163
+ case '&': Qstring("&amp;", f);
1164
+ break;
1165
+ case '<': Qstring("&lt;", f);
1166
+ break;
1167
+ case '\\':
1168
+ case '>': case '#': case '.': case '-':
1169
+ case '+': case '{': case '}': case ']':
1170
+ case '(': case ')': case '"': case '\'':
1171
+ case '!': case '[': case '*': case '_':
1172
+ case '`': Qchar(c, f);
1173
+ break;
1174
+ default:
1175
+ Qchar('\\', f);
1176
+ if ( c != EOF )
1177
+ shift(f,-1);
1178
+ break;
1179
+ }
1180
+ break;
1181
+
1182
+ case '<': if ( !maybe_tag_or_link(f) )
1183
+ Qstring("&lt;", f);
1184
+ break;
1185
+
1186
+ case '&': j = (peek(f,1) == '#' ) ? 2 : 1;
1187
+ while ( isthisalnum(f,j) )
1188
+ ++j;
1189
+
1190
+ if ( peek(f,j) != ';' )
1191
+ Qstring("&amp;", f);
1192
+ else
1193
+ Qchar(c, f);
1194
+ break;
1195
+
1196
+ default: Qchar(c, f);
1197
+ break;
1198
+ }
1199
+ }
1200
+ /* truncate the input string after we've finished processing it */
1201
+ S(f->in) = f->isp = 0;
1202
+ } /* text */
1203
+
1204
+
1205
+ static int
1206
+ iscodeblock(MMIOT *f)
1207
+ {
1208
+ int i=1, single = 1, c;
1209
+
1210
+ if ( peek(f,i) == '`' ) {
1211
+ single=0;
1212
+ i++;
1213
+ }
1214
+ while ( (c=peek(f,i)) != EOF ) {
1215
+ if ( (c == '`') && (single || peek(f,i+1) == '`') )
1216
+ return 1;
1217
+ else if ( c == '\\' )
1218
+ i++;
1219
+ i++;
1220
+ }
1221
+ return 0;
1222
+
1223
+ }
1224
+
1225
+ static int
1226
+ endofcode(int escape, int offset, MMIOT *f)
1227
+ {
1228
+ switch (escape) {
1229
+ case 2: if ( peek(f, offset+1) == '`' ) {
1230
+ shift(f,1);
1231
+ case 1: shift(f,offset);
1232
+ return 1;
1233
+ }
1234
+ default:return 0;
1235
+ }
1236
+ }
1237
+
1238
+
1239
+ /* the only characters that have special meaning in a code block are
1240
+ * `<' and `&' , which are /always/ expanded to &lt; and &amp;
1241
+ */
1242
+ static void
1243
+ code(int escape, MMIOT *f)
1244
+ {
1245
+ int c;
1246
+
1247
+ if ( escape && (peek(f,1) == ' ') )
1248
+ shift(f,1);
1249
+
1250
+ while ( (c = pull(f)) != EOF ) {
1251
+ switch (c) {
1252
+ case ' ': if ( peek(f,1) == '`' && endofcode(escape, 1, f) )
1253
+ return;
1254
+ Qchar(c, f);
1255
+ break;
1256
+
1257
+ case '`': if ( endofcode(escape, 0, f) )
1258
+ return;
1259
+ Qchar(c, f);
1260
+ break;
1261
+
1262
+ case '\\': cputc(c, f);
1263
+ if ( peek(f,1) == '>' || (c = pull(f)) == EOF )
1264
+ break;
1265
+
1266
+ default: cputc(c, f);
1267
+ break;
1268
+ }
1269
+ }
1270
+ } /* code */
1271
+
1272
+
1273
+ /* print a header block
1274
+ */
1275
+ static void
1276
+ printheader(Paragraph *pp, MMIOT *f)
1277
+ {
1278
+ Qprintf(f, "<h%d", pp->hnumber);
1279
+ if ( f->flags & TOC ) {
1280
+ Qprintf(f, " id=\"", pp->hnumber);
1281
+ mkd_string_to_anchor(T(pp->text->text), S(pp->text->text), Qchar, f);
1282
+ Qchar('"', f);
1283
+ }
1284
+ Qchar('>', f);
1285
+ push(T(pp->text->text), S(pp->text->text), f);
1286
+ text(f);
1287
+ Qprintf(f, "</h%d>", pp->hnumber);
1288
+ }
1289
+
1290
+
1291
+ enum e_alignments { a_NONE, a_CENTER, a_LEFT, a_RIGHT };
1292
+
1293
+ static char* alignments[] = { "", " align=\"center\"", " align=\"left\"",
1294
+ " align=\"right\"" };
1295
+
1296
+ typedef STRING(int) Istring;
1297
+
1298
+ static int
1299
+ splat(Line *p, char *block, Istring align, int force, MMIOT *f)
1300
+ {
1301
+ int first,
1302
+ idx = 0,
1303
+ colno = 0;
1304
+
1305
+ Qstring("<tr>\n", f);
1306
+ while ( idx < S(p->text) ) {
1307
+ first = idx;
1308
+ if ( force && (colno >= S(align)-1) )
1309
+ idx = S(p->text);
1310
+ else
1311
+ while ( (idx < S(p->text)) && (T(p->text)[idx] != '|') )
1312
+ ++idx;
1313
+
1314
+ Qprintf(f, "<%s%s>",
1315
+ block,
1316
+ alignments[ (colno < S(align)) ? T(align)[colno] : a_NONE ]);
1317
+ ___mkd_reparse(T(p->text)+first, idx-first, 0, f);
1318
+ Qprintf(f, "</%s>\n", block);
1319
+ idx++;
1320
+ colno++;
1321
+ }
1322
+ if ( force )
1323
+ while (colno < S(align) ) {
1324
+ Qprintf(f, "<%s></%s>\n", block, block);
1325
+ ++colno;
1326
+ }
1327
+ Qstring("</tr>\n", f);
1328
+ return colno;
1329
+ }
1330
+
1331
+ static int
1332
+ printtable(Paragraph *pp, MMIOT *f)
1333
+ {
1334
+ /* header, dashes, then lines of content */
1335
+
1336
+ Line *hdr, *dash, *body;
1337
+ Istring align;
1338
+ int start;
1339
+ int hcols;
1340
+ char *p;
1341
+
1342
+ if ( !(pp->text && pp->text->next) )
1343
+ return 0;
1344
+
1345
+ hdr = pp->text;
1346
+ dash= hdr->next;
1347
+ body= dash->next;
1348
+
1349
+ /* first figure out cell alignments */
1350
+
1351
+ CREATE(align);
1352
+
1353
+ for (p=T(dash->text), start=0; start < S(dash->text); ) {
1354
+ char first, last;
1355
+ int end;
1356
+
1357
+ last=first=0;
1358
+ for (end=start ; (end < S(dash->text)) && p[end] != '|'; ++ end ) {
1359
+ if ( !isspace(p[end]) ) {
1360
+ if ( !first) first = p[end];
1361
+ last = p[end];
1362
+ }
1363
+ }
1364
+ EXPAND(align) = ( first == ':' ) ? (( last == ':') ? a_CENTER : a_LEFT)
1365
+ : (( last == ':') ? a_RIGHT : a_NONE );
1366
+ start = 1+end;
1367
+ }
1368
+
1369
+ Qstring("<table>\n", f);
1370
+ Qstring("<thead>\n", f);
1371
+ hcols = splat(hdr, "th", align, 0, f);
1372
+ Qstring("</thead>\n", f);
1373
+
1374
+ if ( hcols < S(align) )
1375
+ S(align) = hcols;
1376
+ else
1377
+ while ( hcols > S(align) )
1378
+ EXPAND(align) = a_NONE;
1379
+
1380
+ Qstring("<tbody>\n", f);
1381
+ for ( ; body; body = body->next)
1382
+ splat(body, "td", align, 1, f);
1383
+ Qstring("</tbody>\n", f);
1384
+ Qstring("</table>\n", f);
1385
+
1386
+ DELETE(align);
1387
+ return 1;
1388
+ }
1389
+
1390
+
1391
+ static int
1392
+ printblock(Paragraph *pp, MMIOT *f)
1393
+ {
1394
+ Line *t = pp->text;
1395
+ static char *Begin[] = { "", "<p>", "<center>" };
1396
+ static char *End[] = { "", "</p>","</center>" };
1397
+
1398
+ while (t) {
1399
+ if ( S(t->text) ) {
1400
+ if ( S(t->text) > 2 && T(t->text)[S(t->text)-2] == ' '
1401
+ && T(t->text)[S(t->text)-1] == ' ') {
1402
+ push(T(t->text), S(t->text)-2, f);
1403
+ push("\003\n", 2, f);
1404
+ }
1405
+ else {
1406
+ ___mkd_tidy(&t->text);
1407
+ push(T(t->text), S(t->text), f);
1408
+ if ( t->next )
1409
+ push("\n", 1, f);
1410
+ }
1411
+ }
1412
+ t = t->next;
1413
+ }
1414
+ Qstring(Begin[pp->align], f);
1415
+ text(f);
1416
+ Qstring(End[pp->align], f);
1417
+ return 1;
1418
+ }
1419
+
1420
+
1421
+ static void
1422
+ printcode(Line *t, MMIOT *f)
1423
+ {
1424
+ int blanks;
1425
+
1426
+ for ( blanks = 0; t ; t = t->next )
1427
+ if ( S(t->text) > t->dle ) {
1428
+ while ( blanks ) {
1429
+ push("\n", 1, f);
1430
+ --blanks;
1431
+ }
1432
+ push(T(t->text), S(t->text), f);
1433
+ push("\n", 1, f);
1434
+ }
1435
+ else blanks++;
1436
+
1437
+ Qstring("<pre><code>", f);
1438
+ code(0, f);
1439
+ Qstring("</code></pre>", f);
1440
+ }
1441
+
1442
+
1443
+ static void
1444
+ printhtml(Line *t, MMIOT *f)
1445
+ {
1446
+ int blanks;
1447
+
1448
+ for ( blanks=0; t ; t = t->next )
1449
+ if ( S(t->text) ) {
1450
+ for ( ; blanks; --blanks )
1451
+ Qchar('\n', f);
1452
+
1453
+ Qwrite(T(t->text), S(t->text), f);
1454
+ Qchar('\n', f);
1455
+ }
1456
+ else
1457
+ blanks++;
1458
+ }
1459
+
1460
+
1461
+ static void
1462
+ htmlify(Paragraph *p, char *block, char *arguments, MMIOT *f)
1463
+ {
1464
+ ___mkd_emblock(f);
1465
+ if ( block )
1466
+ Qprintf(f, arguments ? "<%s %s>" : "<%s>", block, arguments);
1467
+ ___mkd_emblock(f);
1468
+
1469
+ while (( p = display(p, f) )) {
1470
+ ___mkd_emblock(f);
1471
+ Qstring("\n\n", f);
1472
+ }
1473
+
1474
+ if ( block )
1475
+ Qprintf(f, "</%s>", block);
1476
+ ___mkd_emblock(f);
1477
+ }
1478
+
1479
+
1480
+ #if DL_TAG_EXTENSION
1481
+ static void
1482
+ definitionlist(Paragraph *p, MMIOT *f)
1483
+ {
1484
+ Line *tag;
1485
+
1486
+ if ( p ) {
1487
+ Qstring("<dl>\n", f);
1488
+
1489
+ for ( ; p ; p = p->next) {
1490
+ for ( tag = p->text; tag; tag = tag->next ) {
1491
+ Qstring("<dt>", f);
1492
+ ___mkd_reparse(T(tag->text), S(tag->text), 0, f);
1493
+ Qstring("</dt>\n", f);
1494
+ }
1495
+
1496
+ htmlify(p->down, "dd", p->ident, f);
1497
+ Qchar('\n', f);
1498
+ }
1499
+
1500
+ Qstring("</dl>", f);
1501
+ }
1502
+ }
1503
+ #endif
1504
+
1505
+
1506
+ static void
1507
+ listdisplay(int typ, Paragraph *p, MMIOT* f)
1508
+ {
1509
+ if ( p ) {
1510
+ Qprintf(f, "<%cl", (typ==UL)?'u':'o');
1511
+ if ( typ == AL )
1512
+ Qprintf(f, " type=a");
1513
+ Qprintf(f, ">\n");
1514
+
1515
+ for ( ; p ; p = p->next ) {
1516
+ htmlify(p->down, "li", p->ident, f);
1517
+ Qchar('\n', f);
1518
+ }
1519
+
1520
+ Qprintf(f, "</%cl>\n", (typ==UL)?'u':'o');
1521
+ }
1522
+ }
1523
+
1524
+
1525
+ /* dump out a Paragraph in the desired manner
1526
+ */
1527
+ static Paragraph*
1528
+ display(Paragraph *p, MMIOT *f)
1529
+ {
1530
+ if ( !p ) return 0;
1531
+
1532
+ switch ( p->typ ) {
1533
+ case STYLE:
1534
+ case WHITESPACE:
1535
+ break;
1536
+
1537
+ case HTML:
1538
+ printhtml(p->text, f);
1539
+ break;
1540
+
1541
+ case CODE:
1542
+ printcode(p->text, f);
1543
+ break;
1544
+
1545
+ case QUOTE:
1546
+ htmlify(p->down, p->ident ? "div" : "blockquote", p->ident, f);
1547
+ break;
1548
+
1549
+ case UL:
1550
+ case OL:
1551
+ case AL:
1552
+ listdisplay(p->typ, p->down, f);
1553
+ break;
1554
+
1555
+ #if DL_TAG_EXTENSION
1556
+ case DL:
1557
+ definitionlist(p->down, f);
1558
+ break;
1559
+ #endif
1560
+
1561
+ case HR:
1562
+ Qstring("<hr />", f);
1563
+ break;
1564
+
1565
+ case HDR:
1566
+ printheader(p, f);
1567
+ break;
1568
+
1569
+ case TABLE:
1570
+ printtable(p, f);
1571
+ break;
1572
+
1573
+ case SOURCE:
1574
+ htmlify(p->down, 0, 0, f);
1575
+ break;
1576
+
1577
+ default:
1578
+ printblock(p, f);
1579
+ break;
1580
+ }
1581
+ return p->next;
1582
+ }
1583
+
1584
+
1585
+ /* return a pointer to the compiled markdown
1586
+ * document.
1587
+ */
1588
+ int
1589
+ mkd_document(Document *p, char **res)
1590
+ {
1591
+ if ( p && p->compiled ) {
1592
+ if ( ! p->html ) {
1593
+ htmlify(p->code, 0, 0, p->ctx);
1594
+ p->html = 1;
1595
+ }
1596
+
1597
+ *res = T(p->ctx->out);
1598
+ return S(p->ctx->out);
1599
+ }
1600
+ return EOF;
1601
+ }
1602
+