UNPKG

74.8 kBJavaScriptView Raw
1/**
2 * marked - a markdown parser
3 * Copyright (c) 2011-2022, Christopher Jeffrey. (MIT Licensed)
4 * https://github.com/markedjs/marked
5 */
6
7/**
8 * DO NOT EDIT THIS FILE
9 * The code in this file is generated from files in ./src/
10 */
11
12function getDefaults() {
13 return {
14 baseUrl: null,
15 breaks: false,
16 extensions: null,
17 gfm: true,
18 headerIds: true,
19 headerPrefix: '',
20 highlight: null,
21 langPrefix: 'language-',
22 mangle: true,
23 pedantic: false,
24 renderer: null,
25 sanitize: false,
26 sanitizer: null,
27 silent: false,
28 smartLists: false,
29 smartypants: false,
30 tokenizer: null,
31 walkTokens: null,
32 xhtml: false
33 };
34}
35
36let defaults = getDefaults();
37
38function changeDefaults(newDefaults) {
39 defaults = newDefaults;
40}
41
42/**
43 * Helpers
44 */
45const escapeTest = /[&<>"']/;
46const escapeReplace = /[&<>"']/g;
47const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
48const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
49const escapeReplacements = {
50 '&': '&amp;',
51 '<': '&lt;',
52 '>': '&gt;',
53 '"': '&quot;',
54 "'": '&#39;'
55};
56const getEscapeReplacement = (ch) => escapeReplacements[ch];
57function escape(html, encode) {
58 if (encode) {
59 if (escapeTest.test(html)) {
60 return html.replace(escapeReplace, getEscapeReplacement);
61 }
62 } else {
63 if (escapeTestNoEncode.test(html)) {
64 return html.replace(escapeReplaceNoEncode, getEscapeReplacement);
65 }
66 }
67
68 return html;
69}
70
71const unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;
72
73function unescape(html) {
74 // explicitly match decimal, hex, and named HTML entities
75 return html.replace(unescapeTest, (_, n) => {
76 n = n.toLowerCase();
77 if (n === 'colon') return ':';
78 if (n.charAt(0) === '#') {
79 return n.charAt(1) === 'x'
80 ? String.fromCharCode(parseInt(n.substring(2), 16))
81 : String.fromCharCode(+n.substring(1));
82 }
83 return '';
84 });
85}
86
87const caret = /(^|[^\[])\^/g;
88function edit(regex, opt) {
89 regex = regex.source || regex;
90 opt = opt || '';
91 const obj = {
92 replace: (name, val) => {
93 val = val.source || val;
94 val = val.replace(caret, '$1');
95 regex = regex.replace(name, val);
96 return obj;
97 },
98 getRegex: () => {
99 return new RegExp(regex, opt);
100 }
101 };
102 return obj;
103}
104
105const nonWordAndColonTest = /[^\w:]/g;
106const originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
107function cleanUrl(sanitize, base, href) {
108 if (sanitize) {
109 let prot;
110 try {
111 prot = decodeURIComponent(unescape(href))
112 .replace(nonWordAndColonTest, '')
113 .toLowerCase();
114 } catch (e) {
115 return null;
116 }
117 if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
118 return null;
119 }
120 }
121 if (base && !originIndependentUrl.test(href)) {
122 href = resolveUrl(base, href);
123 }
124 try {
125 href = encodeURI(href).replace(/%25/g, '%');
126 } catch (e) {
127 return null;
128 }
129 return href;
130}
131
132const baseUrls = {};
133const justDomain = /^[^:]+:\/*[^/]*$/;
134const protocol = /^([^:]+:)[\s\S]*$/;
135const domain = /^([^:]+:\/*[^/]*)[\s\S]*$/;
136
137function resolveUrl(base, href) {
138 if (!baseUrls[' ' + base]) {
139 // we can ignore everything in base after the last slash of its path component,
140 // but we might need to add _that_
141 // https://tools.ietf.org/html/rfc3986#section-3
142 if (justDomain.test(base)) {
143 baseUrls[' ' + base] = base + '/';
144 } else {
145 baseUrls[' ' + base] = rtrim(base, '/', true);
146 }
147 }
148 base = baseUrls[' ' + base];
149 const relativeBase = base.indexOf(':') === -1;
150
151 if (href.substring(0, 2) === '//') {
152 if (relativeBase) {
153 return href;
154 }
155 return base.replace(protocol, '$1') + href;
156 } else if (href.charAt(0) === '/') {
157 if (relativeBase) {
158 return href;
159 }
160 return base.replace(domain, '$1') + href;
161 } else {
162 return base + href;
163 }
164}
165
166const noopTest = { exec: function noopTest() {} };
167
168function merge(obj) {
169 let i = 1,
170 target,
171 key;
172
173 for (; i < arguments.length; i++) {
174 target = arguments[i];
175 for (key in target) {
176 if (Object.prototype.hasOwnProperty.call(target, key)) {
177 obj[key] = target[key];
178 }
179 }
180 }
181
182 return obj;
183}
184
185function splitCells(tableRow, count) {
186 // ensure that every cell-delimiting pipe has a space
187 // before it to distinguish it from an escaped pipe
188 const row = tableRow.replace(/\|/g, (match, offset, str) => {
189 let escaped = false,
190 curr = offset;
191 while (--curr >= 0 && str[curr] === '\\') escaped = !escaped;
192 if (escaped) {
193 // odd number of slashes means | is escaped
194 // so we leave it alone
195 return '|';
196 } else {
197 // add space before unescaped |
198 return ' |';
199 }
200 }),
201 cells = row.split(/ \|/);
202 let i = 0;
203
204 // First/last cell in a row cannot be empty if it has no leading/trailing pipe
205 if (!cells[0].trim()) { cells.shift(); }
206 if (!cells[cells.length - 1].trim()) { cells.pop(); }
207
208 if (cells.length > count) {
209 cells.splice(count);
210 } else {
211 while (cells.length < count) cells.push('');
212 }
213
214 for (; i < cells.length; i++) {
215 // leading or trailing whitespace is ignored per the gfm spec
216 cells[i] = cells[i].trim().replace(/\\\|/g, '|');
217 }
218 return cells;
219}
220
221// Remove trailing 'c's. Equivalent to str.replace(/c*$/, '').
222// /c*$/ is vulnerable to REDOS.
223// invert: Remove suffix of non-c chars instead. Default falsey.
224function rtrim(str, c, invert) {
225 const l = str.length;
226 if (l === 0) {
227 return '';
228 }
229
230 // Length of suffix matching the invert condition.
231 let suffLen = 0;
232
233 // Step left until we fail to match the invert condition.
234 while (suffLen < l) {
235 const currChar = str.charAt(l - suffLen - 1);
236 if (currChar === c && !invert) {
237 suffLen++;
238 } else if (currChar !== c && invert) {
239 suffLen++;
240 } else {
241 break;
242 }
243 }
244
245 return str.substr(0, l - suffLen);
246}
247
248function findClosingBracket(str, b) {
249 if (str.indexOf(b[1]) === -1) {
250 return -1;
251 }
252 const l = str.length;
253 let level = 0,
254 i = 0;
255 for (; i < l; i++) {
256 if (str[i] === '\\') {
257 i++;
258 } else if (str[i] === b[0]) {
259 level++;
260 } else if (str[i] === b[1]) {
261 level--;
262 if (level < 0) {
263 return i;
264 }
265 }
266 }
267 return -1;
268}
269
270function checkSanitizeDeprecation(opt) {
271 if (opt && opt.sanitize && !opt.silent) {
272 console.warn('marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options');
273 }
274}
275
276// copied from https://stackoverflow.com/a/5450113/806777
277function repeatString(pattern, count) {
278 if (count < 1) {
279 return '';
280 }
281 let result = '';
282 while (count > 1) {
283 if (count & 1) {
284 result += pattern;
285 }
286 count >>= 1;
287 pattern += pattern;
288 }
289 return result + pattern;
290}
291
292function outputLink(cap, link, raw, lexer) {
293 const href = link.href;
294 const title = link.title ? escape(link.title) : null;
295 const text = cap[1].replace(/\\([\[\]])/g, '$1');
296
297 if (cap[0].charAt(0) !== '!') {
298 lexer.state.inLink = true;
299 const token = {
300 type: 'link',
301 raw,
302 href,
303 title,
304 text,
305 tokens: lexer.inlineTokens(text, [])
306 };
307 lexer.state.inLink = false;
308 return token;
309 } else {
310 return {
311 type: 'image',
312 raw,
313 href,
314 title,
315 text: escape(text)
316 };
317 }
318}
319
320function indentCodeCompensation(raw, text) {
321 const matchIndentToCode = raw.match(/^(\s+)(?:```)/);
322
323 if (matchIndentToCode === null) {
324 return text;
325 }
326
327 const indentToCode = matchIndentToCode[1];
328
329 return text
330 .split('\n')
331 .map(node => {
332 const matchIndentInNode = node.match(/^\s+/);
333 if (matchIndentInNode === null) {
334 return node;
335 }
336
337 const [indentInNode] = matchIndentInNode;
338
339 if (indentInNode.length >= indentToCode.length) {
340 return node.slice(indentToCode.length);
341 }
342
343 return node;
344 })
345 .join('\n');
346}
347
348/**
349 * Tokenizer
350 */
351class Tokenizer {
352 constructor(options) {
353 this.options = options || defaults;
354 }
355
356 space(src) {
357 const cap = this.rules.block.newline.exec(src);
358 if (cap && cap[0].length > 0) {
359 return {
360 type: 'space',
361 raw: cap[0]
362 };
363 }
364 }
365
366 code(src) {
367 const cap = this.rules.block.code.exec(src);
368 if (cap) {
369 const text = cap[0].replace(/^ {1,4}/gm, '');
370 return {
371 type: 'code',
372 raw: cap[0],
373 codeBlockStyle: 'indented',
374 text: !this.options.pedantic
375 ? rtrim(text, '\n')
376 : text
377 };
378 }
379 }
380
381 fences(src) {
382 const cap = this.rules.block.fences.exec(src);
383 if (cap) {
384 const raw = cap[0];
385 const text = indentCodeCompensation(raw, cap[3] || '');
386
387 return {
388 type: 'code',
389 raw,
390 lang: cap[2] ? cap[2].trim() : cap[2],
391 text
392 };
393 }
394 }
395
396 heading(src) {
397 const cap = this.rules.block.heading.exec(src);
398 if (cap) {
399 let text = cap[2].trim();
400
401 // remove trailing #s
402 if (/#$/.test(text)) {
403 const trimmed = rtrim(text, '#');
404 if (this.options.pedantic) {
405 text = trimmed.trim();
406 } else if (!trimmed || / $/.test(trimmed)) {
407 // CommonMark requires space before trailing #s
408 text = trimmed.trim();
409 }
410 }
411
412 const token = {
413 type: 'heading',
414 raw: cap[0],
415 depth: cap[1].length,
416 text: text,
417 tokens: []
418 };
419 this.lexer.inline(token.text, token.tokens);
420 return token;
421 }
422 }
423
424 hr(src) {
425 const cap = this.rules.block.hr.exec(src);
426 if (cap) {
427 return {
428 type: 'hr',
429 raw: cap[0]
430 };
431 }
432 }
433
434 blockquote(src) {
435 const cap = this.rules.block.blockquote.exec(src);
436 if (cap) {
437 const text = cap[0].replace(/^ *> ?/gm, '');
438
439 return {
440 type: 'blockquote',
441 raw: cap[0],
442 tokens: this.lexer.blockTokens(text, []),
443 text
444 };
445 }
446 }
447
448 list(src) {
449 let cap = this.rules.block.list.exec(src);
450 if (cap) {
451 let raw, istask, ischecked, indent, i, blankLine, endsWithBlankLine,
452 line, nextLine, rawLine, itemContents, endEarly;
453
454 let bull = cap[1].trim();
455 const isordered = bull.length > 1;
456
457 const list = {
458 type: 'list',
459 raw: '',
460 ordered: isordered,
461 start: isordered ? +bull.slice(0, -1) : '',
462 loose: false,
463 items: []
464 };
465
466 bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`;
467
468 if (this.options.pedantic) {
469 bull = isordered ? bull : '[*+-]';
470 }
471
472 // Get next list item
473 const itemRegex = new RegExp(`^( {0,3}${bull})((?: [^\\n]*)?(?:\\n|$))`);
474
475 // Check if current bullet point can start a new List Item
476 while (src) {
477 endEarly = false;
478 if (!(cap = itemRegex.exec(src))) {
479 break;
480 }
481
482 if (this.rules.block.hr.test(src)) { // End list if bullet was actually HR (possibly move into itemRegex?)
483 break;
484 }
485
486 raw = cap[0];
487 src = src.substring(raw.length);
488
489 line = cap[2].split('\n', 1)[0];
490 nextLine = src.split('\n', 1)[0];
491
492 if (this.options.pedantic) {
493 indent = 2;
494 itemContents = line.trimLeft();
495 } else {
496 indent = cap[2].search(/[^ ]/); // Find first non-space char
497 indent = indent > 4 ? 1 : indent; // Treat indented code blocks (> 4 spaces) as having only 1 indent
498 itemContents = line.slice(indent);
499 indent += cap[1].length;
500 }
501
502 blankLine = false;
503
504 if (!line && /^ *$/.test(nextLine)) { // Items begin with at most one blank line
505 raw += nextLine + '\n';
506 src = src.substring(nextLine.length + 1);
507 endEarly = true;
508 }
509
510 if (!endEarly) {
511 const nextBulletRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])`);
512
513 // Check if following lines should be included in List Item
514 while (src) {
515 rawLine = src.split('\n', 1)[0];
516 line = rawLine;
517
518 // Re-align to follow commonmark nesting rules
519 if (this.options.pedantic) {
520 line = line.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' ');
521 }
522
523 // End list item if found start of new bullet
524 if (nextBulletRegex.test(line)) {
525 break;
526 }
527
528 if (line.search(/[^ ]/) >= indent || !line.trim()) { // Dedent if possible
529 itemContents += '\n' + line.slice(indent);
530 } else if (!blankLine) { // Until blank line, item doesn't need indentation
531 itemContents += '\n' + line;
532 } else { // Otherwise, improper indentation ends this item
533 break;
534 }
535
536 if (!blankLine && !line.trim()) { // Check if current line is blank
537 blankLine = true;
538 }
539
540 raw += rawLine + '\n';
541 src = src.substring(rawLine.length + 1);
542 }
543 }
544
545 if (!list.loose) {
546 // If the previous item ended with a blank line, the list is loose
547 if (endsWithBlankLine) {
548 list.loose = true;
549 } else if (/\n *\n *$/.test(raw)) {
550 endsWithBlankLine = true;
551 }
552 }
553
554 // Check for task list items
555 if (this.options.gfm) {
556 istask = /^\[[ xX]\] /.exec(itemContents);
557 if (istask) {
558 ischecked = istask[0] !== '[ ] ';
559 itemContents = itemContents.replace(/^\[[ xX]\] +/, '');
560 }
561 }
562
563 list.items.push({
564 type: 'list_item',
565 raw: raw,
566 task: !!istask,
567 checked: ischecked,
568 loose: false,
569 text: itemContents
570 });
571
572 list.raw += raw;
573 }
574
575 // Do not consume newlines at end of final item. Alternatively, make itemRegex *start* with any newlines to simplify/speed up endsWithBlankLine logic
576 list.items[list.items.length - 1].raw = raw.trimRight();
577 list.items[list.items.length - 1].text = itemContents.trimRight();
578 list.raw = list.raw.trimRight();
579
580 const l = list.items.length;
581
582 // Item child tokens handled here at end because we needed to have the final item to trim it first
583 for (i = 0; i < l; i++) {
584 this.lexer.state.top = false;
585 list.items[i].tokens = this.lexer.blockTokens(list.items[i].text, []);
586 const spacers = list.items[i].tokens.filter(t => t.type === 'space');
587 const hasMultipleLineBreaks = spacers.every(t => {
588 const chars = t.raw.split('');
589 let lineBreaks = 0;
590 for (const char of chars) {
591 if (char === '\n') {
592 lineBreaks += 1;
593 }
594 if (lineBreaks > 1) {
595 return true;
596 }
597 }
598
599 return false;
600 });
601
602 if (!list.loose && spacers.length && hasMultipleLineBreaks) {
603 // Having a single line break doesn't mean a list is loose. A single line break is terminating the last list item
604 list.loose = true;
605 list.items[i].loose = true;
606 }
607 }
608
609 return list;
610 }
611 }
612
613 html(src) {
614 const cap = this.rules.block.html.exec(src);
615 if (cap) {
616 const token = {
617 type: 'html',
618 raw: cap[0],
619 pre: !this.options.sanitizer
620 && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
621 text: cap[0]
622 };
623 if (this.options.sanitize) {
624 token.type = 'paragraph';
625 token.text = this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0]);
626 token.tokens = [];
627 this.lexer.inline(token.text, token.tokens);
628 }
629 return token;
630 }
631 }
632
633 def(src) {
634 const cap = this.rules.block.def.exec(src);
635 if (cap) {
636 if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1);
637 const tag = cap[1].toLowerCase().replace(/\s+/g, ' ');
638 return {
639 type: 'def',
640 tag,
641 raw: cap[0],
642 href: cap[2],
643 title: cap[3]
644 };
645 }
646 }
647
648 table(src) {
649 const cap = this.rules.block.table.exec(src);
650 if (cap) {
651 const item = {
652 type: 'table',
653 header: splitCells(cap[1]).map(c => { return { text: c }; }),
654 align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
655 rows: cap[3] ? cap[3].replace(/\n[ \t]*$/, '').split('\n') : []
656 };
657
658 if (item.header.length === item.align.length) {
659 item.raw = cap[0];
660
661 let l = item.align.length;
662 let i, j, k, row;
663 for (i = 0; i < l; 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 l = item.rows.length;
676 for (i = 0; i < l; i++) {
677 item.rows[i] = splitCells(item.rows[i], item.header.length).map(c => { return { text: c }; });
678 }
679
680 // parse child tokens inside headers and cells
681
682 // header child tokens
683 l = item.header.length;
684 for (j = 0; j < l; j++) {
685 item.header[j].tokens = [];
686 this.lexer.inlineTokens(item.header[j].text, item.header[j].tokens);
687 }
688
689 // cell child tokens
690 l = item.rows.length;
691 for (j = 0; j < l; j++) {
692 row = item.rows[j];
693 for (k = 0; k < row.length; k++) {
694 row[k].tokens = [];
695 this.lexer.inlineTokens(row[k].text, row[k].tokens);
696 }
697 }
698
699 return item;
700 }
701 }
702 }
703
704 lheading(src) {
705 const cap = this.rules.block.lheading.exec(src);
706 if (cap) {
707 const token = {
708 type: 'heading',
709 raw: cap[0],
710 depth: cap[2].charAt(0) === '=' ? 1 : 2,
711 text: cap[1],
712 tokens: []
713 };
714 this.lexer.inline(token.text, token.tokens);
715 return token;
716 }
717 }
718
719 paragraph(src) {
720 const cap = this.rules.block.paragraph.exec(src);
721 if (cap) {
722 const token = {
723 type: 'paragraph',
724 raw: cap[0],
725 text: cap[1].charAt(cap[1].length - 1) === '\n'
726 ? cap[1].slice(0, -1)
727 : cap[1],
728 tokens: []
729 };
730 this.lexer.inline(token.text, token.tokens);
731 return token;
732 }
733 }
734
735 text(src) {
736 const cap = this.rules.block.text.exec(src);
737 if (cap) {
738 const token = {
739 type: 'text',
740 raw: cap[0],
741 text: cap[0],
742 tokens: []
743 };
744 this.lexer.inline(token.text, token.tokens);
745 return token;
746 }
747 }
748
749 escape(src) {
750 const cap = this.rules.inline.escape.exec(src);
751 if (cap) {
752 return {
753 type: 'escape',
754 raw: cap[0],
755 text: escape(cap[1])
756 };
757 }
758 }
759
760 tag(src) {
761 const cap = this.rules.inline.tag.exec(src);
762 if (cap) {
763 if (!this.lexer.state.inLink && /^<a /i.test(cap[0])) {
764 this.lexer.state.inLink = true;
765 } else if (this.lexer.state.inLink && /^<\/a>/i.test(cap[0])) {
766 this.lexer.state.inLink = false;
767 }
768 if (!this.lexer.state.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
769 this.lexer.state.inRawBlock = true;
770 } else if (this.lexer.state.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
771 this.lexer.state.inRawBlock = false;
772 }
773
774 return {
775 type: this.options.sanitize
776 ? 'text'
777 : 'html',
778 raw: cap[0],
779 inLink: this.lexer.state.inLink,
780 inRawBlock: this.lexer.state.inRawBlock,
781 text: this.options.sanitize
782 ? (this.options.sanitizer
783 ? this.options.sanitizer(cap[0])
784 : escape(cap[0]))
785 : cap[0]
786 };
787 }
788 }
789
790 link(src) {
791 const cap = this.rules.inline.link.exec(src);
792 if (cap) {
793 const trimmedUrl = cap[2].trim();
794 if (!this.options.pedantic && /^</.test(trimmedUrl)) {
795 // commonmark requires matching angle brackets
796 if (!(/>$/.test(trimmedUrl))) {
797 return;
798 }
799
800 // ending angle bracket cannot be escaped
801 const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\');
802 if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {
803 return;
804 }
805 } else {
806 // find closing parenthesis
807 const lastParenIndex = findClosingBracket(cap[2], '()');
808 if (lastParenIndex > -1) {
809 const start = cap[0].indexOf('!') === 0 ? 5 : 4;
810 const linkLen = start + cap[1].length + lastParenIndex;
811 cap[2] = cap[2].substring(0, lastParenIndex);
812 cap[0] = cap[0].substring(0, linkLen).trim();
813 cap[3] = '';
814 }
815 }
816 let href = cap[2];
817 let title = '';
818 if (this.options.pedantic) {
819 // split pedantic href and title
820 const link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
821
822 if (link) {
823 href = link[1];
824 title = link[3];
825 }
826 } else {
827 title = cap[3] ? cap[3].slice(1, -1) : '';
828 }
829
830 href = href.trim();
831 if (/^</.test(href)) {
832 if (this.options.pedantic && !(/>$/.test(trimmedUrl))) {
833 // pedantic allows starting angle bracket without ending angle bracket
834 href = href.slice(1);
835 } else {
836 href = href.slice(1, -1);
837 }
838 }
839 return outputLink(cap, {
840 href: href ? href.replace(this.rules.inline._escapes, '$1') : href,
841 title: title ? title.replace(this.rules.inline._escapes, '$1') : title
842 }, cap[0], this.lexer);
843 }
844 }
845
846 reflink(src, links) {
847 let cap;
848 if ((cap = this.rules.inline.reflink.exec(src))
849 || (cap = this.rules.inline.nolink.exec(src))) {
850 let link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
851 link = links[link.toLowerCase()];
852 if (!link || !link.href) {
853 const text = cap[0].charAt(0);
854 return {
855 type: 'text',
856 raw: text,
857 text
858 };
859 }
860 return outputLink(cap, link, cap[0], this.lexer);
861 }
862 }
863
864 emStrong(src, maskedSrc, prevChar = '') {
865 let match = this.rules.inline.emStrong.lDelim.exec(src);
866 if (!match) return;
867
868 // _ can't be between two alphanumerics. \p{L}\p{N} includes non-english alphabet/numbers as well
869 if (match[3] && prevChar.match(/[\p{L}\p{N}]/u)) return;
870
871 const nextChar = match[1] || match[2] || '';
872
873 if (!nextChar || (nextChar && (prevChar === '' || this.rules.inline.punctuation.exec(prevChar)))) {
874 const lLength = match[0].length - 1;
875 let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0;
876
877 const endReg = match[0][0] === '*' ? this.rules.inline.emStrong.rDelimAst : this.rules.inline.emStrong.rDelimUnd;
878 endReg.lastIndex = 0;
879
880 // Clip maskedSrc to same section of string as src (move to lexer?)
881 maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
882
883 while ((match = endReg.exec(maskedSrc)) != null) {
884 rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
885
886 if (!rDelim) continue; // skip single * in __abc*abc__
887
888 rLength = rDelim.length;
889
890 if (match[3] || match[4]) { // found another Left Delim
891 delimTotal += rLength;
892 continue;
893 } else if (match[5] || match[6]) { // either Left or Right Delim
894 if (lLength % 3 && !((lLength + rLength) % 3)) {
895 midDelimTotal += rLength;
896 continue; // CommonMark Emphasis Rules 9-10
897 }
898 }
899
900 delimTotal -= rLength;
901
902 if (delimTotal > 0) continue; // Haven't found enough closing delimiters
903
904 // Remove extra characters. *a*** -> *a*
905 rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal);
906
907 // Create `em` if smallest delimiter has odd char count. *a***
908 if (Math.min(lLength, rLength) % 2) {
909 const text = src.slice(1, lLength + match.index + rLength);
910 return {
911 type: 'em',
912 raw: src.slice(0, lLength + match.index + rLength + 1),
913 text,
914 tokens: this.lexer.inlineTokens(text, [])
915 };
916 }
917
918 // Create 'strong' if smallest delimiter has even char count. **a***
919 const text = src.slice(2, lLength + match.index + rLength - 1);
920 return {
921 type: 'strong',
922 raw: src.slice(0, lLength + match.index + rLength + 1),
923 text,
924 tokens: this.lexer.inlineTokens(text, [])
925 };
926 }
927 }
928 }
929
930 codespan(src) {
931 const cap = this.rules.inline.code.exec(src);
932 if (cap) {
933 let text = cap[2].replace(/\n/g, ' ');
934 const hasNonSpaceChars = /[^ ]/.test(text);
935 const hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text);
936 if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
937 text = text.substring(1, text.length - 1);
938 }
939 text = escape(text, true);
940 return {
941 type: 'codespan',
942 raw: cap[0],
943 text
944 };
945 }
946 }
947
948 br(src) {
949 const cap = this.rules.inline.br.exec(src);
950 if (cap) {
951 return {
952 type: 'br',
953 raw: cap[0]
954 };
955 }
956 }
957
958 del(src) {
959 const cap = this.rules.inline.del.exec(src);
960 if (cap) {
961 return {
962 type: 'del',
963 raw: cap[0],
964 text: cap[2],
965 tokens: this.lexer.inlineTokens(cap[2], [])
966 };
967 }
968 }
969
970 autolink(src, mangle) {
971 const cap = this.rules.inline.autolink.exec(src);
972 if (cap) {
973 let text, href;
974 if (cap[2] === '@') {
975 text = escape(this.options.mangle ? mangle(cap[1]) : cap[1]);
976 href = 'mailto:' + text;
977 } else {
978 text = escape(cap[1]);
979 href = text;
980 }
981
982 return {
983 type: 'link',
984 raw: cap[0],
985 text,
986 href,
987 tokens: [
988 {
989 type: 'text',
990 raw: text,
991 text
992 }
993 ]
994 };
995 }
996 }
997
998 url(src, mangle) {
999 let cap;
1000 if (cap = this.rules.inline.url.exec(src)) {
1001 let text, href;
1002 if (cap[2] === '@') {
1003 text = escape(this.options.mangle ? mangle(cap[0]) : cap[0]);
1004 href = 'mailto:' + text;
1005 } else {
1006 // do extended autolink path validation
1007 let prevCapZero;
1008 do {
1009 prevCapZero = cap[0];
1010 cap[0] = this.rules.inline._backpedal.exec(cap[0])[0];
1011 } while (prevCapZero !== cap[0]);
1012 text = escape(cap[0]);
1013 if (cap[1] === 'www.') {
1014 href = 'http://' + text;
1015 } else {
1016 href = text;
1017 }
1018 }
1019 return {
1020 type: 'link',
1021 raw: cap[0],
1022 text,
1023 href,
1024 tokens: [
1025 {
1026 type: 'text',
1027 raw: text,
1028 text
1029 }
1030 ]
1031 };
1032 }
1033 }
1034
1035 inlineText(src, smartypants) {
1036 const cap = this.rules.inline.text.exec(src);
1037 if (cap) {
1038 let text;
1039 if (this.lexer.state.inRawBlock) {
1040 text = this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0];
1041 } else {
1042 text = escape(this.options.smartypants ? smartypants(cap[0]) : cap[0]);
1043 }
1044 return {
1045 type: 'text',
1046 raw: cap[0],
1047 text
1048 };
1049 }
1050 }
1051}
1052
1053/**
1054 * Block-Level Grammar
1055 */
1056const block = {
1057 newline: /^(?: *(?:\n|$))+/,
1058 code: /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,
1059 fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?=\n|$)|$)/,
1060 hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
1061 heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,
1062 blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,
1063 list: /^( {0,3}bull)( [^\n]+?)?(?:\n|$)/,
1064 html: '^ {0,3}(?:' // optional indentation
1065 + '<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1)
1066 + '|comment[^\\n]*(\\n+|$)' // (2)
1067 + '|<\\?[\\s\\S]*?(?:\\?>\\n*|$)' // (3)
1068 + '|<![A-Z][\\s\\S]*?(?:>\\n*|$)' // (4)
1069 + '|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)' // (5)
1070 + '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (6)
1071 + '|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) open tag
1072 + '|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) closing tag
1073 + ')',
1074 def: /^ {0,3}\[(label)\]: *(?:\n *)?<?([^\s>]+)>?(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/,
1075 table: noopTest,
1076 lheading: /^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,
1077 // regex template, placeholders will be replaced according to different paragraph
1078 // interruption rules of commonmark and the original markdown spec:
1079 _paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,
1080 text: /^[^\n]+/
1081};
1082
1083block._label = /(?!\s*\])(?:\\.|[^\[\]\\])+/;
1084block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/;
1085block.def = edit(block.def)
1086 .replace('label', block._label)
1087 .replace('title', block._title)
1088 .getRegex();
1089
1090block.bullet = /(?:[*+-]|\d{1,9}[.)])/;
1091block.listItemStart = edit(/^( *)(bull) */)
1092 .replace('bull', block.bullet)
1093 .getRegex();
1094
1095block.list = edit(block.list)
1096 .replace(/bull/g, block.bullet)
1097 .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))')
1098 .replace('def', '\\n+(?=' + block.def.source + ')')
1099 .getRegex();
1100
1101block._tag = 'address|article|aside|base|basefont|blockquote|body|caption'
1102 + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption'
1103 + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe'
1104 + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option'
1105 + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr'
1106 + '|track|ul';
1107block._comment = /<!--(?!-?>)[\s\S]*?(?:-->|$)/;
1108block.html = edit(block.html, 'i')
1109 .replace('comment', block._comment)
1110 .replace('tag', block._tag)
1111 .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/)
1112 .getRegex();
1113
1114block.paragraph = edit(block._paragraph)
1115 .replace('hr', block.hr)
1116 .replace('heading', ' {0,3}#{1,6} ')
1117 .replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs
1118 .replace('|table', '')
1119 .replace('blockquote', ' {0,3}>')
1120 .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n')
1121 .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
1122 .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)')
1123 .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks
1124 .getRegex();
1125
1126block.blockquote = edit(block.blockquote)
1127 .replace('paragraph', block.paragraph)
1128 .getRegex();
1129
1130/**
1131 * Normal Block Grammar
1132 */
1133
1134block.normal = merge({}, block);
1135
1136/**
1137 * GFM Block Grammar
1138 */
1139
1140block.gfm = merge({}, block.normal, {
1141 table: '^ *([^\\n ].*\\|.*)\\n' // Header
1142 + ' {0,3}(?:\\| *)?(:?-+:? *(?:\\| *:?-+:? *)*)(?:\\| *)?' // Align
1143 + '(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)' // Cells
1144});
1145
1146block.gfm.table = edit(block.gfm.table)
1147 .replace('hr', block.hr)
1148 .replace('heading', ' {0,3}#{1,6} ')
1149 .replace('blockquote', ' {0,3}>')
1150 .replace('code', ' {4}[^\\n]')
1151 .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n')
1152 .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
1153 .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)')
1154 .replace('tag', block._tag) // tables can be interrupted by type (6) html blocks
1155 .getRegex();
1156
1157block.gfm.paragraph = edit(block._paragraph)
1158 .replace('hr', block.hr)
1159 .replace('heading', ' {0,3}#{1,6} ')
1160 .replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs
1161 .replace('table', block.gfm.table) // interrupt paragraphs with table
1162 .replace('blockquote', ' {0,3}>')
1163 .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n')
1164 .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
1165 .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)')
1166 .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks
1167 .getRegex();
1168/**
1169 * Pedantic grammar (original John Gruber's loose markdown specification)
1170 */
1171
1172block.pedantic = merge({}, block.normal, {
1173 html: edit(
1174 '^ *(?:comment *(?:\\n|\\s*$)'
1175 + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag
1176 + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))')
1177 .replace('comment', block._comment)
1178 .replace(/tag/g, '(?!(?:'
1179 + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub'
1180 + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)'
1181 + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b')
1182 .getRegex(),
1183 def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
1184 heading: /^(#{1,6})(.*)(?:\n+|$)/,
1185 fences: noopTest, // fences not supported
1186 paragraph: edit(block.normal._paragraph)
1187 .replace('hr', block.hr)
1188 .replace('heading', ' *#{1,6} *[^\n]')
1189 .replace('lheading', block.lheading)
1190 .replace('blockquote', ' {0,3}>')
1191 .replace('|fences', '')
1192 .replace('|list', '')
1193 .replace('|html', '')
1194 .getRegex()
1195});
1196
1197/**
1198 * Inline-Level Grammar
1199 */
1200const inline = {
1201 escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,
1202 autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/,
1203 url: noopTest,
1204 tag: '^comment'
1205 + '|^</[a-zA-Z][\\w:-]*\\s*>' // self-closing tag
1206 + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag
1207 + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?>
1208 + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html>
1209 + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>', // CDATA section
1210 link: /^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,
1211 reflink: /^!?\[(label)\]\[(ref)\]/,
1212 nolink: /^!?\[(ref)\](?:\[\])?/,
1213 reflinkSearch: 'reflink|nolink(?!\\()',
1214 emStrong: {
1215 lDelim: /^(?:\*+(?:([punct_])|[^\s*]))|^_+(?:([punct*])|([^\s_]))/,
1216 // (1) and (2) can only be a Right Delimiter. (3) and (4) can only be Left. (5) and (6) can be either Left or Right.
1217 // () Skip orphan delim inside strong (1) #*** (2) a***#, a*** (3) #***a, ***a (4) ***# (5) #***# (6) a***a
1218 rDelimAst: /^[^_*]*?\_\_[^_*]*?\*[^_*]*?(?=\_\_)|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/,
1219 rDelimUnd: /^[^_*]*?\*\*[^_*]*?\_[^_*]*?(?=\*\*)|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/ // ^- Not allowed for _
1220 },
1221 code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,
1222 br: /^( {2,}|\\)\n(?!\s*$)/,
1223 del: noopTest,
1224 text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,
1225 punctuation: /^([\spunctuation])/
1226};
1227
1228// list of punctuation marks from CommonMark spec
1229// without * and _ to handle the different emphasis markers * and _
1230inline._punctuation = '!"#$%&\'()+\\-.,/:;<=>?@\\[\\]`^{|}~';
1231inline.punctuation = edit(inline.punctuation).replace(/punctuation/g, inline._punctuation).getRegex();
1232
1233// sequences em should skip over [title](link), `code`, <html>
1234inline.blockSkip = /\[[^\]]*?\]\([^\)]*?\)|`[^`]*?`|<[^>]*?>/g;
1235inline.escapedEmSt = /\\\*|\\_/g;
1236
1237inline._comment = edit(block._comment).replace('(?:-->|$)', '-->').getRegex();
1238
1239inline.emStrong.lDelim = edit(inline.emStrong.lDelim)
1240 .replace(/punct/g, inline._punctuation)
1241 .getRegex();
1242
1243inline.emStrong.rDelimAst = edit(inline.emStrong.rDelimAst, 'g')
1244 .replace(/punct/g, inline._punctuation)
1245 .getRegex();
1246
1247inline.emStrong.rDelimUnd = edit(inline.emStrong.rDelimUnd, 'g')
1248 .replace(/punct/g, inline._punctuation)
1249 .getRegex();
1250
1251inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g;
1252
1253inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/;
1254inline._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])?)+(?![-_])/;
1255inline.autolink = edit(inline.autolink)
1256 .replace('scheme', inline._scheme)
1257 .replace('email', inline._email)
1258 .getRegex();
1259
1260inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/;
1261
1262inline.tag = edit(inline.tag)
1263 .replace('comment', inline._comment)
1264 .replace('attribute', inline._attribute)
1265 .getRegex();
1266
1267inline._label = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/;
1268inline._href = /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/;
1269inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/;
1270
1271inline.link = edit(inline.link)
1272 .replace('label', inline._label)
1273 .replace('href', inline._href)
1274 .replace('title', inline._title)
1275 .getRegex();
1276
1277inline.reflink = edit(inline.reflink)
1278 .replace('label', inline._label)
1279 .replace('ref', block._label)
1280 .getRegex();
1281
1282inline.nolink = edit(inline.nolink)
1283 .replace('ref', block._label)
1284 .getRegex();
1285
1286inline.reflinkSearch = edit(inline.reflinkSearch, 'g')
1287 .replace('reflink', inline.reflink)
1288 .replace('nolink', inline.nolink)
1289 .getRegex();
1290
1291/**
1292 * Normal Inline Grammar
1293 */
1294
1295inline.normal = merge({}, inline);
1296
1297/**
1298 * Pedantic Inline Grammar
1299 */
1300
1301inline.pedantic = merge({}, inline.normal, {
1302 strong: {
1303 start: /^__|\*\*/,
1304 middle: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
1305 endAst: /\*\*(?!\*)/g,
1306 endUnd: /__(?!_)/g
1307 },
1308 em: {
1309 start: /^_|\*/,
1310 middle: /^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,
1311 endAst: /\*(?!\*)/g,
1312 endUnd: /_(?!_)/g
1313 },
1314 link: edit(/^!?\[(label)\]\((.*?)\)/)
1315 .replace('label', inline._label)
1316 .getRegex(),
1317 reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/)
1318 .replace('label', inline._label)
1319 .getRegex()
1320});
1321
1322/**
1323 * GFM Inline Grammar
1324 */
1325
1326inline.gfm = merge({}, inline.normal, {
1327 escape: edit(inline.escape).replace('])', '~|])').getRegex(),
1328 _extended_email: /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,
1329 url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,
1330 _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,
1331 del: /^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,
1332 text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/
1333});
1334
1335inline.gfm.url = edit(inline.gfm.url, 'i')
1336 .replace('email', inline.gfm._extended_email)
1337 .getRegex();
1338/**
1339 * GFM + Line Breaks Inline Grammar
1340 */
1341
1342inline.breaks = merge({}, inline.gfm, {
1343 br: edit(inline.br).replace('{2,}', '*').getRegex(),
1344 text: edit(inline.gfm.text)
1345 .replace('\\b_', '\\b_| {2,}\\n')
1346 .replace(/\{2,\}/g, '*')
1347 .getRegex()
1348});
1349
1350/**
1351 * smartypants text replacement
1352 */
1353function smartypants(text) {
1354 return text
1355 // em-dashes
1356 .replace(/---/g, '\u2014')
1357 // en-dashes
1358 .replace(/--/g, '\u2013')
1359 // opening singles
1360 .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
1361 // closing singles & apostrophes
1362 .replace(/'/g, '\u2019')
1363 // opening doubles
1364 .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
1365 // closing doubles
1366 .replace(/"/g, '\u201d')
1367 // ellipses
1368 .replace(/\.{3}/g, '\u2026');
1369}
1370
1371/**
1372 * mangle email addresses
1373 */
1374function mangle(text) {
1375 let out = '',
1376 i,
1377 ch;
1378
1379 const l = text.length;
1380 for (i = 0; i < l; i++) {
1381 ch = text.charCodeAt(i);
1382 if (Math.random() > 0.5) {
1383 ch = 'x' + ch.toString(16);
1384 }
1385 out += '&#' + ch + ';';
1386 }
1387
1388 return out;
1389}
1390
1391/**
1392 * Block Lexer
1393 */
1394class Lexer {
1395 constructor(options) {
1396 this.tokens = [];
1397 this.tokens.links = Object.create(null);
1398 this.options = options || defaults;
1399 this.options.tokenizer = this.options.tokenizer || new Tokenizer();
1400 this.tokenizer = this.options.tokenizer;
1401 this.tokenizer.options = this.options;
1402 this.tokenizer.lexer = this;
1403 this.inlineQueue = [];
1404 this.state = {
1405 inLink: false,
1406 inRawBlock: false,
1407 top: true
1408 };
1409
1410 const rules = {
1411 block: block.normal,
1412 inline: inline.normal
1413 };
1414
1415 if (this.options.pedantic) {
1416 rules.block = block.pedantic;
1417 rules.inline = inline.pedantic;
1418 } else if (this.options.gfm) {
1419 rules.block = block.gfm;
1420 if (this.options.breaks) {
1421 rules.inline = inline.breaks;
1422 } else {
1423 rules.inline = inline.gfm;
1424 }
1425 }
1426 this.tokenizer.rules = rules;
1427 }
1428
1429 /**
1430 * Expose Rules
1431 */
1432 static get rules() {
1433 return {
1434 block,
1435 inline
1436 };
1437 }
1438
1439 /**
1440 * Static Lex Method
1441 */
1442 static lex(src, options) {
1443 const lexer = new Lexer(options);
1444 return lexer.lex(src);
1445 }
1446
1447 /**
1448 * Static Lex Inline Method
1449 */
1450 static lexInline(src, options) {
1451 const lexer = new Lexer(options);
1452 return lexer.inlineTokens(src);
1453 }
1454
1455 /**
1456 * Preprocessing
1457 */
1458 lex(src) {
1459 src = src
1460 .replace(/\r\n|\r/g, '\n')
1461 .replace(/\t/g, ' ');
1462
1463 this.blockTokens(src, this.tokens);
1464
1465 let next;
1466 while (next = this.inlineQueue.shift()) {
1467 this.inlineTokens(next.src, next.tokens);
1468 }
1469
1470 return this.tokens;
1471 }
1472
1473 /**
1474 * Lexing
1475 */
1476 blockTokens(src, tokens = []) {
1477 if (this.options.pedantic) {
1478 src = src.replace(/^ +$/gm, '');
1479 }
1480 let token, lastToken, cutSrc, lastParagraphClipped;
1481
1482 while (src) {
1483 if (this.options.extensions
1484 && this.options.extensions.block
1485 && this.options.extensions.block.some((extTokenizer) => {
1486 if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
1487 src = src.substring(token.raw.length);
1488 tokens.push(token);
1489 return true;
1490 }
1491 return false;
1492 })) {
1493 continue;
1494 }
1495
1496 // newline
1497 if (token = this.tokenizer.space(src)) {
1498 src = src.substring(token.raw.length);
1499 if (token.raw.length === 1 && tokens.length > 0) {
1500 // if there's a single \n as a spacer, it's terminating the last line,
1501 // so move it there so that we don't get unecessary paragraph tags
1502 tokens[tokens.length - 1].raw += '\n';
1503 } else {
1504 tokens.push(token);
1505 }
1506 continue;
1507 }
1508
1509 // code
1510 if (token = this.tokenizer.code(src)) {
1511 src = src.substring(token.raw.length);
1512 lastToken = tokens[tokens.length - 1];
1513 // An indented code block cannot interrupt a paragraph.
1514 if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) {
1515 lastToken.raw += '\n' + token.raw;
1516 lastToken.text += '\n' + token.text;
1517 this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
1518 } else {
1519 tokens.push(token);
1520 }
1521 continue;
1522 }
1523
1524 // fences
1525 if (token = this.tokenizer.fences(src)) {
1526 src = src.substring(token.raw.length);
1527 tokens.push(token);
1528 continue;
1529 }
1530
1531 // heading
1532 if (token = this.tokenizer.heading(src)) {
1533 src = src.substring(token.raw.length);
1534 tokens.push(token);
1535 continue;
1536 }
1537
1538 // hr
1539 if (token = this.tokenizer.hr(src)) {
1540 src = src.substring(token.raw.length);
1541 tokens.push(token);
1542 continue;
1543 }
1544
1545 // blockquote
1546 if (token = this.tokenizer.blockquote(src)) {
1547 src = src.substring(token.raw.length);
1548 tokens.push(token);
1549 continue;
1550 }
1551
1552 // list
1553 if (token = this.tokenizer.list(src)) {
1554 src = src.substring(token.raw.length);
1555 tokens.push(token);
1556 continue;
1557 }
1558
1559 // html
1560 if (token = this.tokenizer.html(src)) {
1561 src = src.substring(token.raw.length);
1562 tokens.push(token);
1563 continue;
1564 }
1565
1566 // def
1567 if (token = this.tokenizer.def(src)) {
1568 src = src.substring(token.raw.length);
1569 lastToken = tokens[tokens.length - 1];
1570 if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) {
1571 lastToken.raw += '\n' + token.raw;
1572 lastToken.text += '\n' + token.raw;
1573 this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
1574 } else if (!this.tokens.links[token.tag]) {
1575 this.tokens.links[token.tag] = {
1576 href: token.href,
1577 title: token.title
1578 };
1579 }
1580 continue;
1581 }
1582
1583 // table (gfm)
1584 if (token = this.tokenizer.table(src)) {
1585 src = src.substring(token.raw.length);
1586 tokens.push(token);
1587 continue;
1588 }
1589
1590 // lheading
1591 if (token = this.tokenizer.lheading(src)) {
1592 src = src.substring(token.raw.length);
1593 tokens.push(token);
1594 continue;
1595 }
1596
1597 // top-level paragraph
1598 // prevent paragraph consuming extensions by clipping 'src' to extension start
1599 cutSrc = src;
1600 if (this.options.extensions && this.options.extensions.startBlock) {
1601 let startIndex = Infinity;
1602 const tempSrc = src.slice(1);
1603 let tempStart;
1604 this.options.extensions.startBlock.forEach(function(getStartIndex) {
1605 tempStart = getStartIndex.call({ lexer: this }, tempSrc);
1606 if (typeof tempStart === 'number' && tempStart >= 0) { startIndex = Math.min(startIndex, tempStart); }
1607 });
1608 if (startIndex < Infinity && startIndex >= 0) {
1609 cutSrc = src.substring(0, startIndex + 1);
1610 }
1611 }
1612 if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) {
1613 lastToken = tokens[tokens.length - 1];
1614 if (lastParagraphClipped && lastToken.type === 'paragraph') {
1615 lastToken.raw += '\n' + token.raw;
1616 lastToken.text += '\n' + token.text;
1617 this.inlineQueue.pop();
1618 this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
1619 } else {
1620 tokens.push(token);
1621 }
1622 lastParagraphClipped = (cutSrc.length !== src.length);
1623 src = src.substring(token.raw.length);
1624 continue;
1625 }
1626
1627 // text
1628 if (token = this.tokenizer.text(src)) {
1629 src = src.substring(token.raw.length);
1630 lastToken = tokens[tokens.length - 1];
1631 if (lastToken && lastToken.type === 'text') {
1632 lastToken.raw += '\n' + token.raw;
1633 lastToken.text += '\n' + token.text;
1634 this.inlineQueue.pop();
1635 this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
1636 } else {
1637 tokens.push(token);
1638 }
1639 continue;
1640 }
1641
1642 if (src) {
1643 const errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0);
1644 if (this.options.silent) {
1645 console.error(errMsg);
1646 break;
1647 } else {
1648 throw new Error(errMsg);
1649 }
1650 }
1651 }
1652
1653 this.state.top = true;
1654 return tokens;
1655 }
1656
1657 inline(src, tokens) {
1658 this.inlineQueue.push({ src, tokens });
1659 }
1660
1661 /**
1662 * Lexing/Compiling
1663 */
1664 inlineTokens(src, tokens = []) {
1665 let token, lastToken, cutSrc;
1666
1667 // String with links masked to avoid interference with em and strong
1668 let maskedSrc = src;
1669 let match;
1670 let keepPrevChar, prevChar;
1671
1672 // Mask out reflinks
1673 if (this.tokens.links) {
1674 const links = Object.keys(this.tokens.links);
1675 if (links.length > 0) {
1676 while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) {
1677 if (links.includes(match[0].slice(match[0].lastIndexOf('[') + 1, -1))) {
1678 maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex);
1679 }
1680 }
1681 }
1682 }
1683 // Mask out other blocks
1684 while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) {
1685 maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);
1686 }
1687
1688 // Mask out escaped em & strong delimiters
1689 while ((match = this.tokenizer.rules.inline.escapedEmSt.exec(maskedSrc)) != null) {
1690 maskedSrc = maskedSrc.slice(0, match.index) + '++' + maskedSrc.slice(this.tokenizer.rules.inline.escapedEmSt.lastIndex);
1691 }
1692
1693 while (src) {
1694 if (!keepPrevChar) {
1695 prevChar = '';
1696 }
1697 keepPrevChar = false;
1698
1699 // extensions
1700 if (this.options.extensions
1701 && this.options.extensions.inline
1702 && this.options.extensions.inline.some((extTokenizer) => {
1703 if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
1704 src = src.substring(token.raw.length);
1705 tokens.push(token);
1706 return true;
1707 }
1708 return false;
1709 })) {
1710 continue;
1711 }
1712
1713 // escape
1714 if (token = this.tokenizer.escape(src)) {
1715 src = src.substring(token.raw.length);
1716 tokens.push(token);
1717 continue;
1718 }
1719
1720 // tag
1721 if (token = this.tokenizer.tag(src)) {
1722 src = src.substring(token.raw.length);
1723 lastToken = tokens[tokens.length - 1];
1724 if (lastToken && token.type === 'text' && lastToken.type === 'text') {
1725 lastToken.raw += token.raw;
1726 lastToken.text += token.text;
1727 } else {
1728 tokens.push(token);
1729 }
1730 continue;
1731 }
1732
1733 // link
1734 if (token = this.tokenizer.link(src)) {
1735 src = src.substring(token.raw.length);
1736 tokens.push(token);
1737 continue;
1738 }
1739
1740 // reflink, nolink
1741 if (token = this.tokenizer.reflink(src, this.tokens.links)) {
1742 src = src.substring(token.raw.length);
1743 lastToken = tokens[tokens.length - 1];
1744 if (lastToken && token.type === 'text' && lastToken.type === 'text') {
1745 lastToken.raw += token.raw;
1746 lastToken.text += token.text;
1747 } else {
1748 tokens.push(token);
1749 }
1750 continue;
1751 }
1752
1753 // em & strong
1754 if (token = this.tokenizer.emStrong(src, maskedSrc, prevChar)) {
1755 src = src.substring(token.raw.length);
1756 tokens.push(token);
1757 continue;
1758 }
1759
1760 // code
1761 if (token = this.tokenizer.codespan(src)) {
1762 src = src.substring(token.raw.length);
1763 tokens.push(token);
1764 continue;
1765 }
1766
1767 // br
1768 if (token = this.tokenizer.br(src)) {
1769 src = src.substring(token.raw.length);
1770 tokens.push(token);
1771 continue;
1772 }
1773
1774 // del (gfm)
1775 if (token = this.tokenizer.del(src)) {
1776 src = src.substring(token.raw.length);
1777 tokens.push(token);
1778 continue;
1779 }
1780
1781 // autolink
1782 if (token = this.tokenizer.autolink(src, mangle)) {
1783 src = src.substring(token.raw.length);
1784 tokens.push(token);
1785 continue;
1786 }
1787
1788 // url (gfm)
1789 if (!this.state.inLink && (token = this.tokenizer.url(src, mangle))) {
1790 src = src.substring(token.raw.length);
1791 tokens.push(token);
1792 continue;
1793 }
1794
1795 // text
1796 // prevent inlineText consuming extensions by clipping 'src' to extension start
1797 cutSrc = src;
1798 if (this.options.extensions && this.options.extensions.startInline) {
1799 let startIndex = Infinity;
1800 const tempSrc = src.slice(1);
1801 let tempStart;
1802 this.options.extensions.startInline.forEach(function(getStartIndex) {
1803 tempStart = getStartIndex.call({ lexer: this }, tempSrc);
1804 if (typeof tempStart === 'number' && tempStart >= 0) { startIndex = Math.min(startIndex, tempStart); }
1805 });
1806 if (startIndex < Infinity && startIndex >= 0) {
1807 cutSrc = src.substring(0, startIndex + 1);
1808 }
1809 }
1810 if (token = this.tokenizer.inlineText(cutSrc, smartypants)) {
1811 src = src.substring(token.raw.length);
1812 if (token.raw.slice(-1) !== '_') { // Track prevChar before string of ____ started
1813 prevChar = token.raw.slice(-1);
1814 }
1815 keepPrevChar = true;
1816 lastToken = tokens[tokens.length - 1];
1817 if (lastToken && lastToken.type === 'text') {
1818 lastToken.raw += token.raw;
1819 lastToken.text += token.text;
1820 } else {
1821 tokens.push(token);
1822 }
1823 continue;
1824 }
1825
1826 if (src) {
1827 const errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0);
1828 if (this.options.silent) {
1829 console.error(errMsg);
1830 break;
1831 } else {
1832 throw new Error(errMsg);
1833 }
1834 }
1835 }
1836
1837 return tokens;
1838 }
1839}
1840
1841/**
1842 * Renderer
1843 */
1844class Renderer {
1845 constructor(options) {
1846 this.options = options || defaults;
1847 }
1848
1849 code(code, infostring, escaped) {
1850 const lang = (infostring || '').match(/\S*/)[0];
1851 if (this.options.highlight) {
1852 const out = this.options.highlight(code, lang);
1853 if (out != null && out !== code) {
1854 escaped = true;
1855 code = out;
1856 }
1857 }
1858
1859 code = code.replace(/\n$/, '') + '\n';
1860
1861 if (!lang) {
1862 return '<pre><code>'
1863 + (escaped ? code : escape(code, true))
1864 + '</code></pre>\n';
1865 }
1866
1867 return '<pre><code class="'
1868 + this.options.langPrefix
1869 + escape(lang, true)
1870 + '">'
1871 + (escaped ? code : escape(code, true))
1872 + '</code></pre>\n';
1873 }
1874
1875 blockquote(quote) {
1876 return '<blockquote>\n' + quote + '</blockquote>\n';
1877 }
1878
1879 html(html) {
1880 return html;
1881 }
1882
1883 heading(text, level, raw, slugger) {
1884 if (this.options.headerIds) {
1885 return '<h'
1886 + level
1887 + ' id="'
1888 + this.options.headerPrefix
1889 + slugger.slug(raw)
1890 + '">'
1891 + text
1892 + '</h'
1893 + level
1894 + '>\n';
1895 }
1896 // ignore IDs
1897 return '<h' + level + '>' + text + '</h' + level + '>\n';
1898 }
1899
1900 hr() {
1901 return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
1902 }
1903
1904 list(body, ordered, start) {
1905 const type = ordered ? 'ol' : 'ul',
1906 startatt = (ordered && start !== 1) ? (' start="' + start + '"') : '';
1907 return '<' + type + startatt + '>\n' + body + '</' + type + '>\n';
1908 }
1909
1910 listitem(text) {
1911 return '<li>' + text + '</li>\n';
1912 }
1913
1914 checkbox(checked) {
1915 return '<input '
1916 + (checked ? 'checked="" ' : '')
1917 + 'disabled="" type="checkbox"'
1918 + (this.options.xhtml ? ' /' : '')
1919 + '> ';
1920 }
1921
1922 paragraph(text) {
1923 return '<p>' + text + '</p>\n';
1924 }
1925
1926 table(header, body) {
1927 if (body) body = '<tbody>' + body + '</tbody>';
1928
1929 return '<table>\n'
1930 + '<thead>\n'
1931 + header
1932 + '</thead>\n'
1933 + body
1934 + '</table>\n';
1935 }
1936
1937 tablerow(content) {
1938 return '<tr>\n' + content + '</tr>\n';
1939 }
1940
1941 tablecell(content, flags) {
1942 const type = flags.header ? 'th' : 'td';
1943 const tag = flags.align
1944 ? '<' + type + ' align="' + flags.align + '">'
1945 : '<' + type + '>';
1946 return tag + content + '</' + type + '>\n';
1947 }
1948
1949 // span level renderer
1950 strong(text) {
1951 return '<strong>' + text + '</strong>';
1952 }
1953
1954 em(text) {
1955 return '<em>' + text + '</em>';
1956 }
1957
1958 codespan(text) {
1959 return '<code>' + text + '</code>';
1960 }
1961
1962 br() {
1963 return this.options.xhtml ? '<br/>' : '<br>';
1964 }
1965
1966 del(text) {
1967 return '<del>' + text + '</del>';
1968 }
1969
1970 link(href, title, text) {
1971 href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
1972 if (href === null) {
1973 return text;
1974 }
1975 let out = '<a href="' + escape(href) + '"';
1976 if (title) {
1977 out += ' title="' + title + '"';
1978 }
1979 out += '>' + text + '</a>';
1980 return out;
1981 }
1982
1983 image(href, title, text) {
1984 href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
1985 if (href === null) {
1986 return text;
1987 }
1988
1989 let out = '<img src="' + href + '" alt="' + text + '"';
1990 if (title) {
1991 out += ' title="' + title + '"';
1992 }
1993 out += this.options.xhtml ? '/>' : '>';
1994 return out;
1995 }
1996
1997 text(text) {
1998 return text;
1999 }
2000}
2001
2002/**
2003 * TextRenderer
2004 * returns only the textual part of the token
2005 */
2006class TextRenderer {
2007 // no need for block level renderers
2008 strong(text) {
2009 return text;
2010 }
2011
2012 em(text) {
2013 return text;
2014 }
2015
2016 codespan(text) {
2017 return text;
2018 }
2019
2020 del(text) {
2021 return text;
2022 }
2023
2024 html(text) {
2025 return text;
2026 }
2027
2028 text(text) {
2029 return text;
2030 }
2031
2032 link(href, title, text) {
2033 return '' + text;
2034 }
2035
2036 image(href, title, text) {
2037 return '' + text;
2038 }
2039
2040 br() {
2041 return '';
2042 }
2043}
2044
2045/**
2046 * Slugger generates header id
2047 */
2048class Slugger {
2049 constructor() {
2050 this.seen = {};
2051 }
2052
2053 serialize(value) {
2054 return value
2055 .toLowerCase()
2056 .trim()
2057 // remove html tags
2058 .replace(/<[!\/a-z].*?>/ig, '')
2059 // remove unwanted chars
2060 .replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '')
2061 .replace(/\s/g, '-');
2062 }
2063
2064 /**
2065 * Finds the next safe (unique) slug to use
2066 */
2067 getNextSafeSlug(originalSlug, isDryRun) {
2068 let slug = originalSlug;
2069 let occurenceAccumulator = 0;
2070 if (this.seen.hasOwnProperty(slug)) {
2071 occurenceAccumulator = this.seen[originalSlug];
2072 do {
2073 occurenceAccumulator++;
2074 slug = originalSlug + '-' + occurenceAccumulator;
2075 } while (this.seen.hasOwnProperty(slug));
2076 }
2077 if (!isDryRun) {
2078 this.seen[originalSlug] = occurenceAccumulator;
2079 this.seen[slug] = 0;
2080 }
2081 return slug;
2082 }
2083
2084 /**
2085 * Convert string to unique id
2086 * @param {object} options
2087 * @param {boolean} options.dryrun Generates the next unique slug without updating the internal accumulator.
2088 */
2089 slug(value, options = {}) {
2090 const slug = this.serialize(value);
2091 return this.getNextSafeSlug(slug, options.dryrun);
2092 }
2093}
2094
2095/**
2096 * Parsing & Compiling
2097 */
2098class Parser {
2099 constructor(options) {
2100 this.options = options || defaults;
2101 this.options.renderer = this.options.renderer || new Renderer();
2102 this.renderer = this.options.renderer;
2103 this.renderer.options = this.options;
2104 this.textRenderer = new TextRenderer();
2105 this.slugger = new Slugger();
2106 }
2107
2108 /**
2109 * Static Parse Method
2110 */
2111 static parse(tokens, options) {
2112 const parser = new Parser(options);
2113 return parser.parse(tokens);
2114 }
2115
2116 /**
2117 * Static Parse Inline Method
2118 */
2119 static parseInline(tokens, options) {
2120 const parser = new Parser(options);
2121 return parser.parseInline(tokens);
2122 }
2123
2124 /**
2125 * Parse Loop
2126 */
2127 parse(tokens, top = true) {
2128 let out = '',
2129 i,
2130 j,
2131 k,
2132 l2,
2133 l3,
2134 row,
2135 cell,
2136 header,
2137 body,
2138 token,
2139 ordered,
2140 start,
2141 loose,
2142 itemBody,
2143 item,
2144 checked,
2145 task,
2146 checkbox,
2147 ret;
2148
2149 const l = tokens.length;
2150 for (i = 0; i < l; i++) {
2151 token = tokens[i];
2152
2153 // Run any renderer extensions
2154 if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
2155 ret = this.options.extensions.renderers[token.type].call({ parser: this }, token);
2156 if (ret !== false || !['space', 'hr', 'heading', 'code', 'table', 'blockquote', 'list', 'html', 'paragraph', 'text'].includes(token.type)) {
2157 out += ret || '';
2158 continue;
2159 }
2160 }
2161
2162 switch (token.type) {
2163 case 'space': {
2164 continue;
2165 }
2166 case 'hr': {
2167 out += this.renderer.hr();
2168 continue;
2169 }
2170 case 'heading': {
2171 out += this.renderer.heading(
2172 this.parseInline(token.tokens),
2173 token.depth,
2174 unescape(this.parseInline(token.tokens, this.textRenderer)),
2175 this.slugger);
2176 continue;
2177 }
2178 case 'code': {
2179 out += this.renderer.code(token.text,
2180 token.lang,
2181 token.escaped);
2182 continue;
2183 }
2184 case 'table': {
2185 header = '';
2186
2187 // header
2188 cell = '';
2189 l2 = token.header.length;
2190 for (j = 0; j < l2; j++) {
2191 cell += this.renderer.tablecell(
2192 this.parseInline(token.header[j].tokens),
2193 { header: true, align: token.align[j] }
2194 );
2195 }
2196 header += this.renderer.tablerow(cell);
2197
2198 body = '';
2199 l2 = token.rows.length;
2200 for (j = 0; j < l2; j++) {
2201 row = token.rows[j];
2202
2203 cell = '';
2204 l3 = row.length;
2205 for (k = 0; k < l3; k++) {
2206 cell += this.renderer.tablecell(
2207 this.parseInline(row[k].tokens),
2208 { header: false, align: token.align[k] }
2209 );
2210 }
2211
2212 body += this.renderer.tablerow(cell);
2213 }
2214 out += this.renderer.table(header, body);
2215 continue;
2216 }
2217 case 'blockquote': {
2218 body = this.parse(token.tokens);
2219 out += this.renderer.blockquote(body);
2220 continue;
2221 }
2222 case 'list': {
2223 ordered = token.ordered;
2224 start = token.start;
2225 loose = token.loose;
2226 l2 = token.items.length;
2227
2228 body = '';
2229 for (j = 0; j < l2; j++) {
2230 item = token.items[j];
2231 checked = item.checked;
2232 task = item.task;
2233
2234 itemBody = '';
2235 if (item.task) {
2236 checkbox = this.renderer.checkbox(checked);
2237 if (loose) {
2238 if (item.tokens.length > 0 && item.tokens[0].type === 'paragraph') {
2239 item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
2240 if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
2241 item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text;
2242 }
2243 } else {
2244 item.tokens.unshift({
2245 type: 'text',
2246 text: checkbox
2247 });
2248 }
2249 } else {
2250 itemBody += checkbox;
2251 }
2252 }
2253
2254 itemBody += this.parse(item.tokens, loose);
2255 body += this.renderer.listitem(itemBody, task, checked);
2256 }
2257
2258 out += this.renderer.list(body, ordered, start);
2259 continue;
2260 }
2261 case 'html': {
2262 // TODO parse inline content if parameter markdown=1
2263 out += this.renderer.html(token.text);
2264 continue;
2265 }
2266 case 'paragraph': {
2267 out += this.renderer.paragraph(this.parseInline(token.tokens));
2268 continue;
2269 }
2270 case 'text': {
2271 body = token.tokens ? this.parseInline(token.tokens) : token.text;
2272 while (i + 1 < l && tokens[i + 1].type === 'text') {
2273 token = tokens[++i];
2274 body += '\n' + (token.tokens ? this.parseInline(token.tokens) : token.text);
2275 }
2276 out += top ? this.renderer.paragraph(body) : body;
2277 continue;
2278 }
2279
2280 default: {
2281 const errMsg = 'Token with "' + token.type + '" type was not found.';
2282 if (this.options.silent) {
2283 console.error(errMsg);
2284 return;
2285 } else {
2286 throw new Error(errMsg);
2287 }
2288 }
2289 }
2290 }
2291
2292 return out;
2293 }
2294
2295 /**
2296 * Parse Inline Tokens
2297 */
2298 parseInline(tokens, renderer) {
2299 renderer = renderer || this.renderer;
2300 let out = '',
2301 i,
2302 token,
2303 ret;
2304
2305 const l = tokens.length;
2306 for (i = 0; i < l; i++) {
2307 token = tokens[i];
2308
2309 // Run any renderer extensions
2310 if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
2311 ret = this.options.extensions.renderers[token.type].call({ parser: this }, token);
2312 if (ret !== false || !['escape', 'html', 'link', 'image', 'strong', 'em', 'codespan', 'br', 'del', 'text'].includes(token.type)) {
2313 out += ret || '';
2314 continue;
2315 }
2316 }
2317
2318 switch (token.type) {
2319 case 'escape': {
2320 out += renderer.text(token.text);
2321 break;
2322 }
2323 case 'html': {
2324 out += renderer.html(token.text);
2325 break;
2326 }
2327 case 'link': {
2328 out += renderer.link(token.href, token.title, this.parseInline(token.tokens, renderer));
2329 break;
2330 }
2331 case 'image': {
2332 out += renderer.image(token.href, token.title, token.text);
2333 break;
2334 }
2335 case 'strong': {
2336 out += renderer.strong(this.parseInline(token.tokens, renderer));
2337 break;
2338 }
2339 case 'em': {
2340 out += renderer.em(this.parseInline(token.tokens, renderer));
2341 break;
2342 }
2343 case 'codespan': {
2344 out += renderer.codespan(token.text);
2345 break;
2346 }
2347 case 'br': {
2348 out += renderer.br();
2349 break;
2350 }
2351 case 'del': {
2352 out += renderer.del(this.parseInline(token.tokens, renderer));
2353 break;
2354 }
2355 case 'text': {
2356 out += renderer.text(token.text);
2357 break;
2358 }
2359 default: {
2360 const errMsg = 'Token with "' + token.type + '" type was not found.';
2361 if (this.options.silent) {
2362 console.error(errMsg);
2363 return;
2364 } else {
2365 throw new Error(errMsg);
2366 }
2367 }
2368 }
2369 }
2370 return out;
2371 }
2372}
2373
2374/**
2375 * Marked
2376 */
2377function marked(src, opt, callback) {
2378 // throw error in case of non string input
2379 if (typeof src === 'undefined' || src === null) {
2380 throw new Error('marked(): input parameter is undefined or null');
2381 }
2382 if (typeof src !== 'string') {
2383 throw new Error('marked(): input parameter is of type '
2384 + Object.prototype.toString.call(src) + ', string expected');
2385 }
2386
2387 if (typeof opt === 'function') {
2388 callback = opt;
2389 opt = null;
2390 }
2391
2392 opt = merge({}, marked.defaults, opt || {});
2393 checkSanitizeDeprecation(opt);
2394
2395 if (callback) {
2396 const highlight = opt.highlight;
2397 let tokens;
2398
2399 try {
2400 tokens = Lexer.lex(src, opt);
2401 } catch (e) {
2402 return callback(e);
2403 }
2404
2405 const done = function(err) {
2406 let out;
2407
2408 if (!err) {
2409 try {
2410 if (opt.walkTokens) {
2411 marked.walkTokens(tokens, opt.walkTokens);
2412 }
2413 out = Parser.parse(tokens, opt);
2414 } catch (e) {
2415 err = e;
2416 }
2417 }
2418
2419 opt.highlight = highlight;
2420
2421 return err
2422 ? callback(err)
2423 : callback(null, out);
2424 };
2425
2426 if (!highlight || highlight.length < 3) {
2427 return done();
2428 }
2429
2430 delete opt.highlight;
2431
2432 if (!tokens.length) return done();
2433
2434 let pending = 0;
2435 marked.walkTokens(tokens, function(token) {
2436 if (token.type === 'code') {
2437 pending++;
2438 setTimeout(() => {
2439 highlight(token.text, token.lang, function(err, code) {
2440 if (err) {
2441 return done(err);
2442 }
2443 if (code != null && code !== token.text) {
2444 token.text = code;
2445 token.escaped = true;
2446 }
2447
2448 pending--;
2449 if (pending === 0) {
2450 done();
2451 }
2452 });
2453 }, 0);
2454 }
2455 });
2456
2457 if (pending === 0) {
2458 done();
2459 }
2460
2461 return;
2462 }
2463
2464 try {
2465 const tokens = Lexer.lex(src, opt);
2466 if (opt.walkTokens) {
2467 marked.walkTokens(tokens, opt.walkTokens);
2468 }
2469 return Parser.parse(tokens, opt);
2470 } catch (e) {
2471 e.message += '\nPlease report this to https://github.com/markedjs/marked.';
2472 if (opt.silent) {
2473 return '<p>An error occurred:</p><pre>'
2474 + escape(e.message + '', true)
2475 + '</pre>';
2476 }
2477 throw e;
2478 }
2479}
2480
2481/**
2482 * Options
2483 */
2484
2485marked.options =
2486marked.setOptions = function(opt) {
2487 merge(marked.defaults, opt);
2488 changeDefaults(marked.defaults);
2489 return marked;
2490};
2491
2492marked.getDefaults = getDefaults;
2493
2494marked.defaults = defaults;
2495
2496/**
2497 * Use Extension
2498 */
2499
2500marked.use = function(...args) {
2501 const opts = merge({}, ...args);
2502 const extensions = marked.defaults.extensions || { renderers: {}, childTokens: {} };
2503 let hasExtensions;
2504
2505 args.forEach((pack) => {
2506 // ==-- Parse "addon" extensions --== //
2507 if (pack.extensions) {
2508 hasExtensions = true;
2509 pack.extensions.forEach((ext) => {
2510 if (!ext.name) {
2511 throw new Error('extension name required');
2512 }
2513 if (ext.renderer) { // Renderer extensions
2514 const prevRenderer = extensions.renderers ? extensions.renderers[ext.name] : null;
2515 if (prevRenderer) {
2516 // Replace extension with func to run new extension but fall back if false
2517 extensions.renderers[ext.name] = function(...args) {
2518 let ret = ext.renderer.apply(this, args);
2519 if (ret === false) {
2520 ret = prevRenderer.apply(this, args);
2521 }
2522 return ret;
2523 };
2524 } else {
2525 extensions.renderers[ext.name] = ext.renderer;
2526 }
2527 }
2528 if (ext.tokenizer) { // Tokenizer Extensions
2529 if (!ext.level || (ext.level !== 'block' && ext.level !== 'inline')) {
2530 throw new Error("extension level must be 'block' or 'inline'");
2531 }
2532 if (extensions[ext.level]) {
2533 extensions[ext.level].unshift(ext.tokenizer);
2534 } else {
2535 extensions[ext.level] = [ext.tokenizer];
2536 }
2537 if (ext.start) { // Function to check for start of token
2538 if (ext.level === 'block') {
2539 if (extensions.startBlock) {
2540 extensions.startBlock.push(ext.start);
2541 } else {
2542 extensions.startBlock = [ext.start];
2543 }
2544 } else if (ext.level === 'inline') {
2545 if (extensions.startInline) {
2546 extensions.startInline.push(ext.start);
2547 } else {
2548 extensions.startInline = [ext.start];
2549 }
2550 }
2551 }
2552 }
2553 if (ext.childTokens) { // Child tokens to be visited by walkTokens
2554 extensions.childTokens[ext.name] = ext.childTokens;
2555 }
2556 });
2557 }
2558
2559 // ==-- Parse "overwrite" extensions --== //
2560 if (pack.renderer) {
2561 const renderer = marked.defaults.renderer || new Renderer();
2562 for (const prop in pack.renderer) {
2563 const prevRenderer = renderer[prop];
2564 // Replace renderer with func to run extension, but fall back if false
2565 renderer[prop] = (...args) => {
2566 let ret = pack.renderer[prop].apply(renderer, args);
2567 if (ret === false) {
2568 ret = prevRenderer.apply(renderer, args);
2569 }
2570 return ret;
2571 };
2572 }
2573 opts.renderer = renderer;
2574 }
2575 if (pack.tokenizer) {
2576 const tokenizer = marked.defaults.tokenizer || new Tokenizer();
2577 for (const prop in pack.tokenizer) {
2578 const prevTokenizer = tokenizer[prop];
2579 // Replace tokenizer with func to run extension, but fall back if false
2580 tokenizer[prop] = (...args) => {
2581 let ret = pack.tokenizer[prop].apply(tokenizer, args);
2582 if (ret === false) {
2583 ret = prevTokenizer.apply(tokenizer, args);
2584 }
2585 return ret;
2586 };
2587 }
2588 opts.tokenizer = tokenizer;
2589 }
2590
2591 // ==-- Parse WalkTokens extensions --== //
2592 if (pack.walkTokens) {
2593 const walkTokens = marked.defaults.walkTokens;
2594 opts.walkTokens = function(token) {
2595 pack.walkTokens.call(this, token);
2596 if (walkTokens) {
2597 walkTokens.call(this, token);
2598 }
2599 };
2600 }
2601
2602 if (hasExtensions) {
2603 opts.extensions = extensions;
2604 }
2605
2606 marked.setOptions(opts);
2607 });
2608};
2609
2610/**
2611 * Run callback for every token
2612 */
2613
2614marked.walkTokens = function(tokens, callback) {
2615 for (const token of tokens) {
2616 callback.call(marked, token);
2617 switch (token.type) {
2618 case 'table': {
2619 for (const cell of token.header) {
2620 marked.walkTokens(cell.tokens, callback);
2621 }
2622 for (const row of token.rows) {
2623 for (const cell of row) {
2624 marked.walkTokens(cell.tokens, callback);
2625 }
2626 }
2627 break;
2628 }
2629 case 'list': {
2630 marked.walkTokens(token.items, callback);
2631 break;
2632 }
2633 default: {
2634 if (marked.defaults.extensions && marked.defaults.extensions.childTokens && marked.defaults.extensions.childTokens[token.type]) { // Walk any extensions
2635 marked.defaults.extensions.childTokens[token.type].forEach(function(childTokens) {
2636 marked.walkTokens(token[childTokens], callback);
2637 });
2638 } else if (token.tokens) {
2639 marked.walkTokens(token.tokens, callback);
2640 }
2641 }
2642 }
2643 }
2644};
2645
2646/**
2647 * Parse Inline
2648 */
2649marked.parseInline = function(src, opt) {
2650 // throw error in case of non string input
2651 if (typeof src === 'undefined' || src === null) {
2652 throw new Error('marked.parseInline(): input parameter is undefined or null');
2653 }
2654 if (typeof src !== 'string') {
2655 throw new Error('marked.parseInline(): input parameter is of type '
2656 + Object.prototype.toString.call(src) + ', string expected');
2657 }
2658
2659 opt = merge({}, marked.defaults, opt || {});
2660 checkSanitizeDeprecation(opt);
2661
2662 try {
2663 const tokens = Lexer.lexInline(src, opt);
2664 if (opt.walkTokens) {
2665 marked.walkTokens(tokens, opt.walkTokens);
2666 }
2667 return Parser.parseInline(tokens, opt);
2668 } catch (e) {
2669 e.message += '\nPlease report this to https://github.com/markedjs/marked.';
2670 if (opt.silent) {
2671 return '<p>An error occurred:</p><pre>'
2672 + escape(e.message + '', true)
2673 + '</pre>';
2674 }
2675 throw e;
2676 }
2677};
2678
2679/**
2680 * Expose
2681 */
2682marked.Parser = Parser;
2683marked.parser = Parser.parse;
2684marked.Renderer = Renderer;
2685marked.TextRenderer = TextRenderer;
2686marked.Lexer = Lexer;
2687marked.lexer = Lexer.lex;
2688marked.Tokenizer = Tokenizer;
2689marked.Slugger = Slugger;
2690marked.parse = marked;
2691
2692const options = marked.options;
2693const setOptions = marked.setOptions;
2694const use = marked.use;
2695const walkTokens = marked.walkTokens;
2696const parseInline = marked.parseInline;
2697const parse = marked;
2698const parser = Parser.parse;
2699const lexer = Lexer.lex;
2700
2701export { Lexer, Parser, Renderer, Slugger, TextRenderer, Tokenizer, defaults, getDefaults, lexer, marked, options, parse, parseInline, parser, setOptions, use, walkTokens };