diff --git a/lib/marked.js b/lib/marked.js index d5522afce2..4e9a35075d 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -21,7 +21,8 @@ var block = { blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/, list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/, - def: /^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, + def: /^ *\[([^^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, + footnote: noop, table: noop, paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, text: /^[^\n]+/ @@ -78,7 +79,6 @@ block.gfm = merge({}, block.normal, { fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/, paragraph: /^/ }); - block.gfm.paragraph = replace(block.paragraph) ('(?!', '(?!' + block.gfm.fences.source.replace('\\1', '\\2') + '|' @@ -86,13 +86,32 @@ block.gfm.paragraph = replace(block.paragraph) (); /** - * GFM + Tables Block Grammar + * Tables Block Grammar */ -block.tables = merge({}, block.gfm, { +block.tables = { nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ -}); +}; + +/** + * Footnotes Block Grammar + */ +block.footnotes = { + footnote: /^\[\^([^\]]+)\]: *([^\n]*(?:\n+|$)(?: {1,}[^\n]*(?:\n+|$))*)/ +}; +block.footnotes.normal = { + footnote: block.footnotes.footnote +}; +block.footnotes.normal.paragraph = replace(block.paragraph)( + '))+)', '|' + block.footnotes.footnote.source + '))+)' +)(); +block.footnotes.gfm = { + footnote: block.footnotes.footnote +}; +block.footnotes.gfm.paragraph = replace(block.gfm.paragraph)( + '))+)', '|' + block.footnotes.footnote.source + '))+)' +)(); /** * Block Lexer @@ -101,15 +120,21 @@ block.tables = merge({}, block.gfm, { function Lexer(options) { this.tokens = []; this.tokens.links = {}; + this.tokens.footnotes = []; this.options = options || marked.defaults; this.rules = block.normal; if (this.options.gfm) { - if (this.options.tables) { - this.rules = block.tables; - } else { - this.rules = block.gfm; + this.rules = block.gfm; + if (this.options.footnotes) { + this.rules = merge({}, this.rules, block.footnotes.gfm); } + } else if (this.options.footnotes) { + this.rules = merge({}, this.rules, block.footnotes.normal); + } + + if (this.options.tables) { + this.rules = merge({}, this.rules, block.tables); } } @@ -149,6 +174,7 @@ Lexer.prototype.lex = function(src) { Lexer.prototype.token = function(src, top, bq) { var src = src.replace(/^ +$/gm, '') , next + , key , loose , cap , bull @@ -375,6 +401,15 @@ Lexer.prototype.token = function(src, top, bq) { continue; } + // footnote + if (top && (cap = this.rules.footnote.exec(src))) { + src = src.substring(cap[0].length); + key = cap[1].slice(1).toLowerCase(); + this.tokens.footnotes.push({key: key, text: cap[2]}); + this.tokens.footnotes[key] = {text: cap[2], count: this.tokens.footnotes.length}; + continue; + } + // table (gfm) if (top && (cap = this.rules.table.exec(src))) { src = src.substring(cap[0].length); @@ -458,10 +493,11 @@ var inline = { code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, br: /^ {2,}\n(?!\s*$)/, del: noop, + footnote: noop, text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/; inline.link = replace(inline.link) @@ -483,10 +519,10 @@ inline.normal = merge({}, inline); * Pedantic Inline Grammar */ -inline.pedantic = merge({}, inline.normal, { +inline.pedantic = { strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ -}); +}; /** * GFM Inline Grammar @@ -511,13 +547,22 @@ inline.breaks = merge({}, inline.gfm, { text: replace(inline.gfm.text)('{2,}', '*')() }); +/** + * Footnote Inline Grammar + */ + +inline.footnote = { + footnote: /^\[\^([^\]]+)\]/ +}; + /** * Inline Lexer & Compiler */ -function InlineLexer(links, options) { +function InlineLexer(links, footnotes, options) { this.options = options || marked.defaults; this.links = links; + this.footnotes = footnotes || {}; this.rules = inline.normal; this.renderer = this.options.renderer || new Renderer; this.renderer.options = this.options; @@ -533,8 +578,14 @@ function InlineLexer(links, options) { } else { this.rules = inline.gfm; } - } else if (this.options.pedantic) { - this.rules = inline.pedantic; + } + + if (this.options.footnotes) { + this.rules = merge({}, this.rules, inline.footnote); + } + + if (this.options.pedantic) { + this.rules = merge({}, this.rules, inline.pedantic); } } @@ -548,8 +599,8 @@ InlineLexer.rules = inline; * Static Lexing/Compiling Method */ -InlineLexer.output = function(src, links, options) { - var inline = new InlineLexer(links, options); +InlineLexer.output = function(src, links, footnotes, options) { + var inline = new InlineLexer(links, footnotes, options); return inline.output(src); }; @@ -560,6 +611,8 @@ InlineLexer.output = function(src, links, options) { InlineLexer.prototype.output = function(src) { var out = '' , link + , key + , ret , text , href , cap; @@ -611,6 +664,16 @@ InlineLexer.prototype.output = function(src) { continue; } + // footnote + if (cap = this.rules.footnote.exec(src)) { + key = cap[1].toLowerCase(); + if (ret = this.footnotes[key]) { + src = src.substring(cap[0].length); + out += this.renderer.footnoteref(key, ret.count); + continue; + } + } + // link if (cap = this.rules.link.exec(src)) { src = src.substring(cap[0].length); @@ -889,6 +952,24 @@ Renderer.prototype.image = function(href, title, text) { return out; }; +Renderer.prototype.footnoteref = function(key, count) { + var out = ''; + out += '' + count + ''; + return out; +}; + +Renderer.prototype.footnotes = function(notes) { + var out = '
    '; + for (var i = 0; i < notes.length; i++) { + out += '
  1. '; + out += notes[i].text; + out += '' + out += '
  2. '; + } + out += '
'; + return out; +}; + /** * Parsing & Compiling */ @@ -916,7 +997,7 @@ Parser.parse = function(src, options, renderer) { */ Parser.prototype.parse = function(src) { - this.inline = new InlineLexer(src.links, this.options, this.renderer); + this.inline = new InlineLexer(src.links, src.footnotes, this.options); this.tokens = src.reverse(); var out = ''; @@ -924,6 +1005,10 @@ Parser.prototype.parse = function(src) { out += this.tok(); } + if (src.footnotes.length) { + out += this.renderer.footnotes(src.footnotes); + } + return out; }; @@ -1223,6 +1308,7 @@ marked.setOptions = function(opt) { marked.defaults = { gfm: true, tables: true, + footnotes: false, breaks: false, pedantic: false, sanitize: false,