marked/lib/marked.js

1101 lines
23 KiB
JavaScript
Raw Normal View History

2011-07-24 08:15:35 -05:00
/**
2013-01-04 23:19:36 -06:00
* marked - a markdown parser
2013-01-03 06:32:39 -06:00
* Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed)
2013-01-04 23:19:36 -06:00
* https://github.com/chjj/marked
2011-07-24 08:15:35 -05:00
*/
2011-08-13 23:19:46 -05:00
;(function() {
2011-07-24 08:15:35 -05:00
/**
* Block-Level Grammar
*/
2011-08-13 23:19:46 -05:00
var block = {
2011-08-18 18:59:42 -05:00
newline: /^\n+/,
2012-03-05 21:32:48 -06:00
code: /^( {4}[^\n]+\n*)+/,
2012-02-19 20:29:35 -06:00
fences: noop,
hr: /^( *[-*_]){3,} *(?:\n+|$)/,
2012-01-20 06:07:41 -06:00
heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
2013-01-03 03:22:48 -06:00
nptable: noop,
lheading: /^([^\n]+)\n *(=|-){3,} *\n*/,
2012-01-28 06:01:21 -06:00
blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
2012-11-23 10:54:18 +00:00
list: /^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
2012-01-06 14:41:27 -06:00
html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
2013-01-03 03:22:48 -06:00
table: noop,
paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
2012-01-06 14:41:27 -06:00
text: /^[^\n]+/
2011-07-24 08:15:35 -05:00
};
2012-04-22 17:19:08 -05:00
block.bullet = /(?:[*+-]|\d+\.)/;
block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
2012-04-21 18:03:57 -05:00
block.item = replace(block.item, 'gm')
2012-04-22 17:19:08 -05:00
(/bull/g, block.bullet)
2012-04-21 18:03:57 -05:00
();
2012-03-05 15:44:55 -06:00
block.list = replace(block.list)
2012-04-22 17:19:08 -05:00
(/bull/g, block.bullet)
2012-04-26 09:18:41 -05:00
('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)
2012-03-05 15:44:55 -06:00
();
2012-01-10 14:13:57 -06:00
2013-01-03 01:42:48 -06:00
block._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+(?!:/|@)\\b';
2012-03-05 15:44:55 -06:00
block.html = replace(block.html)
2012-11-23 10:54:18 +00:00
('comment', /<!--[\s\S]*?-->/)
('closed', /<(tag)[\s\S]+?<\/\1>/)
('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
2013-01-03 01:42:48 -06:00
(/tag/g, block._tag)
2012-03-05 15:44:55 -06:00
();
2012-09-05 13:20:06 -05:00
block.paragraph = replace(block.paragraph)
('hr', block.hr)
('heading', block.heading)
('lheading', block.lheading)
('blockquote', block.blockquote)
2013-01-03 01:42:48 -06:00
('tag', '<' + block._tag)
2012-09-05 13:20:06 -05:00
('def', block.def)
();
2013-01-04 22:54:31 -06:00
/**
* Normal Block Grammar
*/
2013-01-03 02:55:10 -06:00
block.normal = merge({}, block);
/**
* GFM Block Grammar
*/
2012-02-19 20:29:35 -06:00
2013-01-03 02:55:10 -06:00
block.gfm = merge({}, block.normal, {
fences: /^ *(`{3,}|~{3,}) *(\w+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,
2013-01-03 02:55:10 -06:00
paragraph: /^/
});
2012-02-19 20:29:35 -06:00
2012-03-05 15:44:55 -06:00
block.gfm.paragraph = replace(block.paragraph)
2013-01-03 04:14:16 -06:00
('(?!', '(?!' + block.gfm.fences.source.replace('\\1', '\\2') + '|')
2012-03-05 15:44:55 -06:00
();
2012-02-19 20:29:35 -06:00
2013-01-03 02:55:10 -06:00
/**
* GFM + Tables Block Grammar
*/
block.tables = merge({}, block.gfm, {
2013-01-04 14:14:02 -06:00
nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
2013-01-03 02:55:10 -06:00
});
2011-07-24 08:15:35 -05:00
/**
2011-10-22 23:39:04 -05:00
* Block Lexer
2011-07-24 08:15:35 -05:00
*/
function Lexer(options) {
this.tokens = [];
this.tokens.links = {};
2013-01-03 00:45:07 -06:00
this.options = options || marked.defaults;
2013-01-03 02:55:10 -06:00
this.rules = block.normal;
2013-01-03 00:45:07 -06:00
if (this.options.gfm) {
2013-01-03 02:55:10 -06:00
if (this.options.tables) {
this.rules = block.tables;
} else {
this.rules = block.gfm;
}
}
}
2011-08-13 17:06:08 -05:00
2013-01-05 06:03:46 -06:00
/**
* Expose Block Rules
*/
2013-01-03 01:40:10 -06:00
Lexer.rules = block;
2013-01-05 06:03:46 -06:00
/**
* Static Lex Method
*/
Lexer.lex = function(src, options) {
var lexer = new Lexer(options);
return lexer.lex(src);
};
2013-01-05 06:03:46 -06:00
/**
* Preprocessing
*/
Lexer.prototype.lex = function(src) {
2013-01-03 01:06:58 -06:00
src = src
.replace(/\r\n|\r/g, '\n')
.replace(/\t/g, ' ')
.replace(/\u00a0/g, ' ')
.replace(/\u2424/g, '\n');
2011-07-24 08:15:35 -05:00
2013-01-03 01:06:58 -06:00
return this.token(src, true);
2011-08-13 17:06:08 -05:00
};
2011-08-13 16:53:15 -05:00
2013-01-05 06:03:46 -06:00
/**
* Lexing
*/
Lexer.prototype.token = function(src, top) {
var src = src.replace(/^ +$/gm, '')
2012-01-28 22:33:42 -06:00
, next
2011-10-22 06:09:28 -05:00
, loose
2011-10-22 07:49:11 -05:00
, cap
, bull
, b
2011-10-22 07:49:11 -05:00
, item
, space
, i
, l;
2011-10-22 06:09:28 -05:00
while (src) {
2011-10-22 23:39:04 -05:00
// newline
2013-01-03 02:55:10 -06:00
if (cap = this.rules.newline.exec(src)) {
src = src.substring(cap[0].length);
2011-10-22 06:09:28 -05:00
if (cap[0].length > 1) {
this.tokens.push({
2011-10-22 06:09:28 -05:00
type: 'space'
});
2011-08-14 01:02:14 -05:00
}
2011-10-22 06:09:28 -05:00
}
2011-10-22 23:39:04 -05:00
// code
2013-01-03 02:55:10 -06:00
if (cap = this.rules.code.exec(src)) {
src = src.substring(cap[0].length);
2011-10-22 06:09:28 -05:00
cap = cap[0].replace(/^ {4}/gm, '');
this.tokens.push({
2011-10-22 08:05:31 -05:00
type: 'code',
text: !this.options.pedantic
2012-02-19 03:00:03 -06:00
? cap.replace(/\n+$/, '')
: cap
2011-10-22 06:09:28 -05:00
});
continue;
}
2012-02-19 20:29:35 -06:00
// fences (gfm)
2013-01-03 02:55:10 -06:00
if (cap = this.rules.fences.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
2012-01-02 04:10:23 -06:00
type: 'code',
2012-08-17 10:24:12 -07:00
lang: cap[2],
text: cap[3]
2012-01-02 04:10:23 -06:00
});
continue;
}
2011-10-22 23:39:04 -05:00
// heading
2013-01-03 02:55:10 -06:00
if (cap = this.rules.heading.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
2011-10-22 06:09:28 -05:00
type: 'heading',
depth: cap[1].length,
text: cap[2]
});
continue;
}
// table no leading pipe (gfm)
2013-01-03 02:55:10 -06:00
if (top && (cap = this.rules.nptable.exec(src))) {
2012-08-24 07:26:12 +01:00
src = src.substring(cap[0].length);
2013-01-02 15:07:58 -06:00
item = {
2012-08-24 07:26:12 +01:00
type: 'table',
2013-01-04 15:54:09 -06:00
header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
2013-01-02 15:07:58 -06:00
cells: cap[3].replace(/\n$/, '').split('\n')
};
for (i = 0; i < item.align.length; i++) {
2013-01-02 22:04:56 -06:00
if (/^ *-+: *$/.test(item.align[i])) {
item.align[i] = 'right';
} else if (/^ *:-+: *$/.test(item.align[i])) {
item.align[i] = 'center';
} else if (/^ *:-+ *$/.test(item.align[i])) {
item.align[i] = 'left';
} else {
item.align[i] = null;
}
2013-01-02 15:07:58 -06:00
}
for (i = 0; i < item.cells.length; i++) {
item.cells[i] = item.cells[i].split(/ *\| */);
}
this.tokens.push(item);
2013-01-02 15:07:58 -06:00
2012-08-24 07:26:12 +01:00
continue;
}
2011-10-22 23:39:04 -05:00
// lheading
2013-01-03 02:55:10 -06:00
if (cap = this.rules.lheading.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
2011-10-22 06:09:28 -05:00
type: 'heading',
depth: cap[2] === '=' ? 1 : 2,
text: cap[1]
});
continue;
}
2011-10-22 23:39:04 -05:00
// hr
2013-01-03 02:55:10 -06:00
if (cap = this.rules.hr.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
2011-10-22 06:09:28 -05:00
type: 'hr'
});
continue;
}
2011-10-22 23:39:04 -05:00
// blockquote
2013-01-03 02:55:10 -06:00
if (cap = this.rules.blockquote.exec(src)) {
src = src.substring(cap[0].length);
2012-02-19 03:00:03 -06:00
this.tokens.push({
2011-10-22 06:09:28 -05:00
type: 'blockquote_start'
});
2012-01-06 16:44:48 -06:00
2012-01-20 22:51:25 -06:00
cap = cap[0].replace(/^ *> ?/gm, '');
// Pass `top` to keep the current
// "toplevel" state. This is exactly
// how markdown.pl works.
this.token(cap, top);
this.tokens.push({
2011-10-22 06:09:28 -05:00
type: 'blockquote_end'
});
2012-02-19 03:00:03 -06:00
2011-10-22 06:09:28 -05:00
continue;
2011-08-14 01:02:14 -05:00
}
2011-10-22 06:09:28 -05:00
2011-10-22 23:39:04 -05:00
// list
2013-01-03 02:55:10 -06:00
if (cap = this.rules.list.exec(src)) {
src = src.substring(cap[0].length);
2013-02-09 20:30:01 -06:00
bull = cap[2];
2011-10-22 06:09:28 -05:00
this.tokens.push({
2011-10-22 06:09:28 -05:00
type: 'list_start',
2013-02-09 20:30:01 -06:00
ordered: bull.length > 1
2011-10-22 06:09:28 -05:00
});
2013-02-05 17:23:28 -06:00
// Get each top-level item.
cap = cap[0].match(this.rules.item);
2012-01-28 22:33:42 -06:00
next = false;
2011-10-22 23:39:04 -05:00
l = cap.length;
2012-01-28 22:33:42 -06:00
i = 0;
2011-10-22 07:49:11 -05:00
for (; i < l; i++) {
item = cap[i];
// Remove the list item's bullet
// so it is seen as the next token.
space = item.length;
2012-03-05 21:32:48 -06:00
item = item.replace(/^ *([*+-]|\d+\.) +/, '');
// Outdent whatever the
// list item contains. Hacky.
if (~item.indexOf('\n ')) {
space -= item.length;
item = !this.options.pedantic
2012-02-19 03:00:03 -06:00
? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
: item.replace(/^ {1,4}/gm, '');
2011-08-18 18:59:42 -05:00
}
// Determine whether the next list item belongs here.
// Backpedal if it does not belong in this list.
if (this.options.smartLists && i !== l - 1) {
2013-02-05 17:30:02 -06:00
b = block.bullet.exec(cap[i+1])[0];
if (bull !== b && !(bull.length > 1 && b.length > 1)) {
src = cap.slice(i + 1).join('\n') + src;
i = l - 1;
}
}
2012-01-28 22:33:42 -06:00
// 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;
}
this.tokens.push({
2011-10-22 06:09:28 -05:00
type: loose
? 'loose_item_start'
: 'list_item_start'
2011-08-14 01:04:35 -05:00
});
// Recurse.
2013-01-04 23:12:46 -06:00
this.token(item, false);
this.tokens.push({
2011-10-22 06:09:28 -05:00
type: 'list_item_end'
2011-08-14 01:04:35 -05:00
});
2011-10-22 07:49:11 -05:00
}
2011-10-22 06:09:28 -05:00
this.tokens.push({
2011-10-22 06:09:28 -05:00
type: 'list_end'
});
continue;
}
2011-10-22 23:39:04 -05:00
// html
2013-01-03 02:55:10 -06:00
if (cap = this.rules.html.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
type: this.options.sanitize
? 'paragraph'
: 'html',
pre: cap[1] === 'pre',
2011-10-22 06:09:28 -05:00
text: cap[0]
});
continue;
}
// def
2013-01-03 02:55:10 -06:00
if (top && (cap = this.rules.def.exec(src))) {
src = src.substring(cap[0].length);
this.tokens.links[cap[1].toLowerCase()] = {
href: cap[2],
title: cap[3]
};
continue;
}
// table (gfm)
2013-01-03 02:55:10 -06:00
if (top && (cap = this.rules.table.exec(src))) {
src = src.substring(cap[0].length);
item = {
type: 'table',
2013-01-04 15:54:09 -06:00
header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
};
for (i = 0; i < item.align.length; i++) {
2013-01-02 22:04:56 -06:00
if (/^ *-+: *$/.test(item.align[i])) {
item.align[i] = 'right';
} else if (/^ *:-+: *$/.test(item.align[i])) {
item.align[i] = 'center';
} else if (/^ *:-+ *$/.test(item.align[i])) {
item.align[i] = 'left';
} else {
item.align[i] = null;
}
}
for (i = 0; i < item.cells.length; i++) {
item.cells[i] = item.cells[i]
2013-01-04 15:54:09 -06:00
.replace(/^ *\| *| *\| *$/g, '')
.split(/ *\| */);
}
this.tokens.push(item);
continue;
}
// top-level paragraph
2013-01-03 02:55:10 -06:00
if (top && (cap = this.rules.paragraph.exec(src))) {
src = src.substring(cap[0].length);
this.tokens.push({
type: 'paragraph',
text: cap[1][cap[1].length-1] === '\n'
? cap[1].slice(0, -1)
: cap[1]
2011-10-22 06:09:28 -05:00
});
continue;
2011-08-14 01:04:35 -05:00
}
2012-01-03 02:25:47 -06:00
// text
2013-01-03 02:55:10 -06:00
if (cap = this.rules.text.exec(src)) {
// Top-level should never reach here.
src = src.substring(cap[0].length);
this.tokens.push({
2012-01-03 02:25:47 -06:00
type: 'text',
text: cap[0]
});
continue;
}
2012-11-23 10:33:47 -06:00
if (src) {
throw new
Error('Infinite loop on byte: ' + src.charCodeAt(0));
}
2011-08-13 16:53:15 -05:00
}
2011-07-24 08:15:35 -05:00
return this.tokens;
2011-07-24 08:15:35 -05:00
};
/**
2013-01-03 02:55:10 -06:00
* Inline-Level Grammar
*/
2011-08-13 23:19:46 -05:00
var inline = {
2013-01-09 19:17:58 -06:00
escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
2011-08-18 19:31:53 -05:00
autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
2012-02-19 20:29:35 -06:00
url: noop,
2012-11-23 10:54:18 +00:00
tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
2012-03-05 21:32:48 -06:00
link: /^!?\[(inside)\]\(href\)/,
2012-03-05 15:44:55 -06:00
reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
2011-08-22 11:02:21 -05:00
nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
2012-11-23 10:54:18 +00:00
strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
br: /^ {2,}\n(?!\s*$)/,
2013-01-03 07:38:07 -06:00
del: noop,
2012-11-23 10:54:18 +00:00
text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
2012-02-19 20:29:35 -06:00
};
2013-01-04 22:38:06 -06:00
inline._inside = /(?:\[[^\]]*\]|[^\]]|\](?=[^\[]*\]))*/;
inline._href = /\s*<?([^\s]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
2012-03-05 15:44:55 -06:00
inline.link = replace(inline.link)
2013-01-04 22:38:06 -06:00
('inside', inline._inside)
('href', inline._href)
2012-03-05 15:44:55 -06:00
();
inline.reflink = replace(inline.reflink)
2013-01-04 22:38:06 -06:00
('inside', inline._inside)
2012-03-05 15:44:55 -06:00
();
2013-01-04 22:54:31 -06:00
/**
* Normal Inline Grammar
*/
2013-01-03 02:55:10 -06:00
inline.normal = merge({}, inline);
2012-02-19 20:29:35 -06:00
2013-01-03 02:55:10 -06:00
/**
* Pedantic Inline Grammar
*/
inline.pedantic = merge({}, inline.normal, {
2012-11-23 10:54:18 +00:00
strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
2013-01-03 02:55:10 -06:00
});
2012-02-19 20:29:35 -06:00
2013-01-03 02:55:10 -06:00
/**
* GFM Inline Grammar
*/
inline.gfm = merge({}, inline.normal, {
2013-01-09 19:17:58 -06:00
escape: replace(inline.escape)('])', '~|])')(),
url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
2013-01-06 14:55:18 -06:00
del: /^~~(?=\S)([\s\S]*?\S)~~/,
2013-01-04 22:27:48 -06:00
text: replace(inline.text)
(']|', '~]|')
('|', '|https?://|')
()
2013-01-03 02:55:10 -06:00
});
/**
* GFM + Line Breaks Inline Grammar
*/
2011-08-13 21:04:18 -05:00
2013-01-03 02:55:10 -06:00
inline.breaks = merge({}, inline.gfm, {
2013-01-03 01:54:15 -06:00
br: replace(inline.br)('{2,}', '*')(),
text: replace(inline.gfm.text)('{2,}', '*')()
2013-01-03 02:55:10 -06:00
});
2013-01-03 01:54:15 -06:00
2011-08-13 23:19:46 -05:00
/**
2013-01-03 01:40:10 -06:00
* Inline Lexer & Compiler
2011-08-13 23:19:46 -05:00
*/
2013-01-03 01:40:10 -06:00
function InlineLexer(links, options) {
2013-01-03 07:45:40 -06:00
this.options = options || marked.defaults;
this.links = links;
this.rules = inline.normal;
if (!this.links) {
throw new
Error('Tokens array requires a `links` property.');
}
2013-01-03 01:40:10 -06:00
if (this.options.gfm) {
2013-01-03 02:55:10 -06:00
if (this.options.breaks) {
this.rules = inline.breaks;
2013-01-03 01:54:15 -06:00
} else {
2013-01-03 02:55:10 -06:00
this.rules = inline.gfm;
2013-01-03 01:54:15 -06:00
}
2013-01-03 02:55:10 -06:00
} else if (this.options.pedantic) {
this.rules = inline.pedantic;
2013-01-03 01:40:10 -06:00
}
}
2013-01-05 06:03:46 -06:00
/**
* Expose Inline Rules
*/
2013-01-03 01:40:10 -06:00
InlineLexer.rules = inline;
2013-01-05 06:03:46 -06:00
/**
* Static Lexing/Compiling Method
*/
2013-01-22 12:53:33 -06:00
InlineLexer.output = function(src, links, options) {
var inline = new InlineLexer(links, options);
2013-01-03 04:14:16 -06:00
return inline.output(src);
};
2013-01-05 06:03:46 -06:00
/**
* Lexing/Compiling
*/
2013-01-03 01:40:10 -06:00
InlineLexer.prototype.output = function(src) {
2011-08-14 01:02:14 -05:00
var out = ''
2011-08-18 19:26:23 -05:00
, link
2011-08-14 01:02:14 -05:00
, text
2011-10-22 06:09:28 -05:00
, href
2011-08-14 01:02:14 -05:00
, cap;
2011-08-13 23:36:44 -05:00
while (src) {
2011-10-22 23:39:04 -05:00
// escape
2013-01-03 02:55:10 -06:00
if (cap = this.rules.escape.exec(src)) {
src = src.substring(cap[0].length);
2011-10-22 06:09:28 -05:00
out += cap[1];
continue;
}
2011-10-22 23:39:04 -05:00
// autolink
2013-01-03 02:55:10 -06:00
if (cap = this.rules.autolink.exec(src)) {
src = src.substring(cap[0].length);
2011-10-22 06:09:28 -05:00
if (cap[2] === '@') {
text = cap[1][6] === ':'
2013-01-03 01:40:10 -06:00
? this.mangle(cap[1].substring(7))
: this.mangle(cap[1]);
href = this.mangle('mailto:') + text;
2011-10-22 06:09:28 -05:00
} else {
text = escape(cap[1]);
href = text;
2011-08-13 21:04:18 -05:00
}
2011-10-22 08:05:31 -05:00
out += '<a href="'
+ href
+ '">'
2011-10-22 06:09:28 -05:00
+ text
+ '</a>';
continue;
2011-08-13 21:04:18 -05:00
}
2011-10-22 06:09:28 -05:00
2012-02-19 20:29:35 -06:00
// url (gfm)
2013-01-03 02:55:10 -06:00
if (cap = this.rules.url.exec(src)) {
src = src.substring(cap[0].length);
text = escape(cap[1]);
href = text;
out += '<a href="'
+ href
+ '">'
+ text
+ '</a>';
continue;
}
2011-10-22 23:39:04 -05:00
// tag
2013-01-03 02:55:10 -06:00
if (cap = this.rules.tag.exec(src)) {
src = src.substring(cap[0].length);
2013-01-03 00:45:07 -06:00
out += this.options.sanitize
2012-02-19 03:00:03 -06:00
? escape(cap[0])
: cap[0];
2011-10-22 06:09:28 -05:00
continue;
}
2011-10-22 23:39:04 -05:00
// link
2013-01-03 02:55:10 -06:00
if (cap = this.rules.link.exec(src)) {
src = src.substring(cap[0].length);
out += this.outputLink(cap, {
2012-03-05 15:44:55 -06:00
href: cap[2],
2012-03-10 16:47:38 -06:00
title: cap[3]
2012-01-21 09:28:53 -06:00
});
2011-10-22 06:09:28 -05:00
continue;
}
2011-10-22 23:39:04 -05:00
// reflink, nolink
2013-01-03 02:55:10 -06:00
if ((cap = this.rules.reflink.exec(src))
|| (cap = this.rules.nolink.exec(src))) {
src = src.substring(cap[0].length);
2011-10-22 06:09:28 -05:00
link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
2013-01-03 07:45:40 -06:00
link = this.links[link.toLowerCase()];
2012-01-14 03:33:28 -06:00
if (!link || !link.href) {
2011-10-22 06:09:28 -05:00
out += cap[0][0];
src = cap[0].substring(1) + src;
2011-10-22 06:09:28 -05:00
continue;
}
out += this.outputLink(cap, link);
2011-10-22 06:09:28 -05:00
continue;
}
2011-10-22 23:39:04 -05:00
// strong
2013-01-03 02:55:10 -06:00
if (cap = this.rules.strong.exec(src)) {
src = src.substring(cap[0].length);
2011-10-22 06:09:28 -05:00
out += '<strong>'
2013-01-03 01:40:10 -06:00
+ this.output(cap[2] || cap[1])
2011-10-22 06:09:28 -05:00
+ '</strong>';
continue;
}
2011-10-22 23:39:04 -05:00
// em
2013-01-03 02:55:10 -06:00
if (cap = this.rules.em.exec(src)) {
src = src.substring(cap[0].length);
2011-10-22 06:09:28 -05:00
out += '<em>'
2013-01-03 01:40:10 -06:00
+ this.output(cap[2] || cap[1])
2011-10-22 06:09:28 -05:00
+ '</em>';
continue;
}
2011-10-22 23:39:04 -05:00
// code
2013-01-03 02:55:10 -06:00
if (cap = this.rules.code.exec(src)) {
src = src.substring(cap[0].length);
2011-10-22 06:09:28 -05:00
out += '<code>'
+ escape(cap[2], true)
2011-10-22 06:09:28 -05:00
+ '</code>';
continue;
}
2011-10-22 23:39:04 -05:00
// br
2013-01-03 02:55:10 -06:00
if (cap = this.rules.br.exec(src)) {
src = src.substring(cap[0].length);
2011-10-22 06:09:28 -05:00
out += '<br>';
continue;
}
2013-01-03 21:13:30 -06:00
// del (gfm)
2013-01-03 07:38:07 -06:00
if (cap = this.rules.del.exec(src)) {
src = src.substring(cap[0].length);
out += '<del>'
+ this.output(cap[1])
+ '</del>';
continue;
}
2011-10-22 23:39:04 -05:00
// text
2013-01-03 02:55:10 -06:00
if (cap = this.rules.text.exec(src)) {
src = src.substring(cap[0].length);
2011-10-22 06:09:28 -05:00
out += escape(cap[0]);
continue;
2011-08-14 01:04:35 -05:00
}
2012-11-23 10:33:47 -06:00
if (src) {
throw new
Error('Infinite loop on byte: ' + src.charCodeAt(0));
}
2011-08-13 23:19:46 -05:00
}
2011-08-13 21:04:18 -05:00
2011-08-13 23:19:46 -05:00
return out;
};
2011-08-13 21:04:18 -05:00
2013-01-05 06:03:46 -06:00
/**
* Compile Link
*/
2013-01-03 01:40:10 -06:00
InlineLexer.prototype.outputLink = function(cap, link) {
2011-10-22 06:09:28 -05:00
if (cap[0][0] !== '!') {
return '<a href="'
+ escape(link.href)
+ '"'
+ (link.title
2012-01-14 03:27:06 -06:00
? ' title="'
+ escape(link.title)
+ '"'
: '')
2011-10-22 06:09:28 -05:00
+ '>'
2013-01-03 01:40:10 -06:00
+ this.output(cap[1])
2011-10-22 06:09:28 -05:00
+ '</a>';
} else {
return '<img src="'
+ escape(link.href)
+ '" alt="'
+ escape(cap[1])
+ '"'
+ (link.title
2012-01-14 03:27:06 -06:00
? ' title="'
+ escape(link.title)
+ '"'
: '')
2011-10-22 06:09:28 -05:00
+ '>';
}
2013-01-03 01:40:10 -06:00
};
2013-01-05 06:03:46 -06:00
/**
* Mangle Links
*/
2013-01-03 01:40:10 -06:00
InlineLexer.prototype.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;
};
2011-10-22 06:09:28 -05:00
2013-03-01 11:48:12 +08:00
/**
* Renderer
*/
function Renderer(options) {
this.options = options || {};
}
Renderer.prototype.blockcode = function(code, lang) {
if (!lang) {
return '<pre><code>' + escape(code, true) + '\n</code></pre>';
2013-03-01 11:48:12 +08:00
}
return '<pre><code class="lang-'
+ lang
+ '">'
+ escape(code)
+ '\n</code></pre>\n';
2013-03-01 11:48:12 +08:00
};
Renderer.prototype.blockquote = function(quote) {
return '<blockquote>\n' + quote + '</blockquote>\n';
};
Renderer.prototype.blockhtml = function(html) {
return html;
};
Renderer.prototype.header = function(text, level) {
return '<h' + level + '>' + text + '</h' + level + '>\n';
};
Renderer.prototype.hrule = function() {
return '<hr>\n';
};
Renderer.prototype.list = function(body, ordered) {
var type = ordered ? 'ol' : 'ul';
return '<' + type + '>\n' + body + '</' + type + '>\n';
};
Renderer.prototype.listitem = function(text) {
return '<li>' + text + '</li>\n';
};
Renderer.prototype.paragraph = function(text) {
return '<p>' + text + '</p>\n';
};
Renderer.prototype.table = function(header, body) {
return '<table>\n'
+ '<thead>\n'
+ header
+ '</thead>\n'
+ '<tbody>\n'
+ body
+ '</tbody>\n'
+ '</table>\n';
};
Renderer.prototype.tablerow = function(content) {
return '<tr>\n' + content + '</tr>\n';
};
Renderer.prototype.tablecell = function(content, flags) {
var type = flags.header ? 'th' : 'td';
var tag = flags.align
? '<' + type + ' align="' + flags.align + '">' : '<' + type + '>';
2013-03-01 11:48:12 +08:00
return tag + content + '</' + type + '>\n';
};
2011-07-24 08:15:35 -05:00
/**
2013-01-03 01:40:10 -06:00
* Parsing & Compiling
2011-07-24 08:15:35 -05:00
*/
2013-03-01 11:48:12 +08:00
function Parser(options, renderer) {
this.tokens = [];
this.token = null;
2013-03-01 11:48:12 +08:00
if (options instanceof Renderer) {
options = null;
renderer = options;
}
this.renderer = renderer || new Renderer();
2013-01-03 00:45:07 -06:00
this.options = options || marked.defaults;
}
2011-07-24 08:15:35 -05:00
2013-01-05 06:03:46 -06:00
/**
* Static Parse Method
*/
2013-03-11 16:14:40 +08:00
Parser.parse = function(src, options, renderer) {
var parser = new Parser(options, renderer);
return parser.parse(src);
};
2013-01-05 06:03:46 -06:00
/**
* Parse Loop
*/
Parser.prototype.parse = function(src) {
2013-01-03 01:40:10 -06:00
this.inline = new InlineLexer(src.links, this.options);
this.tokens = src.reverse();
var out = '';
while (this.next()) {
out += this.tok();
}
return out;
};
2013-01-05 06:03:46 -06:00
/**
* Next Token
*/
Parser.prototype.next = function() {
return this.token = this.tokens.pop();
};
2013-01-05 06:03:46 -06:00
/**
* Preview Next Token
*/
2013-01-03 01:06:58 -06:00
Parser.prototype.peek = function() {
return this.tokens[this.tokens.length-1] || 0;
};
2013-01-05 06:03:46 -06:00
/**
* Parse Text Tokens
*/
Parser.prototype.parseText = function() {
2013-01-03 01:06:58 -06:00
var body = this.token.text;
2013-01-03 01:06:58 -06:00
while (this.peek().type === 'text') {
body += '\n' + this.next().text;
}
2013-01-03 01:40:10 -06:00
return this.inline.output(body);
};
2013-01-05 06:03:46 -06:00
/**
* Parse Current Token
*/
Parser.prototype.tok = function() {
2013-03-01 11:48:12 +08:00
var renderer = this.renderer;
switch (this.token.type) {
2011-10-22 06:18:15 -05:00
case 'space': {
2011-08-18 18:59:42 -05:00
return '';
2011-10-22 06:18:15 -05:00
}
case 'hr': {
2013-03-01 11:48:12 +08:00
return renderer.hrule();
2011-10-22 06:18:15 -05:00
}
case 'heading': {
2013-03-01 11:48:12 +08:00
return renderer.header(
this.inline.output(this.token.text),
this.token.depth
);
2011-10-22 06:18:15 -05:00
}
2011-10-22 08:05:31 -05:00
case 'code': {
2013-03-01 11:48:12 +08:00
return renderer.blockcode(this.token.text, this.token.lang);
2011-10-22 06:18:15 -05:00
}
2012-08-24 07:26:12 +01:00
case 'table': {
2013-03-01 11:48:12 +08:00
var header = ''
, body = ''
2013-01-02 15:07:58 -06:00
, i
, row
, cell
, j;
2013-01-02 15:07:58 -06:00
2013-01-02 15:25:10 -06:00
// header
cell = '';
for (i = 0; i < this.token.header.length; i++) {
2013-03-01 11:48:12 +08:00
// render cell
flags = {header: true, align: this.token.align[i]};
cell += renderer.tablecell(
this.inline.output(this.token.header[i]),
{header: true, align: this.token.align[i]}
);
2013-03-01 11:48:12 +08:00
// render row
2013-01-02 15:07:58 -06:00
}
header += renderer.tablerow(cell);
2012-08-24 07:26:12 +01:00
for (i = 0; i < this.token.cells.length; i++) {
row = this.token.cells[i];
2013-03-01 11:48:12 +08:00
cell = '';
2013-01-02 15:07:58 -06:00
for (j = 0; j < row.length; j++) {
cell += renderer.tablecell(
this.inline.output(row[j]),
{header: false, align: this.token.align[j]}
);
2013-01-02 15:07:58 -06:00
}
2012-08-24 07:26:12 +01:00
2013-03-01 11:48:12 +08:00
body += renderer.tablerow(cell);
}
return renderer.table(header, body);
2012-08-24 07:26:12 +01:00
}
2011-10-22 06:18:15 -05:00
case 'blockquote_start': {
var body = '';
2011-10-22 06:18:15 -05:00
while (this.next().type !== 'blockquote_end') {
body += this.tok();
2011-07-24 08:15:35 -05:00
}
2011-10-22 06:18:15 -05:00
2013-03-01 11:48:12 +08:00
return renderer.blockquote(body);
2011-10-22 06:18:15 -05:00
}
case 'list_start': {
var body = '', ordered = this.token.ordered;
2011-10-22 06:18:15 -05:00
while (this.next().type !== 'list_end') {
body += this.tok();
2011-07-24 08:15:35 -05:00
}
2011-10-22 06:18:15 -05:00
return renderer.list(body, ordered);
2011-10-22 06:18:15 -05:00
}
case 'list_item_start': {
var body = '';
2011-10-22 06:18:15 -05:00
while (this.next().type !== 'list_item_end') {
body += this.token.type === 'text'
? this.parseText()
: this.tok();
2011-07-24 08:15:35 -05:00
}
2011-10-22 06:18:15 -05:00
2013-03-01 11:48:12 +08:00
return renderer.listitem(body);
2011-10-22 06:18:15 -05:00
}
case 'loose_item_start': {
var body = '';
2011-10-22 06:18:15 -05:00
while (this.next().type !== 'list_item_end') {
body += this.tok();
2011-08-20 08:59:47 -05:00
}
2011-10-22 06:18:15 -05:00
2013-03-01 11:48:12 +08:00
return renderer.listitem(body);
2011-10-22 06:18:15 -05:00
}
case 'html': {
2013-03-01 11:48:12 +08:00
var html = !this.token.pre && !this.options.pedantic
2013-01-03 01:40:10 -06:00
? this.inline.output(this.token.text)
: this.token.text;
2013-03-01 11:48:12 +08:00
return renderer.blockhtml(html);
2011-10-22 06:18:15 -05:00
}
case 'paragraph': {
2013-03-01 11:48:12 +08:00
return renderer.paragraph(this.inline.output(this.token.text));
}
2011-10-22 06:18:15 -05:00
case 'text': {
2011-10-22 06:09:28 -05:00
return '<p>'
+ this.parseText()
+ '</p>\n';
2011-10-22 06:18:15 -05:00
}
}
};
2011-07-24 08:15:35 -05:00
/**
* Helpers
*/
function escape(html, encode) {
2011-07-24 08:15:35 -05:00
return html
.replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
2011-07-24 08:15:35 -05:00
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
2011-07-24 08:15:35 -05:00
2012-04-21 18:03:57 -05:00
function replace(regex, opt) {
2012-03-05 15:44:55 -06:00
regex = regex.source;
2012-04-21 18:03:57 -05:00
opt = opt || '';
2012-03-05 15:44:55 -06:00
return function self(name, val) {
2012-04-21 18:03:57 -05:00
if (!name) return new RegExp(regex, opt);
2012-09-05 13:20:06 -05:00
val = val.source || val;
val = val.replace(/(^|[^\[])\^/g, '$1');
regex = regex.replace(name, val);
2012-03-05 15:44:55 -06:00
return self;
};
}
2012-02-19 20:29:35 -06:00
function noop() {}
noop.exec = noop;
2013-01-03 02:55:10 -06:00
function merge(obj) {
var i = 1
, target
, key;
2013-01-03 21:13:30 -06:00
for (; i < arguments.length; i++) {
2013-01-03 02:55:10 -06:00
target = arguments[i];
for (key in target) {
if (Object.prototype.hasOwnProperty.call(target, key)) {
obj[key] = target[key];
}
}
}
return obj;
}
2013-03-01 14:12:04 +08:00
2011-07-24 08:15:35 -05:00
/**
2012-02-19 03:00:03 -06:00
* Marked
2011-07-24 08:15:35 -05:00
*/
function marked(src, opt) {
2012-11-23 10:33:47 -06:00
try {
2013-01-09 17:50:29 -06:00
if (opt) opt = merge({}, marked.defaults, opt);
return Parser.parse(Lexer.lex(src, opt), opt);
2012-11-23 10:33:47 -06:00
} catch (e) {
e.message += '\nPlease report this to https://github.com/chjj/marked.';
2013-01-03 00:45:07 -06:00
if ((opt || marked.defaults).silent) {
2013-01-17 09:27:00 -06:00
return '<p>An error occured:</p><pre>'
+ escape(e.message + '', true)
+ '</pre>';
2012-11-23 10:33:47 -06:00
}
throw e;
}
}
2011-07-24 08:15:35 -05:00
2012-02-19 20:29:35 -06:00
/**
* Options
*/
marked.options =
marked.setOptions = function(opt) {
2013-01-09 13:43:36 -06:00
merge(marked.defaults, opt);
return marked;
2012-02-19 20:29:35 -06:00
};
2013-01-03 00:45:07 -06:00
marked.defaults = {
2012-02-19 03:00:03 -06:00
gfm: true,
tables: true,
2013-01-03 01:54:15 -06:00
breaks: false,
2012-02-19 03:00:03 -06:00
pedantic: false,
sanitize: false,
smartLists: false,
2013-03-01 11:48:12 +08:00
silent: false
2013-01-03 00:45:07 -06:00
};
2012-02-19 03:00:03 -06:00
/**
* Expose
*/
2013-01-03 00:45:07 -06:00
marked.Parser = Parser;
2013-01-03 02:55:10 -06:00
marked.parser = Parser.parse;
2012-02-19 20:29:35 -06:00
2013-03-01 13:47:15 +08:00
marked.Renderer = Renderer;
2013-01-03 00:45:07 -06:00
marked.Lexer = Lexer;
2013-01-03 02:55:10 -06:00
marked.lexer = Lexer.lex;
2011-08-13 23:19:46 -05:00
2013-01-03 01:40:10 -06:00
marked.InlineLexer = InlineLexer;
2013-01-03 02:55:10 -06:00
marked.inlineLexer = InlineLexer.output;
2013-01-03 01:40:10 -06:00
2011-11-26 22:12:02 -06:00
marked.parse = marked;
if (typeof exports === 'object') {
2011-08-13 23:19:46 -05:00
module.exports = marked;
2013-01-03 06:32:39 -06:00
} else if (typeof define === 'function' && define.amd) {
define(function() { return marked; });
2011-08-13 23:19:46 -05:00
} else {
this.marked = marked;
}
2011-07-24 08:15:35 -05:00
2012-04-12 02:27:41 -05:00
}).call(function() {
return this || (typeof window !== 'undefined' ? window : global);
}());