/** * marked - A markdown parser * Copyright (c) 2011, Christopher Jeffrey. (MIT Licensed) */ ;(function() { /** * Block-Level Grammar */ var block = { newline: /^\n+/, //block: /^ {4,}[^\n]*(?:\n {4,}[^\n]*)*/, // greedier block block: /^ {4,}[^\n]*(?:\n {4,}[^\n]*|\n)*/, hr: /^( *[\-*_]){3,}/, heading: /^ *(#{1,6}) *([^\n#]*) *#*/, lheading: /^([^\n]+)\n *(=|-){3,}/, blockquote: /^ *>[^\n]*(?:\n *>[^\n]*)*/, //list: /^(?:( *)(\*|\+|-|\d+\.)[^\n]+(?:\n[^\n]+)*(?:\n{1,2}|$)){2,}/, //list: /^(?:( *)(\*|\+|-|\d+\.)[^\n]+(?:\n[^\n]*)*){2,}(?:\n\n\1?(?!\*|\+|-|\d\.)|$)/, //list: /^(?:( *)(\*|\+|-|\d+\.)[^\n]+(?:\n[^\n]+|\n\n)*){2,}/, //list: /^( *)(\*|\+|-|\d+\.)[\s\S]+?(\1\2[\s\S]+?(?=\1\2))*/, // simple //list: /^(?:( *)(\*|\+|-|\d+\.)[^\n]*(?:\n\1 [^\n]+)*){2,}/, // very good, does it all //list: /^(?:( *)(\*|\+|-|\d+\.)[^\n]*(?:\n(?:\1 |)[^\n]+)*){2,}/, // better, faster //list: /^(?:( *)(\*|\+|-|\d+\.)[^\n]*(?:\n[^\n]+)*){2,}/, // fastest //list: /^( *)(\*|\+|-|\d+\.)[^\n]*(?:\n[^\n]+)*/, // fastest with loose list support // list: /^( *)(\*|\+|-|\d+\.)[^\n]*(?:\n[^\n]+|\n{2}\1\2[^\n]*)*/, // fix ordered lists: list: /^( *)([*+-]|\d+\.)[^\n]*(?:\n[^\n]+|\n{2}\1(?:\2|\d+\.)[^\n]*)*/, // the first list item //list: /^( *)(\*|\+|-|\d+\.)[^\n]*(?:\n(?!\1\2)[^\n]+)*/, // all list items //list: /^(?:( *)(\*|\+|-|\d+\.)[^\n]*(?:\n(?!\1\2)[^\n]+)*){2,}/, // need the question mark because a trailing list item might not have a newline after it //list: /^(?:( *)(\*|\+|-|\d+\.)[^\n]*\n(?:(?!\1\2)[^\n]+\n?)*){2,}/, // instead of question mark... //list: /^(?:( *)(\*|\+|-|\d+\.)[^\n]*\n(?:(?!\1\2)[^\n]+(?:\n|$))*){2,}/, //list: /^(?:( *)(\*|\+|-|\d+\.)[^\n]*\n(?:(?!\1\2)[^\n]+(?:\n|$))*){2,}/, html: /^<([^\/\s>]+)[^\n>]*>[^\n]*(?:\n[^\n]+)*\n?<\/\1>/, text: /^[^\n]+/ }; block.keys = [ 'newline', 'block', 'heading', 'lheading', 'hr', 'blockquote', 'list', 'html', 'text' ]; /** * Lexer */ block.lexer = function(str) { var tokens = [] , links = {}; // normalize whitespace str = str.replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .replace(/\t/g, ' '); // experimental // str = str.replace(/^ +$/gm, ''); // grab link definitons str = str.replace( /^ {0,3}\[([^\]]+)\]: *([^ ]+)(?: +"([^"]+)")? *(?:\n|$)/gm, function(__, id, href, title) { links[id] = { href: href, title: title }; return ''; } ); tokens.links = links; return block.token(str, tokens); }; block.token = function(str, tokens) { var rules = block , keys = block.keys , len = keys.length , key , cap , loose; var scan = function() { if (!str) return; for (var i = 0; i < len; i++) { key = keys[i]; if (cap = rules[key].exec(str)) { str = str.substring(cap[0].length); return true; } } }; while (scan()) { switch (key) { case 'newline': if (cap[0].length > 1) { tokens.push({ type: 'space' }); } break; case 'hr': tokens.push({ type: 'hr' }); break; case 'lheading': tokens.push({ type: 'heading', depth: cap[2] === '=' ? 1 : 2, text: cap[1] }); break; case 'heading': tokens.push({ type: 'heading', depth: cap[1].length, text: cap[2] }); break; case 'block': cap = cap[0].replace(/^ {4}/gm, ''); tokens.push({ type: 'block', text: cap }); break; case 'list': tokens.push({ type: 'list_start', ordered: isFinite(cap[2]) }); loose = /\n *\n *(?:\*|\+|-|\d+\.)/.test(cap[0]); //loose = false; // /\n *\n(?! *$)/.test(cap[0]); //loose = '\n *\n' + cap[1] // + '(?:\\*|\\+|-|\\d+\\.)'; //loose = new RegExp(loose).test(cap[0]); // get each top-level // item in the list //console.log('----'); //console.log(cap[0]); //console.log('----'); cap = cap[0].match( // /^( *)(\*|\+|-|\d+\.)[^\n]+(?:\n\1+(?!\*|\+|-|\d\.)[^\n]*)*/gm // /^( *)(\*|\+|-|\d+\.)[^\n]+(?:\n\1+(?!\*|\+|-|\d\.)[^\n]*|\n)*/gm // /^( *)(\*|\+|-|\d+\.)[^\n]*(?:\n(?!\1\2)[^\n]*)*/gm // fix ordered lists: /^( *)([*+-]|\d+\.)[^\n]*(?:\n(?!\1(?:\2|\d+\.))[^\n]*)*/gm ); each(cap, function(item) { // remove the list items sigil // so its seen as the next token item = item.replace(/^ *(\*|\+|-|\d+\.) */, ''); // outdent whatever the // list item contains, hacky var 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' }); block.token(item, tokens); tokens.push({ type: 'list_item_end' }); }); tokens.push({ type: 'list_end' }); break; case 'html': case 'text': tokens.push({ type: key, text: cap[0] }); break; case 'blockquote': tokens.push({ type: 'blockquote_start' }); cap = cap[0].replace(/^ *>/gm, ''); block.token(cap, tokens); tokens.push({ type: 'blockquote_end' }); break; } } return tokens; }; /** * Inline Processing */ var inline = { escape: /^\\([\\`*{}\[\]()#+\-.!])/, autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, tag: /^<[^\n>]+>/, link: /^!?\[([^\]]+)\] *\(([^\)]*)\)/, reflink: /^!?\[([^\]]+)\] *\[([^\]]+)\]/, strong: /^__([\s\S]+?)__|^\*\*([\s\S]+?)\*\*/, em: /^_([^_]+)_|^\*([^*]+)\*/, code: /^`([^`]+)`|^``([\s\S]+?)``/ }; inline.keys = [ 'escape', 'autolink', 'tag', 'link', 'reflink', 'strong', 'em', 'code' ]; // hacky, but performant inline.text = (function(rules) { var keys = rules.keys , i = 0 , l = keys.length , body = []; for (; i < l; i++) { body.push(rules[keys[i]].source .replace(/(^|[^\[])\^/g, '$1')); } keys.push('text'); return new RegExp( '^([\\s\\S]+?)(?=' + body.join('|') + '|$)' ); })(inline); /** * Inline Lexer */ inline.lexer = function(str) { var out = '' , links = tokens.links , link , text , href; var rules = inline , keys = inline.keys , len = keys.length , key , cap; var scan = function() { if (!str) return; for (var i = 0; i < len; i++) { key = keys[i]; if (cap = rules[key].exec(str)) { str = str.substring(cap[0].length); return true; } } }; while (scan()) { switch (key) { case 'escape': out += cap[1]; break; case 'tag': out += cap[0]; break; case 'link': case 'reflink': if (cap[0][0] !== '!') { if (key === 'reflink') { link = links[cap[2]]; if (!link) throw new Error('Undefined Reference: ' + cap[2]); } else { text = /^\s*([^\s]*)(?:\s+"([^"]+)")?\s*$/.exec(cap[2]); link = { href: text[1], title: text[2] }; } out += '' + inline.lexer(cap[1]) + ''; } else { if (key === 'reflink') { link = links[cap[2]]; if (!link) throw new Error('Undefined Reference: ' + cap[2]); } else { text = /^\s*([^\s]*)(?:\s+"([^"]+)")?\s*$/.exec(cap[2]); link = { href: text[1], title: text[2] }; } out += '' 
            + escape(cap[1])
            + ''; } break; case 'autolink': if (cap[2] === '@') { text = mangle(cap[1]); href = mangle('mailto:') + text; } else { text = escape(cap[1]); href = text; } out += '' + text + ''; break; case 'strong': out += '' + inline.lexer(cap[2] || cap[1]) + ''; break; case 'em': out += '' + inline.lexer(cap[2] || cap[1]) + ''; break; case 'code': out += '' + escape(cap[2] || cap[1]) + ''; break; case 'text': out += escape(cap[1]); break; default: break; } } return out; }; /** * 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 'block': return '
' 
        + escape(token.text)
        + '
'; case 'blockquote_start': var body = []; while (next().type !== 'blockquote_end') { body.push(tok()); } return '
' + body.join('') + '
'; case 'list_start': var body = [] , type = token.ordered ? 'ol' : 'ul'; 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' ? inline.lexer(token.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 'text': var body = [ token.text ] , top; while ((top = tokens[tokens.length-1]) && top.type === 'text') { body.push(next().text); } return '

    ' + inline.lexer(body.join(' ')) + '

    '; } }; var parse = function(src) { tokens = src.reverse(); var out = []; while (next()) { out.push(tok()); } tokens = null; token = null; return out.join(' '); }; /** * Helpers */ var escape = function(html) { return html .replace(/&/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; }; var each = function(obj, func) { var i = 0, l = obj.length; for (; i < l; i++) func(obj[i]); }; var unquote = function(str) { if (!str) return str; /*var ch = str[0]; return (ch === '"' || ch === '\'') ? str.slice(1, -1) : str;*/ return str.replace(/^['"\s]+|['"\s]+$/g, ''); }; /** * Expose */ var marked = function(str) { return parse(block.lexer(str)); }; marked.parser = parse; marked.lexer = block.lexer; if (typeof module !== 'undefined') { module.exports = marked; } else { this.marked = marked; } }).call(this);