OLD | NEW |
| (Empty) |
1 /** | |
2 * marked - a markdown parser | |
3 * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) | |
4 * https://github.com/chjj/marked | |
5 */ | |
6 | |
7 ;(function() { | |
8 | |
9 /** | |
10 * Block-Level Grammar | |
11 */ | |
12 | |
13 var block = { | |
14 newline: /^\n+/, | |
15 code: /^( {4}[^\n]+\n*)+/, | |
16 fences: noop, | |
17 hr: /^( *[-*_]){3,} *(?:\n+|$)/, | |
18 heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, | |
19 nptable: noop, | |
20 lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, | |
21 blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/, | |
22 list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, | |
23 html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/, | |
24 def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, | |
25 table: noop, | |
26 paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, | |
27 text: /^[^\n]+/ | |
28 }; | |
29 | |
30 block.bullet = /(?:[*+-]|\d+\.)/; | |
31 block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; | |
32 block.item = replace(block.item, 'gm') | |
33 (/bull/g, block.bullet) | |
34 (); | |
35 | |
36 block.list = replace(block.list) | |
37 (/bull/g, block.bullet) | |
38 ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))') | |
39 ('def', '\\n+(?=' + block.def.source + ')') | |
40 (); | |
41 | |
42 block.blockquote = replace(block.blockquote) | |
43 ('def', block.def) | |
44 (); | |
45 | |
46 block._tag = '(?!(?:' | |
47 + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' | |
48 + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' | |
49 + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b'; | |
50 | |
51 block.html = replace(block.html) | |
52 ('comment', /<!--[\s\S]*?-->/) | |
53 ('closed', /<(tag)[\s\S]+?<\/\1>/) | |
54 ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/) | |
55 (/tag/g, block._tag) | |
56 (); | |
57 | |
58 block.paragraph = replace(block.paragraph) | |
59 ('hr', block.hr) | |
60 ('heading', block.heading) | |
61 ('lheading', block.lheading) | |
62 ('blockquote', block.blockquote) | |
63 ('tag', '<' + block._tag) | |
64 ('def', block.def) | |
65 (); | |
66 | |
67 /** | |
68 * Normal Block Grammar | |
69 */ | |
70 | |
71 block.normal = merge({}, block); | |
72 | |
73 /** | |
74 * GFM Block Grammar | |
75 */ | |
76 | |
77 block.gfm = merge({}, block.normal, { | |
78 fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/, | |
79 paragraph: /^/ | |
80 }); | |
81 | |
82 block.gfm.paragraph = replace(block.paragraph) | |
83 ('(?!', '(?!' | |
84 + block.gfm.fences.source.replace('\\1', '\\2') + '|' | |
85 + block.list.source.replace('\\1', '\\3') + '|') | |
86 (); | |
87 | |
88 /** | |
89 * GFM + Tables Block Grammar | |
90 */ | |
91 | |
92 block.tables = merge({}, block.gfm, { | |
93 nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, | |
94 table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ | |
95 }); | |
96 | |
97 /** | |
98 * Block Lexer | |
99 */ | |
100 | |
101 function Lexer(options) { | |
102 this.tokens = []; | |
103 this.tokens.links = {}; | |
104 this.options = options || marked.defaults; | |
105 this.rules = block.normal; | |
106 | |
107 if (this.options.gfm) { | |
108 if (this.options.tables) { | |
109 this.rules = block.tables; | |
110 } else { | |
111 this.rules = block.gfm; | |
112 } | |
113 } | |
114 } | |
115 | |
116 /** | |
117 * Expose Block Rules | |
118 */ | |
119 | |
120 Lexer.rules = block; | |
121 | |
122 /** | |
123 * Static Lex Method | |
124 */ | |
125 | |
126 Lexer.lex = function(src, options) { | |
127 var lexer = new Lexer(options); | |
128 return lexer.lex(src); | |
129 }; | |
130 | |
131 /** | |
132 * Preprocessing | |
133 */ | |
134 | |
135 Lexer.prototype.lex = function(src) { | |
136 src = src | |
137 .replace(/\r\n|\r/g, '\n') | |
138 .replace(/\t/g, ' ') | |
139 .replace(/\u00a0/g, ' ') | |
140 .replace(/\u2424/g, '\n'); | |
141 | |
142 return this.token(src, true); | |
143 }; | |
144 | |
145 /** | |
146 * Lexing | |
147 */ | |
148 | |
149 Lexer.prototype.token = function(src, top, bq) { | |
150 var src = src.replace(/^ +$/gm, '') | |
151 , next | |
152 , loose | |
153 , cap | |
154 , bull | |
155 , b | |
156 , item | |
157 , space | |
158 , i | |
159 , l; | |
160 | |
161 while (src) { | |
162 // newline | |
163 if (cap = this.rules.newline.exec(src)) { | |
164 src = src.substring(cap[0].length); | |
165 if (cap[0].length > 1) { | |
166 this.tokens.push({ | |
167 type: 'space' | |
168 }); | |
169 } | |
170 } | |
171 | |
172 // code | |
173 if (cap = this.rules.code.exec(src)) { | |
174 src = src.substring(cap[0].length); | |
175 cap = cap[0].replace(/^ {4}/gm, ''); | |
176 this.tokens.push({ | |
177 type: 'code', | |
178 text: !this.options.pedantic | |
179 ? cap.replace(/\n+$/, '') | |
180 : cap | |
181 }); | |
182 continue; | |
183 } | |
184 | |
185 // fences (gfm) | |
186 if (cap = this.rules.fences.exec(src)) { | |
187 src = src.substring(cap[0].length); | |
188 this.tokens.push({ | |
189 type: 'code', | |
190 lang: cap[2], | |
191 text: cap[3] | |
192 }); | |
193 continue; | |
194 } | |
195 | |
196 // heading | |
197 if (cap = this.rules.heading.exec(src)) { | |
198 src = src.substring(cap[0].length); | |
199 this.tokens.push({ | |
200 type: 'heading', | |
201 depth: cap[1].length, | |
202 text: cap[2] | |
203 }); | |
204 continue; | |
205 } | |
206 | |
207 // table no leading pipe (gfm) | |
208 if (top && (cap = this.rules.nptable.exec(src))) { | |
209 src = src.substring(cap[0].length); | |
210 | |
211 item = { | |
212 type: 'table', | |
213 header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), | |
214 align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), | |
215 cells: cap[3].replace(/\n$/, '').split('\n') | |
216 }; | |
217 | |
218 for (i = 0; i < item.align.length; i++) { | |
219 if (/^ *-+: *$/.test(item.align[i])) { | |
220 item.align[i] = 'right'; | |
221 } else if (/^ *:-+: *$/.test(item.align[i])) { | |
222 item.align[i] = 'center'; | |
223 } else if (/^ *:-+ *$/.test(item.align[i])) { | |
224 item.align[i] = 'left'; | |
225 } else { | |
226 item.align[i] = null; | |
227 } | |
228 } | |
229 | |
230 for (i = 0; i < item.cells.length; i++) { | |
231 item.cells[i] = item.cells[i].split(/ *\| */); | |
232 } | |
233 | |
234 this.tokens.push(item); | |
235 | |
236 continue; | |
237 } | |
238 | |
239 // lheading | |
240 if (cap = this.rules.lheading.exec(src)) { | |
241 src = src.substring(cap[0].length); | |
242 this.tokens.push({ | |
243 type: 'heading', | |
244 depth: cap[2] === '=' ? 1 : 2, | |
245 text: cap[1] | |
246 }); | |
247 continue; | |
248 } | |
249 | |
250 // hr | |
251 if (cap = this.rules.hr.exec(src)) { | |
252 src = src.substring(cap[0].length); | |
253 this.tokens.push({ | |
254 type: 'hr' | |
255 }); | |
256 continue; | |
257 } | |
258 | |
259 // blockquote | |
260 if (cap = this.rules.blockquote.exec(src)) { | |
261 src = src.substring(cap[0].length); | |
262 | |
263 this.tokens.push({ | |
264 type: 'blockquote_start' | |
265 }); | |
266 | |
267 cap = cap[0].replace(/^ *> ?/gm, ''); | |
268 | |
269 // Pass `top` to keep the current | |
270 // "toplevel" state. This is exactly | |
271 // how markdown.pl works. | |
272 this.token(cap, top, true); | |
273 | |
274 this.tokens.push({ | |
275 type: 'blockquote_end' | |
276 }); | |
277 | |
278 continue; | |
279 } | |
280 | |
281 // list | |
282 if (cap = this.rules.list.exec(src)) { | |
283 src = src.substring(cap[0].length); | |
284 bull = cap[2]; | |
285 | |
286 this.tokens.push({ | |
287 type: 'list_start', | |
288 ordered: bull.length > 1 | |
289 }); | |
290 | |
291 // Get each top-level item. | |
292 cap = cap[0].match(this.rules.item); | |
293 | |
294 next = false; | |
295 l = cap.length; | |
296 i = 0; | |
297 | |
298 for (; i < l; i++) { | |
299 item = cap[i]; | |
300 | |
301 // Remove the list item's bullet | |
302 // so it is seen as the next token. | |
303 space = item.length; | |
304 item = item.replace(/^ *([*+-]|\d+\.) +/, ''); | |
305 | |
306 // Outdent whatever the | |
307 // list item contains. Hacky. | |
308 if (~item.indexOf('\n ')) { | |
309 space -= item.length; | |
310 item = !this.options.pedantic | |
311 ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') | |
312 : item.replace(/^ {1,4}/gm, ''); | |
313 } | |
314 | |
315 // Determine whether the next list item belongs here. | |
316 // Backpedal if it does not belong in this list. | |
317 if (this.options.smartLists && i !== l - 1) { | |
318 b = block.bullet.exec(cap[i + 1])[0]; | |
319 if (bull !== b && !(bull.length > 1 && b.length > 1)) { | |
320 src = cap.slice(i + 1).join('\n') + src; | |
321 i = l - 1; | |
322 } | |
323 } | |
324 | |
325 // Determine whether item is loose or not. | |
326 // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ | |
327 // for discount behavior. | |
328 loose = next || /\n\n(?!\s*$)/.test(item); | |
329 if (i !== l - 1) { | |
330 next = item.charAt(item.length - 1) === '\n'; | |
331 if (!loose) loose = next; | |
332 } | |
333 | |
334 this.tokens.push({ | |
335 type: loose | |
336 ? 'loose_item_start' | |
337 : 'list_item_start' | |
338 }); | |
339 | |
340 // Recurse. | |
341 this.token(item, false, bq); | |
342 | |
343 this.tokens.push({ | |
344 type: 'list_item_end' | |
345 }); | |
346 } | |
347 | |
348 this.tokens.push({ | |
349 type: 'list_end' | |
350 }); | |
351 | |
352 continue; | |
353 } | |
354 | |
355 // html | |
356 if (cap = this.rules.html.exec(src)) { | |
357 src = src.substring(cap[0].length); | |
358 this.tokens.push({ | |
359 type: this.options.sanitize | |
360 ? 'paragraph' | |
361 : 'html', | |
362 pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style', | |
363 text: cap[0] | |
364 }); | |
365 continue; | |
366 } | |
367 | |
368 // def | |
369 if ((!bq && top) && (cap = this.rules.def.exec(src))) { | |
370 src = src.substring(cap[0].length); | |
371 this.tokens.links[cap[1].toLowerCase()] = { | |
372 href: cap[2], | |
373 title: cap[3] | |
374 }; | |
375 continue; | |
376 } | |
377 | |
378 // table (gfm) | |
379 if (top && (cap = this.rules.table.exec(src))) { | |
380 src = src.substring(cap[0].length); | |
381 | |
382 item = { | |
383 type: 'table', | |
384 header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), | |
385 align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), | |
386 cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') | |
387 }; | |
388 | |
389 for (i = 0; i < item.align.length; i++) { | |
390 if (/^ *-+: *$/.test(item.align[i])) { | |
391 item.align[i] = 'right'; | |
392 } else if (/^ *:-+: *$/.test(item.align[i])) { | |
393 item.align[i] = 'center'; | |
394 } else if (/^ *:-+ *$/.test(item.align[i])) { | |
395 item.align[i] = 'left'; | |
396 } else { | |
397 item.align[i] = null; | |
398 } | |
399 } | |
400 | |
401 for (i = 0; i < item.cells.length; i++) { | |
402 item.cells[i] = item.cells[i] | |
403 .replace(/^ *\| *| *\| *$/g, '') | |
404 .split(/ *\| */); | |
405 } | |
406 | |
407 this.tokens.push(item); | |
408 | |
409 continue; | |
410 } | |
411 | |
412 // top-level paragraph | |
413 if (top && (cap = this.rules.paragraph.exec(src))) { | |
414 src = src.substring(cap[0].length); | |
415 this.tokens.push({ | |
416 type: 'paragraph', | |
417 text: cap[1].charAt(cap[1].length - 1) === '\n' | |
418 ? cap[1].slice(0, -1) | |
419 : cap[1] | |
420 }); | |
421 continue; | |
422 } | |
423 | |
424 // text | |
425 if (cap = this.rules.text.exec(src)) { | |
426 // Top-level should never reach here. | |
427 src = src.substring(cap[0].length); | |
428 this.tokens.push({ | |
429 type: 'text', | |
430 text: cap[0] | |
431 }); | |
432 continue; | |
433 } | |
434 | |
435 if (src) { | |
436 throw new | |
437 Error('Infinite loop on byte: ' + src.charCodeAt(0)); | |
438 } | |
439 } | |
440 | |
441 return this.tokens; | |
442 }; | |
443 | |
444 /** | |
445 * Inline-Level Grammar | |
446 */ | |
447 | |
448 var inline = { | |
449 escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, | |
450 autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, | |
451 url: noop, | |
452 tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, | |
453 link: /^!?\[(inside)\]\(href\)/, | |
454 reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, | |
455 nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, | |
456 strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, | |
457 em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, | |
458 code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, | |
459 br: /^ {2,}\n(?!\s*$)/, | |
460 del: noop, | |
461 text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/ | |
462 }; | |
463 | |
464 inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/; | |
465 inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/; | |
466 | |
467 inline.link = replace(inline.link) | |
468 ('inside', inline._inside) | |
469 ('href', inline._href) | |
470 (); | |
471 | |
472 inline.reflink = replace(inline.reflink) | |
473 ('inside', inline._inside) | |
474 (); | |
475 | |
476 /** | |
477 * Normal Inline Grammar | |
478 */ | |
479 | |
480 inline.normal = merge({}, inline); | |
481 | |
482 /** | |
483 * Pedantic Inline Grammar | |
484 */ | |
485 | |
486 inline.pedantic = merge({}, inline.normal, { | |
487 strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, | |
488 em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ | |
489 }); | |
490 | |
491 /** | |
492 * GFM Inline Grammar | |
493 */ | |
494 | |
495 inline.gfm = merge({}, inline.normal, { | |
496 escape: replace(inline.escape)('])', '~|])')(), | |
497 url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/, | |
498 del: /^~~(?=\S)([\s\S]*?\S)~~/, | |
499 text: replace(inline.text) | |
500 (']|', '~]|') | |
501 ('|', '|https?://|') | |
502 () | |
503 }); | |
504 | |
505 /** | |
506 * GFM + Line Breaks Inline Grammar | |
507 */ | |
508 | |
509 inline.breaks = merge({}, inline.gfm, { | |
510 br: replace(inline.br)('{2,}', '*')(), | |
511 text: replace(inline.gfm.text)('{2,}', '*')() | |
512 }); | |
513 | |
514 /** | |
515 * Inline Lexer & Compiler | |
516 */ | |
517 | |
518 function InlineLexer(links, options) { | |
519 this.options = options || marked.defaults; | |
520 this.links = links; | |
521 this.rules = inline.normal; | |
522 this.renderer = this.options.renderer || new Renderer; | |
523 this.renderer.options = this.options; | |
524 | |
525 if (!this.links) { | |
526 throw new | |
527 Error('Tokens array requires a `links` property.'); | |
528 } | |
529 | |
530 if (this.options.gfm) { | |
531 if (this.options.breaks) { | |
532 this.rules = inline.breaks; | |
533 } else { | |
534 this.rules = inline.gfm; | |
535 } | |
536 } else if (this.options.pedantic) { | |
537 this.rules = inline.pedantic; | |
538 } | |
539 } | |
540 | |
541 /** | |
542 * Expose Inline Rules | |
543 */ | |
544 | |
545 InlineLexer.rules = inline; | |
546 | |
547 /** | |
548 * Static Lexing/Compiling Method | |
549 */ | |
550 | |
551 InlineLexer.output = function(src, links, options) { | |
552 var inline = new InlineLexer(links, options); | |
553 return inline.output(src); | |
554 }; | |
555 | |
556 /** | |
557 * Lexing/Compiling | |
558 */ | |
559 | |
560 InlineLexer.prototype.output = function(src) { | |
561 var out = '' | |
562 , link | |
563 , text | |
564 , href | |
565 , cap; | |
566 | |
567 while (src) { | |
568 // escape | |
569 if (cap = this.rules.escape.exec(src)) { | |
570 src = src.substring(cap[0].length); | |
571 out += cap[1]; | |
572 continue; | |
573 } | |
574 | |
575 // autolink | |
576 if (cap = this.rules.autolink.exec(src)) { | |
577 src = src.substring(cap[0].length); | |
578 if (cap[2] === '@') { | |
579 text = cap[1].charAt(6) === ':' | |
580 ? this.mangle(cap[1].substring(7)) | |
581 : this.mangle(cap[1]); | |
582 href = this.mangle('mailto:') + text; | |
583 } else { | |
584 text = escape(cap[1]); | |
585 href = text; | |
586 } | |
587 out += this.renderer.link(href, null, text); | |
588 continue; | |
589 } | |
590 | |
591 // url (gfm) | |
592 if (!this.inLink && (cap = this.rules.url.exec(src))) { | |
593 src = src.substring(cap[0].length); | |
594 text = escape(cap[1]); | |
595 href = text; | |
596 out += this.renderer.link(href, null, text); | |
597 continue; | |
598 } | |
599 | |
600 // tag | |
601 if (cap = this.rules.tag.exec(src)) { | |
602 if (!this.inLink && /^<a /i.test(cap[0])) { | |
603 this.inLink = true; | |
604 } else if (this.inLink && /^<\/a>/i.test(cap[0])) { | |
605 this.inLink = false; | |
606 } | |
607 src = src.substring(cap[0].length); | |
608 out += this.options.sanitize | |
609 ? escape(cap[0]) | |
610 : cap[0]; | |
611 continue; | |
612 } | |
613 | |
614 // link | |
615 if (cap = this.rules.link.exec(src)) { | |
616 src = src.substring(cap[0].length); | |
617 this.inLink = true; | |
618 out += this.outputLink(cap, { | |
619 href: cap[2], | |
620 title: cap[3] | |
621 }); | |
622 this.inLink = false; | |
623 continue; | |
624 } | |
625 | |
626 // reflink, nolink | |
627 if ((cap = this.rules.reflink.exec(src)) | |
628 || (cap = this.rules.nolink.exec(src))) { | |
629 src = src.substring(cap[0].length); | |
630 link = (cap[2] || cap[1]).replace(/\s+/g, ' '); | |
631 link = this.links[link.toLowerCase()]; | |
632 if (!link || !link.href) { | |
633 out += cap[0].charAt(0); | |
634 src = cap[0].substring(1) + src; | |
635 continue; | |
636 } | |
637 this.inLink = true; | |
638 out += this.outputLink(cap, link); | |
639 this.inLink = false; | |
640 continue; | |
641 } | |
642 | |
643 // strong | |
644 if (cap = this.rules.strong.exec(src)) { | |
645 src = src.substring(cap[0].length); | |
646 out += this.renderer.strong(this.output(cap[2] || cap[1])); | |
647 continue; | |
648 } | |
649 | |
650 // em | |
651 if (cap = this.rules.em.exec(src)) { | |
652 src = src.substring(cap[0].length); | |
653 out += this.renderer.em(this.output(cap[2] || cap[1])); | |
654 continue; | |
655 } | |
656 | |
657 // code | |
658 if (cap = this.rules.code.exec(src)) { | |
659 src = src.substring(cap[0].length); | |
660 out += this.renderer.codespan(escape(cap[2], true)); | |
661 continue; | |
662 } | |
663 | |
664 // br | |
665 if (cap = this.rules.br.exec(src)) { | |
666 src = src.substring(cap[0].length); | |
667 out += this.renderer.br(); | |
668 continue; | |
669 } | |
670 | |
671 // del (gfm) | |
672 if (cap = this.rules.del.exec(src)) { | |
673 src = src.substring(cap[0].length); | |
674 out += this.renderer.del(this.output(cap[1])); | |
675 continue; | |
676 } | |
677 | |
678 // text | |
679 if (cap = this.rules.text.exec(src)) { | |
680 src = src.substring(cap[0].length); | |
681 out += escape(this.smartypants(cap[0])); | |
682 continue; | |
683 } | |
684 | |
685 if (src) { | |
686 throw new | |
687 Error('Infinite loop on byte: ' + src.charCodeAt(0)); | |
688 } | |
689 } | |
690 | |
691 return out; | |
692 }; | |
693 | |
694 /** | |
695 * Compile Link | |
696 */ | |
697 | |
698 InlineLexer.prototype.outputLink = function(cap, link) { | |
699 var href = escape(link.href) | |
700 , title = link.title ? escape(link.title) : null; | |
701 | |
702 return cap[0].charAt(0) !== '!' | |
703 ? this.renderer.link(href, title, this.output(cap[1])) | |
704 : this.renderer.image(href, title, escape(cap[1])); | |
705 }; | |
706 | |
707 /** | |
708 * Smartypants Transformations | |
709 */ | |
710 | |
711 InlineLexer.prototype.smartypants = function(text) { | |
712 if (!this.options.smartypants) return text; | |
713 return text | |
714 // em-dashes | |
715 .replace(/--/g, '\u2014') | |
716 // opening singles | |
717 .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') | |
718 // closing singles & apostrophes | |
719 .replace(/'/g, '\u2019') | |
720 // opening doubles | |
721 .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') | |
722 // closing doubles | |
723 .replace(/"/g, '\u201d') | |
724 // ellipses | |
725 .replace(/\.{3}/g, '\u2026'); | |
726 }; | |
727 | |
728 /** | |
729 * Mangle Links | |
730 */ | |
731 | |
732 InlineLexer.prototype.mangle = function(text) { | |
733 var out = '' | |
734 , l = text.length | |
735 , i = 0 | |
736 , ch; | |
737 | |
738 for (; i < l; i++) { | |
739 ch = text.charCodeAt(i); | |
740 if (Math.random() > 0.5) { | |
741 ch = 'x' + ch.toString(16); | |
742 } | |
743 out += '&#' + ch + ';'; | |
744 } | |
745 | |
746 return out; | |
747 }; | |
748 | |
749 /** | |
750 * Renderer | |
751 */ | |
752 | |
753 function Renderer(options) { | |
754 this.options = options || {}; | |
755 } | |
756 | |
757 Renderer.prototype.code = function(code, lang, escaped) { | |
758 if (this.options.highlight) { | |
759 var out = this.options.highlight(code, lang); | |
760 if (out != null && out !== code) { | |
761 escaped = true; | |
762 code = out; | |
763 } | |
764 } | |
765 | |
766 if (!lang) { | |
767 return '<pre><code>' | |
768 + (escaped ? code : escape(code, true)) | |
769 + '\n</code></pre>'; | |
770 } | |
771 | |
772 return '<pre><code class="' | |
773 + this.options.langPrefix | |
774 + escape(lang, true) | |
775 + '">' | |
776 + (escaped ? code : escape(code, true)) | |
777 + '\n</code></pre>\n'; | |
778 }; | |
779 | |
780 Renderer.prototype.blockquote = function(quote) { | |
781 return '<blockquote>\n' + quote + '</blockquote>\n'; | |
782 }; | |
783 | |
784 Renderer.prototype.html = function(html) { | |
785 return html; | |
786 }; | |
787 | |
788 Renderer.prototype.heading = function(text, level, raw) { | |
789 return '<h' | |
790 + level | |
791 + ' id="' | |
792 + this.options.headerPrefix | |
793 + raw.toLowerCase().replace(/[^\w]+/g, '-') | |
794 + '">' | |
795 + text | |
796 + '</h' | |
797 + level | |
798 + '>\n'; | |
799 }; | |
800 | |
801 Renderer.prototype.hr = function() { | |
802 return this.options.xhtml ? '<hr/>\n' : '<hr>\n'; | |
803 }; | |
804 | |
805 Renderer.prototype.list = function(body, ordered) { | |
806 var type = ordered ? 'ol' : 'ul'; | |
807 return '<' + type + '>\n' + body + '</' + type + '>\n'; | |
808 }; | |
809 | |
810 Renderer.prototype.listitem = function(text) { | |
811 return '<li>' + text + '</li>\n'; | |
812 }; | |
813 | |
814 Renderer.prototype.paragraph = function(text) { | |
815 return '<p>' + text + '</p>\n'; | |
816 }; | |
817 | |
818 Renderer.prototype.table = function(header, body) { | |
819 return '<table>\n' | |
820 + '<thead>\n' | |
821 + header | |
822 + '</thead>\n' | |
823 + '<tbody>\n' | |
824 + body | |
825 + '</tbody>\n' | |
826 + '</table>\n'; | |
827 }; | |
828 | |
829 Renderer.prototype.tablerow = function(content) { | |
830 return '<tr>\n' + content + '</tr>\n'; | |
831 }; | |
832 | |
833 Renderer.prototype.tablecell = function(content, flags) { | |
834 var type = flags.header ? 'th' : 'td'; | |
835 var tag = flags.align | |
836 ? '<' + type + ' style="text-align:' + flags.align + '">' | |
837 : '<' + type + '>'; | |
838 return tag + content + '</' + type + '>\n'; | |
839 }; | |
840 | |
841 // span level renderer | |
842 Renderer.prototype.strong = function(text) { | |
843 return '<strong>' + text + '</strong>'; | |
844 }; | |
845 | |
846 Renderer.prototype.em = function(text) { | |
847 return '<em>' + text + '</em>'; | |
848 }; | |
849 | |
850 Renderer.prototype.codespan = function(text) { | |
851 return '<code>' + text + '</code>'; | |
852 }; | |
853 | |
854 Renderer.prototype.br = function() { | |
855 return this.options.xhtml ? '<br/>' : '<br>'; | |
856 }; | |
857 | |
858 Renderer.prototype.del = function(text) { | |
859 return '<del>' + text + '</del>'; | |
860 }; | |
861 | |
862 Renderer.prototype.link = function(href, title, text) { | |
863 if (this.options.sanitize) { | |
864 try { | |
865 var prot = decodeURIComponent(unescape(href)) | |
866 .replace(/[^\w:]/g, '') | |
867 .toLowerCase(); | |
868 } catch (e) { | |
869 return ''; | |
870 } | |
871 if (prot.indexOf('javascript:') === 0) { | |
872 return ''; | |
873 } | |
874 } | |
875 var out = '<a href="' + href + '"'; | |
876 if (title) { | |
877 out += ' title="' + title + '"'; | |
878 } | |
879 out += '>' + text + '</a>'; | |
880 return out; | |
881 }; | |
882 | |
883 Renderer.prototype.image = function(href, title, text) { | |
884 var out = '<img src="' + href + '" alt="' + text + '"'; | |
885 if (title) { | |
886 out += ' title="' + title + '"'; | |
887 } | |
888 out += this.options.xhtml ? '/>' : '>'; | |
889 return out; | |
890 }; | |
891 | |
892 /** | |
893 * Parsing & Compiling | |
894 */ | |
895 | |
896 function Parser(options) { | |
897 this.tokens = []; | |
898 this.token = null; | |
899 this.options = options || marked.defaults; | |
900 this.options.renderer = this.options.renderer || new Renderer; | |
901 this.renderer = this.options.renderer; | |
902 this.renderer.options = this.options; | |
903 } | |
904 | |
905 /** | |
906 * Static Parse Method | |
907 */ | |
908 | |
909 Parser.parse = function(src, options, renderer) { | |
910 var parser = new Parser(options, renderer); | |
911 return parser.parse(src); | |
912 }; | |
913 | |
914 /** | |
915 * Parse Loop | |
916 */ | |
917 | |
918 Parser.prototype.parse = function(src) { | |
919 this.inline = new InlineLexer(src.links, this.options, this.renderer); | |
920 this.tokens = src.reverse(); | |
921 | |
922 var out = ''; | |
923 while (this.next()) { | |
924 out += this.tok(); | |
925 } | |
926 | |
927 return out; | |
928 }; | |
929 | |
930 /** | |
931 * Next Token | |
932 */ | |
933 | |
934 Parser.prototype.next = function() { | |
935 return this.token = this.tokens.pop(); | |
936 }; | |
937 | |
938 /** | |
939 * Preview Next Token | |
940 */ | |
941 | |
942 Parser.prototype.peek = function() { | |
943 return this.tokens[this.tokens.length - 1] || 0; | |
944 }; | |
945 | |
946 /** | |
947 * Parse Text Tokens | |
948 */ | |
949 | |
950 Parser.prototype.parseText = function() { | |
951 var body = this.token.text; | |
952 | |
953 while (this.peek().type === 'text') { | |
954 body += '\n' + this.next().text; | |
955 } | |
956 | |
957 return this.inline.output(body); | |
958 }; | |
959 | |
960 /** | |
961 * Parse Current Token | |
962 */ | |
963 | |
964 Parser.prototype.tok = function() { | |
965 switch (this.token.type) { | |
966 case 'space': { | |
967 return ''; | |
968 } | |
969 case 'hr': { | |
970 return this.renderer.hr(); | |
971 } | |
972 case 'heading': { | |
973 return this.renderer.heading( | |
974 this.inline.output(this.token.text), | |
975 this.token.depth, | |
976 this.token.text); | |
977 } | |
978 case 'code': { | |
979 return this.renderer.code(this.token.text, | |
980 this.token.lang, | |
981 this.token.escaped); | |
982 } | |
983 case 'table': { | |
984 var header = '' | |
985 , body = '' | |
986 , i | |
987 , row | |
988 , cell | |
989 , flags | |
990 , j; | |
991 | |
992 // header | |
993 cell = ''; | |
994 for (i = 0; i < this.token.header.length; i++) { | |
995 flags = { header: true, align: this.token.align[i] }; | |
996 cell += this.renderer.tablecell( | |
997 this.inline.output(this.token.header[i]), | |
998 { header: true, align: this.token.align[i] } | |
999 ); | |
1000 } | |
1001 header += this.renderer.tablerow(cell); | |
1002 | |
1003 for (i = 0; i < this.token.cells.length; i++) { | |
1004 row = this.token.cells[i]; | |
1005 | |
1006 cell = ''; | |
1007 for (j = 0; j < row.length; j++) { | |
1008 cell += this.renderer.tablecell( | |
1009 this.inline.output(row[j]), | |
1010 { header: false, align: this.token.align[j] } | |
1011 ); | |
1012 } | |
1013 | |
1014 body += this.renderer.tablerow(cell); | |
1015 } | |
1016 return this.renderer.table(header, body); | |
1017 } | |
1018 case 'blockquote_start': { | |
1019 var body = ''; | |
1020 | |
1021 while (this.next().type !== 'blockquote_end') { | |
1022 body += this.tok(); | |
1023 } | |
1024 | |
1025 return this.renderer.blockquote(body); | |
1026 } | |
1027 case 'list_start': { | |
1028 var body = '' | |
1029 , ordered = this.token.ordered; | |
1030 | |
1031 while (this.next().type !== 'list_end') { | |
1032 body += this.tok(); | |
1033 } | |
1034 | |
1035 return this.renderer.list(body, ordered); | |
1036 } | |
1037 case 'list_item_start': { | |
1038 var body = ''; | |
1039 | |
1040 while (this.next().type !== 'list_item_end') { | |
1041 body += this.token.type === 'text' | |
1042 ? this.parseText() | |
1043 : this.tok(); | |
1044 } | |
1045 | |
1046 return this.renderer.listitem(body); | |
1047 } | |
1048 case 'loose_item_start': { | |
1049 var body = ''; | |
1050 | |
1051 while (this.next().type !== 'list_item_end') { | |
1052 body += this.tok(); | |
1053 } | |
1054 | |
1055 return this.renderer.listitem(body); | |
1056 } | |
1057 case 'html': { | |
1058 var html = !this.token.pre && !this.options.pedantic | |
1059 ? this.inline.output(this.token.text) | |
1060 : this.token.text; | |
1061 return this.renderer.html(html); | |
1062 } | |
1063 case 'paragraph': { | |
1064 return this.renderer.paragraph(this.inline.output(this.token.text)); | |
1065 } | |
1066 case 'text': { | |
1067 return this.renderer.paragraph(this.parseText()); | |
1068 } | |
1069 } | |
1070 }; | |
1071 | |
1072 /** | |
1073 * Helpers | |
1074 */ | |
1075 | |
1076 function escape(html, encode) { | |
1077 return html | |
1078 .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') | |
1079 .replace(/</g, '<') | |
1080 .replace(/>/g, '>') | |
1081 .replace(/"/g, '"') | |
1082 .replace(/'/g, '''); | |
1083 } | |
1084 | |
1085 function unescape(html) { | |
1086 return html.replace(/&([#\w]+);/g, function(_, n) { | |
1087 n = n.toLowerCase(); | |
1088 if (n === 'colon') return ':'; | |
1089 if (n.charAt(0) === '#') { | |
1090 return n.charAt(1) === 'x' | |
1091 ? String.fromCharCode(parseInt(n.substring(2), 16)) | |
1092 : String.fromCharCode(+n.substring(1)); | |
1093 } | |
1094 return ''; | |
1095 }); | |
1096 } | |
1097 | |
1098 function replace(regex, opt) { | |
1099 regex = regex.source; | |
1100 opt = opt || ''; | |
1101 return function self(name, val) { | |
1102 if (!name) return new RegExp(regex, opt); | |
1103 val = val.source || val; | |
1104 val = val.replace(/(^|[^\[])\^/g, '$1'); | |
1105 regex = regex.replace(name, val); | |
1106 return self; | |
1107 }; | |
1108 } | |
1109 | |
1110 function noop() {} | |
1111 noop.exec = noop; | |
1112 | |
1113 function merge(obj) { | |
1114 var i = 1 | |
1115 , target | |
1116 , key; | |
1117 | |
1118 for (; i < arguments.length; i++) { | |
1119 target = arguments[i]; | |
1120 for (key in target) { | |
1121 if (Object.prototype.hasOwnProperty.call(target, key)) { | |
1122 obj[key] = target[key]; | |
1123 } | |
1124 } | |
1125 } | |
1126 | |
1127 return obj; | |
1128 } | |
1129 | |
1130 | |
1131 /** | |
1132 * Marked | |
1133 */ | |
1134 | |
1135 function marked(src, opt, callback) { | |
1136 if (callback || typeof opt === 'function') { | |
1137 if (!callback) { | |
1138 callback = opt; | |
1139 opt = null; | |
1140 } | |
1141 | |
1142 opt = merge({}, marked.defaults, opt || {}); | |
1143 | |
1144 var highlight = opt.highlight | |
1145 , tokens | |
1146 , pending | |
1147 , i = 0; | |
1148 | |
1149 try { | |
1150 tokens = Lexer.lex(src, opt) | |
1151 } catch (e) { | |
1152 return callback(e); | |
1153 } | |
1154 | |
1155 pending = tokens.length; | |
1156 | |
1157 var done = function() { | |
1158 var out, err; | |
1159 | |
1160 try { | |
1161 out = Parser.parse(tokens, opt); | |
1162 } catch (e) { | |
1163 err = e; | |
1164 } | |
1165 | |
1166 opt.highlight = highlight; | |
1167 | |
1168 return err | |
1169 ? callback(err) | |
1170 : callback(null, out); | |
1171 }; | |
1172 | |
1173 if (!highlight || highlight.length < 3) { | |
1174 return done(); | |
1175 } | |
1176 | |
1177 delete opt.highlight; | |
1178 | |
1179 if (!pending) return done(); | |
1180 | |
1181 for (; i < tokens.length; i++) { | |
1182 (function(token) { | |
1183 if (token.type !== 'code') { | |
1184 return --pending || done(); | |
1185 } | |
1186 return highlight(token.text, token.lang, function(err, code) { | |
1187 if (code == null || code === token.text) { | |
1188 return --pending || done(); | |
1189 } | |
1190 token.text = code; | |
1191 token.escaped = true; | |
1192 --pending || done(); | |
1193 }); | |
1194 })(tokens[i]); | |
1195 } | |
1196 | |
1197 return; | |
1198 } | |
1199 try { | |
1200 if (opt) opt = merge({}, marked.defaults, opt); | |
1201 return Parser.parse(Lexer.lex(src, opt), opt); | |
1202 } catch (e) { | |
1203 e.message += '\nPlease report this to https://github.com/chjj/marked.'; | |
1204 if ((opt || marked.defaults).silent) { | |
1205 return '<p>An error occured:</p><pre>' | |
1206 + escape(e.message + '', true) | |
1207 + '</pre>'; | |
1208 } | |
1209 throw e; | |
1210 } | |
1211 } | |
1212 | |
1213 /** | |
1214 * Options | |
1215 */ | |
1216 | |
1217 marked.options = | |
1218 marked.setOptions = function(opt) { | |
1219 merge(marked.defaults, opt); | |
1220 return marked; | |
1221 }; | |
1222 | |
1223 marked.defaults = { | |
1224 gfm: true, | |
1225 tables: true, | |
1226 breaks: false, | |
1227 pedantic: false, | |
1228 sanitize: false, | |
1229 smartLists: false, | |
1230 silent: false, | |
1231 highlight: null, | |
1232 langPrefix: 'lang-', | |
1233 smartypants: false, | |
1234 headerPrefix: '', | |
1235 renderer: new Renderer, | |
1236 xhtml: false | |
1237 }; | |
1238 | |
1239 /** | |
1240 * Expose | |
1241 */ | |
1242 | |
1243 marked.Parser = Parser; | |
1244 marked.parser = Parser.parse; | |
1245 | |
1246 marked.Renderer = Renderer; | |
1247 | |
1248 marked.Lexer = Lexer; | |
1249 marked.lexer = Lexer.lex; | |
1250 | |
1251 marked.InlineLexer = InlineLexer; | |
1252 marked.inlineLexer = InlineLexer.output; | |
1253 | |
1254 marked.parse = marked; | |
1255 | |
1256 if (typeof exports === 'object') { | |
1257 module.exports = marked; | |
1258 } else if (typeof define === 'function' && define.amd) { | |
1259 define(function() { return marked; }); | |
1260 } else { | |
1261 this.marked = marked; | |
1262 } | |
1263 | |
1264 }).call(function() { | |
1265 return this || (typeof window !== 'undefined' ? window : global); | |
1266 }()); | |
OLD | NEW |