diff --git a/lib/marked.js b/lib/marked.js index d5522afc..2262e293 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: /^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, + def: /^ *\[([^^\]]+)\]: *([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, + footnote: noop, table: noop, paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, text: /^[^\n]+/ @@ -86,13 +87,37 @@ 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 +126,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 +180,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 +407,21 @@ 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].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); @@ -450,6 +497,7 @@ var inline = { autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, url: noop, tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, + footnote: noop, link: /^!?\[(inside)\]\(href\)/, reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, @@ -461,7 +509,7 @@ var inline = { text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/; inline.link = replace(inline.link) @@ -483,10 +531,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 +559,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 +590,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 +611,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 +623,8 @@ InlineLexer.output = function(src, links, options) { InlineLexer.prototype.output = function(src) { var out = '' , link + , key + , ret , text , href , cap; @@ -611,6 +676,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 +964,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 = '