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