├── main.ts ├── main_notypes.ts └── main_test.ts /main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * marked - a markdown parser 3 | * Copyright (c) 2011-2018, Christopher Jeffrey. (MIT Licensed) 4 | * https://github.com/markedjs/marked 5 | */ 6 | 7 | // Definitions adapted from 8 | // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/marked/index.d.ts 9 | // Original Definitions by: William Orr 10 | // BendingBender 11 | // CrossR 12 | // Mike Wickett 13 | 14 | "use strict"; 15 | 16 | interface MarkedOptions { 17 | /** 18 | * A prefix URL for any relative link. 19 | */ 20 | baseUrl?: string; 21 | 22 | /** 23 | * Enable GFM line breaks. This option requires the gfm option to be true. 24 | */ 25 | breaks?: boolean; 26 | 27 | /** 28 | * Enable GitHub flavored markdown. 29 | */ 30 | gfm?: boolean; 31 | 32 | /** 33 | * Include an id attribute when emitting headings. 34 | */ 35 | headerIds?: boolean; 36 | 37 | /** 38 | * Set the prefix for header tag ids. 39 | */ 40 | headerPrefix?: string; 41 | 42 | /** 43 | * A function to highlight code blocks. The function takes three arguments: code, lang, and callback. 44 | */ 45 | highlight?(code: string, lang: string, callback?: (error: any | undefined, code: string) => void): string; 46 | 47 | /** 48 | * Set the prefix for code block classes. 49 | */ 50 | langPrefix?: string; 51 | 52 | /** 53 | * Mangle autolinks (). 54 | */ 55 | mangle?: boolean; 56 | 57 | /** 58 | * Conform to obscure parts of markdown.pl as much as possible. Don't fix any of the original markdown bugs or poor behavior. 59 | */ 60 | pedantic?: boolean; 61 | 62 | /** 63 | * Type: object Default: new Renderer() 64 | * 65 | * An object containing functions to render tokens to HTML. 66 | */ 67 | renderer?: Renderer; 68 | 69 | /** 70 | * Sanitize the output. Ignore any HTML that has been input. 71 | */ 72 | sanitize?: boolean; 73 | 74 | /** 75 | * Optionally sanitize found HTML with a sanitizer function. 76 | */ 77 | sanitizer?(html: string): string; 78 | 79 | /** 80 | * Shows an HTML error message when rendering fails. 81 | */ 82 | silent?: boolean; 83 | 84 | /** 85 | * Use smarter list behavior than the original markdown. May eventually be default with the old behavior moved into pedantic. 86 | */ 87 | smartLists?: boolean; 88 | 89 | /** 90 | * Use "smart" typograhic punctuation for things like quotes and dashes. 91 | */ 92 | smartypants?: boolean; 93 | 94 | /** 95 | * Enable GFM tables. This option requires the gfm option to be true. 96 | */ 97 | tables?: boolean; 98 | 99 | /** 100 | * Generate closing slash for self-closing tags (
instead of
) 101 | */ 102 | xhtml?: boolean; 103 | } 104 | 105 | interface Rules { 106 | [ruleName: string]: any; 107 | } 108 | 109 | interface Link { 110 | href: string; 111 | title: string; 112 | } 113 | 114 | type TokensList = Token[] & { 115 | links: { 116 | [key: string]: { href: string; title: string; } 117 | } 118 | }; 119 | 120 | type Token = 121 | Tokens.Space 122 | | Tokens.Code 123 | | Tokens.Heading 124 | | Tokens.Table 125 | | Tokens.Hr 126 | | Tokens.BlockquoteStart 127 | | Tokens.BlockquoteEnd 128 | | Tokens.ListStart 129 | | Tokens.LooseItemStart 130 | | Tokens.ListItemStart 131 | | Tokens.ListItemEnd 132 | | Tokens.ListEnd 133 | | Tokens.Paragraph 134 | | Tokens.HTML 135 | | Tokens.Text; 136 | 137 | namespace Tokens { 138 | export interface Space { 139 | type: 'space'; 140 | } 141 | 142 | export interface Code { 143 | type: 'code'; 144 | lang?: string; 145 | text: string; 146 | escaped?: boolean; 147 | } 148 | 149 | export interface Heading { 150 | type: 'heading'; 151 | depth: number; 152 | text: string; 153 | } 154 | 155 | export interface Table { 156 | type: 'table'; 157 | header: string[]; 158 | align: Array<'center' | 'left' | 'right' | null>; 159 | cells: string[][]; 160 | } 161 | 162 | export interface Hr { 163 | type: 'hr'; 164 | } 165 | 166 | export interface BlockquoteStart { 167 | type: 'blockquote_start'; 168 | } 169 | 170 | export interface BlockquoteEnd { 171 | type: 'blockquote_end'; 172 | } 173 | 174 | export interface ListStart { 175 | type: 'list_start'; 176 | ordered: boolean; 177 | } 178 | 179 | export interface LooseItemStart { 180 | type: 'loose_item_start'; 181 | } 182 | 183 | export interface ListItemStart { 184 | type: 'list_item_start'; 185 | } 186 | 187 | export interface ListItemEnd { 188 | type: 'list_item_end'; 189 | } 190 | 191 | export interface ListEnd { 192 | type: 'list_end'; 193 | } 194 | 195 | export interface Paragraph { 196 | type: 'paragraph'; 197 | pre?: boolean; 198 | text: string; 199 | } 200 | 201 | export interface HTML { 202 | type: 'html'; 203 | pre: boolean; 204 | text: string; 205 | } 206 | 207 | export interface Text { 208 | type: 'text'; 209 | text: string; 210 | } 211 | } 212 | 213 | /** 214 | * Block-Level Grammar 215 | */ 216 | 217 | const block: Rules = { 218 | newline: /^\n+/, 219 | code: /^( {4}[^\n]+\n*)+/, 220 | fences: noop, 221 | hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, 222 | heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/, 223 | nptable: noop, 224 | blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, 225 | list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, 226 | html: '^ {0,3}(?:' // optional indentation 227 | + '<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)' // (1) 228 | + '|comment[^\\n]*(\\n+|$)' // (2) 229 | + '|<\\?[\\s\\S]*?\\?>\\n*' // (3) 230 | + '|\\n*' // (4) 231 | + '|\\n*' // (5) 232 | + '|)[\\s\\S]*?(?:\\n{2,}|$)' // (6) 233 | + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag 234 | + '|(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag 235 | + ')', 236 | def: /^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, 237 | table: noop, 238 | lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, 239 | paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/, 240 | text: /^[^\n]+/, 241 | }; 242 | 243 | block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/; 244 | block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; 245 | block.def = edit(block.def) 246 | .replace('label', block._label) 247 | .replace('title', block._title) 248 | .getRegex(); 249 | 250 | block.bullet = /(?:[*+-]|\d{1,9}\.)/; 251 | block.item = /^( *)(bull) ?[^\n]*(?:\n(?!\1bull ?)[^\n]*)*/; 252 | block.item = edit(block.item, 'gm') 253 | .replace(/bull/g, block.bullet) 254 | .getRegex(); 255 | 256 | block.list = edit(block.list) 257 | .replace(/bull/g, block.bullet) 258 | .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))') 259 | .replace('def', `\\n+(?=${block.def.source})`) 260 | .getRegex(); 261 | 262 | block._tag = 'address|article|aside|base|basefont|blockquote|body|caption' 263 | + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' 264 | + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' 265 | + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' 266 | + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' 267 | + '|track|ul'; 268 | block._comment = //; 269 | block.html = edit(block.html, 'i') 270 | .replace('comment', block._comment) 271 | .replace('tag', block._tag) 272 | .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/) 273 | .getRegex(); 274 | 275 | block.paragraph = edit(block.paragraph) 276 | .replace('hr', block.hr) 277 | .replace('heading', block.heading) 278 | .replace('lheading', block.lheading) 279 | .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks 280 | .getRegex(); 281 | 282 | block.blockquote = edit(block.blockquote) 283 | .replace('paragraph', block.paragraph) 284 | .getRegex(); 285 | 286 | /** 287 | * Normal Block Grammar 288 | */ 289 | 290 | block.normal = merge({}, block); 291 | 292 | /** 293 | * GFM Block Grammar 294 | */ 295 | 296 | block.gfm = merge({}, block.normal, { 297 | fences: /^ {0,3}(`{3,}|~{3,})([^`\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/, 298 | paragraph: /^/, 299 | heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ 300 | }); 301 | 302 | block.gfm.paragraph = edit(block.paragraph) 303 | .replace('(?!', `(?!${block.gfm.fences.source.replace('\\1', '\\2')}|${block.list.source.replace('\\1', '\\3')}|`) 304 | .getRegex(); 305 | 306 | /** 307 | * GFM + Tables Block Grammar 308 | */ 309 | 310 | block.tables = merge({}, block.gfm, { 311 | nptable: /^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/, 312 | table: /^ *\|(.+)\n *\|?( *[-:]+[-| :]*)(?:\n((?: *[^>\n ].*(?:\n|$))*)\n*|$)/ 313 | }); 314 | 315 | /** 316 | * Pedantic grammar 317 | */ 318 | 319 | block.pedantic = merge({}, block.normal, { 320 | html: edit( 321 | '^ *(?:comment *(?:\\n|\\s*$)' 322 | + '|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)' // closed tag 323 | + '|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))') 324 | .replace('comment', block._comment) 325 | .replace(/tag/g, '(?!(?:' 326 | + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' 327 | + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' 328 | + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b') 329 | .getRegex(), 330 | def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/ 331 | }); 332 | 333 | /** 334 | * Block Lexer 335 | */ 336 | 337 | class Lexer { 338 | /** 339 | * Expose Block Rules 340 | */ 341 | static rules = block; 342 | /** 343 | * Static Lex Method 344 | */ 345 | static lex(src: string, options?: MarkedOptions) { 346 | const lexer = new Lexer(options); 347 | return lexer.lex(src); 348 | }; 349 | 350 | tokens: TokensList; 351 | options?: MarkedOptions; 352 | rules: Rules; 353 | constructor(options?: MarkedOptions) { 354 | this.tokens = [] as TokensList; 355 | this.tokens.links = Object.create(null); 356 | this.options = options || marked.defaults; 357 | this.rules = block.normal; 358 | 359 | if (this.options.pedantic) { 360 | this.rules = block.pedantic; 361 | } else if (this.options.gfm) { 362 | if (this.options.tables) { 363 | this.rules = block.tables; 364 | } else { 365 | this.rules = block.gfm; 366 | } 367 | } 368 | } 369 | 370 | /** 371 | * Preprocessing 372 | */ 373 | 374 | lex(src: string) { 375 | src = src 376 | .replace(/\r\n|\r/g, '\n') 377 | .replace(/\t/g, ' ') 378 | .replace(/\u00a0/g, ' ') 379 | .replace(/\u2424/g, '\n'); 380 | 381 | return this.token(src, true); 382 | } 383 | 384 | /** 385 | * Lexing 386 | */ 387 | 388 | token(src: string, top: boolean) { 389 | src = src.replace(/^ +$/gm, ''); 390 | let next; 391 | let loose; 392 | let cap; 393 | let bull; 394 | let b; 395 | let item; 396 | let listStart; 397 | let listItems; 398 | let t; 399 | let space; 400 | let i; 401 | let tag; 402 | let l; 403 | let isordered; 404 | let istask; 405 | let ischecked; 406 | 407 | while (src) { 408 | // newline 409 | if (cap = this.rules.newline.exec(src)) { 410 | src = src.substring(cap[0].length); 411 | if (cap[0].length > 1) { 412 | this.tokens.push({ 413 | type: 'space' 414 | }); 415 | } 416 | } 417 | 418 | // code 419 | if (cap = this.rules.code.exec(src)) { 420 | src = src.substring(cap[0].length); 421 | cap = cap[0].replace(/^ {4}/gm, ''); 422 | this.tokens.push({ 423 | type: 'code', 424 | text: !this.options.pedantic 425 | ? rtrim(cap, '\n') 426 | : cap 427 | }); 428 | continue; 429 | } 430 | 431 | // fences (gfm) 432 | if (cap = this.rules.fences.exec(src)) { 433 | src = src.substring(cap[0].length); 434 | this.tokens.push({ 435 | type: 'code', 436 | lang: cap[2] ? cap[2].trim() : cap[2], 437 | text: cap[3] || '' 438 | }); 439 | continue; 440 | } 441 | 442 | // heading 443 | if (cap = this.rules.heading.exec(src)) { 444 | src = src.substring(cap[0].length); 445 | this.tokens.push({ 446 | type: 'heading', 447 | depth: cap[1].length, 448 | text: cap[2] 449 | }); 450 | continue; 451 | } 452 | 453 | // table no leading pipe (gfm) 454 | if (top && (cap = this.rules.nptable.exec(src))) { 455 | item = { 456 | type: 'table', 457 | header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), 458 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 459 | cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] 460 | }; 461 | 462 | if (item.header.length === item.align.length) { 463 | src = src.substring(cap[0].length); 464 | 465 | for (i = 0; i < item.align.length; i++) { 466 | if (/^ *-+: *$/.test(item.align[i])) { 467 | item.align[i] = 'right'; 468 | } else if (/^ *:-+: *$/.test(item.align[i])) { 469 | item.align[i] = 'center'; 470 | } else if (/^ *:-+ *$/.test(item.align[i])) { 471 | item.align[i] = 'left'; 472 | } else { 473 | item.align[i] = null; 474 | } 475 | } 476 | 477 | for (i = 0; i < item.cells.length; i++) { 478 | item.cells[i] = splitCells(item.cells[i], item.header.length); 479 | } 480 | 481 | this.tokens.push(item); 482 | 483 | continue; 484 | } 485 | } 486 | 487 | // hr 488 | if (cap = this.rules.hr.exec(src)) { 489 | src = src.substring(cap[0].length); 490 | this.tokens.push({ 491 | type: 'hr' 492 | }); 493 | continue; 494 | } 495 | 496 | // blockquote 497 | if (cap = this.rules.blockquote.exec(src)) { 498 | src = src.substring(cap[0].length); 499 | 500 | this.tokens.push({ 501 | type: 'blockquote_start' 502 | }); 503 | 504 | cap = cap[0].replace(/^ *> ?/gm, ''); 505 | 506 | // Pass `top` to keep the current 507 | // "toplevel" state. This is exactly 508 | // how markdown.pl works. 509 | this.token(cap, top); 510 | 511 | this.tokens.push({ 512 | type: 'blockquote_end' 513 | }); 514 | 515 | continue; 516 | } 517 | 518 | // list 519 | if (cap = this.rules.list.exec(src)) { 520 | src = src.substring(cap[0].length); 521 | bull = cap[2]; 522 | isordered = bull.length > 1; 523 | 524 | listStart = { 525 | type: 'list_start', 526 | ordered: isordered, 527 | start: isordered ? +bull : '', 528 | loose: false 529 | }; 530 | 531 | this.tokens.push(listStart); 532 | 533 | // Get each top-level item. 534 | cap = cap[0].match(this.rules.item); 535 | 536 | listItems = []; 537 | next = false; 538 | l = cap.length; 539 | i = 0; 540 | 541 | for (; i < l; i++) { 542 | item = cap[i]; 543 | 544 | // Remove the list item's bullet 545 | // so it is seen as the next token. 546 | space = item.length; 547 | item = item.replace(/^ *([*+-]|\d+\.) */, ''); 548 | 549 | // Outdent whatever the 550 | // list item contains. Hacky. 551 | if (~item.indexOf('\n ')) { 552 | space -= item.length; 553 | item = !this.options.pedantic 554 | ? item.replace(new RegExp(`^ {1,${space}}`, 'gm'), '') 555 | : item.replace(/^ {1,4}/gm, ''); 556 | } 557 | 558 | // Determine whether the next list item belongs here. 559 | // Backpedal if it does not belong in this list. 560 | if (i !== l - 1) { 561 | b = block.bullet.exec(cap[i + 1])[0]; 562 | if (bull.length > 1 ? b.length === 1 563 | : (b.length > 1 || (this.options.smartLists && b !== bull))) { 564 | src = cap.slice(i + 1).join('\n') + src; 565 | i = l - 1; 566 | } 567 | } 568 | 569 | // Determine whether item is loose or not. 570 | // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ 571 | // for discount behavior. 572 | loose = next || /\n\n(?!\s*$)/.test(item); 573 | if (i !== l - 1) { 574 | next = item.charAt(item.length - 1) === '\n'; 575 | if (!loose) loose = next; 576 | } 577 | 578 | if (loose) { 579 | listStart.loose = true; 580 | } 581 | 582 | // Check for task list items 583 | istask = /^\[[ xX]\] /.test(item); 584 | ischecked = undefined; 585 | if (istask) { 586 | ischecked = item[1] !== ' '; 587 | item = item.replace(/^\[[ xX]\] +/, ''); 588 | } 589 | 590 | t = { 591 | type: 'list_item_start', 592 | task: istask, 593 | checked: ischecked, 594 | loose 595 | }; 596 | 597 | listItems.push(t); 598 | this.tokens.push(t); 599 | 600 | // Recurse. 601 | this.token(item, false); 602 | 603 | this.tokens.push({ 604 | type: 'list_item_end' 605 | }); 606 | } 607 | 608 | if (listStart.loose) { 609 | l = listItems.length; 610 | i = 0; 611 | for (; i < l; i++) { 612 | listItems[i].loose = true; 613 | } 614 | } 615 | 616 | this.tokens.push({ 617 | type: 'list_end' 618 | }); 619 | 620 | continue; 621 | } 622 | 623 | // html 624 | if (cap = this.rules.html.exec(src)) { 625 | src = src.substring(cap[0].length); 626 | this.tokens.push({ 627 | type: this.options.sanitize 628 | ? 'paragraph' 629 | : 'html', 630 | pre: !this.options.sanitizer 631 | && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), 632 | text: cap[0] 633 | } as Token); 634 | continue; 635 | } 636 | 637 | // def 638 | if (top && (cap = this.rules.def.exec(src))) { 639 | src = src.substring(cap[0].length); 640 | if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1); 641 | tag = cap[1].toLowerCase().replace(/\s+/g, ' '); 642 | if (!this.tokens.links[tag]) { 643 | this.tokens.links[tag] = { 644 | href: cap[2], 645 | title: cap[3] 646 | }; 647 | } 648 | continue; 649 | } 650 | 651 | // table (gfm) 652 | if (top && (cap = this.rules.table.exec(src))) { 653 | item = { 654 | type: 'table', 655 | header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), 656 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 657 | cells: cap[3] ? cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') : [] 658 | }; 659 | 660 | if (item.header.length === item.align.length) { 661 | src = src.substring(cap[0].length); 662 | 663 | for (i = 0; i < item.align.length; i++) { 664 | if (/^ *-+: *$/.test(item.align[i])) { 665 | item.align[i] = 'right'; 666 | } else if (/^ *:-+: *$/.test(item.align[i])) { 667 | item.align[i] = 'center'; 668 | } else if (/^ *:-+ *$/.test(item.align[i])) { 669 | item.align[i] = 'left'; 670 | } else { 671 | item.align[i] = null; 672 | } 673 | } 674 | 675 | for (i = 0; i < item.cells.length; i++) { 676 | item.cells[i] = splitCells( 677 | item.cells[i].replace(/^ *\| *| *\| *$/g, ''), 678 | item.header.length); 679 | } 680 | 681 | this.tokens.push(item); 682 | 683 | continue; 684 | } 685 | } 686 | 687 | // lheading 688 | if (cap = this.rules.lheading.exec(src)) { 689 | src = src.substring(cap[0].length); 690 | this.tokens.push({ 691 | type: 'heading', 692 | depth: cap[2] === '=' ? 1 : 2, 693 | text: cap[1] 694 | }); 695 | continue; 696 | } 697 | 698 | // top-level paragraph 699 | if (top && (cap = this.rules.paragraph.exec(src))) { 700 | src = src.substring(cap[0].length); 701 | this.tokens.push({ 702 | type: 'paragraph', 703 | text: cap[1].charAt(cap[1].length - 1) === '\n' 704 | ? cap[1].slice(0, -1) 705 | : cap[1] 706 | }); 707 | continue; 708 | } 709 | 710 | // text 711 | if (cap = this.rules.text.exec(src)) { 712 | // Top-level should never reach here. 713 | src = src.substring(cap[0].length); 714 | this.tokens.push({ 715 | type: 'text', 716 | text: cap[0] 717 | }); 718 | continue; 719 | } 720 | 721 | if (src) { 722 | throw new Error(`Infinite loop on byte: ${src.charCodeAt(0)}`); 723 | } 724 | } 725 | 726 | return this.tokens; 727 | } 728 | } 729 | 730 | /** 731 | * Inline-Level Grammar 732 | */ 733 | 734 | const inline: Rules = { 735 | escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/, 736 | autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, 737 | url: noop, 738 | tag: '^comment' 739 | + '|^' // self-closing tag 740 | + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag 741 | + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. 742 | + '|^' // declaration, e.g. 743 | + '|^', // CDATA section 744 | link: /^!?\[(label)\]\(href(?:\s+(title))?\s*\)/, 745 | reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/, 746 | nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/, 747 | strong: /^__([^\s_])__(?!_)|^\*\*([^\s*])\*\*(?!\*)|^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)/, 748 | em: /^_([^\s_])_(?!_)|^\*([^\s*"<\[])\*(?!\*)|^_([^\s][\s\S]*?[^\s_])_(?!_|[^\spunctuation])|^_([^\s_][\s\S]*?[^\s])_(?!_|[^\spunctuation])|^\*([^\s"<\[][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*"<\[][\s\S]*?[^\s])\*(?!\*)/, 749 | code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/, 750 | br: /^( {2,}|\\)\n(?!\s*$)/, 751 | del: noop, 752 | text: /^(`+|[^`])[\s\S]*?(?=[\\?@\\[^_{|}~'; 758 | inline.em = edit(inline.em).replace(/punctuation/g, inline._punctuation).getRegex(); 759 | 760 | inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g; 761 | 762 | inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; 763 | inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/; 764 | inline.autolink = edit(inline.autolink) 765 | .replace('scheme', inline._scheme) 766 | .replace('email', inline._email) 767 | .getRegex(); 768 | 769 | inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/; 770 | 771 | inline.tag = edit(inline.tag) 772 | .replace('comment', block._comment) 773 | .replace('attribute', inline._attribute) 774 | .getRegex(); 775 | 776 | inline._label = /(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/; 777 | inline._href = /\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f\\]*\)|[^\s\x00-\x1f()\\])*?)/; 778 | inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; 779 | 780 | inline.link = edit(inline.link) 781 | .replace('label', inline._label) 782 | .replace('href', inline._href) 783 | .replace('title', inline._title) 784 | .getRegex(); 785 | 786 | inline.reflink = edit(inline.reflink) 787 | .replace('label', inline._label) 788 | .getRegex(); 789 | 790 | /** 791 | * Normal Inline Grammar 792 | */ 793 | 794 | inline.normal = merge({}, inline); 795 | 796 | /** 797 | * Pedantic Inline Grammar 798 | */ 799 | 800 | inline.pedantic = merge({}, inline.normal, { 801 | strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, 802 | em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/, 803 | link: edit(/^!?\[(label)\]\((.*?)\)/) 804 | .replace('label', inline._label) 805 | .getRegex(), 806 | reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/) 807 | .replace('label', inline._label) 808 | .getRegex() 809 | }); 810 | 811 | /** 812 | * GFM Inline Grammar 813 | */ 814 | 815 | inline.gfm = merge({}, inline.normal, { 816 | escape: edit(inline.escape).replace('])', '~|])').getRegex(), 817 | _extended_email: /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/, 818 | url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, 819 | _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, 820 | del: /^~+(?=\S)([\s\S]*?\S)~+/, 821 | text: edit(inline.text) 822 | .replace(']|', '~]|') 823 | .replace('|$', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|$') 824 | .getRegex() 825 | }); 826 | 827 | inline.gfm.url = edit(inline.gfm.url, 'i') 828 | .replace('email', inline.gfm._extended_email) 829 | .getRegex(); 830 | /** 831 | * GFM + Line Breaks Inline Grammar 832 | */ 833 | 834 | inline.breaks = merge({}, inline.gfm, { 835 | br: edit(inline.br).replace('{2,}', '*').getRegex(), 836 | text: edit(inline.gfm.text).replace('{2,}', '*').getRegex() 837 | }); 838 | 839 | /** 840 | * Inline Lexer & Compiler 841 | */ 842 | 843 | class InlineLexer { 844 | /** 845 | * Expose Inline Rules 846 | */ 847 | 848 | static rules = inline; 849 | 850 | /** 851 | * Static Lexing/Compiling Method 852 | */ 853 | 854 | static output(src: string, links: {[key: string]: Link}, options?: MarkedOptions) { 855 | const inline = new InlineLexer(links, options); 856 | return inline.output(src); 857 | }; 858 | 859 | static escapes = 860 | (text?: string) => text ? text.replace(InlineLexer.rules._escapes, '$1') : text; 861 | 862 | options: MarkedOptions; 863 | links: {[key: string]: Link}; 864 | rules: Rules; 865 | renderer: any; 866 | inLink: any; 867 | inRawBlock: any; 868 | 869 | constructor(links: {[key: string]: Link}, options?: MarkedOptions) { 870 | this.options = options || marked.defaults; 871 | this.links = links; 872 | this.rules = inline.normal; 873 | this.renderer = this.options.renderer || new Renderer(); 874 | this.renderer.options = this.options; 875 | 876 | if (!this.links) { 877 | throw new Error('Tokens array requires a `links` property.'); 878 | } 879 | 880 | if (this.options.pedantic) { 881 | this.rules = inline.pedantic; 882 | } else if (this.options.gfm) { 883 | if (this.options.breaks) { 884 | this.rules = inline.breaks; 885 | } else { 886 | this.rules = inline.gfm; 887 | } 888 | } 889 | } 890 | 891 | /** 892 | * Lexing/Compiling 893 | */ 894 | 895 | output(src: string) { 896 | let out = ''; 897 | let link; 898 | let text; 899 | let href; 900 | let title; 901 | let cap; 902 | let prevCapZero; 903 | 904 | while (src) { 905 | // escape 906 | if (cap = this.rules.escape.exec(src)) { 907 | src = src.substring(cap[0].length); 908 | out += escape(cap[1]); 909 | continue; 910 | } 911 | 912 | // tag 913 | if (cap = this.rules.tag.exec(src)) { 914 | if (!this.inLink && /^/i.test(cap[0])) { 917 | this.inLink = false; 918 | } 919 | if (!this.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { 920 | this.inRawBlock = true; 921 | } else if (this.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { 922 | this.inRawBlock = false; 923 | } 924 | 925 | src = src.substring(cap[0].length); 926 | out += this.options.sanitize 927 | ? this.options.sanitizer 928 | ? this.options.sanitizer(cap[0]) 929 | : escape(cap[0]) 930 | : cap[0]; 931 | continue; 932 | } 933 | 934 | // link 935 | if (cap = this.rules.link.exec(src)) { 936 | src = src.substring(cap[0].length); 937 | this.inLink = true; 938 | href = cap[2]; 939 | if (this.options.pedantic) { 940 | link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); 941 | 942 | if (link) { 943 | href = link[1]; 944 | title = link[3]; 945 | } else { 946 | title = ''; 947 | } 948 | } else { 949 | title = cap[3] ? cap[3].slice(1, -1) : ''; 950 | } 951 | href = href.trim().replace(/^<([\s\S]*)>$/, '$1'); 952 | out += this.outputLink(cap, { 953 | href: InlineLexer.escapes(href), 954 | title: InlineLexer.escapes(title) 955 | }); 956 | this.inLink = false; 957 | continue; 958 | } 959 | 960 | // reflink, nolink 961 | if ((cap = this.rules.reflink.exec(src)) 962 | || (cap = this.rules.nolink.exec(src))) { 963 | src = src.substring(cap[0].length); 964 | link = (cap[2] || cap[1]).replace(/\s+/g, ' '); 965 | link = this.links[link.toLowerCase()]; 966 | if (!link || !link.href) { 967 | out += cap[0].charAt(0); 968 | src = cap[0].substring(1) + src; 969 | continue; 970 | } 971 | this.inLink = true; 972 | out += this.outputLink(cap, link); 973 | this.inLink = false; 974 | continue; 975 | } 976 | 977 | // strong 978 | if (cap = this.rules.strong.exec(src)) { 979 | src = src.substring(cap[0].length); 980 | out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1])); 981 | continue; 982 | } 983 | 984 | // em 985 | if (cap = this.rules.em.exec(src)) { 986 | src = src.substring(cap[0].length); 987 | out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1])); 988 | continue; 989 | } 990 | 991 | // code 992 | if (cap = this.rules.code.exec(src)) { 993 | src = src.substring(cap[0].length); 994 | out += this.renderer.codespan(escape(cap[2].trim(), true)); 995 | continue; 996 | } 997 | 998 | // br 999 | if (cap = this.rules.br.exec(src)) { 1000 | src = src.substring(cap[0].length); 1001 | out += this.renderer.br(); 1002 | continue; 1003 | } 1004 | 1005 | // del (gfm) 1006 | if (cap = this.rules.del.exec(src)) { 1007 | src = src.substring(cap[0].length); 1008 | out += this.renderer.del(this.output(cap[1])); 1009 | continue; 1010 | } 1011 | 1012 | // autolink 1013 | if (cap = this.rules.autolink.exec(src)) { 1014 | src = src.substring(cap[0].length); 1015 | if (cap[2] === '@') { 1016 | text = escape(this.mangle(cap[1])); 1017 | href = `mailto:${text}`; 1018 | } else { 1019 | text = escape(cap[1]); 1020 | href = text; 1021 | } 1022 | out += this.renderer.link(href, null, text); 1023 | continue; 1024 | } 1025 | 1026 | // url (gfm) 1027 | if (!this.inLink && (cap = this.rules.url.exec(src))) { 1028 | if (cap[2] === '@') { 1029 | text = escape(cap[0]); 1030 | href = `mailto:${text}`; 1031 | } else { 1032 | // do extended autolink path validation 1033 | do { 1034 | prevCapZero = cap[0]; 1035 | cap[0] = this.rules._backpedal.exec(cap[0])[0]; 1036 | } while (prevCapZero !== cap[0]); 1037 | text = escape(cap[0]); 1038 | if (cap[1] === 'www.') { 1039 | href = `http://${text}`; 1040 | } else { 1041 | href = text; 1042 | } 1043 | } 1044 | src = src.substring(cap[0].length); 1045 | out += this.renderer.link(href, null, text); 1046 | continue; 1047 | } 1048 | 1049 | // text 1050 | if (cap = this.rules.text.exec(src)) { 1051 | src = src.substring(cap[0].length); 1052 | if (this.inRawBlock) { 1053 | out += this.renderer.text(cap[0]); 1054 | } else { 1055 | out += this.renderer.text(escape(this.smartypants(cap[0]))); 1056 | } 1057 | continue; 1058 | } 1059 | 1060 | if (src) { 1061 | throw new Error(`Infinite loop on byte: ${src.charCodeAt(0)}`); 1062 | } 1063 | } 1064 | 1065 | return out; 1066 | } 1067 | 1068 | /** 1069 | * Compile Link 1070 | */ 1071 | 1072 | outputLink(cap: string, link: Link) { 1073 | const href = link.href; 1074 | const title = link.title ? escape(link.title) : null; 1075 | 1076 | return cap[0].charAt(0) !== '!' 1077 | ? this.renderer.link(href, title, this.output(cap[1])) 1078 | : this.renderer.image(href, title, escape(cap[1])); 1079 | } 1080 | 1081 | /** 1082 | * Smartypants Transformations 1083 | */ 1084 | 1085 | smartypants(text: string) { 1086 | if (!this.options.smartypants) return text; 1087 | return text 1088 | // em-dashes 1089 | .replace(/---/g, '\u2014') 1090 | // en-dashes 1091 | .replace(/--/g, '\u2013') 1092 | // opening singles 1093 | .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') 1094 | // closing singles & apostrophes 1095 | .replace(/'/g, '\u2019') 1096 | // opening doubles 1097 | .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') 1098 | // closing doubles 1099 | .replace(/"/g, '\u201d') 1100 | // ellipses 1101 | .replace(/\.{3}/g, '\u2026'); 1102 | } 1103 | 1104 | /** 1105 | * Mangle Links 1106 | */ 1107 | 1108 | mangle(text: string) { 1109 | if (!this.options.mangle) return text; 1110 | let out = ''; 1111 | const l = text.length; 1112 | let i = 0; 1113 | let ch; 1114 | 1115 | for (; i < l; i++) { 1116 | ch = text.charCodeAt(i); 1117 | if (Math.random() > 0.5) { 1118 | ch = `x${ch.toString(16)}`; 1119 | } 1120 | out += `&#${ch};`; 1121 | } 1122 | 1123 | return out; 1124 | } 1125 | } 1126 | 1127 | /** 1128 | * Renderer 1129 | */ 1130 | 1131 | class Renderer { 1132 | options: MarkedOptions; 1133 | 1134 | constructor(options?: MarkedOptions) { 1135 | this.options = options || marked.defaults; 1136 | } 1137 | 1138 | code(code: string, infostring: string, escaped: boolean) { 1139 | const lang = (infostring || '').match(/\S*/)[0]; 1140 | if (this.options.highlight) { 1141 | const out = this.options.highlight(code, lang); 1142 | if (out != null && out !== code) { 1143 | escaped = true; 1144 | code = out; 1145 | } 1146 | } 1147 | 1148 | if (!lang) { 1149 | return `
${escaped ? code : escape(code, true)}
`; 1150 | } 1151 | 1152 | return `
${escaped ? code : escape(code, true)}
\n`; 1153 | } 1154 | 1155 | blockquote(quote: string) { 1156 | return `
\n${quote}
\n`; 1157 | } 1158 | 1159 | html(html: string) { 1160 | return html; 1161 | } 1162 | 1163 | heading(text: string, level: number, raw: string, slugger?: Slugger) { 1164 | if (this.options.headerIds) { 1165 | return `${text}\n`; 1166 | } 1167 | // ignore IDs 1168 | return `${text}\n`; 1169 | } 1170 | 1171 | hr() { 1172 | return this.options.xhtml ? '
\n' : '
\n'; 1173 | } 1174 | 1175 | list(body: string, ordered: boolean, start: number) { 1176 | const type = ordered ? 'ol' : 'ul'; 1177 | const startatt = (ordered && start !== 1) ? (` start="${start}"`) : ''; 1178 | return `<${type}${startatt}>\n${body}\n`; 1179 | } 1180 | 1181 | listitem(text: string) { 1182 | return `
  • ${text}
  • \n`; 1183 | } 1184 | 1185 | checkbox(checked: boolean) { 1186 | return ` `; 1187 | } 1188 | 1189 | paragraph(text: string) { 1190 | return `

    ${text}

    \n`; 1191 | } 1192 | 1193 | table(header: string, body: string) { 1194 | if (body) body = `${body}`; 1195 | 1196 | return `\n\n${header}\n${body}
    \n`; 1197 | } 1198 | 1199 | tablerow(content: string) { 1200 | return `\n${content}\n`; 1201 | } 1202 | 1203 | tablecell(content: string, {header, align}) { 1204 | const type = header ? 'th' : 'td'; 1205 | const tag = align 1206 | ? `<${type} align="${align}">` 1207 | : `<${type}>`; 1208 | return `${tag + content}\n`; 1209 | } 1210 | 1211 | // span level renderer 1212 | strong(text: string) { 1213 | return `${text}`; 1214 | } 1215 | 1216 | em(text: string) { 1217 | return `${text}`; 1218 | } 1219 | 1220 | codespan(text: string) { 1221 | return `${text}`; 1222 | } 1223 | 1224 | br() { 1225 | return this.options.xhtml ? '
    ' : '
    '; 1226 | } 1227 | 1228 | del(text: string) { 1229 | return `${text}`; 1230 | } 1231 | 1232 | link(href: string, title: string, text: string) { 1233 | href = cleanUrl(this.options.sanitize, this.options.baseUrl, href); 1234 | if (href === null) { 1235 | return text; 1236 | } 1237 | let out = `
    ${text}`; 1242 | return out; 1243 | } 1244 | 1245 | image(href: string, title: string, text: string) { 1246 | href = cleanUrl(this.options.sanitize, this.options.baseUrl, href); 1247 | if (href === null) { 1248 | return text; 1249 | } 1250 | 1251 | let out = `${text}' : '>'; 1256 | return out; 1257 | } 1258 | 1259 | text(text: string) { 1260 | return text; 1261 | } 1262 | } 1263 | 1264 | /** 1265 | * TextRenderer 1266 | * returns only the textual part of the token 1267 | */ 1268 | 1269 | class TextRenderer { 1270 | // no need for block level renderers 1271 | strong = (text: string) => text; 1272 | em = (text: string) => text; 1273 | codespan = (text: string) => text; 1274 | del = (text: string) => text; 1275 | text = (text: string) => text; 1276 | 1277 | link = (href: string, title: string, text: string) => `${text}`; 1278 | image = (href: string, title: string, text: string) => `${text}`; 1279 | 1280 | br() { 1281 | return ''; 1282 | } 1283 | } 1284 | 1285 | /** 1286 | * Parsing & Compiling 1287 | */ 1288 | 1289 | class Parser { 1290 | /** 1291 | * Static Parse Method 1292 | */ 1293 | static parse(src: TokensList, options?: MarkedOptions) { 1294 | const parser = new Parser(options); 1295 | return parser.parse(src); 1296 | }; 1297 | 1298 | tokens: any; 1299 | token: any; 1300 | options: MarkedOptions; 1301 | renderer: Renderer; 1302 | slugger: Slugger; 1303 | inline: any; 1304 | inlineText: any; 1305 | 1306 | constructor(options?: MarkedOptions) { 1307 | this.tokens = []; 1308 | this.token = null; 1309 | this.options = options || marked.defaults; 1310 | this.options.renderer = this.options.renderer || new Renderer(); 1311 | this.renderer = this.options.renderer; 1312 | this.renderer.options = this.options; 1313 | this.slugger = new Slugger(); 1314 | } 1315 | 1316 | /** 1317 | * Parse Loop 1318 | */ 1319 | 1320 | parse(src: TokensList) { 1321 | this.inline = new InlineLexer(src.links, this.options); 1322 | // use an InlineLexer with a TextRenderer to extract pure text 1323 | this.inlineText = new InlineLexer( 1324 | src.links, 1325 | merge({}, this.options, {renderer: new TextRenderer()}) 1326 | ); 1327 | this.tokens = src.reverse(); 1328 | 1329 | let out = ''; 1330 | while (this.next()) { 1331 | out += this.tok(); 1332 | } 1333 | 1334 | return out; 1335 | } 1336 | 1337 | /** 1338 | * Next Token 1339 | */ 1340 | 1341 | next() { 1342 | return this.token = this.tokens.pop(); 1343 | } 1344 | 1345 | /** 1346 | * Preview Next Token 1347 | */ 1348 | 1349 | peek() { 1350 | return this.tokens[this.tokens.length - 1] || 0; 1351 | } 1352 | 1353 | /** 1354 | * Parse Text Tokens 1355 | */ 1356 | 1357 | parseText() { 1358 | let body = this.token.text; 1359 | 1360 | while (this.peek().type === 'text') { 1361 | body += `\n${this.next().text}`; 1362 | } 1363 | 1364 | return this.inline.output(body); 1365 | } 1366 | 1367 | /** 1368 | * Parse Current Token 1369 | */ 1370 | 1371 | tok() { 1372 | switch (this.token.type) { 1373 | case 'space': { 1374 | return ''; 1375 | } 1376 | case 'hr': { 1377 | return this.renderer.hr(); 1378 | } 1379 | case 'heading': { 1380 | return this.renderer.heading( 1381 | this.inline.output(this.token.text), 1382 | this.token.depth, 1383 | unescape(this.inlineText.output(this.token.text)), 1384 | this.slugger); 1385 | } 1386 | case 'code': { 1387 | return this.renderer.code(this.token.text, 1388 | this.token.lang, 1389 | this.token.escaped); 1390 | } 1391 | case 'table': { 1392 | let header = ''; 1393 | var body = ''; 1394 | let i; 1395 | let row; 1396 | let cell; 1397 | let j; 1398 | 1399 | // header 1400 | cell = ''; 1401 | for (i = 0; i < this.token.header.length; i++) { 1402 | cell += this.renderer.tablecell( 1403 | this.inline.output(this.token.header[i]), 1404 | { header: true, align: this.token.align[i] } 1405 | ); 1406 | } 1407 | header += this.renderer.tablerow(cell); 1408 | 1409 | for (i = 0; i < this.token.cells.length; i++) { 1410 | row = this.token.cells[i]; 1411 | 1412 | cell = ''; 1413 | for (j = 0; j < row.length; j++) { 1414 | cell += this.renderer.tablecell( 1415 | this.inline.output(row[j]), 1416 | { header: false, align: this.token.align[j] } 1417 | ); 1418 | } 1419 | 1420 | body += this.renderer.tablerow(cell); 1421 | } 1422 | return this.renderer.table(header, body); 1423 | } 1424 | case 'blockquote_start': { 1425 | body = ''; 1426 | 1427 | while (this.next().type !== 'blockquote_end') { 1428 | body += this.tok(); 1429 | } 1430 | 1431 | return this.renderer.blockquote(body); 1432 | } 1433 | case 'list_start': { 1434 | body = ''; 1435 | const ordered = this.token.ordered; 1436 | const start = this.token.start; 1437 | 1438 | while (this.next().type !== 'list_end') { 1439 | body += this.tok(); 1440 | } 1441 | 1442 | return this.renderer.list(body, ordered, start); 1443 | } 1444 | case 'list_item_start': { 1445 | body = ''; 1446 | const loose = this.token.loose; 1447 | 1448 | if (this.token.task) { 1449 | body += this.renderer.checkbox(this.token.checked); 1450 | } 1451 | 1452 | while (this.next().type !== 'list_item_end') { 1453 | body += !loose && this.token.type === 'text' 1454 | ? this.parseText() 1455 | : this.tok(); 1456 | } 1457 | 1458 | return this.renderer.listitem(body); 1459 | } 1460 | case 'html': { 1461 | // TODO parse inline content if parameter markdown=1 1462 | return this.renderer.html(this.token.text); 1463 | } 1464 | case 'paragraph': { 1465 | return this.renderer.paragraph(this.inline.output(this.token.text)); 1466 | } 1467 | case 'text': { 1468 | return this.renderer.paragraph(this.parseText()); 1469 | } 1470 | default: { 1471 | const errMsg = `Token with "${this.token.type}" type was not found.`; 1472 | if (this.options.silent) { 1473 | console.log(errMsg); 1474 | } else { 1475 | throw new Error(errMsg); 1476 | } 1477 | } 1478 | } 1479 | } 1480 | } 1481 | 1482 | /** 1483 | * Slugger generates header id 1484 | */ 1485 | 1486 | class Slugger { 1487 | seen: {[key: string]: number}; 1488 | 1489 | constructor() { 1490 | this.seen = {}; 1491 | } 1492 | 1493 | /** 1494 | * Convert string to unique id 1495 | */ 1496 | 1497 | slug(value: string) { 1498 | let slug = value 1499 | .toLowerCase() 1500 | .trim() 1501 | .replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '') 1502 | .replace(/\s/g, '-'); 1503 | 1504 | if (this.seen.hasOwnProperty(slug)) { 1505 | const originalSlug = slug; 1506 | do { 1507 | this.seen[originalSlug]++; 1508 | slug = `${originalSlug}-${this.seen[originalSlug]}`; 1509 | } while (this.seen.hasOwnProperty(slug)); 1510 | } 1511 | this.seen[slug] = 0; 1512 | 1513 | return slug; 1514 | } 1515 | } 1516 | 1517 | /** 1518 | * Helpers 1519 | */ 1520 | 1521 | function escape(html: string, encode?: boolean) { 1522 | if (encode) { 1523 | if (escape.escapeTest.test(html)) { 1524 | return html.replace(escape.escapeReplace, ch => escape.replacements[ch]); 1525 | } 1526 | } else { 1527 | if (escape.escapeTestNoEncode.test(html)) { 1528 | return html.replace(escape.escapeReplaceNoEncode, ch => escape.replacements[ch]); 1529 | } 1530 | } 1531 | 1532 | return html; 1533 | } 1534 | 1535 | escape.escapeTest = /[&<>"']/; 1536 | escape.escapeReplace = /[&<>"']/g; 1537 | escape.replacements = { 1538 | '&': '&', 1539 | '<': '<', 1540 | '>': '>', 1541 | '"': '"', 1542 | "'": ''' 1543 | }; 1544 | 1545 | escape.escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/; 1546 | escape.escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g; 1547 | 1548 | function unescape(html: string) { 1549 | // explicitly match decimal, hex, and named HTML entities 1550 | return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, (_, n) => { 1551 | n = n.toLowerCase(); 1552 | if (n === 'colon') return ':'; 1553 | if (n.charAt(0) === '#') { 1554 | return n.charAt(1) === 'x' 1555 | ? String.fromCharCode(parseInt(n.substring(2), 16)) 1556 | : String.fromCharCode(+n.substring(1)); 1557 | } 1558 | return ''; 1559 | }); 1560 | } 1561 | 1562 | function edit(regex, opt?: string) { 1563 | regex = regex.source || regex; 1564 | opt = opt || ''; 1565 | return { 1566 | replace(name, val) { 1567 | val = val.source || val; 1568 | val = val.replace(/(^|[^\[])\^/g, '$1'); 1569 | regex = regex.replace(name, val); 1570 | return this; 1571 | }, 1572 | getRegex() { 1573 | return new RegExp(regex, opt); 1574 | } 1575 | }; 1576 | } 1577 | 1578 | function cleanUrl(sanitize: boolean, base: string, href: string) { 1579 | if (sanitize) { 1580 | try { 1581 | var prot = decodeURIComponent(unescape(href)) 1582 | .replace(/[^\w:]/g, '') 1583 | .toLowerCase(); 1584 | } catch (e) { 1585 | return null; 1586 | } 1587 | if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { 1588 | return null; 1589 | } 1590 | } 1591 | if (base && !originIndependentUrl.test(href)) { 1592 | href = resolveUrl(base, href); 1593 | } 1594 | try { 1595 | href = encodeURI(href).replace(/%25/g, '%'); 1596 | } catch (e) { 1597 | return null; 1598 | } 1599 | return href; 1600 | } 1601 | 1602 | function resolveUrl(base: string, href: string) { 1603 | if (!baseUrls[` ${base}`]) { 1604 | // we can ignore everything in base after the last slash of its path component, 1605 | // but we might need to add _that_ 1606 | // https://tools.ietf.org/html/rfc3986#section-3 1607 | if (/^[^:]+:\/*[^/]*$/.test(base)) { 1608 | baseUrls[` ${base}`] = `${base}/`; 1609 | } else { 1610 | baseUrls[` ${base}`] = rtrim(base, '/', true); 1611 | } 1612 | } 1613 | base = baseUrls[` ${base}`]; 1614 | 1615 | if (href.slice(0, 2) === '//') { 1616 | return base.replace(/:[\s\S]*/, ':') + href; 1617 | } else if (href.charAt(0) === '/') { 1618 | return base.replace(/(:\/*[^/]*)[\s\S]*/, '$1') + href; 1619 | } else { 1620 | return base + href; 1621 | } 1622 | } 1623 | var baseUrls = {}; 1624 | var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; 1625 | 1626 | function noop() {} 1627 | noop.exec = noop; 1628 | 1629 | function merge(obj, ...args: {[key: string]: any}[]) { 1630 | let i = 0; 1631 | let target; 1632 | let key; 1633 | 1634 | for (; i < args.length; i++) { 1635 | target = args[i]; 1636 | for (key in target) { 1637 | if (Object.prototype.hasOwnProperty.call(target, key)) { 1638 | obj[key] = target[key]; 1639 | } 1640 | } 1641 | } 1642 | 1643 | return obj; 1644 | } 1645 | 1646 | function splitCells(tableRow: string, count?: number) { 1647 | // ensure that every cell-delimiting pipe has a space 1648 | // before it to distinguish it from an escaped pipe 1649 | const row = tableRow.replace(/\|/g, (match, offset, str) => { 1650 | let escaped = false, curr = offset; 1651 | while (--curr >= 0 && str[curr] === '\\') escaped = !escaped; 1652 | if (escaped) { 1653 | // odd number of slashes means | is escaped 1654 | // so we leave it alone 1655 | return '|'; 1656 | } else { 1657 | // add space before unescaped | 1658 | return ' |'; 1659 | } 1660 | }); 1661 | 1662 | const cells = row.split(/ \|/); 1663 | let i = 0; 1664 | 1665 | if (cells.length > count) { 1666 | cells.splice(count); 1667 | } else { 1668 | while (cells.length < count) cells.push(''); 1669 | } 1670 | 1671 | for (; i < cells.length; i++) { 1672 | // leading or trailing whitespace is ignored per the gfm spec 1673 | cells[i] = cells[i].trim().replace(/\\\|/g, '|'); 1674 | } 1675 | return cells; 1676 | } 1677 | 1678 | // Remove trailing 'c's. Equivalent to str.replace(/c*$/, ''). 1679 | // /c*$/ is vulnerable to REDOS. 1680 | // invert: Remove suffix of non-c chars instead. Default falsey. 1681 | function rtrim(str: string, c: string, invert?: boolean) { 1682 | if (str.length === 0) { 1683 | return ''; 1684 | } 1685 | 1686 | // Length of suffix matching the invert condition. 1687 | let suffLen = 0; 1688 | 1689 | // Step left until we fail to match the invert condition. 1690 | while (suffLen < str.length) { 1691 | const currChar = str.charAt(str.length - suffLen - 1); 1692 | if (currChar === c && !invert) { 1693 | suffLen++; 1694 | } else if (currChar !== c && invert) { 1695 | suffLen++; 1696 | } else { 1697 | break; 1698 | } 1699 | } 1700 | 1701 | return str.substr(0, str.length - suffLen); 1702 | } 1703 | 1704 | type MarkedCallback = (error: any | undefined, parseResult?: string) => void; 1705 | 1706 | /** 1707 | * Marked 1708 | */ 1709 | function marked(src: string): string; 1710 | function marked(src: string, optOrCallback: MarkedOptions | MarkedCallback) 1711 | function marked(src: string, optOrCallback?: MarkedOptions | MarkedCallback, callback?: MarkedCallback): string | void { 1712 | // throw error in case of non string input 1713 | if (typeof src === 'undefined' || src === null) { 1714 | throw new Error('marked(): input parameter is undefined or null'); 1715 | } 1716 | if (typeof src !== 'string') { 1717 | throw new Error(`marked(): input parameter is of type ${Object.prototype.toString.call(src)}, string expected`); 1718 | } 1719 | 1720 | let opt: MarkedOptions; 1721 | 1722 | if (callback || typeof optOrCallback === "function") { 1723 | if (!callback) { 1724 | callback = optOrCallback as MarkedCallback; 1725 | opt = null; 1726 | } 1727 | 1728 | opt = merge({}, marked.defaults, optOrCallback || {}) as MarkedOptions; 1729 | 1730 | const highlight = opt.highlight; 1731 | let tokens: TokensList; 1732 | let pending; 1733 | let i = 0; 1734 | 1735 | try { 1736 | tokens = Lexer.lex(src, opt); 1737 | } catch (e) { 1738 | return callback(e); 1739 | } 1740 | 1741 | pending = tokens.length; 1742 | 1743 | const done = (err?: any) => { 1744 | if (err) { 1745 | opt.highlight = highlight; 1746 | return callback(err); 1747 | } 1748 | 1749 | let out; 1750 | 1751 | try { 1752 | out = Parser.parse(tokens, opt); 1753 | } catch (e) { 1754 | err = e; 1755 | } 1756 | 1757 | opt.highlight = highlight; 1758 | 1759 | return err 1760 | ? callback(err) 1761 | : callback(null, out); 1762 | }; 1763 | 1764 | if (!highlight || highlight.length < 3) { 1765 | return done(); 1766 | } 1767 | 1768 | delete opt.highlight; 1769 | 1770 | if (!pending) return done(); 1771 | 1772 | for (; i < tokens.length; i++) { 1773 | (token => { 1774 | if (token.type !== 'code') { 1775 | return --pending || done(); 1776 | } 1777 | return highlight(token.text, token.lang, (err, code) => { 1778 | if (err) return done(err); 1779 | if (code == null || code === token.text) { 1780 | return --pending || done(); 1781 | } 1782 | token.text = code; 1783 | token.escaped = true; 1784 | --pending || done(); 1785 | }); 1786 | })(tokens[i]); 1787 | } 1788 | 1789 | return; 1790 | } 1791 | try { 1792 | if (opt) opt = merge({}, marked.defaults, opt); 1793 | return Parser.parse(Lexer.lex(src, opt), opt); 1794 | } catch (e) { 1795 | e.message += '\nPlease report this to https://github.com/markedjs/marked.'; 1796 | if ((opt || marked.defaults).silent) { 1797 | return `

    An error occurred:

    ${escape(`${e.message}`, true)}
    `; 1798 | } 1799 | throw e; 1800 | } 1801 | } 1802 | 1803 | /** 1804 | * Options 1805 | */ 1806 | 1807 | marked.options = 1808 | marked.setOptions = (opt: MarkedOptions) => { 1809 | merge(marked.defaults, opt); 1810 | return marked; 1811 | }; 1812 | 1813 | marked.getDefaults = () => ({ 1814 | baseUrl: null, 1815 | breaks: false, 1816 | gfm: true, 1817 | headerIds: true, 1818 | headerPrefix: '', 1819 | highlight: null, 1820 | langPrefix: 'language-', 1821 | mangle: true, 1822 | pedantic: false, 1823 | renderer: new Renderer(), 1824 | sanitize: false, 1825 | sanitizer: null, 1826 | silent: false, 1827 | smartLists: false, 1828 | smartypants: false, 1829 | tables: true, 1830 | xhtml: false 1831 | }); 1832 | 1833 | marked.defaults = marked.getDefaults(); 1834 | 1835 | /** 1836 | * Expose 1837 | */ 1838 | 1839 | marked.Parser = Parser; 1840 | marked.parser = Parser.parse; 1841 | 1842 | marked.Renderer = Renderer; 1843 | marked.TextRenderer = TextRenderer; 1844 | 1845 | marked.Lexer = Lexer; 1846 | marked.lexer = Lexer.lex; 1847 | 1848 | marked.InlineLexer = InlineLexer; 1849 | marked.inlineLexer = InlineLexer.output; 1850 | 1851 | marked.Slugger = Slugger; 1852 | 1853 | marked.parse = marked; 1854 | 1855 | export default marked; 1856 | -------------------------------------------------------------------------------- /main_notypes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * marked - a markdown parser 3 | * Copyright (c) 2011-2018, Christopher Jeffrey. (MIT Licensed) 4 | * https://github.com/markedjs/marked 5 | */ 6 | 'use strict'; 7 | 8 | /** 9 | * Block-Level Grammar 10 | */ 11 | 12 | var block: {[key: string]: any} = { 13 | newline: /^\n+/, 14 | code: /^( {4}[^\n]+\n*)+/, 15 | fences: noop, 16 | hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, 17 | heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/, 18 | nptable: noop, 19 | blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, 20 | list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, 21 | html: '^ {0,3}(?:' // optional indentation 22 | + '<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)' // (1) 23 | + '|comment[^\\n]*(\\n+|$)' // (2) 24 | + '|<\\?[\\s\\S]*?\\?>\\n*' // (3) 25 | + '|\\n*' // (4) 26 | + '|\\n*' // (5) 27 | + '|)[\\s\\S]*?(?:\\n{2,}|$)' // (6) 28 | + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag 29 | + '|(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag 30 | + ')', 31 | def: /^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, 32 | table: noop, 33 | lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, 34 | paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/, 35 | text: /^[^\n]+/ 36 | }; 37 | 38 | block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/; 39 | block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; 40 | block.def = edit(block.def) 41 | .replace('label', block._label) 42 | .replace('title', block._title) 43 | .getRegex(); 44 | 45 | block.bullet = /(?:[*+-]|\d{1,9}\.)/; 46 | block.item = /^( *)(bull) ?[^\n]*(?:\n(?!\1bull ?)[^\n]*)*/; 47 | block.item = edit(block.item, 'gm') 48 | .replace(/bull/g, block.bullet) 49 | .getRegex(); 50 | 51 | block.list = edit(block.list) 52 | .replace(/bull/g, block.bullet) 53 | .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))') 54 | .replace('def', '\\n+(?=' + block.def.source + ')') 55 | .getRegex(); 56 | 57 | block._tag = 'address|article|aside|base|basefont|blockquote|body|caption' 58 | + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' 59 | + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' 60 | + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' 61 | + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' 62 | + '|track|ul'; 63 | block._comment = //; 64 | block.html = edit(block.html, 'i') 65 | .replace('comment', block._comment) 66 | .replace('tag', block._tag) 67 | .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/) 68 | .getRegex(); 69 | 70 | block.paragraph = edit(block.paragraph) 71 | .replace('hr', block.hr) 72 | .replace('heading', block.heading) 73 | .replace('lheading', block.lheading) 74 | .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks 75 | .getRegex(); 76 | 77 | block.blockquote = edit(block.blockquote) 78 | .replace('paragraph', block.paragraph) 79 | .getRegex(); 80 | 81 | /** 82 | * Normal Block Grammar 83 | */ 84 | 85 | block.normal = merge({}, block); 86 | 87 | /** 88 | * GFM Block Grammar 89 | */ 90 | 91 | block.gfm = merge({}, block.normal, { 92 | fences: /^ {0,3}(`{3,}|~{3,})([^`\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/, 93 | paragraph: /^/, 94 | heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ 95 | }); 96 | 97 | block.gfm.paragraph = edit(block.paragraph) 98 | .replace('(?!', '(?!' 99 | + block.gfm.fences.source.replace('\\1', '\\2') + '|' 100 | + block.list.source.replace('\\1', '\\3') + '|') 101 | .getRegex(); 102 | 103 | /** 104 | * GFM + Tables Block Grammar 105 | */ 106 | 107 | block.tables = merge({}, block.gfm, { 108 | nptable: /^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/, 109 | table: /^ *\|(.+)\n *\|?( *[-:]+[-| :]*)(?:\n((?: *[^>\n ].*(?:\n|$))*)\n*|$)/ 110 | }); 111 | 112 | /** 113 | * Pedantic grammar 114 | */ 115 | 116 | block.pedantic = merge({}, block.normal, { 117 | html: edit( 118 | '^ *(?:comment *(?:\\n|\\s*$)' 119 | + '|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)' // closed tag 120 | + '|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))') 121 | .replace('comment', block._comment) 122 | .replace(/tag/g, '(?!(?:' 123 | + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' 124 | + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' 125 | + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b') 126 | .getRegex(), 127 | def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/ 128 | }); 129 | 130 | /** 131 | * Block Lexer 132 | */ 133 | 134 | function Lexer(options) { 135 | this.tokens = []; 136 | this.tokens.links = Object.create(null); 137 | this.options = options || marked.defaults; 138 | this.rules = block.normal; 139 | 140 | if (this.options.pedantic) { 141 | this.rules = block.pedantic; 142 | } else if (this.options.gfm) { 143 | if (this.options.tables) { 144 | this.rules = block.tables; 145 | } else { 146 | this.rules = block.gfm; 147 | } 148 | } 149 | } 150 | 151 | /** 152 | * Expose Block Rules 153 | */ 154 | 155 | Lexer.rules = block; 156 | 157 | /** 158 | * Static Lex Method 159 | */ 160 | 161 | Lexer.lex = function(src, options) { 162 | var lexer = new Lexer(options); 163 | return lexer.lex(src); 164 | }; 165 | 166 | /** 167 | * Preprocessing 168 | */ 169 | 170 | Lexer.prototype.lex = function(src) { 171 | src = src 172 | .replace(/\r\n|\r/g, '\n') 173 | .replace(/\t/g, ' ') 174 | .replace(/\u00a0/g, ' ') 175 | .replace(/\u2424/g, '\n'); 176 | 177 | return this.token(src, true); 178 | }; 179 | 180 | /** 181 | * Lexing 182 | */ 183 | 184 | Lexer.prototype.token = function(src, top) { 185 | src = src.replace(/^ +$/gm, ''); 186 | var next, 187 | loose, 188 | cap, 189 | bull, 190 | b, 191 | item, 192 | listStart, 193 | listItems, 194 | t, 195 | space, 196 | i, 197 | tag, 198 | l, 199 | isordered, 200 | istask, 201 | ischecked; 202 | 203 | while (src) { 204 | // newline 205 | if (cap = this.rules.newline.exec(src)) { 206 | src = src.substring(cap[0].length); 207 | if (cap[0].length > 1) { 208 | this.tokens.push({ 209 | type: 'space' 210 | }); 211 | } 212 | } 213 | 214 | // code 215 | if (cap = this.rules.code.exec(src)) { 216 | src = src.substring(cap[0].length); 217 | cap = cap[0].replace(/^ {4}/gm, ''); 218 | this.tokens.push({ 219 | type: 'code', 220 | text: !this.options.pedantic 221 | ? rtrim(cap, '\n') 222 | : cap 223 | }); 224 | continue; 225 | } 226 | 227 | // fences (gfm) 228 | if (cap = this.rules.fences.exec(src)) { 229 | src = src.substring(cap[0].length); 230 | this.tokens.push({ 231 | type: 'code', 232 | lang: cap[2] ? cap[2].trim() : cap[2], 233 | text: cap[3] || '' 234 | }); 235 | continue; 236 | } 237 | 238 | // heading 239 | if (cap = this.rules.heading.exec(src)) { 240 | src = src.substring(cap[0].length); 241 | this.tokens.push({ 242 | type: 'heading', 243 | depth: cap[1].length, 244 | text: cap[2] 245 | }); 246 | continue; 247 | } 248 | 249 | // table no leading pipe (gfm) 250 | if (top && (cap = this.rules.nptable.exec(src))) { 251 | item = { 252 | type: 'table', 253 | header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), 254 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 255 | cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] 256 | }; 257 | 258 | if (item.header.length === item.align.length) { 259 | src = src.substring(cap[0].length); 260 | 261 | for (i = 0; i < item.align.length; i++) { 262 | if (/^ *-+: *$/.test(item.align[i])) { 263 | item.align[i] = 'right'; 264 | } else if (/^ *:-+: *$/.test(item.align[i])) { 265 | item.align[i] = 'center'; 266 | } else if (/^ *:-+ *$/.test(item.align[i])) { 267 | item.align[i] = 'left'; 268 | } else { 269 | item.align[i] = null; 270 | } 271 | } 272 | 273 | for (i = 0; i < item.cells.length; i++) { 274 | item.cells[i] = splitCells(item.cells[i], item.header.length); 275 | } 276 | 277 | this.tokens.push(item); 278 | 279 | continue; 280 | } 281 | } 282 | 283 | // hr 284 | if (cap = this.rules.hr.exec(src)) { 285 | src = src.substring(cap[0].length); 286 | this.tokens.push({ 287 | type: 'hr' 288 | }); 289 | continue; 290 | } 291 | 292 | // blockquote 293 | if (cap = this.rules.blockquote.exec(src)) { 294 | src = src.substring(cap[0].length); 295 | 296 | this.tokens.push({ 297 | type: 'blockquote_start' 298 | }); 299 | 300 | cap = cap[0].replace(/^ *> ?/gm, ''); 301 | 302 | // Pass `top` to keep the current 303 | // "toplevel" state. This is exactly 304 | // how markdown.pl works. 305 | this.token(cap, top); 306 | 307 | this.tokens.push({ 308 | type: 'blockquote_end' 309 | }); 310 | 311 | continue; 312 | } 313 | 314 | // list 315 | if (cap = this.rules.list.exec(src)) { 316 | src = src.substring(cap[0].length); 317 | bull = cap[2]; 318 | isordered = bull.length > 1; 319 | 320 | listStart = { 321 | type: 'list_start', 322 | ordered: isordered, 323 | start: isordered ? +bull : '', 324 | loose: false 325 | }; 326 | 327 | this.tokens.push(listStart); 328 | 329 | // Get each top-level item. 330 | cap = cap[0].match(this.rules.item); 331 | 332 | listItems = []; 333 | next = false; 334 | l = cap.length; 335 | i = 0; 336 | 337 | for (; i < l; i++) { 338 | item = cap[i]; 339 | 340 | // Remove the list item's bullet 341 | // so it is seen as the next token. 342 | space = item.length; 343 | item = item.replace(/^ *([*+-]|\d+\.) */, ''); 344 | 345 | // Outdent whatever the 346 | // list item contains. Hacky. 347 | if (~item.indexOf('\n ')) { 348 | space -= item.length; 349 | item = !this.options.pedantic 350 | ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') 351 | : item.replace(/^ {1,4}/gm, ''); 352 | } 353 | 354 | // Determine whether the next list item belongs here. 355 | // Backpedal if it does not belong in this list. 356 | if (i !== l - 1) { 357 | b = block.bullet.exec(cap[i + 1])[0]; 358 | if (bull.length > 1 ? b.length === 1 359 | : (b.length > 1 || (this.options.smartLists && b !== bull))) { 360 | src = cap.slice(i + 1).join('\n') + src; 361 | i = l - 1; 362 | } 363 | } 364 | 365 | // Determine whether item is loose or not. 366 | // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ 367 | // for discount behavior. 368 | loose = next || /\n\n(?!\s*$)/.test(item); 369 | if (i !== l - 1) { 370 | next = item.charAt(item.length - 1) === '\n'; 371 | if (!loose) loose = next; 372 | } 373 | 374 | if (loose) { 375 | listStart.loose = true; 376 | } 377 | 378 | // Check for task list items 379 | istask = /^\[[ xX]\] /.test(item); 380 | ischecked = undefined; 381 | if (istask) { 382 | ischecked = item[1] !== ' '; 383 | item = item.replace(/^\[[ xX]\] +/, ''); 384 | } 385 | 386 | t = { 387 | type: 'list_item_start', 388 | task: istask, 389 | checked: ischecked, 390 | loose: loose 391 | }; 392 | 393 | listItems.push(t); 394 | this.tokens.push(t); 395 | 396 | // Recurse. 397 | this.token(item, false); 398 | 399 | this.tokens.push({ 400 | type: 'list_item_end' 401 | }); 402 | } 403 | 404 | if (listStart.loose) { 405 | l = listItems.length; 406 | i = 0; 407 | for (; i < l; i++) { 408 | listItems[i].loose = true; 409 | } 410 | } 411 | 412 | this.tokens.push({ 413 | type: 'list_end' 414 | }); 415 | 416 | continue; 417 | } 418 | 419 | // html 420 | if (cap = this.rules.html.exec(src)) { 421 | src = src.substring(cap[0].length); 422 | this.tokens.push({ 423 | type: this.options.sanitize 424 | ? 'paragraph' 425 | : 'html', 426 | pre: !this.options.sanitizer 427 | && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), 428 | text: cap[0] 429 | }); 430 | continue; 431 | } 432 | 433 | // def 434 | if (top && (cap = this.rules.def.exec(src))) { 435 | src = src.substring(cap[0].length); 436 | if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1); 437 | tag = cap[1].toLowerCase().replace(/\s+/g, ' '); 438 | if (!this.tokens.links[tag]) { 439 | this.tokens.links[tag] = { 440 | href: cap[2], 441 | title: cap[3] 442 | }; 443 | } 444 | continue; 445 | } 446 | 447 | // table (gfm) 448 | if (top && (cap = this.rules.table.exec(src))) { 449 | item = { 450 | type: 'table', 451 | header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), 452 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 453 | cells: cap[3] ? cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') : [] 454 | }; 455 | 456 | if (item.header.length === item.align.length) { 457 | src = src.substring(cap[0].length); 458 | 459 | for (i = 0; i < item.align.length; i++) { 460 | if (/^ *-+: *$/.test(item.align[i])) { 461 | item.align[i] = 'right'; 462 | } else if (/^ *:-+: *$/.test(item.align[i])) { 463 | item.align[i] = 'center'; 464 | } else if (/^ *:-+ *$/.test(item.align[i])) { 465 | item.align[i] = 'left'; 466 | } else { 467 | item.align[i] = null; 468 | } 469 | } 470 | 471 | for (i = 0; i < item.cells.length; i++) { 472 | item.cells[i] = splitCells( 473 | item.cells[i].replace(/^ *\| *| *\| *$/g, ''), 474 | item.header.length); 475 | } 476 | 477 | this.tokens.push(item); 478 | 479 | continue; 480 | } 481 | } 482 | 483 | // lheading 484 | if (cap = this.rules.lheading.exec(src)) { 485 | src = src.substring(cap[0].length); 486 | this.tokens.push({ 487 | type: 'heading', 488 | depth: cap[2] === '=' ? 1 : 2, 489 | text: cap[1] 490 | }); 491 | continue; 492 | } 493 | 494 | // top-level paragraph 495 | if (top && (cap = this.rules.paragraph.exec(src))) { 496 | src = src.substring(cap[0].length); 497 | this.tokens.push({ 498 | type: 'paragraph', 499 | text: cap[1].charAt(cap[1].length - 1) === '\n' 500 | ? cap[1].slice(0, -1) 501 | : cap[1] 502 | }); 503 | continue; 504 | } 505 | 506 | // text 507 | if (cap = this.rules.text.exec(src)) { 508 | // Top-level should never reach here. 509 | src = src.substring(cap[0].length); 510 | this.tokens.push({ 511 | type: 'text', 512 | text: cap[0] 513 | }); 514 | continue; 515 | } 516 | 517 | if (src) { 518 | throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); 519 | } 520 | } 521 | 522 | return this.tokens; 523 | }; 524 | 525 | /** 526 | * Inline-Level Grammar 527 | */ 528 | 529 | var inline: {[key: string]: any} = { 530 | escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/, 531 | autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, 532 | url: noop, 533 | tag: '^comment' 534 | + '|^' // self-closing tag 535 | + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag 536 | + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. 537 | + '|^' // declaration, e.g. 538 | + '|^', // CDATA section 539 | link: /^!?\[(label)\]\(href(?:\s+(title))?\s*\)/, 540 | reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/, 541 | nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/, 542 | strong: /^__([^\s_])__(?!_)|^\*\*([^\s*])\*\*(?!\*)|^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)/, 543 | em: /^_([^\s_])_(?!_)|^\*([^\s*"<\[])\*(?!\*)|^_([^\s][\s\S]*?[^\s_])_(?!_|[^\spunctuation])|^_([^\s_][\s\S]*?[^\s])_(?!_|[^\spunctuation])|^\*([^\s"<\[][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*"<\[][\s\S]*?[^\s])\*(?!\*)/, 544 | code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/, 545 | br: /^( {2,}|\\)\n(?!\s*$)/, 546 | del: noop, 547 | text: /^(`+|[^`])[\s\S]*?(?=[\\?@\\[^_{|}~'; 553 | inline.em = edit(inline.em).replace(/punctuation/g, inline._punctuation).getRegex(); 554 | 555 | inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g; 556 | 557 | inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; 558 | inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/; 559 | inline.autolink = edit(inline.autolink) 560 | .replace('scheme', inline._scheme) 561 | .replace('email', inline._email) 562 | .getRegex(); 563 | 564 | inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/; 565 | 566 | inline.tag = edit(inline.tag) 567 | .replace('comment', block._comment) 568 | .replace('attribute', inline._attribute) 569 | .getRegex(); 570 | 571 | inline._label = /(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/; 572 | inline._href = /\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f\\]*\)|[^\s\x00-\x1f()\\])*?)/; 573 | inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; 574 | 575 | inline.link = edit(inline.link) 576 | .replace('label', inline._label) 577 | .replace('href', inline._href) 578 | .replace('title', inline._title) 579 | .getRegex(); 580 | 581 | inline.reflink = edit(inline.reflink) 582 | .replace('label', inline._label) 583 | .getRegex(); 584 | 585 | /** 586 | * Normal Inline Grammar 587 | */ 588 | 589 | inline.normal = merge({}, inline); 590 | 591 | /** 592 | * Pedantic Inline Grammar 593 | */ 594 | 595 | inline.pedantic = merge({}, inline.normal, { 596 | strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, 597 | em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/, 598 | link: edit(/^!?\[(label)\]\((.*?)\)/) 599 | .replace('label', inline._label) 600 | .getRegex(), 601 | reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/) 602 | .replace('label', inline._label) 603 | .getRegex() 604 | }); 605 | 606 | /** 607 | * GFM Inline Grammar 608 | */ 609 | 610 | inline.gfm = merge({}, inline.normal, { 611 | escape: edit(inline.escape).replace('])', '~|])').getRegex(), 612 | _extended_email: /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/, 613 | url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, 614 | _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, 615 | del: /^~+(?=\S)([\s\S]*?\S)~+/, 616 | text: edit(inline.text) 617 | .replace(']|', '~]|') 618 | .replace('|$', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|$') 619 | .getRegex() 620 | }); 621 | 622 | inline.gfm.url = edit(inline.gfm.url, 'i') 623 | .replace('email', inline.gfm._extended_email) 624 | .getRegex(); 625 | /** 626 | * GFM + Line Breaks Inline Grammar 627 | */ 628 | 629 | inline.breaks = merge({}, inline.gfm, { 630 | br: edit(inline.br).replace('{2,}', '*').getRegex(), 631 | text: edit(inline.gfm.text).replace('{2,}', '*').getRegex() 632 | }); 633 | 634 | /** 635 | * Inline Lexer & Compiler 636 | */ 637 | 638 | function InlineLexer(links, options) { 639 | this.options = options || marked.defaults; 640 | this.links = links; 641 | this.rules = inline.normal; 642 | this.renderer = this.options.renderer || new Renderer(); 643 | this.renderer.options = this.options; 644 | 645 | if (!this.links) { 646 | throw new Error('Tokens array requires a `links` property.'); 647 | } 648 | 649 | if (this.options.pedantic) { 650 | this.rules = inline.pedantic; 651 | } else if (this.options.gfm) { 652 | if (this.options.breaks) { 653 | this.rules = inline.breaks; 654 | } else { 655 | this.rules = inline.gfm; 656 | } 657 | } 658 | } 659 | 660 | /** 661 | * Expose Inline Rules 662 | */ 663 | 664 | InlineLexer.rules = inline; 665 | 666 | /** 667 | * Static Lexing/Compiling Method 668 | */ 669 | 670 | InlineLexer.output = function(src, links, options) { 671 | var inline = new InlineLexer(links, options); 672 | return inline.output(src); 673 | }; 674 | 675 | /** 676 | * Lexing/Compiling 677 | */ 678 | 679 | InlineLexer.prototype.output = function(src) { 680 | var out = '', 681 | link, 682 | text, 683 | href, 684 | title, 685 | cap, 686 | prevCapZero; 687 | 688 | while (src) { 689 | // escape 690 | if (cap = this.rules.escape.exec(src)) { 691 | src = src.substring(cap[0].length); 692 | out += escape(cap[1]); 693 | continue; 694 | } 695 | 696 | // tag 697 | if (cap = this.rules.tag.exec(src)) { 698 | if (!this.inLink && /^/i.test(cap[0])) { 701 | this.inLink = false; 702 | } 703 | if (!this.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { 704 | this.inRawBlock = true; 705 | } else if (this.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { 706 | this.inRawBlock = false; 707 | } 708 | 709 | src = src.substring(cap[0].length); 710 | out += this.options.sanitize 711 | ? this.options.sanitizer 712 | ? this.options.sanitizer(cap[0]) 713 | : escape(cap[0]) 714 | : cap[0]; 715 | continue; 716 | } 717 | 718 | // link 719 | if (cap = this.rules.link.exec(src)) { 720 | src = src.substring(cap[0].length); 721 | this.inLink = true; 722 | href = cap[2]; 723 | if (this.options.pedantic) { 724 | link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); 725 | 726 | if (link) { 727 | href = link[1]; 728 | title = link[3]; 729 | } else { 730 | title = ''; 731 | } 732 | } else { 733 | title = cap[3] ? cap[3].slice(1, -1) : ''; 734 | } 735 | href = href.trim().replace(/^<([\s\S]*)>$/, '$1'); 736 | out += this.outputLink(cap, { 737 | href: InlineLexer.escapes(href), 738 | title: InlineLexer.escapes(title) 739 | }); 740 | this.inLink = false; 741 | continue; 742 | } 743 | 744 | // reflink, nolink 745 | if ((cap = this.rules.reflink.exec(src)) 746 | || (cap = this.rules.nolink.exec(src))) { 747 | src = src.substring(cap[0].length); 748 | link = (cap[2] || cap[1]).replace(/\s+/g, ' '); 749 | link = this.links[link.toLowerCase()]; 750 | if (!link || !link.href) { 751 | out += cap[0].charAt(0); 752 | src = cap[0].substring(1) + src; 753 | continue; 754 | } 755 | this.inLink = true; 756 | out += this.outputLink(cap, link); 757 | this.inLink = false; 758 | continue; 759 | } 760 | 761 | // strong 762 | if (cap = this.rules.strong.exec(src)) { 763 | src = src.substring(cap[0].length); 764 | out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1])); 765 | continue; 766 | } 767 | 768 | // em 769 | if (cap = this.rules.em.exec(src)) { 770 | src = src.substring(cap[0].length); 771 | out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1])); 772 | continue; 773 | } 774 | 775 | // code 776 | if (cap = this.rules.code.exec(src)) { 777 | src = src.substring(cap[0].length); 778 | out += this.renderer.codespan(escape(cap[2].trim(), true)); 779 | continue; 780 | } 781 | 782 | // br 783 | if (cap = this.rules.br.exec(src)) { 784 | src = src.substring(cap[0].length); 785 | out += this.renderer.br(); 786 | continue; 787 | } 788 | 789 | // del (gfm) 790 | if (cap = this.rules.del.exec(src)) { 791 | src = src.substring(cap[0].length); 792 | out += this.renderer.del(this.output(cap[1])); 793 | continue; 794 | } 795 | 796 | // autolink 797 | if (cap = this.rules.autolink.exec(src)) { 798 | src = src.substring(cap[0].length); 799 | if (cap[2] === '@') { 800 | text = escape(this.mangle(cap[1])); 801 | href = 'mailto:' + text; 802 | } else { 803 | text = escape(cap[1]); 804 | href = text; 805 | } 806 | out += this.renderer.link(href, null, text); 807 | continue; 808 | } 809 | 810 | // url (gfm) 811 | if (!this.inLink && (cap = this.rules.url.exec(src))) { 812 | if (cap[2] === '@') { 813 | text = escape(cap[0]); 814 | href = 'mailto:' + text; 815 | } else { 816 | // do extended autolink path validation 817 | do { 818 | prevCapZero = cap[0]; 819 | cap[0] = this.rules._backpedal.exec(cap[0])[0]; 820 | } while (prevCapZero !== cap[0]); 821 | text = escape(cap[0]); 822 | if (cap[1] === 'www.') { 823 | href = 'http://' + text; 824 | } else { 825 | href = text; 826 | } 827 | } 828 | src = src.substring(cap[0].length); 829 | out += this.renderer.link(href, null, text); 830 | continue; 831 | } 832 | 833 | // text 834 | if (cap = this.rules.text.exec(src)) { 835 | src = src.substring(cap[0].length); 836 | if (this.inRawBlock) { 837 | out += this.renderer.text(cap[0]); 838 | } else { 839 | out += this.renderer.text(escape(this.smartypants(cap[0]))); 840 | } 841 | continue; 842 | } 843 | 844 | if (src) { 845 | throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); 846 | } 847 | } 848 | 849 | return out; 850 | }; 851 | 852 | InlineLexer.escapes = function(text) { 853 | return text ? text.replace(InlineLexer.rules._escapes, '$1') : text; 854 | }; 855 | 856 | /** 857 | * Compile Link 858 | */ 859 | 860 | InlineLexer.prototype.outputLink = function(cap, link) { 861 | var href = link.href, 862 | title = link.title ? escape(link.title) : null; 863 | 864 | return cap[0].charAt(0) !== '!' 865 | ? this.renderer.link(href, title, this.output(cap[1])) 866 | : this.renderer.image(href, title, escape(cap[1])); 867 | }; 868 | 869 | /** 870 | * Smartypants Transformations 871 | */ 872 | 873 | InlineLexer.prototype.smartypants = function(text) { 874 | if (!this.options.smartypants) return text; 875 | return text 876 | // em-dashes 877 | .replace(/---/g, '\u2014') 878 | // en-dashes 879 | .replace(/--/g, '\u2013') 880 | // opening singles 881 | .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') 882 | // closing singles & apostrophes 883 | .replace(/'/g, '\u2019') 884 | // opening doubles 885 | .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') 886 | // closing doubles 887 | .replace(/"/g, '\u201d') 888 | // ellipses 889 | .replace(/\.{3}/g, '\u2026'); 890 | }; 891 | 892 | /** 893 | * Mangle Links 894 | */ 895 | 896 | InlineLexer.prototype.mangle = function(text) { 897 | if (!this.options.mangle) return text; 898 | var out = '', 899 | l = text.length, 900 | i = 0, 901 | ch; 902 | 903 | for (; i < l; i++) { 904 | ch = text.charCodeAt(i); 905 | if (Math.random() > 0.5) { 906 | ch = 'x' + ch.toString(16); 907 | } 908 | out += '&#' + ch + ';'; 909 | } 910 | 911 | return out; 912 | }; 913 | 914 | /** 915 | * Renderer 916 | */ 917 | 918 | function Renderer(options?: any) { 919 | this.options = options || marked.defaults; 920 | } 921 | 922 | Renderer.prototype.code = function(code, infostring, escaped) { 923 | var lang = (infostring || '').match(/\S*/)[0]; 924 | if (this.options.highlight) { 925 | var out = this.options.highlight(code, lang); 926 | if (out != null && out !== code) { 927 | escaped = true; 928 | code = out; 929 | } 930 | } 931 | 932 | if (!lang) { 933 | return '
    '
     934 |       + (escaped ? code : escape(code, true))
     935 |       + '
    '; 936 | } 937 | 938 | return '
    '
     942 |     + (escaped ? code : escape(code, true))
     943 |     + '
    \n'; 944 | }; 945 | 946 | Renderer.prototype.blockquote = function(quote) { 947 | return '
    \n' + quote + '
    \n'; 948 | }; 949 | 950 | Renderer.prototype.html = function(html) { 951 | return html; 952 | }; 953 | 954 | Renderer.prototype.heading = function(text, level, raw, slugger) { 955 | if (this.options.headerIds) { 956 | return '' 962 | + text 963 | + '\n'; 966 | } 967 | // ignore IDs 968 | return '' + text + '\n'; 969 | }; 970 | 971 | Renderer.prototype.hr = function() { 972 | return this.options.xhtml ? '
    \n' : '
    \n'; 973 | }; 974 | 975 | Renderer.prototype.list = function(body, ordered, start) { 976 | var type = ordered ? 'ol' : 'ul', 977 | startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''; 978 | return '<' + type + startatt + '>\n' + body + '\n'; 979 | }; 980 | 981 | Renderer.prototype.listitem = function(text) { 982 | return '
  • ' + text + '
  • \n'; 983 | }; 984 | 985 | Renderer.prototype.checkbox = function(checked) { 986 | return ' '; 991 | }; 992 | 993 | Renderer.prototype.paragraph = function(text) { 994 | return '

    ' + text + '

    \n'; 995 | }; 996 | 997 | Renderer.prototype.table = function(header, body) { 998 | if (body) body = '' + body + ''; 999 | 1000 | return '\n' 1001 | + '\n' 1002 | + header 1003 | + '\n' 1004 | + body 1005 | + '
    \n'; 1006 | }; 1007 | 1008 | Renderer.prototype.tablerow = function(content) { 1009 | return '\n' + content + '\n'; 1010 | }; 1011 | 1012 | Renderer.prototype.tablecell = function(content, flags) { 1013 | var type = flags.header ? 'th' : 'td'; 1014 | var tag = flags.align 1015 | ? '<' + type + ' align="' + flags.align + '">' 1016 | : '<' + type + '>'; 1017 | return tag + content + '\n'; 1018 | }; 1019 | 1020 | // span level renderer 1021 | Renderer.prototype.strong = function(text) { 1022 | return '' + text + ''; 1023 | }; 1024 | 1025 | Renderer.prototype.em = function(text) { 1026 | return '' + text + ''; 1027 | }; 1028 | 1029 | Renderer.prototype.codespan = function(text) { 1030 | return '' + text + ''; 1031 | }; 1032 | 1033 | Renderer.prototype.br = function() { 1034 | return this.options.xhtml ? '
    ' : '
    '; 1035 | }; 1036 | 1037 | Renderer.prototype.del = function(text) { 1038 | return '' + text + ''; 1039 | }; 1040 | 1041 | Renderer.prototype.link = function(href, title, text) { 1042 | href = cleanUrl(this.options.sanitize, this.options.baseUrl, href); 1043 | if (href === null) { 1044 | return text; 1045 | } 1046 | var out = '
    '; 1051 | return out; 1052 | }; 1053 | 1054 | Renderer.prototype.image = function(href, title, text) { 1055 | href = cleanUrl(this.options.sanitize, this.options.baseUrl, href); 1056 | if (href === null) { 1057 | return text; 1058 | } 1059 | 1060 | var out = '' + text + '' : '>'; 1065 | return out; 1066 | }; 1067 | 1068 | Renderer.prototype.text = function(text) { 1069 | return text; 1070 | }; 1071 | 1072 | /** 1073 | * TextRenderer 1074 | * returns only the textual part of the token 1075 | */ 1076 | 1077 | function TextRenderer() {} 1078 | 1079 | // no need for block level renderers 1080 | 1081 | TextRenderer.prototype.strong = 1082 | TextRenderer.prototype.em = 1083 | TextRenderer.prototype.codespan = 1084 | TextRenderer.prototype.del = 1085 | TextRenderer.prototype.text = function (text) { 1086 | return text; 1087 | }; 1088 | 1089 | TextRenderer.prototype.link = 1090 | TextRenderer.prototype.image = function(href, title, text) { 1091 | return '' + text; 1092 | }; 1093 | 1094 | TextRenderer.prototype.br = function() { 1095 | return ''; 1096 | }; 1097 | 1098 | /** 1099 | * Parsing & Compiling 1100 | */ 1101 | 1102 | function Parser(options) { 1103 | this.tokens = []; 1104 | this.token = null; 1105 | this.options = options || marked.defaults; 1106 | this.options.renderer = this.options.renderer || new Renderer(); 1107 | this.renderer = this.options.renderer; 1108 | this.renderer.options = this.options; 1109 | this.slugger = new Slugger(); 1110 | } 1111 | 1112 | /** 1113 | * Static Parse Method 1114 | */ 1115 | 1116 | Parser.parse = function(src, options) { 1117 | var parser = new Parser(options); 1118 | return parser.parse(src); 1119 | }; 1120 | 1121 | /** 1122 | * Parse Loop 1123 | */ 1124 | 1125 | Parser.prototype.parse = function(src) { 1126 | this.inline = new InlineLexer(src.links, this.options); 1127 | // use an InlineLexer with a TextRenderer to extract pure text 1128 | this.inlineText = new InlineLexer( 1129 | src.links, 1130 | merge({}, this.options, {renderer: new TextRenderer()}) 1131 | ); 1132 | this.tokens = src.reverse(); 1133 | 1134 | var out = ''; 1135 | while (this.next()) { 1136 | out += this.tok(); 1137 | } 1138 | 1139 | return out; 1140 | }; 1141 | 1142 | /** 1143 | * Next Token 1144 | */ 1145 | 1146 | Parser.prototype.next = function() { 1147 | return this.token = this.tokens.pop(); 1148 | }; 1149 | 1150 | /** 1151 | * Preview Next Token 1152 | */ 1153 | 1154 | Parser.prototype.peek = function() { 1155 | return this.tokens[this.tokens.length - 1] || 0; 1156 | }; 1157 | 1158 | /** 1159 | * Parse Text Tokens 1160 | */ 1161 | 1162 | Parser.prototype.parseText = function() { 1163 | var body = this.token.text; 1164 | 1165 | while (this.peek().type === 'text') { 1166 | body += '\n' + this.next().text; 1167 | } 1168 | 1169 | return this.inline.output(body); 1170 | }; 1171 | 1172 | /** 1173 | * Parse Current Token 1174 | */ 1175 | 1176 | Parser.prototype.tok = function() { 1177 | switch (this.token.type) { 1178 | case 'space': { 1179 | return ''; 1180 | } 1181 | case 'hr': { 1182 | return this.renderer.hr(); 1183 | } 1184 | case 'heading': { 1185 | return this.renderer.heading( 1186 | this.inline.output(this.token.text), 1187 | this.token.depth, 1188 | unescape(this.inlineText.output(this.token.text)), 1189 | this.slugger); 1190 | } 1191 | case 'code': { 1192 | return this.renderer.code(this.token.text, 1193 | this.token.lang, 1194 | this.token.escaped); 1195 | } 1196 | case 'table': { 1197 | var header = '', 1198 | body = '', 1199 | i, 1200 | row, 1201 | cell, 1202 | j; 1203 | 1204 | // header 1205 | cell = ''; 1206 | for (i = 0; i < this.token.header.length; i++) { 1207 | cell += this.renderer.tablecell( 1208 | this.inline.output(this.token.header[i]), 1209 | { header: true, align: this.token.align[i] } 1210 | ); 1211 | } 1212 | header += this.renderer.tablerow(cell); 1213 | 1214 | for (i = 0; i < this.token.cells.length; i++) { 1215 | row = this.token.cells[i]; 1216 | 1217 | cell = ''; 1218 | for (j = 0; j < row.length; j++) { 1219 | cell += this.renderer.tablecell( 1220 | this.inline.output(row[j]), 1221 | { header: false, align: this.token.align[j] } 1222 | ); 1223 | } 1224 | 1225 | body += this.renderer.tablerow(cell); 1226 | } 1227 | return this.renderer.table(header, body); 1228 | } 1229 | case 'blockquote_start': { 1230 | body = ''; 1231 | 1232 | while (this.next().type !== 'blockquote_end') { 1233 | body += this.tok(); 1234 | } 1235 | 1236 | return this.renderer.blockquote(body); 1237 | } 1238 | case 'list_start': { 1239 | body = ''; 1240 | var ordered = this.token.ordered, 1241 | start = this.token.start; 1242 | 1243 | while (this.next().type !== 'list_end') { 1244 | body += this.tok(); 1245 | } 1246 | 1247 | return this.renderer.list(body, ordered, start); 1248 | } 1249 | case 'list_item_start': { 1250 | body = ''; 1251 | var loose = this.token.loose; 1252 | 1253 | if (this.token.task) { 1254 | body += this.renderer.checkbox(this.token.checked); 1255 | } 1256 | 1257 | while (this.next().type !== 'list_item_end') { 1258 | body += !loose && this.token.type === 'text' 1259 | ? this.parseText() 1260 | : this.tok(); 1261 | } 1262 | 1263 | return this.renderer.listitem(body); 1264 | } 1265 | case 'html': { 1266 | // TODO parse inline content if parameter markdown=1 1267 | return this.renderer.html(this.token.text); 1268 | } 1269 | case 'paragraph': { 1270 | return this.renderer.paragraph(this.inline.output(this.token.text)); 1271 | } 1272 | case 'text': { 1273 | return this.renderer.paragraph(this.parseText()); 1274 | } 1275 | default: { 1276 | var errMsg = 'Token with "' + this.token.type + '" type was not found.'; 1277 | if (this.options.silent) { 1278 | console.log(errMsg); 1279 | } else { 1280 | throw new Error(errMsg); 1281 | } 1282 | } 1283 | } 1284 | }; 1285 | 1286 | /** 1287 | * Slugger generates header id 1288 | */ 1289 | 1290 | function Slugger () { 1291 | this.seen = {}; 1292 | } 1293 | 1294 | /** 1295 | * Convert string to unique id 1296 | */ 1297 | 1298 | Slugger.prototype.slug = function (value) { 1299 | var slug = value 1300 | .toLowerCase() 1301 | .trim() 1302 | .replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '') 1303 | .replace(/\s/g, '-'); 1304 | 1305 | if (this.seen.hasOwnProperty(slug)) { 1306 | var originalSlug = slug; 1307 | do { 1308 | this.seen[originalSlug]++; 1309 | slug = originalSlug + '-' + this.seen[originalSlug]; 1310 | } while (this.seen.hasOwnProperty(slug)); 1311 | } 1312 | this.seen[slug] = 0; 1313 | 1314 | return slug; 1315 | }; 1316 | 1317 | /** 1318 | * Helpers 1319 | */ 1320 | 1321 | function escape(html, encode?: any) { 1322 | if (encode) { 1323 | if (escape.escapeTest.test(html)) { 1324 | return html.replace(escape.escapeReplace, function (ch) { return escape.replacements[ch]; }); 1325 | } 1326 | } else { 1327 | if (escape.escapeTestNoEncode.test(html)) { 1328 | return html.replace(escape.escapeReplaceNoEncode, function (ch) { return escape.replacements[ch]; }); 1329 | } 1330 | } 1331 | 1332 | return html; 1333 | } 1334 | 1335 | escape.escapeTest = /[&<>"']/; 1336 | escape.escapeReplace = /[&<>"']/g; 1337 | escape.replacements = { 1338 | '&': '&', 1339 | '<': '<', 1340 | '>': '>', 1341 | '"': '"', 1342 | "'": ''' 1343 | }; 1344 | 1345 | escape.escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/; 1346 | escape.escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g; 1347 | 1348 | function unescape(html) { 1349 | // explicitly match decimal, hex, and named HTML entities 1350 | return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) { 1351 | n = n.toLowerCase(); 1352 | if (n === 'colon') return ':'; 1353 | if (n.charAt(0) === '#') { 1354 | return n.charAt(1) === 'x' 1355 | ? String.fromCharCode(parseInt(n.substring(2), 16)) 1356 | : String.fromCharCode(+n.substring(1)); 1357 | } 1358 | return ''; 1359 | }); 1360 | } 1361 | 1362 | function edit(regex, opt?: any) { 1363 | regex = regex.source || regex; 1364 | opt = opt || ''; 1365 | return { 1366 | replace: function(name, val) { 1367 | val = val.source || val; 1368 | val = val.replace(/(^|[^\[])\^/g, '$1'); 1369 | regex = regex.replace(name, val); 1370 | return this; 1371 | }, 1372 | getRegex: function() { 1373 | return new RegExp(regex, opt); 1374 | } 1375 | }; 1376 | } 1377 | 1378 | function cleanUrl(sanitize, base, href) { 1379 | if (sanitize) { 1380 | try { 1381 | var prot = decodeURIComponent(unescape(href)) 1382 | .replace(/[^\w:]/g, '') 1383 | .toLowerCase(); 1384 | } catch (e) { 1385 | return null; 1386 | } 1387 | if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { 1388 | return null; 1389 | } 1390 | } 1391 | if (base && !originIndependentUrl.test(href)) { 1392 | href = resolveUrl(base, href); 1393 | } 1394 | try { 1395 | href = encodeURI(href).replace(/%25/g, '%'); 1396 | } catch (e) { 1397 | return null; 1398 | } 1399 | return href; 1400 | } 1401 | 1402 | function resolveUrl(base, href) { 1403 | if (!baseUrls[' ' + base]) { 1404 | // we can ignore everything in base after the last slash of its path component, 1405 | // but we might need to add _that_ 1406 | // https://tools.ietf.org/html/rfc3986#section-3 1407 | if (/^[^:]+:\/*[^/]*$/.test(base)) { 1408 | baseUrls[' ' + base] = base + '/'; 1409 | } else { 1410 | baseUrls[' ' + base] = rtrim(base, '/', true); 1411 | } 1412 | } 1413 | base = baseUrls[' ' + base]; 1414 | 1415 | if (href.slice(0, 2) === '//') { 1416 | return base.replace(/:[\s\S]*/, ':') + href; 1417 | } else if (href.charAt(0) === '/') { 1418 | return base.replace(/(:\/*[^/]*)[\s\S]*/, '$1') + href; 1419 | } else { 1420 | return base + href; 1421 | } 1422 | } 1423 | var baseUrls = {}; 1424 | var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; 1425 | 1426 | function noop() {} 1427 | noop.exec = noop; 1428 | 1429 | function merge(obj, ...args) { 1430 | var i = 1, 1431 | target, 1432 | key; 1433 | 1434 | for (; i < arguments.length; i++) { 1435 | target = arguments[i]; 1436 | for (key in target) { 1437 | if (Object.prototype.hasOwnProperty.call(target, key)) { 1438 | obj[key] = target[key]; 1439 | } 1440 | } 1441 | } 1442 | 1443 | return obj; 1444 | } 1445 | 1446 | function splitCells(tableRow, count?: any) { 1447 | // ensure that every cell-delimiting pipe has a space 1448 | // before it to distinguish it from an escaped pipe 1449 | var row = tableRow.replace(/\|/g, function (match, offset, str) { 1450 | var escaped = false, 1451 | curr = offset; 1452 | while (--curr >= 0 && str[curr] === '\\') escaped = !escaped; 1453 | if (escaped) { 1454 | // odd number of slashes means | is escaped 1455 | // so we leave it alone 1456 | return '|'; 1457 | } else { 1458 | // add space before unescaped | 1459 | return ' |'; 1460 | } 1461 | }), 1462 | cells = row.split(/ \|/), 1463 | i = 0; 1464 | 1465 | if (cells.length > count) { 1466 | cells.splice(count); 1467 | } else { 1468 | while (cells.length < count) cells.push(''); 1469 | } 1470 | 1471 | for (; i < cells.length; i++) { 1472 | // leading or trailing whitespace is ignored per the gfm spec 1473 | cells[i] = cells[i].trim().replace(/\\\|/g, '|'); 1474 | } 1475 | return cells; 1476 | } 1477 | 1478 | // Remove trailing 'c's. Equivalent to str.replace(/c*$/, ''). 1479 | // /c*$/ is vulnerable to REDOS. 1480 | // invert: Remove suffix of non-c chars instead. Default falsey. 1481 | function rtrim(str, c, invert?: any) { 1482 | if (str.length === 0) { 1483 | return ''; 1484 | } 1485 | 1486 | // Length of suffix matching the invert condition. 1487 | var suffLen = 0; 1488 | 1489 | // Step left until we fail to match the invert condition. 1490 | while (suffLen < str.length) { 1491 | var currChar = str.charAt(str.length - suffLen - 1); 1492 | if (currChar === c && !invert) { 1493 | suffLen++; 1494 | } else if (currChar !== c && invert) { 1495 | suffLen++; 1496 | } else { 1497 | break; 1498 | } 1499 | } 1500 | 1501 | return str.substr(0, str.length - suffLen); 1502 | } 1503 | 1504 | /** 1505 | * Marked 1506 | */ 1507 | 1508 | function marked(src, opt, callback) { 1509 | // throw error in case of non string input 1510 | if (typeof src === 'undefined' || src === null) { 1511 | throw new Error('marked(): input parameter is undefined or null'); 1512 | } 1513 | if (typeof src !== 'string') { 1514 | throw new Error('marked(): input parameter is of type ' 1515 | + Object.prototype.toString.call(src) + ', string expected'); 1516 | } 1517 | 1518 | if (callback || typeof opt === 'function') { 1519 | if (!callback) { 1520 | callback = opt; 1521 | opt = null; 1522 | } 1523 | 1524 | opt = merge({}, marked.defaults, opt || {}); 1525 | 1526 | var highlight = opt.highlight, 1527 | tokens, 1528 | pending, 1529 | i = 0; 1530 | 1531 | try { 1532 | tokens = Lexer.lex(src, opt); 1533 | } catch (e) { 1534 | return callback(e); 1535 | } 1536 | 1537 | pending = tokens.length; 1538 | 1539 | var done = function(err?: any) { 1540 | if (err) { 1541 | opt.highlight = highlight; 1542 | return callback(err); 1543 | } 1544 | 1545 | var out; 1546 | 1547 | try { 1548 | out = Parser.parse(tokens, opt); 1549 | } catch (e) { 1550 | err = e; 1551 | } 1552 | 1553 | opt.highlight = highlight; 1554 | 1555 | return err 1556 | ? callback(err) 1557 | : callback(null, out); 1558 | }; 1559 | 1560 | if (!highlight || highlight.length < 3) { 1561 | return done(); 1562 | } 1563 | 1564 | delete opt.highlight; 1565 | 1566 | if (!pending) return done(); 1567 | 1568 | for (; i < tokens.length; i++) { 1569 | (function(token) { 1570 | if (token.type !== 'code') { 1571 | return --pending || done(); 1572 | } 1573 | return highlight(token.text, token.lang, function(err, code) { 1574 | if (err) return done(err); 1575 | if (code == null || code === token.text) { 1576 | return --pending || done(); 1577 | } 1578 | token.text = code; 1579 | token.escaped = true; 1580 | --pending || done(); 1581 | }); 1582 | })(tokens[i]); 1583 | } 1584 | 1585 | return; 1586 | } 1587 | try { 1588 | if (opt) opt = merge({}, marked.defaults, opt); 1589 | return Parser.parse(Lexer.lex(src, opt), opt); 1590 | } catch (e) { 1591 | e.message += '\nPlease report this to https://github.com/markedjs/marked.'; 1592 | if ((opt || marked.defaults).silent) { 1593 | return '

    An error occurred:

    '
    1594 |         + escape(e.message + '', true)
    1595 |         + '
    '; 1596 | } 1597 | throw e; 1598 | } 1599 | } 1600 | 1601 | /** 1602 | * Options 1603 | */ 1604 | 1605 | marked.options = 1606 | marked.setOptions = function(opt) { 1607 | merge(marked.defaults, opt); 1608 | return marked; 1609 | }; 1610 | 1611 | marked.getDefaults = function () { 1612 | return { 1613 | baseUrl: null, 1614 | breaks: false, 1615 | gfm: true, 1616 | headerIds: true, 1617 | headerPrefix: '', 1618 | highlight: null, 1619 | langPrefix: 'language-', 1620 | mangle: true, 1621 | pedantic: false, 1622 | renderer: new Renderer(), 1623 | sanitize: false, 1624 | sanitizer: null, 1625 | silent: false, 1626 | smartLists: false, 1627 | smartypants: false, 1628 | tables: true, 1629 | xhtml: false 1630 | }; 1631 | }; 1632 | 1633 | marked.defaults = marked.getDefaults(); 1634 | 1635 | /** 1636 | * Expose 1637 | */ 1638 | 1639 | marked.Parser = Parser; 1640 | marked.parser = Parser.parse; 1641 | 1642 | marked.Renderer = Renderer; 1643 | marked.TextRenderer = TextRenderer; 1644 | 1645 | marked.Lexer = Lexer; 1646 | marked.lexer = Lexer.lex; 1647 | 1648 | marked.InlineLexer = InlineLexer; 1649 | marked.inlineLexer = InlineLexer.output; 1650 | 1651 | marked.Slugger = Slugger; 1652 | 1653 | marked.parse = marked; 1654 | 1655 | export { marked }; 1656 | -------------------------------------------------------------------------------- /main_test.ts: -------------------------------------------------------------------------------- 1 | import marked from "./main.ts"; 2 | import { test, runTests } from "https://deno.land/std@v0.3.2/testing/mod.ts"; 3 | import { assert } from "https://deno.land/std@v0.3.2/testing/asserts.ts"; 4 | 5 | test({ 6 | name: "[Marked] Test heading ID functionality - add id attribute by default", 7 | fn() { 8 | var renderer = new marked.Renderer(); 9 | var slugger = new marked.Slugger(); 10 | var header = renderer.heading("test", 1, "test", slugger); 11 | assert(header === '

    test

    \n'); 12 | } 13 | }); 14 | 15 | test({ 16 | name: 17 | "[Marked] Test heading ID functionality - NOT add id attribute when options set false", 18 | fn() { 19 | var renderer = new marked.Renderer({ headerIds: false }); 20 | var header = renderer.heading("test", 1, "test"); 21 | assert(header === "

    test

    \n"); 22 | } 23 | }); 24 | 25 | test({ 26 | name: "[Marked] Test slugger functionality - should use lowercase slug", 27 | fn() { 28 | var slugger = new marked.Slugger(); 29 | assert(slugger.slug("Test") === "test"); 30 | } 31 | }); 32 | 33 | test({ 34 | name: 35 | "[Marked] Test slugger functionality - should be unique to avoid collisions 1280", 36 | fn() { 37 | var slugger = new marked.Slugger(); 38 | assert(slugger.slug("test") === "test"); 39 | assert(slugger.slug("test") === "test-1"); 40 | assert(slugger.slug("test") === "test-2"); 41 | } 42 | }); 43 | 44 | test({ 45 | name: 46 | "[Marked] Test slugger functionality - should be unique when slug ends with number", 47 | fn() { 48 | var slugger = new marked.Slugger(); 49 | assert(slugger.slug("test 1") === "test-1"); 50 | assert(slugger.slug("test") === "test"); 51 | assert(slugger.slug("test") === "test-2"); 52 | } 53 | }); 54 | 55 | test({ 56 | name: 57 | "[Marked] Test slugger functionality - should be unique when slug ends with hyphen number", 58 | fn() { 59 | var slugger = new marked.Slugger(); 60 | assert(slugger.slug("foo") === "foo"); 61 | assert(slugger.slug("foo") === "foo-1"); 62 | assert(slugger.slug("foo 1") === "foo-1-1"); 63 | assert(slugger.slug("foo-1") === "foo-1-2"); 64 | assert(slugger.slug("foo") === "foo-2"); 65 | } 66 | }); 67 | 68 | test({ 69 | name: "[Marked] Test slugger functionality - should allow non-latin chars", 70 | fn() { 71 | var slugger = new marked.Slugger(); 72 | assert(slugger.slug("привет") === "привет"); 73 | } 74 | }); 75 | 76 | test({ 77 | name: "[Marked] Test slugger functionality - should remove ampersands 857", 78 | fn() { 79 | var slugger = new marked.Slugger(); 80 | assert(slugger.slug("This & That Section") === "this--that-section"); 81 | } 82 | }); 83 | 84 | test({ 85 | name: "[Marked] Test slugger functionality - should remove periods", 86 | fn() { 87 | var slugger = new marked.Slugger(); 88 | assert(slugger.slug("file.txt") === "filetxt"); 89 | } 90 | }); 91 | 92 | test({ 93 | name: 94 | '[Marked] Test paragraph token type - should use the "paragraph" type on top level', 95 | fn() { 96 | const md = "A Paragraph.\n\n> A blockquote\n\n- list item\n"; 97 | const tokens = marked.lexer(md); 98 | assert(tokens[0].type === "paragraph"); 99 | assert(tokens[3].type === "paragraph"); 100 | assert(tokens[7].type === "text"); 101 | } 102 | }); 103 | 104 | runTests(); 105 | --------------------------------------------------------------------------------