UNPKG

13.5 kBJavaScriptView Raw
1/**
2 * marked - A markdown parser (https://github.com/chjj/marked)
3 * Copyright (c) 2011-2012, Christopher Jeffrey. (MIT Licensed)
4 */
5
6;(function() {
7
8/**
9 * Block-Level Grammar
10 */
11
12var block = {
13 newline: /^\n+/,
14 code: /^ {4,}[^\n]*(?:\n {4,}[^\n]*|\n)*(?:\n+|$)/,
15 gfm_code: /^ *``` *(\w+)? *\n([^\0]+?)\s*``` *(?:\n+|$)/,
16 hr: /^( *[\-*_]){3,} *(?:\n+|$)/,
17 heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
18 lheading: /^([^\n]+)\n *(=|-){3,} *\n*/,
19 blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
20 list: /^( *)([*+-]|\d+\.) [^\0]+?(?:\n{2,}(?! )|\s*$)(?!\1bullet)\n*/,
21 html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
22 def: /^ *\[([^\]]+)\]: *([^\s]+)(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
23 paragraph: /^([^\n]+\n?(?!body))+\n*/,
24 text: /^[^\n]+/
25};
26
27block.list = (function() {
28 var list = block.list.source;
29
30 list = list
31 .replace('bullet', /(?:[*+-](?!(?: *[-*]){2,})|\d+\.)/.source);
32
33 return new RegExp(list);
34})();
35
36block.html = (function() {
37 var html = block.html.source;
38
39 html = html
40 .replace('comment', /<!--[^\0]*?-->/.source)
41 .replace('closed', /<(tag)[^\0]+?<\/\1>/.source)
42 .replace('closing', /<tag(?!:\/|@)\b(?:"[^"]*"|'[^']*'|[^'">])*?>/.source)
43 .replace(/tag/g, tag());
44
45 return new RegExp(html);
46})();
47
48block.paragraph = (function() {
49 var paragraph = block.paragraph.source
50 , body = [];
51
52 (function push(rule) {
53 rule = block[rule] ? block[rule].source : rule;
54 body.push(rule.replace(/(^|[^\[])\^/g, '$1'));
55 return push;
56 })
57 ('gfm_code')
58 ('hr')
59 ('heading')
60 ('lheading')
61 ('blockquote')
62 ('<' + tag())
63 ('def');
64
65 return new
66 RegExp(paragraph.replace('body', body.join('|')));
67})();
68
69/**
70 * Block Lexer
71 */
72
73block.lexer = function(src) {
74 var tokens = [];
75
76 tokens.links = {};
77
78 src = src
79 .replace(/\r\n|\r/g, '\n')
80 .replace(/\t/g, ' ');
81
82 return block.token(src, tokens, true);
83};
84
85block.token = function(src, tokens, top) {
86 var src = src.replace(/^ +$/gm, '')
87 , next
88 , loose
89 , cap
90 , item
91 , space
92 , i
93 , l;
94
95 while (src) {
96 // newline
97 if (cap = block.newline.exec(src)) {
98 src = src.substring(cap[0].length);
99 if (cap[0].length > 1) {
100 tokens.push({
101 type: 'space'
102 });
103 }
104 }
105
106 // code
107 if (cap = block.code.exec(src)) {
108 src = src.substring(cap[0].length);
109 cap = cap[0].replace(/^ {4}/gm, '');
110 tokens.push({
111 type: 'code',
112 text: cap.replace(/\n+$/, '')
113 });
114 continue;
115 }
116
117 // gfm_code
118 if (cap = block.gfm_code.exec(src)) {
119 src = src.substring(cap[0].length);
120 tokens.push({
121 type: 'code',
122 lang: cap[1],
123 text: cap[2]
124 });
125 continue;
126 }
127
128 // heading
129 if (cap = block.heading.exec(src)) {
130 src = src.substring(cap[0].length);
131 tokens.push({
132 type: 'heading',
133 depth: cap[1].length,
134 text: cap[2]
135 });
136 continue;
137 }
138
139 // lheading
140 if (cap = block.lheading.exec(src)) {
141 src = src.substring(cap[0].length);
142 tokens.push({
143 type: 'heading',
144 depth: cap[2] === '=' ? 1 : 2,
145 text: cap[1]
146 });
147 continue;
148 }
149
150 // hr
151 if (cap = block.hr.exec(src)) {
152 src = src.substring(cap[0].length);
153 tokens.push({
154 type: 'hr'
155 });
156 continue;
157 }
158
159 // blockquote
160 if (cap = block.blockquote.exec(src)) {
161 src = src.substring(cap[0].length);
162 tokens.push({
163 type: 'blockquote_start'
164 });
165
166 cap = cap[0].replace(/^ *> ?/gm, '');
167
168 // Pass `top` to keep the current
169 // "toplevel" state. This is exactly
170 // how markdown.pl works.
171 block.token(cap, tokens, top);
172
173 tokens.push({
174 type: 'blockquote_end'
175 });
176 continue;
177 }
178
179 // list
180 if (cap = block.list.exec(src)) {
181 src = src.substring(cap[0].length);
182
183 tokens.push({
184 type: 'list_start',
185 ordered: isFinite(cap[2])
186 });
187
188 // Get each top-level item.
189 cap = cap[0].match(
190 /^( *)([*+-]|\d+\.)[^\n]*(?:\n(?!\1(?:[*+-]|\d+\.))[^\n]*)*/gm
191 );
192
193 next = false;
194 l = cap.length;
195 i = 0;
196
197 for (; i < l; i++) {
198 item = cap[i];
199
200 // Remove the list item's bullet
201 // so it is seen as the next token.
202 space = item.length;
203 item = item.replace(/^ *([*+-]|\d+\.) */, '');
204
205 // Outdent whatever the
206 // list item contains. Hacky.
207 if (~item.indexOf('\n ')) {
208 space -= item.length;
209 item = item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '');
210 }
211
212 // Determine whether item is loose or not.
213 // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
214 // for discount behavior.
215 loose = next || /\n\n(?!\s*$)/.test(item);
216 if (i !== l - 1) {
217 next = item[item.length-1] === '\n';
218 if (!loose) loose = next;
219 }
220
221 tokens.push({
222 type: loose
223 ? 'loose_item_start'
224 : 'list_item_start'
225 });
226
227 // Recurse.
228 block.token(item, tokens);
229
230 tokens.push({
231 type: 'list_item_end'
232 });
233 }
234
235 tokens.push({
236 type: 'list_end'
237 });
238
239 continue;
240 }
241
242 // html
243 if (cap = block.html.exec(src)) {
244 src = src.substring(cap[0].length);
245 tokens.push({
246 type: 'html',
247 pre: cap[1] === 'pre',
248 text: cap[0]
249 });
250 continue;
251 }
252
253 // def
254 if (top && (cap = block.def.exec(src))) {
255 src = src.substring(cap[0].length);
256 tokens.links[cap[1].toLowerCase()] = {
257 href: cap[2],
258 title: cap[3]
259 };
260 continue;
261 }
262
263 // top-level paragraph
264 if (top && (cap = block.paragraph.exec(src))) {
265 src = src.substring(cap[0].length);
266 tokens.push({
267 type: 'paragraph',
268 text: cap[0]
269 });
270 continue;
271 }
272
273 // text
274 if (cap = block.text.exec(src)) {
275 // Top-level should never reach here.
276 src = src.substring(cap[0].length);
277 tokens.push({
278 type: 'text',
279 text: cap[0]
280 });
281 continue;
282 }
283 }
284
285 return tokens;
286};
287
288/**
289 * Inline Processing
290 */
291
292var inline = {
293 escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
294 autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
295 gfm_autolink: /^(\w+:\/\/[^\s]+[^.,:;"')\]\s])/,
296 tag: /^<!--[^\0]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
297 link: /^!?\[((?:\[[^\]]*\]|[^\[\]]|\[|\](?=[^[\]]*\]))*)\]\(([^\)]*)\)/,
298 reflink: /^!?\[((?:\[[^\]]*\]|[^\[\]]|\[|\](?=[^[\]]*\]))*)\]\s*\[([^\]]*)\]/,
299 nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
300 strong: /^__([^\0]+?)__(?!_)|^\*\*([^\0]+?)\*\*(?!\*)/,
301 em: /^\b_((?:__|[^\0])+?)_\b|^\*((?:\*\*|[^\0])+?)\*(?!\*)/,
302 code: /^(`+)([^\0]*?[^`])\1(?!`)/,
303 br: /^ {2,}\n(?!\s*$)/,
304 text: /^[^\0]+?(?=[\\<!\[_*`]|\w+:\/\/| {2,}\n|$)/
305};
306
307/**
308 * Inline Lexer
309 */
310
311inline.lexer = function(src) {
312 var out = ''
313 , links = tokens.links
314 , link
315 , text
316 , href
317 , cap;
318
319 while (src) {
320 // escape
321 if (cap = inline.escape.exec(src)) {
322 src = src.substring(cap[0].length);
323 out += cap[1];
324 continue;
325 }
326
327 // autolink
328 if (cap = inline.autolink.exec(src)) {
329 src = src.substring(cap[0].length);
330 if (cap[2] === '@') {
331 text = cap[1][6] === ':'
332 ? mangle(cap[1].substring(7))
333 : mangle(cap[1]);
334 href = mangle('mailto:') + text;
335 } else {
336 text = escape(cap[1]);
337 href = text;
338 }
339 out += '<a href="'
340 + href
341 + '">'
342 + text
343 + '</a>';
344 continue;
345 }
346
347 // gfm_autolink
348 if (cap = inline.gfm_autolink.exec(src)) {
349 src = src.substring(cap[0].length);
350 text = escape(cap[1]);
351 href = text;
352 out += '<a href="'
353 + href
354 + '">'
355 + text
356 + '</a>';
357 continue;
358 }
359
360 // tag
361 if (cap = inline.tag.exec(src)) {
362 src = src.substring(cap[0].length);
363 out += cap[0];
364 continue;
365 }
366
367 // link
368 if (cap = inline.link.exec(src)) {
369 src = src.substring(cap[0].length);
370 text = /^\s*<?([^\s]*?)>?(?:\s+"([^\n]+)")?\s*$/.exec(cap[2]);
371 if (!text) {
372 out += cap[0][0];
373 src = cap[0].substring(1) + src;
374 continue;
375 }
376 out += outputLink(cap, {
377 href: text[1],
378 title: text[2]
379 });
380 continue;
381 }
382
383 // reflink, nolink
384 if ((cap = inline.reflink.exec(src))
385 || (cap = inline.nolink.exec(src))) {
386 src = src.substring(cap[0].length);
387 link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
388 link = links[link.toLowerCase()];
389 if (!link || !link.href) {
390 out += cap[0][0];
391 src = cap[0].substring(1) + src;
392 continue;
393 }
394 out += outputLink(cap, link);
395 continue;
396 }
397
398 // strong
399 if (cap = inline.strong.exec(src)) {
400 src = src.substring(cap[0].length);
401 out += '<strong>'
402 + inline.lexer(cap[2] || cap[1])
403 + '</strong>';
404 continue;
405 }
406
407 // em
408 if (cap = inline.em.exec(src)) {
409 src = src.substring(cap[0].length);
410 out += '<em>'
411 + inline.lexer(cap[2] || cap[1])
412 + '</em>';
413 continue;
414 }
415
416 // code
417 if (cap = inline.code.exec(src)) {
418 src = src.substring(cap[0].length);
419 out += '<code>'
420 + escape(cap[2], true)
421 + '</code>';
422 continue;
423 }
424
425 // br
426 if (cap = inline.br.exec(src)) {
427 src = src.substring(cap[0].length);
428 out += '<br>';
429 continue;
430 }
431
432 // text
433 if (cap = inline.text.exec(src)) {
434 src = src.substring(cap[0].length);
435 out += escape(cap[0]);
436 continue;
437 }
438 }
439
440 return out;
441};
442
443var outputLink = function(cap, link) {
444 if (cap[0][0] !== '!') {
445 return '<a href="'
446 + escape(link.href)
447 + '"'
448 + (link.title
449 ? ' title="'
450 + escape(link.title)
451 + '"'
452 : '')
453 + '>'
454 + inline.lexer(cap[1])
455 + '</a>';
456 } else {
457 return '<img src="'
458 + escape(link.href)
459 + '" alt="'
460 + escape(cap[1])
461 + '"'
462 + (link.title
463 ? ' title="'
464 + escape(link.title)
465 + '"'
466 : '')
467 + '>';
468 }
469};
470
471/**
472 * Parsing
473 */
474
475var tokens
476 , token;
477
478var next = function() {
479 return token = tokens.pop();
480};
481
482var tok = function() {
483 switch (token.type) {
484 case 'space': {
485 return '';
486 }
487 case 'hr': {
488 return '<hr>\n';
489 }
490 case 'heading': {
491 return '<h'
492 + token.depth
493 + '>'
494 + inline.lexer(token.text)
495 + '</h'
496 + token.depth
497 + '>\n';
498 }
499 case 'code': {
500 return '<pre><code'
501 + (token.lang
502 ? ' class="'
503 + token.lang
504 + '"'
505 : '')
506 + '>'
507 + (token.escaped
508 ? token.text
509 : escape(token.text, true))
510 + '</code></pre>\n';
511 }
512 case 'blockquote_start': {
513 var body = '';
514
515 while (next().type !== 'blockquote_end') {
516 body += tok();
517 }
518
519 return '<blockquote>\n'
520 + body
521 + '</blockquote>\n';
522 }
523 case 'list_start': {
524 var type = token.ordered ? 'ol' : 'ul'
525 , body = '';
526
527 while (next().type !== 'list_end') {
528 body += tok();
529 }
530
531 return '<'
532 + type
533 + '>\n'
534 + body
535 + '</'
536 + type
537 + '>\n';
538 }
539 case 'list_item_start': {
540 var body = '';
541
542 while (next().type !== 'list_item_end') {
543 body += token.type === 'text'
544 ? parseText()
545 : tok();
546 }
547
548 return '<li>'
549 + body
550 + '</li>\n';
551 }
552 case 'loose_item_start': {
553 var body = '';
554
555 while (next().type !== 'list_item_end') {
556 body += tok();
557 }
558
559 return '<li>'
560 + body
561 + '</li>\n';
562 }
563 case 'html': {
564 return !token.pre
565 ? inline.lexer(token.text)
566 : token.text;
567 }
568 case 'paragraph': {
569 return '<p>'
570 + inline.lexer(token.text)
571 + '</p>\n';
572 }
573 case 'text': {
574 return '<p>'
575 + parseText()
576 + '</p>\n';
577 }
578 }
579};
580
581var parseText = function() {
582 var body = token.text
583 , top;
584
585 while ((top = tokens[tokens.length-1])
586 && top.type === 'text') {
587 body += '\n' + next().text;
588 }
589
590 return inline.lexer(body);
591};
592
593var parse = function(src) {
594 tokens = src.reverse();
595
596 var out = '';
597 while (next()) {
598 out += tok();
599 }
600
601 tokens = null;
602 token = null;
603
604 return out;
605};
606
607/**
608 * Helpers
609 */
610
611var escape = function(html, encode) {
612 return html
613 .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
614 .replace(/</g, '&lt;')
615 .replace(/>/g, '&gt;')
616 .replace(/"/g, '&quot;')
617 .replace(/'/g, '&#39;');
618};
619
620var mangle = function(text) {
621 var out = ''
622 , l = text.length
623 , i = 0
624 , ch;
625
626 for (; i < l; i++) {
627 ch = text.charCodeAt(i);
628 if (Math.random() > 0.5) {
629 ch = 'x' + ch.toString(16);
630 }
631 out += '&#' + ch + ';';
632 }
633
634 return out;
635};
636
637function tag() {
638 var tag = '(?!(?:'
639 + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
640 + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
641 + '|span|br|wbr|ins|del|img)\\b)\\w+';
642
643 return tag;
644}
645
646/**
647 * Expose
648 */
649
650var marked = function(src) {
651 return parse(block.lexer(src));
652};
653
654marked.parser = parse;
655marked.lexer = block.lexer;
656
657marked.parse = marked;
658
659if (typeof module !== 'undefined') {
660 module.exports = marked;
661} else {
662 this.marked = marked;
663}
664
665}).call(this);