| /** |
| * marked - A markdown parser (https://github.com/chjj/marked) |
| * Copyright (c) 2011-2012, Christopher Jeffrey. (MIT Licensed) |
| */ |
| |
| ;(function() { |
| |
| /** |
| * Block-Level Grammar |
| */ |
| |
| var block = { |
| newline: /^\n+/, |
| code: /^ {4,}[^\n]*(?:\n {4,}[^\n]*|\n)*(?:\n+|$)/, |
| gfm_code: /^ *``` *(\w+)? *\n([^\0]+?)\s*``` *(?:\n+|$)/, |
| hr: /^( *[\-*_]){3,} *(?:\n+|$)/, |
| heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, |
| lheading: /^([^\n]+)\n *(=|-){3,} *\n*/, |
| blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/, |
| list: /^( *)([*+-]|\d+\.) [^\0]+?(?:\n{2,}(?! )|\s*$)(?!\1bullet)\n*/, |
| html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/, |
| def: /^ *\[([^\]]+)\]: *([^\s]+)(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, |
| paragraph: /^([^\n]+\n?(?!body))+\n*/, |
| text: /^[^\n]+/ |
| }; |
| |
| block.list = (function() { |
| var list = block.list.source; |
| |
| list = list |
| .replace('bullet', /(?:[*+-](?!(?: *[-*]){2,})|\d+\.)/.source); |
| |
| return new RegExp(list); |
| })(); |
| |
| block.html = (function() { |
| var html = block.html.source; |
| |
| html = html |
| .replace('comment', /<!--[^\0]*?-->/.source) |
| .replace('closed', /<(tag)[^\0]+?<\/\1>/.source) |
| .replace('closing', /<tag(?!:\/|@)\b(?:"[^"]*"|'[^']*'|[^'">])*?>/.source) |
| .replace(/tag/g, tag()); |
| |
| return new RegExp(html); |
| })(); |
| |
| block.paragraph = (function() { |
| var paragraph = block.paragraph.source |
| , body = []; |
| |
| (function push(rule) { |
| rule = block[rule] ? block[rule].source : rule; |
| body.push(rule.replace(/(^|[^\[])\^/g, '$1')); |
| return push; |
| }) |
| ('gfm_code') |
| ('hr') |
| ('heading') |
| ('lheading') |
| ('blockquote') |
| ('<' + tag()) |
| ('def'); |
| |
| return new |
| RegExp(paragraph.replace('body', body.join('|'))); |
| })(); |
| |
| /** |
| * Block Lexer |
| */ |
| |
| block.lexer = function(src) { |
| var tokens = []; |
| |
| tokens.links = {}; |
| |
| src = src |
| .replace(/\r\n|\r/g, '\n') |
| .replace(/\t/g, ' '); |
| |
| return block.token(src, tokens, true); |
| }; |
| |
| block.token = function(src, tokens, top) { |
| var src = src.replace(/^ +$/gm, '') |
| , next |
| , loose |
| , cap |
| , item |
| , space |
| , i |
| , l; |
| |
| while (src) { |
| // newline |
| if (cap = block.newline.exec(src)) { |
| src = src.substring(cap[0].length); |
| if (cap[0].length > 1) { |
| tokens.push({ |
| type: 'space' |
| }); |
| } |
| } |
| |
| // code |
| if (cap = block.code.exec(src)) { |
| src = src.substring(cap[0].length); |
| cap = cap[0].replace(/^ {4}/gm, ''); |
| tokens.push({ |
| type: 'code', |
| text: cap.replace(/\n+$/, '') |
| }); |
| continue; |
| } |
| |
| // gfm_code |
| if (cap = block.gfm_code.exec(src)) { |
| src = src.substring(cap[0].length); |
| tokens.push({ |
| type: 'code', |
| lang: cap[1], |
| text: cap[2] |
| }); |
| continue; |
| } |
| |
| // heading |
| if (cap = block.heading.exec(src)) { |
| src = src.substring(cap[0].length); |
| tokens.push({ |
| type: 'heading', |
| depth: cap[1].length, |
| text: cap[2] |
| }); |
| continue; |
| } |
| |
| // lheading |
| if (cap = block.lheading.exec(src)) { |
| src = src.substring(cap[0].length); |
| tokens.push({ |
| type: 'heading', |
| depth: cap[2] === '=' ? 1 : 2, |
| text: cap[1] |
| }); |
| continue; |
| } |
| |
| // hr |
| if (cap = block.hr.exec(src)) { |
| src = src.substring(cap[0].length); |
| tokens.push({ |
| type: 'hr' |
| }); |
| continue; |
| } |
| |
| // blockquote |
| if (cap = block.blockquote.exec(src)) { |
| src = src.substring(cap[0].length); |
| tokens.push({ |
| type: 'blockquote_start' |
| }); |
| |
| cap = cap[0].replace(/^ *> ?/gm, ''); |
| |
| // Pass `top` to keep the current |
| // "toplevel" state. This is exactly |
| // how markdown.pl works. |
| block.token(cap, tokens, top); |
| |
| tokens.push({ |
| type: 'blockquote_end' |
| }); |
| continue; |
| } |
| |
| // list |
| if (cap = block.list.exec(src)) { |
| src = src.substring(cap[0].length); |
| |
| tokens.push({ |
| type: 'list_start', |
| ordered: isFinite(cap[2]) |
| }); |
| |
| // Get each top-level item. |
| cap = cap[0].match( |
| /^( *)([*+-]|\d+\.)[^\n]*(?:\n(?!\1(?:[*+-]|\d+\.))[^\n]*)*/gm |
| ); |
| |
| next = false; |
| l = cap.length; |
| i = 0; |
| |
| for (; i < l; i++) { |
| item = cap[i]; |
| |
| // Remove the list item's bullet |
| // so it is seen as the next token. |
| space = item.length; |
| item = item.replace(/^ *([*+-]|\d+\.) */, ''); |
| |
| // Outdent whatever the |
| // list item contains. Hacky. |
| if (~item.indexOf('\n ')) { |
| space -= item.length; |
| item = item.replace(new RegExp('^ {1,' + space + '}', 'gm'), ''); |
| } |
| |
| // Determine whether item is loose or not. |
| // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ |
| // for discount behavior. |
| loose = next || /\n\n(?!\s*$)/.test(item); |
| if (i !== l - 1) { |
| next = item[item.length-1] === '\n'; |
| if (!loose) loose = next; |
| } |
| |
| tokens.push({ |
| type: loose |
| ? 'loose_item_start' |
| : 'list_item_start' |
| }); |
| |
| // Recurse. |
| block.token(item, tokens); |
| |
| tokens.push({ |
| type: 'list_item_end' |
| }); |
| } |
| |
| tokens.push({ |
| type: 'list_end' |
| }); |
| |
| continue; |
| } |
| |
| // html |
| if (cap = block.html.exec(src)) { |
| src = src.substring(cap[0].length); |
| tokens.push({ |
| type: 'html', |
| text: cap[0] |
| }); |
| continue; |
| } |
| |
| // def |
| if (top && (cap = block.def.exec(src))) { |
| src = src.substring(cap[0].length); |
| tokens.links[cap[1].toLowerCase()] = { |
| href: cap[2], |
| title: cap[3] |
| }; |
| continue; |
| } |
| |
| // top-level paragraph |
| if (top && (cap = block.paragraph.exec(src))) { |
| src = src.substring(cap[0].length); |
| tokens.push({ |
| type: 'paragraph', |
| text: cap[0] |
| }); |
| continue; |
| } |
| |
| // text |
| if (cap = block.text.exec(src)) { |
| // Top-level should never reach here. |
| src = src.substring(cap[0].length); |
| tokens.push({ |
| type: 'text', |
| text: cap[0] |
| }); |
| continue; |
| } |
| } |
| |
| return tokens; |
| }; |
| |
| /** |
| * Inline Processing |
| */ |
| |
| var inline = { |
| escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, |
| autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, |
| gfm_autolink: /^(\w+:\/\/[^\s]+[^.,:;"')\]\s])/, |
| tag: /^<!--[^\0]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, |
| link: /^!?\[((?:\[[^\]]*\]|[^\[\]]|\[|\](?=[^[\]]*\]))*)\]\(([^\)]*)\)/, |
| reflink: /^!?\[((?:\[[^\]]*\]|[^\[\]]|\[|\](?=[^[\]]*\]))*)\]\s*\[([^\]]*)\]/, |
| nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, |
| strong: /^__([^\0]+?)__(?!_)|^\*\*([^\0]+?)\*\*(?!\*)/, |
| em: /^\b_([^\0]+?)_\b|^\*((?:\*\*|[^\0])+?)\*(?!\*)/, |
| code: /^(`+)([^\0]*?[^`])\1(?!`)/, |
| br: /^ {2,}\n(?!\s*$)/, |
| text: /^[^\0]+?(?=[\\<!\[_*`]|\w+:\/\/| {2,}\n|$)/ |
| }; |
| |
| /** |
| * Inline Lexer |
| */ |
| |
| inline.lexer = function(src) { |
| var out = '' |
| , links = tokens.links |
| , link |
| , text |
| , href |
| , cap; |
| |
| while (src) { |
| // escape |
| if (cap = inline.escape.exec(src)) { |
| src = src.substring(cap[0].length); |
| out += cap[1]; |
| continue; |
| } |
| |
| // autolink |
| if (cap = inline.autolink.exec(src)) { |
| src = src.substring(cap[0].length); |
| if (cap[2] === '@') { |
| text = cap[1][6] === ':' |
| ? mangle(cap[1].substring(7)) |
| : mangle(cap[1]); |
| href = mangle('mailto:') + text; |
| } else { |
| text = escape(cap[1]); |
| href = text; |
| } |
| out += '<a href="' |
| + href |
| + '">' |
| + text |
| + '</a>'; |
| continue; |
| } |
| |
| // gfm_autolink |
| if (cap = inline.gfm_autolink.exec(src)) { |
| src = src.substring(cap[0].length); |
| text = escape(cap[1]); |
| href = text; |
| out += '<a href="' |
| + href |
| + '">' |
| + text |
| + '</a>'; |
| continue; |
| } |
| |
| // tag |
| if (cap = inline.tag.exec(src)) { |
| src = src.substring(cap[0].length); |
| out += cap[0]; |
| continue; |
| } |
| |
| // link |
| if (cap = inline.link.exec(src)) { |
| src = src.substring(cap[0].length); |
| text = /^\s*<?([^\s]*?)>?(?:\s+"([^\n]+)")?\s*$/.exec(cap[2]); |
| if (!text) { |
| out += cap[0][0]; |
| src = cap[0].substring(1) + src; |
| continue; |
| } |
| out += outputLink(cap, { |
| href: text[1], |
| title: text[2] |
| }); |
| continue; |
| } |
| |
| // reflink, nolink |
| if ((cap = inline.reflink.exec(src)) |
| || (cap = inline.nolink.exec(src))) { |
| src = src.substring(cap[0].length); |
| link = (cap[2] || cap[1]).replace(/\s+/g, ' '); |
| link = links[link.toLowerCase()]; |
| if (!link || !link.href) { |
| out += cap[0][0]; |
| src = cap[0].substring(1) + src; |
| continue; |
| } |
| out += outputLink(cap, link); |
| continue; |
| } |
| |
| // strong |
| if (cap = inline.strong.exec(src)) { |
| src = src.substring(cap[0].length); |
| out += '<strong>' |
| + inline.lexer(cap[2] || cap[1]) |
| + '</strong>'; |
| continue; |
| } |
| |
| // em |
| if (cap = inline.em.exec(src)) { |
| src = src.substring(cap[0].length); |
| out += '<em>' |
| + inline.lexer(cap[2] || cap[1]) |
| + '</em>'; |
| continue; |
| } |
| |
| // code |
| if (cap = inline.code.exec(src)) { |
| src = src.substring(cap[0].length); |
| out += '<code>' |
| + escape(cap[2], true) |
| + '</code>'; |
| continue; |
| } |
| |
| // br |
| if (cap = inline.br.exec(src)) { |
| src = src.substring(cap[0].length); |
| out += '<br>'; |
| continue; |
| } |
| |
| // text |
| if (cap = inline.text.exec(src)) { |
| src = src.substring(cap[0].length); |
| out += escape(cap[0]); |
| continue; |
| } |
| } |
| |
| return out; |
| }; |
| |
| var outputLink = function(cap, link) { |
| if (cap[0][0] !== '!') { |
| return '<a href="' |
| + escape(link.href) |
| + '"' |
| + (link.title |
| ? ' title="' |
| + escape(link.title) |
| + '"' |
| : '') |
| + '>' |
| + inline.lexer(cap[1]) |
| + '</a>'; |
| } else { |
| return '<img src="' |
| + escape(link.href) |
| + '" alt="' |
| + escape(cap[1]) |
| + '"' |
| + (link.title |
| ? ' title="' |
| + escape(link.title) |
| + '"' |
| : '') |
| + '>'; |
| } |
| }; |
| |
| /** |
| * Parsing |
| */ |
| |
| var tokens |
| , token; |
| |
| var next = function() { |
| return token = tokens.pop(); |
| }; |
| |
| var tok = function() { |
| switch (token.type) { |
| case 'space': { |
| return ''; |
| } |
| case 'hr': { |
| return '<hr>\n'; |
| } |
| case 'heading': { |
| return '<h' |
| + token.depth |
| + '>' |
| + inline.lexer(token.text) |
| + '</h' |
| + token.depth |
| + '>\n'; |
| } |
| case 'code': { |
| return '<pre><code' |
| + (token.lang |
| ? ' class="' |
| + token.lang |
| + '"' |
| : '') |
| + '>' |
| + (token.escaped |
| ? token.text |
| : escape(token.text, true)) |
| + '</code></pre>\n'; |
| } |
| case 'blockquote_start': { |
| var body = ''; |
| |
| while (next().type !== 'blockquote_end') { |
| body += tok(); |
| } |
| |
| return '<blockquote>\n' |
| + body |
| + '</blockquote>\n'; |
| } |
| case 'list_start': { |
| var type = token.ordered ? 'ol' : 'ul' |
| , body = ''; |
| |
| while (next().type !== 'list_end') { |
| body += tok(); |
| } |
| |
| return '<' |
| + type |
| + '>\n' |
| + body |
| + '</' |
| + type |
| + '>\n'; |
| } |
| case 'list_item_start': { |
| var body = ''; |
| |
| while (next().type !== 'list_item_end') { |
| body += token.type === 'text' |
| ? parseText() |
| : tok(); |
| } |
| |
| return '<li>' |
| + body |
| + '</li>\n'; |
| } |
| case 'loose_item_start': { |
| var body = ''; |
| |
| while (next().type !== 'list_item_end') { |
| body += tok(); |
| } |
| |
| return '<li>' |
| + body |
| + '</li>\n'; |
| } |
| case 'html': { |
| return inline.lexer(token.text); |
| } |
| case 'paragraph': { |
| return '<p>' |
| + inline.lexer(token.text) |
| + '</p>\n'; |
| } |
| case 'text': { |
| return '<p>' |
| + parseText() |
| + '</p>\n'; |
| } |
| } |
| }; |
| |
| var parseText = function() { |
| var body = token.text |
| , top; |
| |
| while ((top = tokens[tokens.length-1]) |
| && top.type === 'text') { |
| body += '\n' + next().text; |
| } |
| |
| return inline.lexer(body); |
| }; |
| |
| var parse = function(src) { |
| tokens = src.reverse(); |
| |
| var out = ''; |
| while (next()) { |
| out += tok(); |
| } |
| |
| tokens = null; |
| token = null; |
| |
| return out; |
| }; |
| |
| /** |
| * Helpers |
| */ |
| |
| var escape = function(html, encode) { |
| return html |
| .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') |
| .replace(/</g, '<') |
| .replace(/>/g, '>') |
| .replace(/"/g, '"') |
| .replace(/'/g, '''); |
| }; |
| |
| var mangle = function(text) { |
| var out = '' |
| , l = text.length |
| , i = 0 |
| , ch; |
| |
| for (; i < l; i++) { |
| ch = text.charCodeAt(i); |
| if (Math.random() > 0.5) { |
| ch = 'x' + ch.toString(16); |
| } |
| out += '&#' + ch + ';'; |
| } |
| |
| return out; |
| }; |
| |
| function tag() { |
| var tag = '(?!(?:' |
| + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' |
| + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' |
| + '|span|br|wbr|ins|del|img)\\b)\\w+'; |
| |
| return tag; |
| } |
| |
| /** |
| * Expose |
| */ |
| |
| var marked = function(src) { |
| return parse(block.lexer(src)); |
| }; |
| |
| marked.parser = parse; |
| marked.lexer = block.lexer; |
| |
| marked.parse = marked; |
| |
| if (typeof module !== 'undefined') { |
| module.exports = marked; |
| } else { |
| this.marked = marked; |
| } |
| |
| }).call(this); |