marked.js 22 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072
  1. /**
  2. * marked - a markdown parser
  3. * Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed)
  4. * https://github.com/chjj/marked
  5. */
  6. ;(function() {
  7. /**
  8. * Block-Level Grammar
  9. */
  10. var block = {
  11. newline: /^\n+/,
  12. code: /^( {4}[^\n]+\n*)+/,
  13. fences: noop,
  14. hr: /^( *[-*_]){3,} *(?:\n+|$)/,
  15. heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
  16. nptable: noop,
  17. lheading: /^([^\n]+)\n *(=|-){3,} *\n*/,
  18. blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
  19. list: /^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
  20. html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
  21. def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
  22. table: noop,
  23. paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
  24. text: /^[^\n]+/
  25. };
  26. block.bullet = /(?:[*+-]|\d+\.)/;
  27. block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
  28. block.item = replace(block.item, 'gm')
  29. (/bull/g, block.bullet)
  30. ();
  31. block.list = replace(block.list)
  32. (/bull/g, block.bullet)
  33. ('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)
  34. ();
  35. block._tag = '(?!(?:'
  36. + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
  37. + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
  38. + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b';
  39. block.html = replace(block.html)
  40. ('comment', /<!--[\s\S]*?-->/)
  41. ('closed', /<(tag)[\s\S]+?<\/\1>/)
  42. ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
  43. (/tag/g, block._tag)
  44. ();
  45. block.paragraph = replace(block.paragraph)
  46. ('hr', block.hr)
  47. ('heading', block.heading)
  48. ('lheading', block.lheading)
  49. ('blockquote', block.blockquote)
  50. ('tag', '<' + block._tag)
  51. ('def', block.def)
  52. ();
  53. /**
  54. * Normal Block Grammar
  55. */
  56. block.normal = merge({}, block);
  57. /**
  58. * GFM Block Grammar
  59. */
  60. block.gfm = merge({}, block.normal, {
  61. fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,
  62. paragraph: /^/
  63. });
  64. block.gfm.paragraph = replace(block.paragraph)
  65. ('(?!', '(?!' + block.gfm.fences.source.replace('\\1', '\\2') + '|')
  66. ();
  67. /**
  68. * GFM + Tables Block Grammar
  69. */
  70. block.tables = merge({}, block.gfm, {
  71. nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
  72. table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
  73. });
  74. /**
  75. * Block Lexer
  76. */
  77. function Lexer(options) {
  78. this.tokens = [];
  79. this.tokens.links = {};
  80. this.options = options || marked.defaults;
  81. this.rules = block.normal;
  82. if (this.options.gfm) {
  83. if (this.options.tables) {
  84. this.rules = block.tables;
  85. } else {
  86. this.rules = block.gfm;
  87. }
  88. }
  89. }
  90. /**
  91. * Expose Block Rules
  92. */
  93. Lexer.rules = block;
  94. /**
  95. * Static Lex Method
  96. */
  97. Lexer.lex = function(src, options) {
  98. var lexer = new Lexer(options);
  99. return lexer.lex(src);
  100. };
  101. /**
  102. * Preprocessing
  103. */
  104. Lexer.prototype.lex = function(src) {
  105. src = src
  106. .replace(/\r\n|\r/g, '\n')
  107. .replace(/\t/g, ' ')
  108. .replace(/\u00a0/g, ' ')
  109. .replace(/\u2424/g, '\n');
  110. return this.token(src, true);
  111. };
  112. /**
  113. * Lexing
  114. */
  115. Lexer.prototype.token = function(src, top) {
  116. var src = src.replace(/^ +$/gm, '')
  117. , next
  118. , loose
  119. , cap
  120. , bull
  121. , b
  122. , item
  123. , space
  124. , i
  125. , l;
  126. while (src) {
  127. // newline
  128. if (cap = this.rules.newline.exec(src)) {
  129. src = src.substring(cap[0].length);
  130. if (cap[0].length > 1) {
  131. this.tokens.push({
  132. type: 'space'
  133. });
  134. }
  135. }
  136. // code
  137. if (cap = this.rules.code.exec(src)) {
  138. src = src.substring(cap[0].length);
  139. cap = cap[0].replace(/^ {4}/gm, '');
  140. this.tokens.push({
  141. type: 'code',
  142. text: !this.options.pedantic
  143. ? cap.replace(/\n+$/, '')
  144. : cap
  145. });
  146. continue;
  147. }
  148. // fences (gfm)
  149. if (cap = this.rules.fences.exec(src)) {
  150. src = src.substring(cap[0].length);
  151. this.tokens.push({
  152. type: 'code',
  153. lang: cap[2],
  154. text: cap[3]
  155. });
  156. continue;
  157. }
  158. // heading
  159. if (cap = this.rules.heading.exec(src)) {
  160. src = src.substring(cap[0].length);
  161. this.tokens.push({
  162. type: 'heading',
  163. depth: cap[1].length,
  164. text: cap[2]
  165. });
  166. continue;
  167. }
  168. // table no leading pipe (gfm)
  169. if (top && (cap = this.rules.nptable.exec(src))) {
  170. src = src.substring(cap[0].length);
  171. item = {
  172. type: 'table',
  173. header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
  174. align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
  175. cells: cap[3].replace(/\n$/, '').split('\n')
  176. };
  177. for (i = 0; i < item.align.length; i++) {
  178. if (/^ *-+: *$/.test(item.align[i])) {
  179. item.align[i] = 'right';
  180. } else if (/^ *:-+: *$/.test(item.align[i])) {
  181. item.align[i] = 'center';
  182. } else if (/^ *:-+ *$/.test(item.align[i])) {
  183. item.align[i] = 'left';
  184. } else {
  185. item.align[i] = null;
  186. }
  187. }
  188. for (i = 0; i < item.cells.length; i++) {
  189. item.cells[i] = item.cells[i].split(/ *\| */);
  190. }
  191. this.tokens.push(item);
  192. continue;
  193. }
  194. // lheading
  195. if (cap = this.rules.lheading.exec(src)) {
  196. src = src.substring(cap[0].length);
  197. this.tokens.push({
  198. type: 'heading',
  199. depth: cap[2] === '=' ? 1 : 2,
  200. text: cap[1]
  201. });
  202. continue;
  203. }
  204. // hr
  205. if (cap = this.rules.hr.exec(src)) {
  206. src = src.substring(cap[0].length);
  207. this.tokens.push({
  208. type: 'hr'
  209. });
  210. continue;
  211. }
  212. // blockquote
  213. if (cap = this.rules.blockquote.exec(src)) {
  214. src = src.substring(cap[0].length);
  215. this.tokens.push({
  216. type: 'blockquote_start'
  217. });
  218. cap = cap[0].replace(/^ *> ?/gm, '');
  219. // Pass `top` to keep the current
  220. // "toplevel" state. This is exactly
  221. // how markdown.pl works.
  222. this.token(cap, top);
  223. this.tokens.push({
  224. type: 'blockquote_end'
  225. });
  226. continue;
  227. }
  228. // list
  229. if (cap = this.rules.list.exec(src)) {
  230. src = src.substring(cap[0].length);
  231. bull = cap[2];
  232. this.tokens.push({
  233. type: 'list_start',
  234. ordered: bull.length > 1
  235. });
  236. // Get each top-level item.
  237. cap = cap[0].match(this.rules.item);
  238. next = false;
  239. l = cap.length;
  240. i = 0;
  241. for (; i < l; i++) {
  242. item = cap[i];
  243. // Remove the list item's bullet
  244. // so it is seen as the next token.
  245. space = item.length;
  246. item = item.replace(/^ *([*+-]|\d+\.) +/, '');
  247. // Outdent whatever the
  248. // list item contains. Hacky.
  249. if (~item.indexOf('\n ')) {
  250. space -= item.length;
  251. item = !this.options.pedantic
  252. ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
  253. : item.replace(/^ {1,4}/gm, '');
  254. }
  255. // Determine whether the next list item belongs here.
  256. // Backpedal if it does not belong in this list.
  257. if (this.options.smartLists && i !== l - 1) {
  258. b = block.bullet.exec(cap[i+1])[0];
  259. if (bull !== b && !(bull.length > 1 && b.length > 1)) {
  260. src = cap.slice(i + 1).join('\n') + src;
  261. i = l - 1;
  262. }
  263. }
  264. // Determine whether item is loose or not.
  265. // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
  266. // for discount behavior.
  267. loose = next || /\n\n(?!\s*$)/.test(item);
  268. if (i !== l - 1) {
  269. next = item[item.length-1] === '\n';
  270. if (!loose) loose = next;
  271. }
  272. this.tokens.push({
  273. type: loose
  274. ? 'loose_item_start'
  275. : 'list_item_start'
  276. });
  277. // Recurse.
  278. this.token(item, false);
  279. this.tokens.push({
  280. type: 'list_item_end'
  281. });
  282. }
  283. this.tokens.push({
  284. type: 'list_end'
  285. });
  286. continue;
  287. }
  288. // html
  289. if (cap = this.rules.html.exec(src)) {
  290. src = src.substring(cap[0].length);
  291. this.tokens.push({
  292. type: this.options.sanitize
  293. ? 'paragraph'
  294. : 'html',
  295. pre: cap[1] === 'pre',
  296. text: cap[0]
  297. });
  298. continue;
  299. }
  300. // def
  301. if (top && (cap = this.rules.def.exec(src))) {
  302. src = src.substring(cap[0].length);
  303. this.tokens.links[cap[1].toLowerCase()] = {
  304. href: cap[2],
  305. title: cap[3]
  306. };
  307. continue;
  308. }
  309. // table (gfm)
  310. if (top && (cap = this.rules.table.exec(src))) {
  311. src = src.substring(cap[0].length);
  312. item = {
  313. type: 'table',
  314. header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
  315. align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
  316. cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
  317. };
  318. for (i = 0; i < item.align.length; i++) {
  319. if (/^ *-+: *$/.test(item.align[i])) {
  320. item.align[i] = 'right';
  321. } else if (/^ *:-+: *$/.test(item.align[i])) {
  322. item.align[i] = 'center';
  323. } else if (/^ *:-+ *$/.test(item.align[i])) {
  324. item.align[i] = 'left';
  325. } else {
  326. item.align[i] = null;
  327. }
  328. }
  329. for (i = 0; i < item.cells.length; i++) {
  330. item.cells[i] = item.cells[i]
  331. .replace(/^ *\| *| *\| *$/g, '')
  332. .split(/ *\| */);
  333. }
  334. this.tokens.push(item);
  335. continue;
  336. }
  337. // top-level paragraph
  338. if (top && (cap = this.rules.paragraph.exec(src))) {
  339. src = src.substring(cap[0].length);
  340. this.tokens.push({
  341. type: 'paragraph',
  342. text: cap[1][cap[1].length-1] === '\n'
  343. ? cap[1].slice(0, -1)
  344. : cap[1]
  345. });
  346. continue;
  347. }
  348. // text
  349. if (cap = this.rules.text.exec(src)) {
  350. // Top-level should never reach here.
  351. src = src.substring(cap[0].length);
  352. this.tokens.push({
  353. type: 'text',
  354. text: cap[0]
  355. });
  356. continue;
  357. }
  358. if (src) {
  359. throw new
  360. Error('Infinite loop on byte: ' + src.charCodeAt(0));
  361. }
  362. }
  363. return this.tokens;
  364. };
  365. /**
  366. * Inline-Level Grammar
  367. */
  368. var inline = {
  369. escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
  370. autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
  371. url: noop,
  372. tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
  373. link: /^!?\[(inside)\]\(href\)/,
  374. reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
  375. nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
  376. strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
  377. em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
  378. code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
  379. br: /^ {2,}\n(?!\s*$)/,
  380. del: noop,
  381. text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
  382. };
  383. inline._inside = /(?:\[[^\]]*\]|[^\]]|\](?=[^\[]*\]))*/;
  384. inline._href = /\s*<?([^\s]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
  385. inline.link = replace(inline.link)
  386. ('inside', inline._inside)
  387. ('href', inline._href)
  388. ();
  389. inline.reflink = replace(inline.reflink)
  390. ('inside', inline._inside)
  391. ();
  392. /**
  393. * Normal Inline Grammar
  394. */
  395. inline.normal = merge({}, inline);
  396. /**
  397. * Pedantic Inline Grammar
  398. */
  399. inline.pedantic = merge({}, inline.normal, {
  400. strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
  401. em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
  402. });
  403. /**
  404. * GFM Inline Grammar
  405. */
  406. inline.gfm = merge({}, inline.normal, {
  407. escape: replace(inline.escape)('])', '~|])')(),
  408. url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
  409. del: /^~~(?=\S)([\s\S]*?\S)~~/,
  410. text: replace(inline.text)
  411. (']|', '~]|')
  412. ('|', '|https?://|')
  413. ()
  414. });
  415. /**
  416. * GFM + Line Breaks Inline Grammar
  417. */
  418. inline.breaks = merge({}, inline.gfm, {
  419. br: replace(inline.br)('{2,}', '*')(),
  420. text: replace(inline.gfm.text)('{2,}', '*')()
  421. });
  422. /**
  423. * Inline Lexer & Compiler
  424. */
  425. function InlineLexer(links, options) {
  426. this.options = options || marked.defaults;
  427. this.links = links;
  428. this.rules = inline.normal;
  429. if (!this.links) {
  430. throw new
  431. Error('Tokens array requires a `links` property.');
  432. }
  433. if (this.options.gfm) {
  434. if (this.options.breaks) {
  435. this.rules = inline.breaks;
  436. } else {
  437. this.rules = inline.gfm;
  438. }
  439. } else if (this.options.pedantic) {
  440. this.rules = inline.pedantic;
  441. }
  442. }
  443. /**
  444. * Expose Inline Rules
  445. */
  446. InlineLexer.rules = inline;
  447. /**
  448. * Static Lexing/Compiling Method
  449. */
  450. InlineLexer.output = function(src, links, options) {
  451. var inline = new InlineLexer(links, options);
  452. return inline.output(src);
  453. };
  454. /**
  455. * Lexing/Compiling
  456. */
  457. InlineLexer.prototype.output = function(src) {
  458. var out = ''
  459. , link
  460. , text
  461. , href
  462. , cap;
  463. while (src) {
  464. // escape
  465. if (cap = this.rules.escape.exec(src)) {
  466. src = src.substring(cap[0].length);
  467. out += cap[1];
  468. continue;
  469. }
  470. // autolink
  471. if (cap = this.rules.autolink.exec(src)) {
  472. src = src.substring(cap[0].length);
  473. if (cap[2] === '@') {
  474. text = cap[1][6] === ':'
  475. ? this.mangle(cap[1].substring(7))
  476. : this.mangle(cap[1]);
  477. href = this.mangle('mailto:') + text;
  478. } else {
  479. text = escape(cap[1]);
  480. href = text;
  481. }
  482. out += '<a href="'
  483. + href
  484. + '">'
  485. + text
  486. + '</a>';
  487. continue;
  488. }
  489. // url (gfm)
  490. if (cap = this.rules.url.exec(src)) {
  491. src = src.substring(cap[0].length);
  492. text = escape(cap[1]);
  493. href = text;
  494. out += '<a href="'
  495. + href
  496. + '">'
  497. + text
  498. + '</a>';
  499. continue;
  500. }
  501. // tag
  502. if (cap = this.rules.tag.exec(src)) {
  503. src = src.substring(cap[0].length);
  504. out += this.options.sanitize
  505. ? escape(cap[0])
  506. : cap[0];
  507. continue;
  508. }
  509. // link
  510. if (cap = this.rules.link.exec(src)) {
  511. src = src.substring(cap[0].length);
  512. out += this.outputLink(cap, {
  513. href: cap[2],
  514. title: cap[3]
  515. });
  516. continue;
  517. }
  518. // reflink, nolink
  519. if ((cap = this.rules.reflink.exec(src))
  520. || (cap = this.rules.nolink.exec(src))) {
  521. src = src.substring(cap[0].length);
  522. link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
  523. link = this.links[link.toLowerCase()];
  524. if (!link || !link.href) {
  525. out += cap[0][0];
  526. src = cap[0].substring(1) + src;
  527. continue;
  528. }
  529. out += this.outputLink(cap, link);
  530. continue;
  531. }
  532. // strong
  533. if (cap = this.rules.strong.exec(src)) {
  534. src = src.substring(cap[0].length);
  535. out += '<strong>'
  536. + this.output(cap[2] || cap[1])
  537. + '</strong>';
  538. continue;
  539. }
  540. // em
  541. if (cap = this.rules.em.exec(src)) {
  542. src = src.substring(cap[0].length);
  543. out += '<em>'
  544. + this.output(cap[2] || cap[1])
  545. + '</em>';
  546. continue;
  547. }
  548. // code
  549. if (cap = this.rules.code.exec(src)) {
  550. src = src.substring(cap[0].length);
  551. out += '<code>'
  552. + escape(cap[2], true)
  553. + '</code>';
  554. continue;
  555. }
  556. // br
  557. if (cap = this.rules.br.exec(src)) {
  558. src = src.substring(cap[0].length);
  559. out += '<br>';
  560. continue;
  561. }
  562. // del (gfm)
  563. if (cap = this.rules.del.exec(src)) {
  564. src = src.substring(cap[0].length);
  565. out += '<del>'
  566. + this.output(cap[1])
  567. + '</del>';
  568. continue;
  569. }
  570. // text
  571. if (cap = this.rules.text.exec(src)) {
  572. src = src.substring(cap[0].length);
  573. out += escape(cap[0]);
  574. continue;
  575. }
  576. if (src) {
  577. throw new
  578. Error('Infinite loop on byte: ' + src.charCodeAt(0));
  579. }
  580. }
  581. return out;
  582. };
  583. /**
  584. * Compile Link
  585. */
  586. InlineLexer.prototype.outputLink = function(cap, link) {
  587. if (cap[0][0] !== '!') {
  588. return '<a href="'
  589. + escape(link.href)
  590. + '"'
  591. + (link.title
  592. ? ' title="'
  593. + escape(link.title)
  594. + '"'
  595. : '')
  596. + '>'
  597. + this.output(cap[1])
  598. + '</a>';
  599. } else {
  600. return '<img src="'
  601. + escape(link.href)
  602. + '" alt="'
  603. + escape(cap[1])
  604. + '"'
  605. + (link.title
  606. ? ' title="'
  607. + escape(link.title)
  608. + '"'
  609. : '')
  610. + '>';
  611. }
  612. };
  613. /**
  614. * Mangle Links
  615. */
  616. InlineLexer.prototype.mangle = function(text) {
  617. var out = ''
  618. , l = text.length
  619. , i = 0
  620. , ch;
  621. for (; i < l; i++) {
  622. ch = text.charCodeAt(i);
  623. if (Math.random() > 0.5) {
  624. ch = 'x' + ch.toString(16);
  625. }
  626. out += '&#' + ch + ';';
  627. }
  628. return out;
  629. };
  630. /**
  631. * Parsing & Compiling
  632. */
  633. function Parser(options) {
  634. this.tokens = [];
  635. this.token = null;
  636. this.options = options || marked.defaults;
  637. }
  638. /**
  639. * Static Parse Method
  640. */
  641. Parser.parse = function(src, options) {
  642. var parser = new Parser(options);
  643. return parser.parse(src);
  644. };
  645. /**
  646. * Parse Loop
  647. */
  648. Parser.prototype.parse = function(src) {
  649. this.inline = new InlineLexer(src.links, this.options);
  650. this.tokens = src.reverse();
  651. var out = '';
  652. while (this.next()) {
  653. out += this.tok();
  654. }
  655. return out;
  656. };
  657. /**
  658. * Next Token
  659. */
  660. Parser.prototype.next = function() {
  661. return this.token = this.tokens.pop();
  662. };
  663. /**
  664. * Preview Next Token
  665. */
  666. Parser.prototype.peek = function() {
  667. return this.tokens[this.tokens.length-1] || 0;
  668. };
  669. /**
  670. * Parse Text Tokens
  671. */
  672. Parser.prototype.parseText = function() {
  673. var body = this.token.text;
  674. while (this.peek().type === 'text') {
  675. body += '\n' + this.next().text;
  676. }
  677. return this.inline.output(body);
  678. };
  679. /**
  680. * Parse Current Token
  681. */
  682. Parser.prototype.tok = function() {
  683. switch (this.token.type) {
  684. case 'space': {
  685. return '';
  686. }
  687. case 'hr': {
  688. return '<hr>\n';
  689. }
  690. case 'heading': {
  691. return '<h'
  692. + this.token.depth
  693. + '>'
  694. + this.inline.output(this.token.text)
  695. + '</h'
  696. + this.token.depth
  697. + '>\n';
  698. }
  699. case 'code': {
  700. if (this.options.highlight) {
  701. var code = this.options.highlight(this.token.text, this.token.lang);
  702. if (code != null && code !== this.token.text) {
  703. this.token.escaped = true;
  704. this.token.text = code;
  705. }
  706. }
  707. if (!this.token.escaped) {
  708. this.token.text = escape(this.token.text, true);
  709. }
  710. return '<pre><code'
  711. + (this.token.lang
  712. ? ' class="'
  713. + this.options.langPrefix
  714. + this.token.lang
  715. + '"'
  716. : '')
  717. + '>'
  718. + this.token.text
  719. + '</code></pre>\n';
  720. }
  721. case 'table': {
  722. var body = ''
  723. , heading
  724. , i
  725. , row
  726. , cell
  727. , j;
  728. // header
  729. body += '<thead>\n<tr>\n';
  730. for (i = 0; i < this.token.header.length; i++) {
  731. heading = this.inline.output(this.token.header[i]);
  732. body += this.token.align[i]
  733. ? '<th align="' + this.token.align[i] + '">' + heading + '</th>\n'
  734. : '<th>' + heading + '</th>\n';
  735. }
  736. body += '</tr>\n</thead>\n';
  737. // body
  738. body += '<tbody>\n'
  739. for (i = 0; i < this.token.cells.length; i++) {
  740. row = this.token.cells[i];
  741. body += '<tr>\n';
  742. for (j = 0; j < row.length; j++) {
  743. cell = this.inline.output(row[j]);
  744. body += this.token.align[j]
  745. ? '<td align="' + this.token.align[j] + '">' + cell + '</td>\n'
  746. : '<td>' + cell + '</td>\n';
  747. }
  748. body += '</tr>\n';
  749. }
  750. body += '</tbody>\n';
  751. return '<table>\n'
  752. + body
  753. + '</table>\n';
  754. }
  755. case 'blockquote_start': {
  756. var body = '';
  757. while (this.next().type !== 'blockquote_end') {
  758. body += this.tok();
  759. }
  760. return '<blockquote>\n'
  761. + body
  762. + '</blockquote>\n';
  763. }
  764. case 'list_start': {
  765. var type = this.token.ordered ? 'ol' : 'ul'
  766. , body = '';
  767. while (this.next().type !== 'list_end') {
  768. body += this.tok();
  769. }
  770. return '<'
  771. + type
  772. + '>\n'
  773. + body
  774. + '</'
  775. + type
  776. + '>\n';
  777. }
  778. case 'list_item_start': {
  779. var body = '';
  780. while (this.next().type !== 'list_item_end') {
  781. body += this.token.type === 'text'
  782. ? this.parseText()
  783. : this.tok();
  784. }
  785. return '<li>'
  786. + body
  787. + '</li>\n';
  788. }
  789. case 'loose_item_start': {
  790. var body = '';
  791. while (this.next().type !== 'list_item_end') {
  792. body += this.tok();
  793. }
  794. return '<li>'
  795. + body
  796. + '</li>\n';
  797. }
  798. case 'html': {
  799. return !this.token.pre && !this.options.pedantic
  800. ? this.inline.output(this.token.text)
  801. : this.token.text;
  802. }
  803. case 'paragraph': {
  804. return '<p>'
  805. + this.inline.output(this.token.text)
  806. + '</p>\n';
  807. }
  808. case 'text': {
  809. return '<p>'
  810. + this.parseText()
  811. + '</p>\n';
  812. }
  813. }
  814. };
  815. /**
  816. * Helpers
  817. */
  818. function escape(html, encode) {
  819. return html
  820. .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
  821. .replace(/</g, '&lt;')
  822. .replace(/>/g, '&gt;')
  823. .replace(/"/g, '&quot;')
  824. .replace(/'/g, '&#39;');
  825. }
  826. function replace(regex, opt) {
  827. regex = regex.source;
  828. opt = opt || '';
  829. return function self(name, val) {
  830. if (!name) return new RegExp(regex, opt);
  831. val = val.source || val;
  832. val = val.replace(/(^|[^\[])\^/g, '$1');
  833. regex = regex.replace(name, val);
  834. return self;
  835. };
  836. }
  837. function noop() {}
  838. noop.exec = noop;
  839. function merge(obj) {
  840. var i = 1
  841. , target
  842. , key;
  843. for (; i < arguments.length; i++) {
  844. target = arguments[i];
  845. for (key in target) {
  846. if (Object.prototype.hasOwnProperty.call(target, key)) {
  847. obj[key] = target[key];
  848. }
  849. }
  850. }
  851. return obj;
  852. }
  853. /**
  854. * Marked
  855. */
  856. function marked(src, opt) {
  857. try {
  858. if (opt) opt = merge({}, marked.defaults, opt);
  859. return Parser.parse(Lexer.lex(src, opt), opt);
  860. } catch (e) {
  861. e.message += '\nPlease report this to https://github.com/chjj/marked.';
  862. if ((opt || marked.defaults).silent) {
  863. return '<p>An error occured:</p><pre>'
  864. + escape(e.message + '', true)
  865. + '</pre>';
  866. }
  867. throw e;
  868. }
  869. }
  870. /**
  871. * Options
  872. */
  873. marked.options =
  874. marked.setOptions = function(opt) {
  875. merge(marked.defaults, opt);
  876. return marked;
  877. };
  878. marked.defaults = {
  879. gfm: true,
  880. tables: true,
  881. breaks: false,
  882. pedantic: false,
  883. sanitize: false,
  884. smartLists: false,
  885. silent: false,
  886. highlight: null,
  887. langPrefix: 'lang-'
  888. };
  889. /**
  890. * Expose
  891. */
  892. marked.Parser = Parser;
  893. marked.parser = Parser.parse;
  894. marked.Lexer = Lexer;
  895. marked.lexer = Lexer.lex;
  896. marked.InlineLexer = InlineLexer;
  897. marked.inlineLexer = InlineLexer.output;
  898. marked.parse = marked;
  899. if (typeof exports === 'object') {
  900. module.exports = marked;
  901. } else if (typeof define === 'function' && define.amd) {
  902. define(function() { return marked; });
  903. } else {
  904. this.marked = marked;
  905. }
  906. }).call(function() {
  907. return this || (typeof window !== 'undefined' ? window : global);
  908. }());