1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | ;(function() {
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | var 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 |
|
27 | block.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 |
|
36 | block.html = (function() {
|
37 | var html = block.html.source;
|
38 |
|
39 | html = html
|
40 | .replace('comment', //.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 |
|
48 | block.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 |
|
73 | block.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 |
|
85 | block.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 |
|
292 | var 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 |
|
311 | inline.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 |
|
443 | var 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 |
|
475 | var tokens
|
476 | , token;
|
477 |
|
478 | var next = function() {
|
479 | return token = tokens.pop();
|
480 | };
|
481 |
|
482 | var 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 |
|
581 | var 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 |
|
593 | var 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 |
|
611 | var escape = function(html, encode) {
|
612 | return html
|
613 | .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&')
|
614 | .replace(/</g, '<')
|
615 | .replace(/>/g, '>')
|
616 | .replace(/"/g, '"')
|
617 | .replace(/'/g, ''');
|
618 | };
|
619 |
|
620 | var 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 |
|
637 | function 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 |
|
650 | var marked = function(src) {
|
651 | return parse(block.lexer(src));
|
652 | };
|
653 |
|
654 | marked.parser = parse;
|
655 | marked.lexer = block.lexer;
|
656 |
|
657 | marked.parse = marked;
|
658 |
|
659 | if (typeof module !== 'undefined') {
|
660 | module.exports = marked;
|
661 | } else {
|
662 | this.marked = marked;
|
663 | }
|
664 |
|
665 | }).call(this);
|