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