/** * 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}) *([^\0]+?) *#* *\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*$)/, 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; // The real markdown checks for block elements. // It's better to check for non-inline elements. // Unknown elements will then be treated as block elements. var closed = '<(?!(?:' + '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+)[^\\0]+?'; html = html .replace('comment', //.source) .replace('closed', closed) .replace('closing', /<\w+(?!:\/|@)\b(?:"[^"]*"|'[^']*'|[^>])*>/.source); return new RegExp(html); })(); block.paragraph = (function() { var paragraph = block.paragraph.source , body = []; (function push(rule) { rule = rule.source || block[rule].source; body.push(rule.replace(/(^|[^\[])\^/g, '$1')); return push; }) ('gfm_code') ('hr') ('heading') ('lheading') ('blockquote') (new RegExp('<' + '(?:article|aside|audio|blockquote|canvas|caption|col|colgroup|dialog|div' + '|d[ltd]|embed|fieldset|figure|figcaption|footer|form|h[1-6r]|header' + '|hgroup|input|label|legend|li|nav|noscript|object|[ou]l|optgroup|option' + '|p|param|pre|script|section|select|source|table|t(?:body|foot|head)' + '|t[dhr]|textarea|video)\\b')); return new RegExp(paragraph.replace('body', body.join('|'))); })(); /** * Block Lexer */ block.lexer = function(str) { var tokens = [] , links = {}; str = str.replace(/\r\n|\r/g, '\n') .replace(/\t/g, ' '); str = str.replace( /^ {0,3}\[([^\]]+)\]: *([^ ]+)(?: +["(]([^\n]+)[")])? *$/gm, function(__, id, href, title) { links[id.toLowerCase()] = { href: href, title: title }; return ''; } ); tokens.links = links; return block.token(str, tokens, true); }; block.token = function(str, tokens, top) { var str = str.replace(/^ +$/gm, '') , loose , cap , item , space , i , l; while (str) { // newline if (cap = block.newline.exec(str)) { str = str.substring(cap[0].length); if (cap[0].length > 1) { tokens.push({ type: 'space' }); } } // code if (cap = block.code.exec(str)) { str = str.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(str)) { str = str.substring(cap[0].length); tokens.push({ type: 'code', lang: cap[1], text: cap[2] }); continue; } // heading if (cap = block.heading.exec(str)) { str = str.substring(cap[0].length); tokens.push({ type: 'heading', depth: cap[1].length, text: cap[2] }); continue; } // lheading if (cap = block.lheading.exec(str)) { str = str.substring(cap[0].length); tokens.push({ type: 'heading', depth: cap[2] === '=' ? 1 : 2, text: cap[1] }); continue; } // hr if (cap = block.hr.exec(str)) { str = str.substring(cap[0].length); tokens.push({ type: 'hr' }); continue; } // blockquote if (cap = block.blockquote.exec(str)) { str = str.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(str)) { str = str.substring(cap[0].length); tokens.push({ type: 'list_start', ordered: isFinite(cap[2]) }); loose = /\n *\n *(?:[*+-]|\d+\.)/.test(cap[0]); // Get each top-level item. cap = cap[0].match( /^( *)([*+-]|\d+\.)[^\n]*(?:\n(?!\1(?:[*+-]|\d+\.))[^\n]*)*/gm ); i = 0; l = cap.length; for (; i < l; i++) { // Remove the list item's bullet // so it is seen as the next token. item = cap[i].replace(/^ *([*+-]|\d+\.) */, ''); // Outdent whatever the // list item contains. Hacky. space = /\n( +)/.exec(item); if (space) { space = new RegExp('^' + space[1], 'gm'); item = item.replace(space, ''); } 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(str)) { str = str.substring(cap[0].length); tokens.push({ type: 'html', text: cap[0] }); continue; } // top-level paragraph if (top && (cap = block.paragraph.exec(str))) { str = str.substring(cap[0].length); tokens.push({ type: 'paragraph', text: cap[0] }); continue; } // text if (cap = block.text.exec(str)) { // Top-level should never reach here. str = str.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: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^>])*>/, link: /^!?\[((?:\[[^\]]*\]|[^\[\]]|\[|\](?=[^[\]]*\]))*)\]\(([^\)]*)\)/, reflink: /^!?\[((?:\[[^\]]*\]|[^\[\]]|\[|\](?=[^[\]]*\]))*)\]\s*\[([^\]]*)\]/, nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, strong: /^__(?=\S)([^\0]*?\S)__(?!_)|^\*\*(?=\S)([^\0]*?\S)\*\*(?!\*)/, em: /^\b_(?=\S)([^\0]*?\S)_\b|^\*(?=\S)((?:\*\*|[^\0])*?)\*(?!\*)/, code: /^(`+)([^\0]*?[^`])\1(?!`)/, br: /^ {2,}\n(?!\s*$)/, text: /^[^\0]+?(?=body|$)/ }; inline.text = (function() { var text = inline.text.source , body = []; (function push(rule) { rule = inline[rule].source; body.push(rule.replace(/(^|[^\[])\^/g, '$1')); return push; }) ('escape') ('gfm_autolink') ('tag') ('nolink') ('strong') ('em') ('code') ('br'); return new RegExp(text.replace('body', body.join('|'))); })(); /** * Inline Lexer */ inline.lexer = function(str) { var out = '' , links = tokens.links , link , text , href , cap; while (str) { // escape if (cap = inline.escape.exec(str)) { str = str.substring(cap[0].length); out += cap[1]; continue; } // autolink if (cap = inline.autolink.exec(str)) { str = str.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 += '' + text + ''; continue; } // gfm_autolink if (cap = inline.gfm_autolink.exec(str)) { str = str.substring(cap[0].length); text = escape(cap[1]); href = text; out += '' + text + ''; continue; } // tag if (cap = inline.tag.exec(str)) { str = str.substring(cap[0].length); out += cap[0]; continue; } // link if (cap = inline.link.exec(str)) { str = str.substring(cap[0].length); text = /^\s*?(?:\s+"([^\n]+)")?\s*$/.exec(cap[2]); if (!text) { out += cap[0][0]; str = cap[0].substring(1) + str; continue; } link = { href: text[1], title: text[2] }; out += mlink(cap, link); continue; } // reflink, nolink if ((cap = inline.reflink.exec(str)) || (cap = inline.nolink.exec(str))) { str = str.substring(cap[0].length); link = (cap[2] || cap[1]).replace(/\s+/g, ' '); link = links[link.toLowerCase()]; if (!link || !link.href) { out += cap[0][0]; str = cap[0].substring(1) + str; continue; } out += mlink(cap, link); continue; } // strong if (cap = inline.strong.exec(str)) { str = str.substring(cap[0].length); out += '' + inline.lexer(cap[2] || cap[1]) + ''; continue; } // em if (cap = inline.em.exec(str)) { str = str.substring(cap[0].length); out += '' + inline.lexer(cap[2] || cap[1]) + ''; continue; } // code if (cap = inline.code.exec(str)) { str = str.substring(cap[0].length); out += '' + escape(cap[2] || cap[1], true) + ''; continue; } // br if (cap = inline.br.exec(str)) { str = str.substring(cap[0].length); out += '
'; continue; } // text if (cap = inline.text.exec(str)) { str = str.substring(cap[0].length); out += escape(cap[0]); continue; } } return out; }; var mlink = function(cap, link) { if (cap[0][0] !== '!') { return '' + inline.lexer(cap[1]) + ''; } else { return ''
      + escape(cap[1])
      + ''; } }; /** * Parsing */ var tokens , token; var next = function() { return token = tokens.pop(); }; var tok = function() { switch (token.type) { case 'space': { return ''; } case 'hr': { return '
'; } case 'heading': { return '' + inline.lexer(token.text) + ''; } case 'code': { return '
'
        + (token.escaped
        ? token.text
        : escape(token.text, true))
        + '
'; } case 'blockquote_start': { var body = []; while (next().type !== 'blockquote_end') { body.push(tok()); } return '
' + body.join('') + '
'; } case 'list_start': { var type = token.ordered ? 'ol' : 'ul' , body = []; while (next().type !== 'list_end') { body.push(tok()); } return '<' + type + '>' + body.join('') + ''; } case 'list_item_start': { var body = []; while (next().type !== 'list_item_end') { body.push(token.type === 'text' ? text() : tok()); } return '
  • ' + body.join(' ') + '
  • '; } case 'loose_item_start': { var body = []; while (next().type !== 'list_item_end') { body.push(tok()); } return '
  • ' + body.join(' ') + '
  • '; } case 'html': { return inline.lexer(token.text); } case 'paragraph': { return '

    ' + inline.lexer(token.text) + '

    '; } case 'text': { return '

    ' + text() + '

    '; } } }; var text = function() { var body = [ token.text ] , top; while ((top = tokens[tokens.length-1]) && top.type === 'text') { body.push(next().text); } return inline.lexer(body.join('\n')); }; var parse = function(src) { tokens = src.reverse(); var out = []; while (next()) { out.push(tok()); } tokens = null; token = null; return out.join('\n'); }; /** * Helpers */ var escape = function(html, dbl) { return html .replace(!dbl ? /&(?!#?\w+;)/g : /&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }; var mangle = function(str) { var out = '' , ch , i = 0 , l = str.length; for (; i < l; i++) { ch = str.charCodeAt(i); if (Math.random() > 0.5) { ch = 'x' + ch.toString(16); } out += '&#' + ch + ';'; } return out; }; /** * Expose */ var marked = function(str) { return parse(block.lexer(str)); }; marked.parser = parse; marked.lexer = block.lexer; marked.parse = marked; if (typeof module !== 'undefined') { module.exports = marked; } else { this.marked = marked; } }).call(this);