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