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