marked 0.7.0 → 1.0.0

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.
package/src/Parser.js ADDED
@@ -0,0 +1,255 @@
1
+ const Renderer = require('./Renderer.js');
2
+ const TextRenderer = require('./TextRenderer.js');
3
+ const Slugger = require('./Slugger.js');
4
+ const { defaults } = require('./defaults.js');
5
+ const {
6
+ unescape
7
+ } = require('./helpers.js');
8
+
9
+ /**
10
+ * Parsing & Compiling
11
+ */
12
+ module.exports = class Parser {
13
+ constructor(options) {
14
+ this.options = options || defaults;
15
+ this.options.renderer = this.options.renderer || new Renderer();
16
+ this.renderer = this.options.renderer;
17
+ this.renderer.options = this.options;
18
+ this.textRenderer = new TextRenderer();
19
+ this.slugger = new Slugger();
20
+ }
21
+
22
+ /**
23
+ * Static Parse Method
24
+ */
25
+ static parse(tokens, options) {
26
+ const parser = new Parser(options);
27
+ return parser.parse(tokens);
28
+ }
29
+
30
+ /**
31
+ * Parse Loop
32
+ */
33
+ parse(tokens, top = true) {
34
+ let out = '',
35
+ i,
36
+ j,
37
+ k,
38
+ l2,
39
+ l3,
40
+ row,
41
+ cell,
42
+ header,
43
+ body,
44
+ token,
45
+ ordered,
46
+ start,
47
+ loose,
48
+ itemBody,
49
+ item,
50
+ checked,
51
+ task,
52
+ checkbox;
53
+
54
+ const l = tokens.length;
55
+ for (i = 0; i < l; i++) {
56
+ token = tokens[i];
57
+ switch (token.type) {
58
+ case 'space': {
59
+ continue;
60
+ }
61
+ case 'hr': {
62
+ out += this.renderer.hr();
63
+ continue;
64
+ }
65
+ case 'heading': {
66
+ out += this.renderer.heading(
67
+ this.parseInline(token.tokens),
68
+ token.depth,
69
+ unescape(this.parseInline(token.tokens, this.textRenderer)),
70
+ this.slugger);
71
+ continue;
72
+ }
73
+ case 'code': {
74
+ out += this.renderer.code(token.text,
75
+ token.lang,
76
+ token.escaped);
77
+ continue;
78
+ }
79
+ case 'table': {
80
+ header = '';
81
+
82
+ // header
83
+ cell = '';
84
+ l2 = token.header.length;
85
+ for (j = 0; j < l2; j++) {
86
+ cell += this.renderer.tablecell(
87
+ this.parseInline(token.tokens.header[j]),
88
+ { header: true, align: token.align[j] }
89
+ );
90
+ }
91
+ header += this.renderer.tablerow(cell);
92
+
93
+ body = '';
94
+ l2 = token.cells.length;
95
+ for (j = 0; j < l2; j++) {
96
+ row = token.tokens.cells[j];
97
+
98
+ cell = '';
99
+ l3 = row.length;
100
+ for (k = 0; k < l3; k++) {
101
+ cell += this.renderer.tablecell(
102
+ this.parseInline(row[k]),
103
+ { header: false, align: token.align[k] }
104
+ );
105
+ }
106
+
107
+ body += this.renderer.tablerow(cell);
108
+ }
109
+ out += this.renderer.table(header, body);
110
+ continue;
111
+ }
112
+ case 'blockquote': {
113
+ body = this.parse(token.tokens);
114
+ out += this.renderer.blockquote(body);
115
+ continue;
116
+ }
117
+ case 'list': {
118
+ ordered = token.ordered;
119
+ start = token.start;
120
+ loose = token.loose;
121
+ l2 = token.items.length;
122
+
123
+ body = '';
124
+ for (j = 0; j < l2; j++) {
125
+ item = token.items[j];
126
+ checked = item.checked;
127
+ task = item.task;
128
+
129
+ itemBody = '';
130
+ if (item.task) {
131
+ checkbox = this.renderer.checkbox(checked);
132
+ if (loose) {
133
+ if (item.tokens[0].type === 'text') {
134
+ item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
135
+ if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
136
+ item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text;
137
+ }
138
+ } else {
139
+ item.tokens.unshift({
140
+ type: 'text',
141
+ text: checkbox
142
+ });
143
+ }
144
+ } else {
145
+ itemBody += checkbox;
146
+ }
147
+ }
148
+
149
+ itemBody += this.parse(item.tokens, loose);
150
+ body += this.renderer.listitem(itemBody, task, checked);
151
+ }
152
+
153
+ out += this.renderer.list(body, ordered, start);
154
+ continue;
155
+ }
156
+ case 'html': {
157
+ // TODO parse inline content if parameter markdown=1
158
+ out += this.renderer.html(token.text);
159
+ continue;
160
+ }
161
+ case 'paragraph': {
162
+ out += this.renderer.paragraph(this.parseInline(token.tokens));
163
+ continue;
164
+ }
165
+ case 'text': {
166
+ body = token.tokens ? this.parseInline(token.tokens) : token.text;
167
+ while (i + 1 < l && tokens[i + 1].type === 'text') {
168
+ token = tokens[++i];
169
+ body += '\n' + (token.tokens ? this.parseInline(token.tokens) : token.text);
170
+ }
171
+ out += top ? this.renderer.paragraph(body) : body;
172
+ continue;
173
+ }
174
+ default: {
175
+ const errMsg = 'Token with "' + token.type + '" type was not found.';
176
+ if (this.options.silent) {
177
+ console.error(errMsg);
178
+ return;
179
+ } else {
180
+ throw new Error(errMsg);
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ return out;
187
+ }
188
+
189
+ /**
190
+ * Parse Inline Tokens
191
+ */
192
+ parseInline(tokens, renderer) {
193
+ renderer = renderer || this.renderer;
194
+ let out = '',
195
+ i,
196
+ token;
197
+
198
+ const l = tokens.length;
199
+ for (i = 0; i < l; i++) {
200
+ token = tokens[i];
201
+ switch (token.type) {
202
+ case 'escape': {
203
+ out += renderer.text(token.text);
204
+ break;
205
+ }
206
+ case 'html': {
207
+ out += renderer.html(token.text);
208
+ break;
209
+ }
210
+ case 'link': {
211
+ out += renderer.link(token.href, token.title, this.parseInline(token.tokens, renderer));
212
+ break;
213
+ }
214
+ case 'image': {
215
+ out += renderer.image(token.href, token.title, token.text);
216
+ break;
217
+ }
218
+ case 'strong': {
219
+ out += renderer.strong(this.parseInline(token.tokens, renderer));
220
+ break;
221
+ }
222
+ case 'em': {
223
+ out += renderer.em(this.parseInline(token.tokens, renderer));
224
+ break;
225
+ }
226
+ case 'codespan': {
227
+ out += renderer.codespan(token.text);
228
+ break;
229
+ }
230
+ case 'br': {
231
+ out += renderer.br();
232
+ break;
233
+ }
234
+ case 'del': {
235
+ out += renderer.del(this.parseInline(token.tokens, renderer));
236
+ break;
237
+ }
238
+ case 'text': {
239
+ out += renderer.text(token.text);
240
+ break;
241
+ }
242
+ default: {
243
+ const errMsg = 'Token with "' + token.type + '" type was not found.';
244
+ if (this.options.silent) {
245
+ console.error(errMsg);
246
+ return;
247
+ } else {
248
+ throw new Error(errMsg);
249
+ }
250
+ }
251
+ }
252
+ }
253
+ return out;
254
+ }
255
+ };
@@ -0,0 +1,164 @@
1
+ const { defaults } = require('./defaults.js');
2
+ const {
3
+ cleanUrl,
4
+ escape
5
+ } = require('./helpers.js');
6
+
7
+ /**
8
+ * Renderer
9
+ */
10
+ module.exports = class Renderer {
11
+ constructor(options) {
12
+ this.options = options || defaults;
13
+ }
14
+
15
+ code(code, infostring, escaped) {
16
+ const lang = (infostring || '').match(/\S*/)[0];
17
+ if (this.options.highlight) {
18
+ const out = this.options.highlight(code, lang);
19
+ if (out != null && out !== code) {
20
+ escaped = true;
21
+ code = out;
22
+ }
23
+ }
24
+
25
+ if (!lang) {
26
+ return '<pre><code>'
27
+ + (escaped ? code : escape(code, true))
28
+ + '</code></pre>';
29
+ }
30
+
31
+ return '<pre><code class="'
32
+ + this.options.langPrefix
33
+ + escape(lang, true)
34
+ + '">'
35
+ + (escaped ? code : escape(code, true))
36
+ + '</code></pre>\n';
37
+ }
38
+
39
+ blockquote(quote) {
40
+ return '<blockquote>\n' + quote + '</blockquote>\n';
41
+ }
42
+
43
+ html(html) {
44
+ return html;
45
+ }
46
+
47
+ heading(text, level, raw, slugger) {
48
+ if (this.options.headerIds) {
49
+ return '<h'
50
+ + level
51
+ + ' id="'
52
+ + this.options.headerPrefix
53
+ + slugger.slug(raw)
54
+ + '">'
55
+ + text
56
+ + '</h'
57
+ + level
58
+ + '>\n';
59
+ }
60
+ // ignore IDs
61
+ return '<h' + level + '>' + text + '</h' + level + '>\n';
62
+ }
63
+
64
+ hr() {
65
+ return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
66
+ }
67
+
68
+ list(body, ordered, start) {
69
+ const type = ordered ? 'ol' : 'ul',
70
+ startatt = (ordered && start !== 1) ? (' start="' + start + '"') : '';
71
+ return '<' + type + startatt + '>\n' + body + '</' + type + '>\n';
72
+ }
73
+
74
+ listitem(text) {
75
+ return '<li>' + text + '</li>\n';
76
+ }
77
+
78
+ checkbox(checked) {
79
+ return '<input '
80
+ + (checked ? 'checked="" ' : '')
81
+ + 'disabled="" type="checkbox"'
82
+ + (this.options.xhtml ? ' /' : '')
83
+ + '> ';
84
+ }
85
+
86
+ paragraph(text) {
87
+ return '<p>' + text + '</p>\n';
88
+ }
89
+
90
+ table(header, body) {
91
+ if (body) body = '<tbody>' + body + '</tbody>';
92
+
93
+ return '<table>\n'
94
+ + '<thead>\n'
95
+ + header
96
+ + '</thead>\n'
97
+ + body
98
+ + '</table>\n';
99
+ }
100
+
101
+ tablerow(content) {
102
+ return '<tr>\n' + content + '</tr>\n';
103
+ }
104
+
105
+ tablecell(content, flags) {
106
+ const type = flags.header ? 'th' : 'td';
107
+ const tag = flags.align
108
+ ? '<' + type + ' align="' + flags.align + '">'
109
+ : '<' + type + '>';
110
+ return tag + content + '</' + type + '>\n';
111
+ }
112
+
113
+ // span level renderer
114
+ strong(text) {
115
+ return '<strong>' + text + '</strong>';
116
+ }
117
+
118
+ em(text) {
119
+ return '<em>' + text + '</em>';
120
+ }
121
+
122
+ codespan(text) {
123
+ return '<code>' + text + '</code>';
124
+ }
125
+
126
+ br() {
127
+ return this.options.xhtml ? '<br/>' : '<br>';
128
+ }
129
+
130
+ del(text) {
131
+ return '<del>' + text + '</del>';
132
+ }
133
+
134
+ link(href, title, text) {
135
+ href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
136
+ if (href === null) {
137
+ return text;
138
+ }
139
+ let out = '<a href="' + escape(href) + '"';
140
+ if (title) {
141
+ out += ' title="' + title + '"';
142
+ }
143
+ out += '>' + text + '</a>';
144
+ return out;
145
+ }
146
+
147
+ image(href, title, text) {
148
+ href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
149
+ if (href === null) {
150
+ return text;
151
+ }
152
+
153
+ let out = '<img src="' + href + '" alt="' + text + '"';
154
+ if (title) {
155
+ out += ' title="' + title + '"';
156
+ }
157
+ out += this.options.xhtml ? '/>' : '>';
158
+ return out;
159
+ }
160
+
161
+ text(text) {
162
+ return text;
163
+ }
164
+ };
package/src/Slugger.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Slugger generates header id
3
+ */
4
+ module.exports = class Slugger {
5
+ constructor() {
6
+ this.seen = {};
7
+ }
8
+
9
+ /**
10
+ * Convert string to unique id
11
+ */
12
+ slug(value) {
13
+ let slug = value
14
+ .toLowerCase()
15
+ .trim()
16
+ // remove html tags
17
+ .replace(/<[!\/a-z].*?>/ig, '')
18
+ // remove unwanted chars
19
+ .replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '')
20
+ .replace(/\s/g, '-');
21
+
22
+ if (this.seen.hasOwnProperty(slug)) {
23
+ const originalSlug = slug;
24
+ do {
25
+ this.seen[originalSlug]++;
26
+ slug = originalSlug + '-' + this.seen[originalSlug];
27
+ } while (this.seen.hasOwnProperty(slug));
28
+ }
29
+ this.seen[slug] = 0;
30
+
31
+ return slug;
32
+ }
33
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * TextRenderer
3
+ * returns only the textual part of the token
4
+ */
5
+ module.exports = class TextRenderer {
6
+ // no need for block level renderers
7
+ strong(text) {
8
+ return text;
9
+ }
10
+
11
+ em(text) {
12
+ return text;
13
+ }
14
+
15
+ codespan(text) {
16
+ return text;
17
+ }
18
+
19
+ del(text) {
20
+ return text;
21
+ }
22
+
23
+ html(text) {
24
+ return text;
25
+ }
26
+
27
+ text(text) {
28
+ return text;
29
+ }
30
+
31
+ link(href, title, text) {
32
+ return '' + text;
33
+ }
34
+
35
+ image(href, title, text) {
36
+ return '' + text;
37
+ }
38
+
39
+ br() {
40
+ return '';
41
+ }
42
+ };