marked.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  1. /**
  2. * marked - A markdown parser (https://github.com/chjj/marked)
  3. * Copyright (c) 2011-2012, Christopher Jeffrey. (MIT Licensed)
  4. */
  5. ;(function() {
  6. /**
  7. * Block-Level Grammar
  8. */
  9. var block = {
  10. newline: /^\n+/,
  11. code: /^( {4}[^\n]+\n*)+/,
  12. fences: noop,
  13. hr: /^( *[\-*_]){3,} *(?:\n+|$)/,
  14. heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
  15. lheading: /^([^\n]+)\n *(=|-){3,} *\n*/,
  16. blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
  17. list: /^( *)([*+-]|\d+\.) [^\0]+?(?:\n{2,}(?! )(?!\1bullet)\n*|\s*$)/,
  18. html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
  19. def: /^ *\[([^\]]+)\]: *([^\s]+)(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
  20. paragraph: /^([^\n]+\n?(?!body))+\n*/,
  21. text: /^[^\n]+/
  22. };
  23. block.list = replace(block.list)
  24. ('bullet', /(?:[*+-](?!(?: *[-*]){2,})|\d+\.)/)
  25. ();
  26. block.html = replace(block.html)
  27. ('comment', /<!--[^\0]*?-->/)
  28. ('closed', /<(tag)[^\0]+?<\/\1>/)
  29. ('closing', /<tag(?!:\/|@)\b(?:"[^"]*"|'[^']*'|[^'">])*?>/)
  30. (/tag/g, tag())
  31. ();
  32. block.paragraph = (function() {
  33. var paragraph = block.paragraph.source
  34. , body = [];
  35. (function push(rule) {
  36. rule = block[rule] ? block[rule].source : rule;
  37. body.push(rule.replace(/(^|[^\[])\^/g, '$1'));
  38. return push;
  39. })
  40. ('hr')
  41. ('heading')
  42. ('lheading')
  43. ('blockquote')
  44. ('<' + tag())
  45. ('def');
  46. return new
  47. RegExp(paragraph.replace('body', body.join('|')));
  48. })();
  49. block.normal = {
  50. fences: block.fences,
  51. paragraph: block.paragraph
  52. };
  53. block.gfm = {
  54. fences: /^ *``` *(\w+)? *\n([^\0]+?)\s*``` *(?:\n+|$)/,
  55. paragraph: /^/
  56. };
  57. block.gfm.paragraph = replace(block.paragraph)
  58. ('(?!', '(?!' + block.gfm.fences.source.replace(/(^|[^\[])\^/g, '$1') + '|')
  59. ();
  60. /**
  61. * Block Lexer
  62. */
  63. block.lexer = function(src) {
  64. var tokens = [];
  65. tokens.links = {};
  66. src = src
  67. .replace(/\r\n|\r/g, '\n')
  68. .replace(/\t/g, ' ');
  69. return block.token(src, tokens, true);
  70. };
  71. block.token = function(src, tokens, top) {
  72. var src = src.replace(/^ +$/gm, '')
  73. , next
  74. , loose
  75. , cap
  76. , item
  77. , space
  78. , i
  79. , l;
  80. while (src) {
  81. // newline
  82. if (cap = block.newline.exec(src)) {
  83. src = src.substring(cap[0].length);
  84. if (cap[0].length > 1) {
  85. tokens.push({
  86. type: 'space'
  87. });
  88. }
  89. }
  90. // code
  91. if (cap = block.code.exec(src)) {
  92. src = src.substring(cap[0].length);
  93. cap = cap[0].replace(/^ {4}/gm, '');
  94. tokens.push({
  95. type: 'code',
  96. text: !options.pedantic
  97. ? cap.replace(/\n+$/, '')
  98. : cap
  99. });
  100. continue;
  101. }
  102. // fences (gfm)
  103. if (cap = block.fences.exec(src)) {
  104. src = src.substring(cap[0].length);
  105. tokens.push({
  106. type: 'code',
  107. lang: cap[1],
  108. text: cap[2]
  109. });
  110. continue;
  111. }
  112. // heading
  113. if (cap = block.heading.exec(src)) {
  114. src = src.substring(cap[0].length);
  115. tokens.push({
  116. type: 'heading',
  117. depth: cap[1].length,
  118. text: cap[2]
  119. });
  120. continue;
  121. }
  122. // lheading
  123. if (cap = block.lheading.exec(src)) {
  124. src = src.substring(cap[0].length);
  125. tokens.push({
  126. type: 'heading',
  127. depth: cap[2] === '=' ? 1 : 2,
  128. text: cap[1]
  129. });
  130. continue;
  131. }
  132. // hr
  133. if (cap = block.hr.exec(src)) {
  134. src = src.substring(cap[0].length);
  135. tokens.push({
  136. type: 'hr'
  137. });
  138. continue;
  139. }
  140. // blockquote
  141. if (cap = block.blockquote.exec(src)) {
  142. src = src.substring(cap[0].length);
  143. tokens.push({
  144. type: 'blockquote_start'
  145. });
  146. cap = cap[0].replace(/^ *> ?/gm, '');
  147. // Pass `top` to keep the current
  148. // "toplevel" state. This is exactly
  149. // how markdown.pl works.
  150. block.token(cap, tokens, top);
  151. tokens.push({
  152. type: 'blockquote_end'
  153. });
  154. continue;
  155. }
  156. // list
  157. if (cap = block.list.exec(src)) {
  158. src = src.substring(cap[0].length);
  159. tokens.push({
  160. type: 'list_start',
  161. ordered: isFinite(cap[2])
  162. });
  163. // Get each top-level item.
  164. cap = cap[0].match(
  165. /^( *)([*+-]|\d+\.) [^\n]*(?:\n(?!\1(?:[*+-]|\d+\.) )[^\n]*)*/gm
  166. );
  167. next = false;
  168. l = cap.length;
  169. i = 0;
  170. for (; i < l; i++) {
  171. item = cap[i];
  172. // Remove the list item's bullet
  173. // so it is seen as the next token.
  174. space = item.length;
  175. item = item.replace(/^ *([*+-]|\d+\.) +/, '');
  176. // Outdent whatever the
  177. // list item contains. Hacky.
  178. if (~item.indexOf('\n ')) {
  179. space -= item.length;
  180. item = !options.pedantic
  181. ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
  182. : item.replace(/^ {1,4}/gm, '');
  183. }
  184. // Determine whether item is loose or not.
  185. // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
  186. // for discount behavior.
  187. loose = next || /\n\n(?!\s*$)/.test(item);
  188. if (i !== l - 1) {
  189. next = item[item.length-1] === '\n';
  190. if (!loose) loose = next;
  191. }
  192. tokens.push({
  193. type: loose
  194. ? 'loose_item_start'
  195. : 'list_item_start'
  196. });
  197. // Recurse.
  198. block.token(item, tokens);
  199. tokens.push({
  200. type: 'list_item_end'
  201. });
  202. }
  203. tokens.push({
  204. type: 'list_end'
  205. });
  206. continue;
  207. }
  208. // html
  209. if (cap = block.html.exec(src)) {
  210. src = src.substring(cap[0].length);
  211. tokens.push({
  212. type: 'html',
  213. pre: cap[1] === 'pre',
  214. text: cap[0]
  215. });
  216. continue;
  217. }
  218. // def
  219. if (top && (cap = block.def.exec(src))) {
  220. src = src.substring(cap[0].length);
  221. tokens.links[cap[1].toLowerCase()] = {
  222. href: cap[2],
  223. title: cap[3]
  224. };
  225. continue;
  226. }
  227. // top-level paragraph
  228. if (top && (cap = block.paragraph.exec(src))) {
  229. src = src.substring(cap[0].length);
  230. tokens.push({
  231. type: 'paragraph',
  232. text: cap[0]
  233. });
  234. continue;
  235. }
  236. // text
  237. if (cap = block.text.exec(src)) {
  238. // Top-level should never reach here.
  239. src = src.substring(cap[0].length);
  240. tokens.push({
  241. type: 'text',
  242. text: cap[0]
  243. });
  244. continue;
  245. }
  246. }
  247. return tokens;
  248. };
  249. /**
  250. * Inline Processing
  251. */
  252. var inline = {
  253. escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
  254. autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
  255. url: noop,
  256. tag: /^<!--[^\0]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
  257. link: /^!?\[(inside)\]\(href\)/,
  258. reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
  259. nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
  260. strong: /^__([^\0]+?)__(?!_)|^\*\*([^\0]+?)\*\*(?!\*)/,
  261. em: /^\b_((?:__|[^\0])+?)_\b|^\*((?:\*\*|[^\0])+?)\*(?!\*)/,
  262. code: /^(`+)([^\0]*?[^`])\1(?!`)/,
  263. br: /^ {2,}\n(?!\s*$)/,
  264. text: /^[^\0]+?(?=[\\<!\[_*`]| {2,}\n|$)/
  265. };
  266. inline._linkInside = /(?:\[[^\]]*\]|[^\]]|\](?=[^\[]*\]))*/;
  267. inline._linkHref = /\s*<?([^\s]*?)>?(?:\s+['"]([^\0]*?)['"])?\s*/;
  268. inline.link = replace(inline.link)
  269. ('inside', inline._linkInside)
  270. ('href', inline._linkHref)
  271. ();
  272. inline.reflink = replace(inline.reflink)
  273. ('inside', inline._linkInside)
  274. ();
  275. inline.normal = {
  276. url: inline.url,
  277. strong: inline.strong,
  278. em: inline.em,
  279. text: inline.text
  280. };
  281. inline.pedantic = {
  282. strong: /^__(?=\S)([^\0]*?\S)__(?!_)|^\*\*(?=\S)([^\0]*?\S)\*\*(?!\*)/,
  283. em: /^_(?=\S)([^\0]*?\S)_(?!_)|^\*(?=\S)([^\0]*?\S)\*(?!\*)/
  284. };
  285. inline.gfm = {
  286. url: /^(https?:\/\/[^\s]+[^.,:;"')\]\s])/,
  287. text: /^[^\0]+?(?=[\\<!\[_*`]|https?:\/\/| {2,}\n|$)/
  288. };
  289. /**
  290. * Inline Lexer
  291. */
  292. inline.lexer = function(src) {
  293. var out = ''
  294. , links = tokens.links
  295. , link
  296. , text
  297. , href
  298. , cap;
  299. while (src) {
  300. // escape
  301. if (cap = inline.escape.exec(src)) {
  302. src = src.substring(cap[0].length);
  303. out += cap[1];
  304. continue;
  305. }
  306. // autolink
  307. if (cap = inline.autolink.exec(src)) {
  308. src = src.substring(cap[0].length);
  309. if (cap[2] === '@') {
  310. text = cap[1][6] === ':'
  311. ? mangle(cap[1].substring(7))
  312. : mangle(cap[1]);
  313. href = mangle('mailto:') + text;
  314. } else {
  315. text = escape(cap[1]);
  316. href = text;
  317. }
  318. out += '<a href="'
  319. + href
  320. + '">'
  321. + text
  322. + '</a>';
  323. continue;
  324. }
  325. // url (gfm)
  326. if (cap = inline.url.exec(src)) {
  327. src = src.substring(cap[0].length);
  328. text = escape(cap[1]);
  329. href = text;
  330. out += '<a href="'
  331. + href
  332. + '">'
  333. + text
  334. + '</a>';
  335. continue;
  336. }
  337. // tag
  338. if (cap = inline.tag.exec(src)) {
  339. src = src.substring(cap[0].length);
  340. out += options.sanitize
  341. ? escape(cap[0])
  342. : cap[0];
  343. continue;
  344. }
  345. // link
  346. if (cap = inline.link.exec(src)) {
  347. src = src.substring(cap[0].length);
  348. out += outputLink(cap, {
  349. href: cap[2],
  350. title: cap[3]
  351. });
  352. continue;
  353. }
  354. // reflink, nolink
  355. if ((cap = inline.reflink.exec(src))
  356. || (cap = inline.nolink.exec(src))) {
  357. src = src.substring(cap[0].length);
  358. link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
  359. link = links[link.toLowerCase()];
  360. if (!link || !link.href) {
  361. out += cap[0][0];
  362. src = cap[0].substring(1) + src;
  363. continue;
  364. }
  365. out += outputLink(cap, link);
  366. continue;
  367. }
  368. // strong
  369. if (cap = inline.strong.exec(src)) {
  370. src = src.substring(cap[0].length);
  371. out += '<strong>'
  372. + inline.lexer(cap[2] || cap[1])
  373. + '</strong>';
  374. continue;
  375. }
  376. // em
  377. if (cap = inline.em.exec(src)) {
  378. src = src.substring(cap[0].length);
  379. out += '<em>'
  380. + inline.lexer(cap[2] || cap[1])
  381. + '</em>';
  382. continue;
  383. }
  384. // code
  385. if (cap = inline.code.exec(src)) {
  386. src = src.substring(cap[0].length);
  387. out += '<code>'
  388. + escape(cap[2], true)
  389. + '</code>';
  390. continue;
  391. }
  392. // br
  393. if (cap = inline.br.exec(src)) {
  394. src = src.substring(cap[0].length);
  395. out += '<br>';
  396. continue;
  397. }
  398. // text
  399. if (cap = inline.text.exec(src)) {
  400. src = src.substring(cap[0].length);
  401. out += escape(cap[0]);
  402. continue;
  403. }
  404. }
  405. return out;
  406. };
  407. var outputLink = function(cap, link) {
  408. if (cap[0][0] !== '!') {
  409. return '<a href="'
  410. + escape(link.href)
  411. + '"'
  412. + (link.title
  413. ? ' title="'
  414. + escape(link.title)
  415. + '"'
  416. : '')
  417. + '>'
  418. + inline.lexer(cap[1])
  419. + '</a>';
  420. } else {
  421. return '<img src="'
  422. + escape(link.href)
  423. + '" alt="'
  424. + escape(cap[1])
  425. + '"'
  426. + (link.title
  427. ? ' title="'
  428. + escape(link.title)
  429. + '"'
  430. : '')
  431. + '>';
  432. }
  433. };
  434. /**
  435. * Parsing
  436. */
  437. var tokens
  438. , token;
  439. var next = function() {
  440. return token = tokens.pop();
  441. };
  442. var tok = function() {
  443. switch (token.type) {
  444. case 'space': {
  445. return '';
  446. }
  447. case 'hr': {
  448. return '<hr>\n';
  449. }
  450. case 'heading': {
  451. return '<h'
  452. + token.depth
  453. + '>'
  454. + inline.lexer(token.text)
  455. + '</h'
  456. + token.depth
  457. + '>\n';
  458. }
  459. case 'code': {
  460. return '<pre><code'
  461. + (token.lang
  462. ? ' class="'
  463. + token.lang
  464. + '"'
  465. : '')
  466. + '>'
  467. + (token.escaped
  468. ? token.text
  469. : escape(token.text, true))
  470. + '</code></pre>\n';
  471. }
  472. case 'blockquote_start': {
  473. var body = '';
  474. while (next().type !== 'blockquote_end') {
  475. body += tok();
  476. }
  477. return '<blockquote>\n'
  478. + body
  479. + '</blockquote>\n';
  480. }
  481. case 'list_start': {
  482. var type = token.ordered ? 'ol' : 'ul'
  483. , body = '';
  484. while (next().type !== 'list_end') {
  485. body += tok();
  486. }
  487. return '<'
  488. + type
  489. + '>\n'
  490. + body
  491. + '</'
  492. + type
  493. + '>\n';
  494. }
  495. case 'list_item_start': {
  496. var body = '';
  497. while (next().type !== 'list_item_end') {
  498. body += token.type === 'text'
  499. ? parseText()
  500. : tok();
  501. }
  502. return '<li>'
  503. + body
  504. + '</li>\n';
  505. }
  506. case 'loose_item_start': {
  507. var body = '';
  508. while (next().type !== 'list_item_end') {
  509. body += tok();
  510. }
  511. return '<li>'
  512. + body
  513. + '</li>\n';
  514. }
  515. case 'html': {
  516. if (options.sanitize) {
  517. return inline.lexer(token.text);
  518. }
  519. return !token.pre && !options.pedantic
  520. ? inline.lexer(token.text)
  521. : token.text;
  522. }
  523. case 'paragraph': {
  524. return '<p>'
  525. + inline.lexer(token.text)
  526. + '</p>\n';
  527. }
  528. case 'text': {
  529. return '<p>'
  530. + parseText()
  531. + '</p>\n';
  532. }
  533. }
  534. };
  535. var parseText = function() {
  536. var body = token.text
  537. , top;
  538. while ((top = tokens[tokens.length-1])
  539. && top.type === 'text') {
  540. body += '\n' + next().text;
  541. }
  542. return inline.lexer(body);
  543. };
  544. var parse = function(src) {
  545. tokens = src.reverse();
  546. var out = '';
  547. while (next()) {
  548. out += tok();
  549. }
  550. tokens = null;
  551. token = null;
  552. return out;
  553. };
  554. /**
  555. * Helpers
  556. */
  557. var escape = function(html, encode) {
  558. return html
  559. .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
  560. .replace(/</g, '&lt;')
  561. .replace(/>/g, '&gt;')
  562. .replace(/"/g, '&quot;')
  563. .replace(/'/g, '&#39;');
  564. };
  565. var mangle = function(text) {
  566. var out = ''
  567. , l = text.length
  568. , i = 0
  569. , ch;
  570. for (; i < l; i++) {
  571. ch = text.charCodeAt(i);
  572. if (Math.random() > 0.5) {
  573. ch = 'x' + ch.toString(16);
  574. }
  575. out += '&#' + ch + ';';
  576. }
  577. return out;
  578. };
  579. function tag() {
  580. var tag = '(?!(?:'
  581. + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
  582. + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
  583. + '|span|br|wbr|ins|del|img)\\b)\\w+';
  584. return tag;
  585. }
  586. function replace(regex) {
  587. regex = regex.source;
  588. return function self(name, val) {
  589. if (!name) return new RegExp(regex);
  590. regex = regex.replace(name, val.source || val);
  591. return self;
  592. };
  593. }
  594. function noop() {}
  595. noop.exec = noop;
  596. /**
  597. * Marked
  598. */
  599. var marked = function(src, opt) {
  600. setOptions(opt);
  601. return parse(block.lexer(src));
  602. };
  603. /**
  604. * Options
  605. */
  606. var options
  607. , defaults;
  608. var setOptions = function(opt) {
  609. if (!opt) opt = defaults;
  610. if (options === opt) return;
  611. options = opt;
  612. if (options.gfm) {
  613. block.fences = block.gfm.fences;
  614. block.paragraph = block.gfm.paragraph;
  615. inline.text = inline.gfm.text;
  616. inline.url = inline.gfm.url;
  617. } else {
  618. block.fences = block.normal.fences;
  619. block.paragraph = block.normal.paragraph;
  620. inline.text = inline.normal.text;
  621. inline.url = inline.normal.url;
  622. }
  623. if (options.pedantic) {
  624. inline.em = inline.pedantic.em;
  625. inline.strong = inline.pedantic.strong;
  626. } else {
  627. inline.em = inline.normal.em;
  628. inline.strong = inline.normal.strong;
  629. }
  630. };
  631. marked.options =
  632. marked.setOptions = function(opt) {
  633. defaults = opt;
  634. setOptions(opt);
  635. };
  636. marked.options({
  637. gfm: true,
  638. pedantic: false,
  639. sanitize: false
  640. });
  641. /**
  642. * Expose
  643. */
  644. marked.parser = function(src, opt) {
  645. setOptions(opt);
  646. return parse(src);
  647. };
  648. marked.lexer = function(src, opt) {
  649. setOptions(opt);
  650. return block.lexer(src);
  651. };
  652. marked.parse = marked;
  653. if (typeof module !== 'undefined') {
  654. module.exports = marked;
  655. } else {
  656. this.marked = marked;
  657. }
  658. }).call(this);