UNPKG

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