UNPKG

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