marked 14.1.4 → 15.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/marked.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * marked v14.1.4 - a markdown parser
2
+ * marked v15.0.1 - a markdown parser
3
3
  * Copyright (c) 2011-2024, Christopher Jeffrey. (MIT Licensed)
4
4
  * https://github.com/markedjs/marked
5
5
  */
@@ -31,42 +31,13 @@ function changeDefaults(newDefaults) {
31
31
  _defaults = newDefaults;
32
32
  }
33
33
 
34
- /**
35
- * Helpers
36
- */
37
- const escapeTest = /[&<>"']/;
38
- const escapeReplace = new RegExp(escapeTest.source, 'g');
39
- const escapeTestNoEncode = /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/;
40
- const escapeReplaceNoEncode = new RegExp(escapeTestNoEncode.source, 'g');
41
- const escapeReplacements = {
42
- '&': '&amp;',
43
- '<': '&lt;',
44
- '>': '&gt;',
45
- '"': '&quot;',
46
- "'": '&#39;',
47
- };
48
- const getEscapeReplacement = (ch) => escapeReplacements[ch];
49
- function escape$1(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
- }
62
- const caret = /(^|[^\[])\^/g;
63
- function edit(regex, opt) {
34
+ const noopTest = { exec: () => null };
35
+ function edit(regex, opt = '') {
64
36
  let source = typeof regex === 'string' ? regex : regex.source;
65
- opt = opt || '';
66
37
  const obj = {
67
38
  replace: (name, val) => {
68
39
  let valSource = typeof val === 'string' ? val : val.source;
69
- valSource = valSource.replace(caret, '$1');
40
+ valSource = valSource.replace(other.caret, '$1');
70
41
  source = source.replace(name, valSource);
71
42
  return obj;
72
43
  },
@@ -76,230 +47,596 @@ function edit(regex, opt) {
76
47
  };
77
48
  return obj;
78
49
  }
79
- function cleanUrl(href) {
80
- try {
81
- href = encodeURI(href).replace(/%25/g, '%');
82
- }
83
- catch {
84
- return null;
85
- }
86
- return href;
87
- }
88
- const noopTest = { exec: () => null };
89
- function splitCells(tableRow, count) {
90
- // ensure that every cell-delimiting pipe has a space
91
- // before it to distinguish it from an escaped pipe
92
- const row = tableRow.replace(/\|/g, (match, offset, str) => {
93
- let escaped = false;
94
- let curr = offset;
95
- while (--curr >= 0 && str[curr] === '\\')
96
- escaped = !escaped;
97
- if (escaped) {
98
- // odd number of slashes means | is escaped
99
- // so we leave it alone
100
- return '|';
101
- }
102
- else {
103
- // add space before unescaped |
104
- return ' |';
105
- }
106
- }), cells = row.split(/ \|/);
107
- let i = 0;
108
- // First/last cell in a row cannot be empty if it has no leading/trailing pipe
109
- if (!cells[0].trim()) {
110
- cells.shift();
111
- }
112
- if (cells.length > 0 && !cells[cells.length - 1].trim()) {
113
- cells.pop();
114
- }
115
- if (count) {
116
- if (cells.length > count) {
117
- cells.splice(count);
118
- }
119
- else {
120
- while (cells.length < count)
121
- cells.push('');
122
- }
123
- }
124
- for (; i < cells.length; i++) {
125
- // leading or trailing whitespace is ignored per the gfm spec
126
- cells[i] = cells[i].trim().replace(/\\\|/g, '|');
127
- }
128
- return cells;
129
- }
50
+ const other = {
51
+ codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm,
52
+ outputLinkReplace: /\\([\[\]])/g,
53
+ indentCodeCompensation: /^(\s+)(?:```)/,
54
+ beginningSpace: /^\s+/,
55
+ endingHash: /#$/,
56
+ startingSpaceChar: /^ /,
57
+ endingSpaceChar: / $/,
58
+ nonSpaceChar: /[^ ]/,
59
+ newLineCharGlobal: /\n/g,
60
+ tabCharGlobal: /\t/g,
61
+ multipleSpaceGlobal: /\s+/g,
62
+ blankLine: /^[ \t]*$/,
63
+ doubleBlankLine: /\n[ \t]*\n[ \t]*$/,
64
+ blockquoteStart: /^ {0,3}>/,
65
+ blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g,
66
+ blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm,
67
+ listReplaceTabs: /^\t+/,
68
+ listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g,
69
+ listIsTask: /^\[[ xX]\] /,
70
+ listReplaceTask: /^\[[ xX]\] +/,
71
+ anyLine: /\n.*\n/,
72
+ hrefBrackets: /^<(.*)>$/,
73
+ tableDelimiter: /[:|]/,
74
+ tableAlignChars: /^\||\| *$/g,
75
+ tableRowBlankLine: /\n[ \t]*$/,
76
+ tableAlignRight: /^ *-+: *$/,
77
+ tableAlignCenter: /^ *:-+: *$/,
78
+ tableAlignLeft: /^ *:-+ *$/,
79
+ startATag: /^<a /i,
80
+ endATag: /^<\/a>/i,
81
+ startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i,
82
+ endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i,
83
+ startAngleBracket: /^</,
84
+ endAngleBracket: />$/,
85
+ pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/,
86
+ unicodeAlphaNumeric: /[\p{L}\p{N}]/u,
87
+ escapeTest: /[&<>"']/,
88
+ escapeReplace: /[&<>"']/g,
89
+ escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,
90
+ escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,
91
+ unescapeTest: /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig,
92
+ caret: /(^|[^\[])\^/g,
93
+ percentDecode: /%25/g,
94
+ findPipe: /\|/g,
95
+ splitPipe: / \|/,
96
+ slashPipe: /\\\|/g,
97
+ carriageReturn: /\r\n|\r/g,
98
+ spaceLine: /^ +$/gm,
99
+ notSpaceStart: /^\S*/,
100
+ endingNewline: /\n$/,
101
+ listItemRegex: (bull) => new RegExp(`^( {0,3}${bull})((?:[\t ][^\\n]*)?(?:\\n|$))`),
102
+ nextBulletRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),
103
+ hrRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),
104
+ fencesBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`),
105
+ headingBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`),
106
+ htmlBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}<(?:[a-z].*>|!--)`, 'i'),
107
+ };
130
108
  /**
131
- * Remove trailing 'c's. Equivalent to str.replace(/c*$/, '').
132
- * /c*$/ is vulnerable to REDOS.
133
- *
134
- * @param str
135
- * @param c
136
- * @param invert Remove suffix of non-c chars instead. Default falsey.
109
+ * Block-Level Grammar
137
110
  */
138
- function rtrim(str, c, invert) {
139
- const l = str.length;
140
- if (l === 0) {
141
- return '';
142
- }
143
- // Length of suffix matching the invert condition.
144
- let suffLen = 0;
145
- // Step left until we fail to match the invert condition.
146
- while (suffLen < l) {
147
- const currChar = str.charAt(l - suffLen - 1);
148
- if (currChar === c && !invert) {
149
- suffLen++;
150
- }
151
- else if (currChar !== c && invert) {
152
- suffLen++;
153
- }
154
- else {
155
- break;
156
- }
157
- }
158
- return str.slice(0, l - suffLen);
159
- }
160
- function findClosingBracket(str, b) {
161
- if (str.indexOf(b[1]) === -1) {
162
- return -1;
163
- }
164
- let level = 0;
165
- for (let i = 0; i < str.length; i++) {
166
- if (str[i] === '\\') {
167
- i++;
168
- }
169
- else if (str[i] === b[0]) {
170
- level++;
171
- }
172
- else if (str[i] === b[1]) {
173
- level--;
174
- if (level < 0) {
175
- return i;
176
- }
177
- }
178
- }
179
- return -1;
180
- }
181
-
182
- function outputLink(cap, link, raw, lexer) {
183
- const href = link.href;
184
- const title = link.title ? escape$1(link.title) : null;
185
- const text = cap[1].replace(/\\([\[\]])/g, '$1');
186
- if (cap[0].charAt(0) !== '!') {
187
- lexer.state.inLink = true;
188
- const token = {
189
- type: 'link',
190
- raw,
191
- href,
192
- title,
193
- text,
194
- tokens: lexer.inlineTokens(text),
195
- };
196
- lexer.state.inLink = false;
197
- return token;
198
- }
199
- return {
200
- type: 'image',
201
- raw,
202
- href,
203
- title,
204
- text: escape$1(text),
205
- };
206
- }
207
- function indentCodeCompensation(raw, text) {
208
- const matchIndentToCode = raw.match(/^(\s+)(?:```)/);
209
- if (matchIndentToCode === null) {
210
- return text;
211
- }
212
- const indentToCode = matchIndentToCode[1];
213
- return text
214
- .split('\n')
215
- .map(node => {
216
- const matchIndentInNode = node.match(/^\s+/);
217
- if (matchIndentInNode === null) {
218
- return node;
219
- }
220
- const [indentInNode] = matchIndentInNode;
221
- if (indentInNode.length >= indentToCode.length) {
222
- return node.slice(indentToCode.length);
223
- }
224
- return node;
225
- })
226
- .join('\n');
227
- }
111
+ const newline = /^(?:[ \t]*(?:\n|$))+/;
112
+ const blockCode = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/;
113
+ const fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
114
+ const hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
115
+ const heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
116
+ const bullet = /(?:[*+-]|\d{1,9}[.)])/;
117
+ const lheading = edit(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/)
118
+ .replace(/bull/g, bullet) // lists can interrupt
119
+ .replace(/blockCode/g, /(?: {4}| {0,3}\t)/) // indented code blocks can interrupt
120
+ .replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/) // fenced code blocks can interrupt
121
+ .replace(/blockquote/g, / {0,3}>/) // blockquote can interrupt
122
+ .replace(/heading/g, / {0,3}#{1,6}/) // ATX heading can interrupt
123
+ .replace(/html/g, / {0,3}<[^\n>]+>\n/) // block html can interrupt
124
+ .getRegex();
125
+ const _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/;
126
+ const blockText = /^[^\n]+/;
127
+ const _blockLabel = /(?!\s*\])(?:\\.|[^\[\]\\])+/;
128
+ const def = edit(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/)
129
+ .replace('label', _blockLabel)
130
+ .replace('title', /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/)
131
+ .getRegex();
132
+ const list = edit(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/)
133
+ .replace(/bull/g, bullet)
134
+ .getRegex();
135
+ const _tag = 'address|article|aside|base|basefont|blockquote|body|caption'
136
+ + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption'
137
+ + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe'
138
+ + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option'
139
+ + '|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title'
140
+ + '|tr|track|ul';
141
+ const _comment = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
142
+ const html = edit('^ {0,3}(?:' // optional indentation
143
+ + '<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1)
144
+ + '|comment[^\\n]*(\\n+|$)' // (2)
145
+ + '|<\\?[\\s\\S]*?(?:\\?>\\n*|$)' // (3)
146
+ + '|<![A-Z][\\s\\S]*?(?:>\\n*|$)' // (4)
147
+ + '|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)' // (5)
148
+ + '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (6)
149
+ + '|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (7) open tag
150
+ + '|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (7) closing tag
151
+ + ')', 'i')
152
+ .replace('comment', _comment)
153
+ .replace('tag', _tag)
154
+ .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/)
155
+ .getRegex();
156
+ const paragraph = edit(_paragraph)
157
+ .replace('hr', hr)
158
+ .replace('heading', ' {0,3}#{1,6}(?:\\s|$)')
159
+ .replace('|lheading', '') // setext headings don't interrupt commonmark paragraphs
160
+ .replace('|table', '')
161
+ .replace('blockquote', ' {0,3}>')
162
+ .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n')
163
+ .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
164
+ .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)')
165
+ .replace('tag', _tag) // pars can be interrupted by type (6) html blocks
166
+ .getRegex();
167
+ const blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/)
168
+ .replace('paragraph', paragraph)
169
+ .getRegex();
228
170
  /**
229
- * Tokenizer
171
+ * Normal Block Grammar
230
172
  */
231
- class _Tokenizer {
232
- options;
233
- rules; // set by the lexer
234
- lexer; // set by the lexer
235
- constructor(options) {
236
- this.options = options || _defaults;
237
- }
238
- space(src) {
239
- const cap = this.rules.block.newline.exec(src);
240
- if (cap && cap[0].length > 0) {
241
- return {
242
- type: 'space',
243
- raw: cap[0],
244
- };
245
- }
246
- }
247
- code(src) {
248
- const cap = this.rules.block.code.exec(src);
249
- if (cap) {
250
- const text = cap[0].replace(/^(?: {1,4}| {0,3}\t)/gm, '');
251
- return {
252
- type: 'code',
253
- raw: cap[0],
254
- codeBlockStyle: 'indented',
255
- text: !this.options.pedantic
256
- ? rtrim(text, '\n')
257
- : text,
258
- };
259
- }
260
- }
261
- fences(src) {
262
- const cap = this.rules.block.fences.exec(src);
263
- if (cap) {
264
- const raw = cap[0];
265
- const text = indentCodeCompensation(raw, cap[3] || '');
266
- return {
267
- type: 'code',
268
- raw,
269
- lang: cap[2] ? cap[2].trim().replace(this.rules.inline.anyPunctuation, '$1') : cap[2],
270
- text,
271
- };
173
+ const blockNormal = {
174
+ blockquote,
175
+ code: blockCode,
176
+ def,
177
+ fences,
178
+ heading,
179
+ hr,
180
+ html,
181
+ lheading,
182
+ list,
183
+ newline,
184
+ paragraph,
185
+ table: noopTest,
186
+ text: blockText,
187
+ };
188
+ /**
189
+ * GFM Block Grammar
190
+ */
191
+ const gfmTable = edit('^ *([^\\n ].*)\\n' // Header
192
+ + ' {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)' // Align
193
+ + '(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)') // Cells
194
+ .replace('hr', hr)
195
+ .replace('heading', ' {0,3}#{1,6}(?:\\s|$)')
196
+ .replace('blockquote', ' {0,3}>')
197
+ .replace('code', '(?: {4}| {0,3}\t)[^\\n]')
198
+ .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n')
199
+ .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
200
+ .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)')
201
+ .replace('tag', _tag) // tables can be interrupted by type (6) html blocks
202
+ .getRegex();
203
+ const blockGfm = {
204
+ ...blockNormal,
205
+ table: gfmTable,
206
+ paragraph: edit(_paragraph)
207
+ .replace('hr', hr)
208
+ .replace('heading', ' {0,3}#{1,6}(?:\\s|$)')
209
+ .replace('|lheading', '') // setext headings don't interrupt commonmark paragraphs
210
+ .replace('table', gfmTable) // interrupt paragraphs with table
211
+ .replace('blockquote', ' {0,3}>')
212
+ .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n')
213
+ .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
214
+ .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)')
215
+ .replace('tag', _tag) // pars can be interrupted by type (6) html blocks
216
+ .getRegex(),
217
+ };
218
+ /**
219
+ * Pedantic grammar (original John Gruber's loose markdown specification)
220
+ */
221
+ const blockPedantic = {
222
+ ...blockNormal,
223
+ html: edit('^ *(?:comment *(?:\\n|\\s*$)'
224
+ + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag
225
+ + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))')
226
+ .replace('comment', _comment)
227
+ .replace(/tag/g, '(?!(?:'
228
+ + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub'
229
+ + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)'
230
+ + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b')
231
+ .getRegex(),
232
+ def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
233
+ heading: /^(#{1,6})(.*)(?:\n+|$)/,
234
+ fences: noopTest, // fences not supported
235
+ lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,
236
+ paragraph: edit(_paragraph)
237
+ .replace('hr', hr)
238
+ .replace('heading', ' *#{1,6} *[^\n]')
239
+ .replace('lheading', lheading)
240
+ .replace('|table', '')
241
+ .replace('blockquote', ' {0,3}>')
242
+ .replace('|fences', '')
243
+ .replace('|list', '')
244
+ .replace('|html', '')
245
+ .replace('|tag', '')
246
+ .getRegex(),
247
+ };
248
+ /**
249
+ * Inline-Level Grammar
250
+ */
251
+ const escape$1 = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
252
+ const inlineCode = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
253
+ const br = /^( {2,}|\\)\n(?!\s*$)/;
254
+ const inlineText = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/;
255
+ // list of unicode punctuation marks, plus any missing characters from CommonMark spec
256
+ const _punctuation = '\\p{P}\\p{S}';
257
+ const punctuation = edit(/^((?![*_])[\spunctuation])/, 'u')
258
+ .replace(/punctuation/g, _punctuation).getRegex();
259
+ // sequences em should skip over [title](link), `code`, <html>
260
+ const blockSkip = /\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g;
261
+ const emStrongLDelim = edit(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/, 'u')
262
+ .replace(/punct/g, _punctuation)
263
+ .getRegex();
264
+ const emStrongRDelimAst = edit('^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)' // Skip orphan inside strong
265
+ + '|[^*]+(?=[^*])' // Consume to delim
266
+ + '|(?!\\*)[punct](\\*+)(?=[\\s]|$)' // (1) #*** can only be a Right Delimiter
267
+ + '|[^punct\\s](\\*+)(?!\\*)(?=[punct\\s]|$)' // (2) a***#, a*** can only be a Right Delimiter
268
+ + '|(?!\\*)[punct\\s](\\*+)(?=[^punct\\s])' // (3) #***a, ***a can only be Left Delimiter
269
+ + '|[\\s](\\*+)(?!\\*)(?=[punct])' // (4) ***# can only be Left Delimiter
270
+ + '|(?!\\*)[punct](\\*+)(?!\\*)(?=[punct])' // (5) #***# can be either Left or Right Delimiter
271
+ + '|[^punct\\s](\\*+)(?=[^punct\\s])', 'gu') // (6) a***a can be either Left or Right Delimiter
272
+ .replace(/punct/g, _punctuation)
273
+ .getRegex();
274
+ // (6) Not allowed for _
275
+ const emStrongRDelimUnd = edit('^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)' // Skip orphan inside strong
276
+ + '|[^_]+(?=[^_])' // Consume to delim
277
+ + '|(?!_)[punct](_+)(?=[\\s]|$)' // (1) #___ can only be a Right Delimiter
278
+ + '|[^punct\\s](_+)(?!_)(?=[punct\\s]|$)' // (2) a___#, a___ can only be a Right Delimiter
279
+ + '|(?!_)[punct\\s](_+)(?=[^punct\\s])' // (3) #___a, ___a can only be Left Delimiter
280
+ + '|[\\s](_+)(?!_)(?=[punct])' // (4) ___# can only be Left Delimiter
281
+ + '|(?!_)[punct](_+)(?!_)(?=[punct])', 'gu') // (5) #___# can be either Left or Right Delimiter
282
+ .replace(/punct/g, _punctuation)
283
+ .getRegex();
284
+ const anyPunctuation = edit(/\\([punct])/, 'gu')
285
+ .replace(/punct/g, _punctuation)
286
+ .getRegex();
287
+ const autolink = edit(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/)
288
+ .replace('scheme', /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/)
289
+ .replace('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])?)+(?![-_])/)
290
+ .getRegex();
291
+ const _inlineComment = edit(_comment).replace('(?:-->|$)', '-->').getRegex();
292
+ const tag = edit('^comment'
293
+ + '|^</[a-zA-Z][\\w:-]*\\s*>' // self-closing tag
294
+ + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag
295
+ + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?>
296
+ + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html>
297
+ + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>') // CDATA section
298
+ .replace('comment', _inlineComment)
299
+ .replace('attribute', /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/)
300
+ .getRegex();
301
+ const _inlineLabel = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/;
302
+ const link = edit(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/)
303
+ .replace('label', _inlineLabel)
304
+ .replace('href', /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/)
305
+ .replace('title', /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/)
306
+ .getRegex();
307
+ const reflink = edit(/^!?\[(label)\]\[(ref)\]/)
308
+ .replace('label', _inlineLabel)
309
+ .replace('ref', _blockLabel)
310
+ .getRegex();
311
+ const nolink = edit(/^!?\[(ref)\](?:\[\])?/)
312
+ .replace('ref', _blockLabel)
313
+ .getRegex();
314
+ const reflinkSearch = edit('reflink|nolink(?!\\()', 'g')
315
+ .replace('reflink', reflink)
316
+ .replace('nolink', nolink)
317
+ .getRegex();
318
+ /**
319
+ * Normal Inline Grammar
320
+ */
321
+ const inlineNormal = {
322
+ _backpedal: noopTest, // only used for GFM url
323
+ anyPunctuation,
324
+ autolink,
325
+ blockSkip,
326
+ br,
327
+ code: inlineCode,
328
+ del: noopTest,
329
+ emStrongLDelim,
330
+ emStrongRDelimAst,
331
+ emStrongRDelimUnd,
332
+ escape: escape$1,
333
+ link,
334
+ nolink,
335
+ punctuation,
336
+ reflink,
337
+ reflinkSearch,
338
+ tag,
339
+ text: inlineText,
340
+ url: noopTest,
341
+ };
342
+ /**
343
+ * Pedantic Inline Grammar
344
+ */
345
+ const inlinePedantic = {
346
+ ...inlineNormal,
347
+ link: edit(/^!?\[(label)\]\((.*?)\)/)
348
+ .replace('label', _inlineLabel)
349
+ .getRegex(),
350
+ reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/)
351
+ .replace('label', _inlineLabel)
352
+ .getRegex(),
353
+ };
354
+ /**
355
+ * GFM Inline Grammar
356
+ */
357
+ const inlineGfm = {
358
+ ...inlineNormal,
359
+ escape: edit(escape$1).replace('])', '~|])').getRegex(),
360
+ url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, 'i')
361
+ .replace('email', /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/)
362
+ .getRegex(),
363
+ _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,
364
+ del: /^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/,
365
+ text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/,
366
+ };
367
+ /**
368
+ * GFM + Line Breaks Inline Grammar
369
+ */
370
+ const inlineBreaks = {
371
+ ...inlineGfm,
372
+ br: edit(br).replace('{2,}', '*').getRegex(),
373
+ text: edit(inlineGfm.text)
374
+ .replace('\\b_', '\\b_| {2,}\\n')
375
+ .replace(/\{2,\}/g, '*')
376
+ .getRegex(),
377
+ };
378
+ /**
379
+ * exports
380
+ */
381
+ const block = {
382
+ normal: blockNormal,
383
+ gfm: blockGfm,
384
+ pedantic: blockPedantic,
385
+ };
386
+ const inline = {
387
+ normal: inlineNormal,
388
+ gfm: inlineGfm,
389
+ breaks: inlineBreaks,
390
+ pedantic: inlinePedantic,
391
+ };
392
+
393
+ /**
394
+ * Helpers
395
+ */
396
+ const escapeReplacements = {
397
+ '&': '&amp;',
398
+ '<': '&lt;',
399
+ '>': '&gt;',
400
+ '"': '&quot;',
401
+ "'": '&#39;',
402
+ };
403
+ const getEscapeReplacement = (ch) => escapeReplacements[ch];
404
+ function escape(html, encode) {
405
+ if (encode) {
406
+ if (other.escapeTest.test(html)) {
407
+ return html.replace(other.escapeReplace, getEscapeReplacement);
272
408
  }
273
409
  }
274
- heading(src) {
275
- const cap = this.rules.block.heading.exec(src);
276
- if (cap) {
277
- let text = cap[2].trim();
278
- // remove trailing #s
279
- if (/#$/.test(text)) {
280
- const trimmed = rtrim(text, '#');
281
- if (this.options.pedantic) {
282
- text = trimmed.trim();
283
- }
284
- else if (!trimmed || / $/.test(trimmed)) {
285
- // CommonMark requires space before trailing #s
286
- text = trimmed.trim();
287
- }
288
- }
289
- return {
290
- type: 'heading',
291
- raw: cap[0],
292
- depth: cap[1].length,
293
- text,
294
- tokens: this.lexer.inline(text),
295
- };
410
+ else {
411
+ if (other.escapeTestNoEncode.test(html)) {
412
+ return html.replace(other.escapeReplaceNoEncode, getEscapeReplacement);
296
413
  }
297
414
  }
298
- hr(src) {
299
- const cap = this.rules.block.hr.exec(src);
300
- if (cap) {
301
- return {
302
- type: 'hr',
415
+ return html;
416
+ }
417
+ function cleanUrl(href) {
418
+ try {
419
+ href = encodeURI(href).replace(other.percentDecode, '%');
420
+ }
421
+ catch {
422
+ return null;
423
+ }
424
+ return href;
425
+ }
426
+ function splitCells(tableRow, count) {
427
+ // ensure that every cell-delimiting pipe has a space
428
+ // before it to distinguish it from an escaped pipe
429
+ const row = tableRow.replace(other.findPipe, (match, offset, str) => {
430
+ let escaped = false;
431
+ let curr = offset;
432
+ while (--curr >= 0 && str[curr] === '\\')
433
+ escaped = !escaped;
434
+ if (escaped) {
435
+ // odd number of slashes means | is escaped
436
+ // so we leave it alone
437
+ return '|';
438
+ }
439
+ else {
440
+ // add space before unescaped |
441
+ return ' |';
442
+ }
443
+ }), cells = row.split(other.splitPipe);
444
+ let i = 0;
445
+ // First/last cell in a row cannot be empty if it has no leading/trailing pipe
446
+ if (!cells[0].trim()) {
447
+ cells.shift();
448
+ }
449
+ if (cells.length > 0 && !cells.at(-1)?.trim()) {
450
+ cells.pop();
451
+ }
452
+ if (count) {
453
+ if (cells.length > count) {
454
+ cells.splice(count);
455
+ }
456
+ else {
457
+ while (cells.length < count)
458
+ cells.push('');
459
+ }
460
+ }
461
+ for (; i < cells.length; i++) {
462
+ // leading or trailing whitespace is ignored per the gfm spec
463
+ cells[i] = cells[i].trim().replace(other.slashPipe, '|');
464
+ }
465
+ return cells;
466
+ }
467
+ /**
468
+ * Remove trailing 'c's. Equivalent to str.replace(/c*$/, '').
469
+ * /c*$/ is vulnerable to REDOS.
470
+ *
471
+ * @param str
472
+ * @param c
473
+ * @param invert Remove suffix of non-c chars instead. Default falsey.
474
+ */
475
+ function rtrim(str, c, invert) {
476
+ const l = str.length;
477
+ if (l === 0) {
478
+ return '';
479
+ }
480
+ // Length of suffix matching the invert condition.
481
+ let suffLen = 0;
482
+ // Step left until we fail to match the invert condition.
483
+ while (suffLen < l) {
484
+ const currChar = str.charAt(l - suffLen - 1);
485
+ if (currChar === c && !invert) {
486
+ suffLen++;
487
+ }
488
+ else if (currChar !== c && invert) {
489
+ suffLen++;
490
+ }
491
+ else {
492
+ break;
493
+ }
494
+ }
495
+ return str.slice(0, l - suffLen);
496
+ }
497
+ function findClosingBracket(str, b) {
498
+ if (str.indexOf(b[1]) === -1) {
499
+ return -1;
500
+ }
501
+ let level = 0;
502
+ for (let i = 0; i < str.length; i++) {
503
+ if (str[i] === '\\') {
504
+ i++;
505
+ }
506
+ else if (str[i] === b[0]) {
507
+ level++;
508
+ }
509
+ else if (str[i] === b[1]) {
510
+ level--;
511
+ if (level < 0) {
512
+ return i;
513
+ }
514
+ }
515
+ }
516
+ return -1;
517
+ }
518
+
519
+ function outputLink(cap, link, raw, lexer, rules) {
520
+ const href = link.href;
521
+ const title = link.title || null;
522
+ const text = cap[1].replace(rules.other.outputLinkReplace, '$1');
523
+ if (cap[0].charAt(0) !== '!') {
524
+ lexer.state.inLink = true;
525
+ const token = {
526
+ type: 'link',
527
+ raw,
528
+ href,
529
+ title,
530
+ text,
531
+ tokens: lexer.inlineTokens(text),
532
+ };
533
+ lexer.state.inLink = false;
534
+ return token;
535
+ }
536
+ return {
537
+ type: 'image',
538
+ raw,
539
+ href,
540
+ title,
541
+ text,
542
+ };
543
+ }
544
+ function indentCodeCompensation(raw, text, rules) {
545
+ const matchIndentToCode = raw.match(rules.other.indentCodeCompensation);
546
+ if (matchIndentToCode === null) {
547
+ return text;
548
+ }
549
+ const indentToCode = matchIndentToCode[1];
550
+ return text
551
+ .split('\n')
552
+ .map(node => {
553
+ const matchIndentInNode = node.match(rules.other.beginningSpace);
554
+ if (matchIndentInNode === null) {
555
+ return node;
556
+ }
557
+ const [indentInNode] = matchIndentInNode;
558
+ if (indentInNode.length >= indentToCode.length) {
559
+ return node.slice(indentToCode.length);
560
+ }
561
+ return node;
562
+ })
563
+ .join('\n');
564
+ }
565
+ /**
566
+ * Tokenizer
567
+ */
568
+ class _Tokenizer {
569
+ options;
570
+ rules; // set by the lexer
571
+ lexer; // set by the lexer
572
+ constructor(options) {
573
+ this.options = options || _defaults;
574
+ }
575
+ space(src) {
576
+ const cap = this.rules.block.newline.exec(src);
577
+ if (cap && cap[0].length > 0) {
578
+ return {
579
+ type: 'space',
580
+ raw: cap[0],
581
+ };
582
+ }
583
+ }
584
+ code(src) {
585
+ const cap = this.rules.block.code.exec(src);
586
+ if (cap) {
587
+ const text = cap[0].replace(this.rules.other.codeRemoveIndent, '');
588
+ return {
589
+ type: 'code',
590
+ raw: cap[0],
591
+ codeBlockStyle: 'indented',
592
+ text: !this.options.pedantic
593
+ ? rtrim(text, '\n')
594
+ : text,
595
+ };
596
+ }
597
+ }
598
+ fences(src) {
599
+ const cap = this.rules.block.fences.exec(src);
600
+ if (cap) {
601
+ const raw = cap[0];
602
+ const text = indentCodeCompensation(raw, cap[3] || '', this.rules);
603
+ return {
604
+ type: 'code',
605
+ raw,
606
+ lang: cap[2] ? cap[2].trim().replace(this.rules.inline.anyPunctuation, '$1') : cap[2],
607
+ text,
608
+ };
609
+ }
610
+ }
611
+ heading(src) {
612
+ const cap = this.rules.block.heading.exec(src);
613
+ if (cap) {
614
+ let text = cap[2].trim();
615
+ // remove trailing #s
616
+ if (this.rules.other.endingHash.test(text)) {
617
+ const trimmed = rtrim(text, '#');
618
+ if (this.options.pedantic) {
619
+ text = trimmed.trim();
620
+ }
621
+ else if (!trimmed || this.rules.other.endingSpaceChar.test(trimmed)) {
622
+ // CommonMark requires space before trailing #s
623
+ text = trimmed.trim();
624
+ }
625
+ }
626
+ return {
627
+ type: 'heading',
628
+ raw: cap[0],
629
+ depth: cap[1].length,
630
+ text,
631
+ tokens: this.lexer.inline(text),
632
+ };
633
+ }
634
+ }
635
+ hr(src) {
636
+ const cap = this.rules.block.hr.exec(src);
637
+ if (cap) {
638
+ return {
639
+ type: 'hr',
303
640
  raw: rtrim(cap[0], '\n'),
304
641
  };
305
642
  }
@@ -317,7 +654,7 @@ class _Tokenizer {
317
654
  let i;
318
655
  for (i = 0; i < lines.length; i++) {
319
656
  // get lines up to a continuation
320
- if (/^ {0,3}>/.test(lines[i])) {
657
+ if (this.rules.other.blockquoteStart.test(lines[i])) {
321
658
  currentLines.push(lines[i]);
322
659
  inBlockquote = true;
323
660
  }
@@ -332,8 +669,8 @@ class _Tokenizer {
332
669
  const currentRaw = currentLines.join('\n');
333
670
  const currentText = currentRaw
334
671
  // precede setext continuation with 4 spaces so it isn't a setext
335
- .replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g, '\n $1')
336
- .replace(/^ {0,3}>[ \t]?/gm, '');
672
+ .replace(this.rules.other.blockquoteSetextReplace, '\n $1')
673
+ .replace(this.rules.other.blockquoteSetextReplace2, '');
337
674
  raw = raw ? `${raw}\n${currentRaw}` : currentRaw;
338
675
  text = text ? `${text}\n${currentText}` : currentText;
339
676
  // parse blockquote lines as top level tokens
@@ -346,7 +683,7 @@ class _Tokenizer {
346
683
  if (lines.length === 0) {
347
684
  break;
348
685
  }
349
- const lastToken = tokens[tokens.length - 1];
686
+ const lastToken = tokens.at(-1);
350
687
  if (lastToken?.type === 'code') {
351
688
  // blockquote continuation cannot be preceded by a code block
352
689
  break;
@@ -369,7 +706,7 @@ class _Tokenizer {
369
706
  tokens[tokens.length - 1] = newToken;
370
707
  raw = raw.substring(0, raw.length - lastToken.raw.length) + newToken.raw;
371
708
  text = text.substring(0, text.length - oldToken.raw.length) + newToken.raw;
372
- lines = newText.substring(tokens[tokens.length - 1].raw.length).split('\n');
709
+ lines = newText.substring(tokens.at(-1).raw.length).split('\n');
373
710
  continue;
374
711
  }
375
712
  }
@@ -399,7 +736,7 @@ class _Tokenizer {
399
736
  bull = isordered ? bull : '[*+-]';
400
737
  }
401
738
  // Get next list item
402
- const itemRegex = new RegExp(`^( {0,3}${bull})((?:[\t ][^\\n]*)?(?:\\n|$))`);
739
+ const itemRegex = this.rules.other.listItemRegex(bull);
403
740
  let endsWithBlankLine = false;
404
741
  // Check if current bullet point can start a new List Item
405
742
  while (src) {
@@ -414,7 +751,7 @@ class _Tokenizer {
414
751
  }
415
752
  raw = cap[0];
416
753
  src = src.substring(raw.length);
417
- let line = cap[2].split('\n', 1)[0].replace(/^\t+/, (t) => ' '.repeat(3 * t.length));
754
+ let line = cap[2].split('\n', 1)[0].replace(this.rules.other.listReplaceTabs, (t) => ' '.repeat(3 * t.length));
418
755
  let nextLine = src.split('\n', 1)[0];
419
756
  let blankLine = !line.trim();
420
757
  let indent = 0;
@@ -426,22 +763,22 @@ class _Tokenizer {
426
763
  indent = cap[1].length + 1;
427
764
  }
428
765
  else {
429
- indent = cap[2].search(/[^ ]/); // Find first non-space char
766
+ indent = cap[2].search(this.rules.other.nonSpaceChar); // Find first non-space char
430
767
  indent = indent > 4 ? 1 : indent; // Treat indented code blocks (> 4 spaces) as having only 1 indent
431
768
  itemContents = line.slice(indent);
432
769
  indent += cap[1].length;
433
770
  }
434
- if (blankLine && /^[ \t]*$/.test(nextLine)) { // Items begin with at most one blank line
771
+ if (blankLine && this.rules.other.blankLine.test(nextLine)) { // Items begin with at most one blank line
435
772
  raw += nextLine + '\n';
436
773
  src = src.substring(nextLine.length + 1);
437
774
  endEarly = true;
438
775
  }
439
776
  if (!endEarly) {
440
- const nextBulletRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`);
441
- const hrRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`);
442
- const fencesBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`);
443
- const headingBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`);
444
- const htmlBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}<(?:[a-z].*>|!--)`, 'i');
777
+ const nextBulletRegex = this.rules.other.nextBulletRegex(indent);
778
+ const hrRegex = this.rules.other.hrRegex(indent);
779
+ const fencesBeginRegex = this.rules.other.fencesBeginRegex(indent);
780
+ const headingBeginRegex = this.rules.other.headingBeginRegex(indent);
781
+ const htmlBeginRegex = this.rules.other.htmlBeginRegex(indent);
445
782
  // Check if following lines should be included in List Item
446
783
  while (src) {
447
784
  const rawLine = src.split('\n', 1)[0];
@@ -449,11 +786,11 @@ class _Tokenizer {
449
786
  nextLine = rawLine;
450
787
  // Re-align to follow commonmark nesting rules
451
788
  if (this.options.pedantic) {
452
- nextLine = nextLine.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' ');
789
+ nextLine = nextLine.replace(this.rules.other.listReplaceNesting, ' ');
453
790
  nextLineWithoutTabs = nextLine;
454
791
  }
455
792
  else {
456
- nextLineWithoutTabs = nextLine.replace(/\t/g, ' ');
793
+ nextLineWithoutTabs = nextLine.replace(this.rules.other.tabCharGlobal, ' ');
457
794
  }
458
795
  // End list item if found code fences
459
796
  if (fencesBeginRegex.test(nextLine)) {
@@ -475,7 +812,7 @@ class _Tokenizer {
475
812
  if (hrRegex.test(nextLine)) {
476
813
  break;
477
814
  }
478
- if (nextLineWithoutTabs.search(/[^ ]/) >= indent || !nextLine.trim()) { // Dedent if possible
815
+ if (nextLineWithoutTabs.search(this.rules.other.nonSpaceChar) >= indent || !nextLine.trim()) { // Dedent if possible
479
816
  itemContents += '\n' + nextLineWithoutTabs.slice(indent);
480
817
  }
481
818
  else {
@@ -484,7 +821,7 @@ class _Tokenizer {
484
821
  break;
485
822
  }
486
823
  // paragraph continuation unless last line was a different block level element
487
- if (line.replace(/\t/g, ' ').search(/[^ ]/) >= 4) { // indented code block
824
+ if (line.replace(this.rules.other.tabCharGlobal, ' ').search(this.rules.other.nonSpaceChar) >= 4) { // indented code block
488
825
  break;
489
826
  }
490
827
  if (fencesBeginRegex.test(line)) {
@@ -511,7 +848,7 @@ class _Tokenizer {
511
848
  if (endsWithBlankLine) {
512
849
  list.loose = true;
513
850
  }
514
- else if (/\n[ \t]*\n[ \t]*$/.test(raw)) {
851
+ else if (this.rules.other.doubleBlankLine.test(raw)) {
515
852
  endsWithBlankLine = true;
516
853
  }
517
854
  }
@@ -519,10 +856,10 @@ class _Tokenizer {
519
856
  let ischecked;
520
857
  // Check for task list items
521
858
  if (this.options.gfm) {
522
- istask = /^\[[ xX]\] /.exec(itemContents);
859
+ istask = this.rules.other.listIsTask.exec(itemContents);
523
860
  if (istask) {
524
861
  ischecked = istask[0] !== '[ ] ';
525
- itemContents = itemContents.replace(/^\[[ xX]\] +/, '');
862
+ itemContents = itemContents.replace(this.rules.other.listReplaceTask, '');
526
863
  }
527
864
  }
528
865
  list.items.push({
@@ -537,8 +874,11 @@ class _Tokenizer {
537
874
  list.raw += raw;
538
875
  }
539
876
  // Do not consume newlines at end of final item. Alternatively, make itemRegex *start* with any newlines to simplify/speed up endsWithBlankLine logic
540
- list.items[list.items.length - 1].raw = list.items[list.items.length - 1].raw.trimEnd();
541
- list.items[list.items.length - 1].text = list.items[list.items.length - 1].text.trimEnd();
877
+ const lastItem = list.items.at(-1);
878
+ if (lastItem) {
879
+ lastItem.raw = lastItem.raw.trimEnd();
880
+ lastItem.text = lastItem.text.trimEnd();
881
+ }
542
882
  list.raw = list.raw.trimEnd();
543
883
  // Item child tokens handled here at end because we needed to have the final item to trim it first
544
884
  for (let i = 0; i < list.items.length; i++) {
@@ -547,7 +887,7 @@ class _Tokenizer {
547
887
  if (!list.loose) {
548
888
  // Check if list should be loose
549
889
  const spacers = list.items[i].tokens.filter(t => t.type === 'space');
550
- const hasMultipleLineBreaks = spacers.length > 0 && spacers.some(t => /\n.*\n/.test(t.raw));
890
+ const hasMultipleLineBreaks = spacers.length > 0 && spacers.some(t => this.rules.other.anyLine.test(t.raw));
551
891
  list.loose = hasMultipleLineBreaks;
552
892
  }
553
893
  }
@@ -576,8 +916,8 @@ class _Tokenizer {
576
916
  def(src) {
577
917
  const cap = this.rules.block.def.exec(src);
578
918
  if (cap) {
579
- const tag = cap[1].toLowerCase().replace(/\s+/g, ' ');
580
- const href = cap[2] ? cap[2].replace(/^<(.*)>$/, '$1').replace(this.rules.inline.anyPunctuation, '$1') : '';
919
+ const tag = cap[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal, ' ');
920
+ const href = cap[2] ? cap[2].replace(this.rules.other.hrefBrackets, '$1').replace(this.rules.inline.anyPunctuation, '$1') : '';
581
921
  const title = cap[3] ? cap[3].substring(1, cap[3].length - 1).replace(this.rules.inline.anyPunctuation, '$1') : cap[3];
582
922
  return {
583
923
  type: 'def',
@@ -593,13 +933,13 @@ class _Tokenizer {
593
933
  if (!cap) {
594
934
  return;
595
935
  }
596
- if (!/[:|]/.test(cap[2])) {
936
+ if (!this.rules.other.tableDelimiter.test(cap[2])) {
597
937
  // delimiter row must have a pipe (|) or colon (:) otherwise it is a setext heading
598
938
  return;
599
939
  }
600
940
  const headers = splitCells(cap[1]);
601
- const aligns = cap[2].replace(/^\||\| *$/g, '').split('|');
602
- const rows = cap[3] && cap[3].trim() ? cap[3].replace(/\n[ \t]*$/, '').split('\n') : [];
941
+ const aligns = cap[2].replace(this.rules.other.tableAlignChars, '').split('|');
942
+ const rows = cap[3]?.trim() ? cap[3].replace(this.rules.other.tableRowBlankLine, '').split('\n') : [];
603
943
  const item = {
604
944
  type: 'table',
605
945
  raw: cap[0],
@@ -612,13 +952,13 @@ class _Tokenizer {
612
952
  return;
613
953
  }
614
954
  for (const align of aligns) {
615
- if (/^ *-+: *$/.test(align)) {
955
+ if (this.rules.other.tableAlignRight.test(align)) {
616
956
  item.align.push('right');
617
957
  }
618
- else if (/^ *:-+: *$/.test(align)) {
958
+ else if (this.rules.other.tableAlignCenter.test(align)) {
619
959
  item.align.push('center');
620
960
  }
621
- else if (/^ *:-+ *$/.test(align)) {
961
+ else if (this.rules.other.tableAlignLeft.test(align)) {
622
962
  item.align.push('left');
623
963
  }
624
964
  else {
@@ -688,572 +1028,281 @@ class _Tokenizer {
688
1028
  return {
689
1029
  type: 'escape',
690
1030
  raw: cap[0],
691
- text: escape$1(cap[1]),
1031
+ text: cap[1],
692
1032
  };
693
1033
  }
694
1034
  }
695
1035
  tag(src) {
696
1036
  const cap = this.rules.inline.tag.exec(src);
697
1037
  if (cap) {
698
- if (!this.lexer.state.inLink && /^<a /i.test(cap[0])) {
1038
+ if (!this.lexer.state.inLink && this.rules.other.startATag.test(cap[0])) {
699
1039
  this.lexer.state.inLink = true;
700
1040
  }
701
- else if (this.lexer.state.inLink && /^<\/a>/i.test(cap[0])) {
1041
+ else if (this.lexer.state.inLink && this.rules.other.endATag.test(cap[0])) {
702
1042
  this.lexer.state.inLink = false;
703
1043
  }
704
- if (!this.lexer.state.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
1044
+ if (!this.lexer.state.inRawBlock && this.rules.other.startPreScriptTag.test(cap[0])) {
705
1045
  this.lexer.state.inRawBlock = true;
706
1046
  }
707
- else if (this.lexer.state.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
708
- this.lexer.state.inRawBlock = false;
709
- }
710
- return {
711
- type: 'html',
712
- raw: cap[0],
713
- inLink: this.lexer.state.inLink,
714
- inRawBlock: this.lexer.state.inRawBlock,
715
- block: false,
716
- text: cap[0],
717
- };
718
- }
719
- }
720
- link(src) {
721
- const cap = this.rules.inline.link.exec(src);
722
- if (cap) {
723
- const trimmedUrl = cap[2].trim();
724
- if (!this.options.pedantic && /^</.test(trimmedUrl)) {
725
- // commonmark requires matching angle brackets
726
- if (!(/>$/.test(trimmedUrl))) {
727
- return;
728
- }
729
- // ending angle bracket cannot be escaped
730
- const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\');
731
- if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {
732
- return;
733
- }
734
- }
735
- else {
736
- // find closing parenthesis
737
- const lastParenIndex = findClosingBracket(cap[2], '()');
738
- if (lastParenIndex > -1) {
739
- const start = cap[0].indexOf('!') === 0 ? 5 : 4;
740
- const linkLen = start + cap[1].length + lastParenIndex;
741
- cap[2] = cap[2].substring(0, lastParenIndex);
742
- cap[0] = cap[0].substring(0, linkLen).trim();
743
- cap[3] = '';
744
- }
745
- }
746
- let href = cap[2];
747
- let title = '';
748
- if (this.options.pedantic) {
749
- // split pedantic href and title
750
- const link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
751
- if (link) {
752
- href = link[1];
753
- title = link[3];
754
- }
755
- }
756
- else {
757
- title = cap[3] ? cap[3].slice(1, -1) : '';
758
- }
759
- href = href.trim();
760
- if (/^</.test(href)) {
761
- if (this.options.pedantic && !(/>$/.test(trimmedUrl))) {
762
- // pedantic allows starting angle bracket without ending angle bracket
763
- href = href.slice(1);
764
- }
765
- else {
766
- href = href.slice(1, -1);
767
- }
768
- }
769
- return outputLink(cap, {
770
- href: href ? href.replace(this.rules.inline.anyPunctuation, '$1') : href,
771
- title: title ? title.replace(this.rules.inline.anyPunctuation, '$1') : title,
772
- }, cap[0], this.lexer);
773
- }
774
- }
775
- reflink(src, links) {
776
- let cap;
777
- if ((cap = this.rules.inline.reflink.exec(src))
778
- || (cap = this.rules.inline.nolink.exec(src))) {
779
- const linkString = (cap[2] || cap[1]).replace(/\s+/g, ' ');
780
- const link = links[linkString.toLowerCase()];
781
- if (!link) {
782
- const text = cap[0].charAt(0);
783
- return {
784
- type: 'text',
785
- raw: text,
786
- text,
787
- };
788
- }
789
- return outputLink(cap, link, cap[0], this.lexer);
790
- }
791
- }
792
- emStrong(src, maskedSrc, prevChar = '') {
793
- let match = this.rules.inline.emStrongLDelim.exec(src);
794
- if (!match)
795
- return;
796
- // _ can't be between two alphanumerics. \p{L}\p{N} includes non-english alphabet/numbers as well
797
- if (match[3] && prevChar.match(/[\p{L}\p{N}]/u))
798
- return;
799
- const nextChar = match[1] || match[2] || '';
800
- if (!nextChar || !prevChar || this.rules.inline.punctuation.exec(prevChar)) {
801
- // unicode Regex counts emoji as 1 char; spread into array for proper count (used multiple times below)
802
- const lLength = [...match[0]].length - 1;
803
- let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0;
804
- const endReg = match[0][0] === '*' ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd;
805
- endReg.lastIndex = 0;
806
- // Clip maskedSrc to same section of string as src (move to lexer?)
807
- maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
808
- while ((match = endReg.exec(maskedSrc)) != null) {
809
- rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
810
- if (!rDelim)
811
- continue; // skip single * in __abc*abc__
812
- rLength = [...rDelim].length;
813
- if (match[3] || match[4]) { // found another Left Delim
814
- delimTotal += rLength;
815
- continue;
816
- }
817
- else if (match[5] || match[6]) { // either Left or Right Delim
818
- if (lLength % 3 && !((lLength + rLength) % 3)) {
819
- midDelimTotal += rLength;
820
- continue; // CommonMark Emphasis Rules 9-10
821
- }
822
- }
823
- delimTotal -= rLength;
824
- if (delimTotal > 0)
825
- continue; // Haven't found enough closing delimiters
826
- // Remove extra characters. *a*** -> *a*
827
- rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal);
828
- // char length can be >1 for unicode characters;
829
- const lastCharLength = [...match[0]][0].length;
830
- const raw = src.slice(0, lLength + match.index + lastCharLength + rLength);
831
- // Create `em` if smallest delimiter has odd char count. *a***
832
- if (Math.min(lLength, rLength) % 2) {
833
- const text = raw.slice(1, -1);
834
- return {
835
- type: 'em',
836
- raw,
837
- text,
838
- tokens: this.lexer.inlineTokens(text),
839
- };
840
- }
841
- // Create 'strong' if smallest delimiter has even char count. **a***
842
- const text = raw.slice(2, -2);
843
- return {
844
- type: 'strong',
845
- raw,
846
- text,
847
- tokens: this.lexer.inlineTokens(text),
848
- };
849
- }
850
- }
851
- }
852
- codespan(src) {
853
- const cap = this.rules.inline.code.exec(src);
854
- if (cap) {
855
- let text = cap[2].replace(/\n/g, ' ');
856
- const hasNonSpaceChars = /[^ ]/.test(text);
857
- const hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text);
858
- if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
859
- text = text.substring(1, text.length - 1);
860
- }
861
- text = escape$1(text, true);
862
- return {
863
- type: 'codespan',
864
- raw: cap[0],
865
- text,
866
- };
867
- }
868
- }
869
- br(src) {
870
- const cap = this.rules.inline.br.exec(src);
871
- if (cap) {
872
- return {
873
- type: 'br',
874
- raw: cap[0],
875
- };
876
- }
877
- }
878
- del(src) {
879
- const cap = this.rules.inline.del.exec(src);
880
- if (cap) {
881
- return {
882
- type: 'del',
883
- raw: cap[0],
884
- text: cap[2],
885
- tokens: this.lexer.inlineTokens(cap[2]),
886
- };
887
- }
888
- }
889
- autolink(src) {
890
- const cap = this.rules.inline.autolink.exec(src);
891
- if (cap) {
892
- let text, href;
893
- if (cap[2] === '@') {
894
- text = escape$1(cap[1]);
895
- href = 'mailto:' + text;
896
- }
897
- else {
898
- text = escape$1(cap[1]);
899
- href = text;
900
- }
901
- return {
902
- type: 'link',
903
- raw: cap[0],
904
- text,
905
- href,
906
- tokens: [
907
- {
908
- type: 'text',
909
- raw: text,
910
- text,
911
- },
912
- ],
913
- };
914
- }
915
- }
916
- url(src) {
917
- let cap;
918
- if (cap = this.rules.inline.url.exec(src)) {
919
- let text, href;
920
- if (cap[2] === '@') {
921
- text = escape$1(cap[0]);
922
- href = 'mailto:' + text;
923
- }
924
- else {
925
- // do extended autolink path validation
926
- let prevCapZero;
927
- do {
928
- prevCapZero = cap[0];
929
- cap[0] = this.rules.inline._backpedal.exec(cap[0])?.[0] ?? '';
930
- } while (prevCapZero !== cap[0]);
931
- text = escape$1(cap[0]);
932
- if (cap[1] === 'www.') {
933
- href = 'http://' + cap[0];
934
- }
935
- else {
936
- href = cap[0];
937
- }
938
- }
939
- return {
940
- type: 'link',
941
- raw: cap[0],
942
- text,
943
- href,
944
- tokens: [
945
- {
946
- type: 'text',
947
- raw: text,
948
- text,
949
- },
950
- ],
951
- };
952
- }
953
- }
954
- inlineText(src) {
955
- const cap = this.rules.inline.text.exec(src);
956
- if (cap) {
957
- let text;
958
- if (this.lexer.state.inRawBlock) {
959
- text = cap[0];
960
- }
961
- else {
962
- text = escape$1(cap[0]);
963
- }
964
- return {
965
- type: 'text',
966
- raw: cap[0],
967
- text,
968
- };
969
- }
970
- }
971
- }
972
-
973
- /**
974
- * Block-Level Grammar
975
- */
976
- const newline = /^(?:[ \t]*(?:\n|$))+/;
977
- const blockCode = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/;
978
- const fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
979
- const hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
980
- const heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
981
- const bullet = /(?:[*+-]|\d{1,9}[.)])/;
982
- const lheading = edit(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/)
983
- .replace(/bull/g, bullet) // lists can interrupt
984
- .replace(/blockCode/g, /(?: {4}| {0,3}\t)/) // indented code blocks can interrupt
985
- .replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/) // fenced code blocks can interrupt
986
- .replace(/blockquote/g, / {0,3}>/) // blockquote can interrupt
987
- .replace(/heading/g, / {0,3}#{1,6}/) // ATX heading can interrupt
988
- .replace(/html/g, / {0,3}<[^\n>]+>\n/) // block html can interrupt
989
- .getRegex();
990
- const _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/;
991
- const blockText = /^[^\n]+/;
992
- const _blockLabel = /(?!\s*\])(?:\\.|[^\[\]\\])+/;
993
- const def = edit(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/)
994
- .replace('label', _blockLabel)
995
- .replace('title', /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/)
996
- .getRegex();
997
- const list = edit(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/)
998
- .replace(/bull/g, bullet)
999
- .getRegex();
1000
- const _tag = 'address|article|aside|base|basefont|blockquote|body|caption'
1001
- + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption'
1002
- + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe'
1003
- + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option'
1004
- + '|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title'
1005
- + '|tr|track|ul';
1006
- const _comment = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
1007
- const html = edit('^ {0,3}(?:' // optional indentation
1008
- + '<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1)
1009
- + '|comment[^\\n]*(\\n+|$)' // (2)
1010
- + '|<\\?[\\s\\S]*?(?:\\?>\\n*|$)' // (3)
1011
- + '|<![A-Z][\\s\\S]*?(?:>\\n*|$)' // (4)
1012
- + '|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)' // (5)
1013
- + '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (6)
1014
- + '|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (7) open tag
1015
- + '|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (7) closing tag
1016
- + ')', 'i')
1017
- .replace('comment', _comment)
1018
- .replace('tag', _tag)
1019
- .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/)
1020
- .getRegex();
1021
- const paragraph = edit(_paragraph)
1022
- .replace('hr', hr)
1023
- .replace('heading', ' {0,3}#{1,6}(?:\\s|$)')
1024
- .replace('|lheading', '') // setext headings don't interrupt commonmark paragraphs
1025
- .replace('|table', '')
1026
- .replace('blockquote', ' {0,3}>')
1027
- .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n')
1028
- .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
1029
- .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)')
1030
- .replace('tag', _tag) // pars can be interrupted by type (6) html blocks
1031
- .getRegex();
1032
- const blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/)
1033
- .replace('paragraph', paragraph)
1034
- .getRegex();
1035
- /**
1036
- * Normal Block Grammar
1037
- */
1038
- const blockNormal = {
1039
- blockquote,
1040
- code: blockCode,
1041
- def,
1042
- fences,
1043
- heading,
1044
- hr,
1045
- html,
1046
- lheading,
1047
- list,
1048
- newline,
1049
- paragraph,
1050
- table: noopTest,
1051
- text: blockText,
1052
- };
1053
- /**
1054
- * GFM Block Grammar
1055
- */
1056
- const gfmTable = edit('^ *([^\\n ].*)\\n' // Header
1057
- + ' {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)' // Align
1058
- + '(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)') // Cells
1059
- .replace('hr', hr)
1060
- .replace('heading', ' {0,3}#{1,6}(?:\\s|$)')
1061
- .replace('blockquote', ' {0,3}>')
1062
- .replace('code', '(?: {4}| {0,3}\t)[^\\n]')
1063
- .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n')
1064
- .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
1065
- .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)')
1066
- .replace('tag', _tag) // tables can be interrupted by type (6) html blocks
1067
- .getRegex();
1068
- const blockGfm = {
1069
- ...blockNormal,
1070
- table: gfmTable,
1071
- paragraph: edit(_paragraph)
1072
- .replace('hr', hr)
1073
- .replace('heading', ' {0,3}#{1,6}(?:\\s|$)')
1074
- .replace('|lheading', '') // setext headings don't interrupt commonmark paragraphs
1075
- .replace('table', gfmTable) // interrupt paragraphs with table
1076
- .replace('blockquote', ' {0,3}>')
1077
- .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n')
1078
- .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
1079
- .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)')
1080
- .replace('tag', _tag) // pars can be interrupted by type (6) html blocks
1081
- .getRegex(),
1082
- };
1083
- /**
1084
- * Pedantic grammar (original John Gruber's loose markdown specification)
1085
- */
1086
- const blockPedantic = {
1087
- ...blockNormal,
1088
- html: edit('^ *(?:comment *(?:\\n|\\s*$)'
1089
- + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag
1090
- + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))')
1091
- .replace('comment', _comment)
1092
- .replace(/tag/g, '(?!(?:'
1093
- + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub'
1094
- + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)'
1095
- + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b')
1096
- .getRegex(),
1097
- def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
1098
- heading: /^(#{1,6})(.*)(?:\n+|$)/,
1099
- fences: noopTest, // fences not supported
1100
- lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,
1101
- paragraph: edit(_paragraph)
1102
- .replace('hr', hr)
1103
- .replace('heading', ' *#{1,6} *[^\n]')
1104
- .replace('lheading', lheading)
1105
- .replace('|table', '')
1106
- .replace('blockquote', ' {0,3}>')
1107
- .replace('|fences', '')
1108
- .replace('|list', '')
1109
- .replace('|html', '')
1110
- .replace('|tag', '')
1111
- .getRegex(),
1112
- };
1113
- /**
1114
- * Inline-Level Grammar
1115
- */
1116
- const escape = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
1117
- const inlineCode = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
1118
- const br = /^( {2,}|\\)\n(?!\s*$)/;
1119
- const inlineText = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/;
1120
- // list of unicode punctuation marks, plus any missing characters from CommonMark spec
1121
- const _punctuation = '\\p{P}\\p{S}';
1122
- const punctuation = edit(/^((?![*_])[\spunctuation])/, 'u')
1123
- .replace(/punctuation/g, _punctuation).getRegex();
1124
- // sequences em should skip over [title](link), `code`, <html>
1125
- const blockSkip = /\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g;
1126
- const emStrongLDelim = edit(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/, 'u')
1127
- .replace(/punct/g, _punctuation)
1128
- .getRegex();
1129
- const emStrongRDelimAst = edit('^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)' // Skip orphan inside strong
1130
- + '|[^*]+(?=[^*])' // Consume to delim
1131
- + '|(?!\\*)[punct](\\*+)(?=[\\s]|$)' // (1) #*** can only be a Right Delimiter
1132
- + '|[^punct\\s](\\*+)(?!\\*)(?=[punct\\s]|$)' // (2) a***#, a*** can only be a Right Delimiter
1133
- + '|(?!\\*)[punct\\s](\\*+)(?=[^punct\\s])' // (3) #***a, ***a can only be Left Delimiter
1134
- + '|[\\s](\\*+)(?!\\*)(?=[punct])' // (4) ***# can only be Left Delimiter
1135
- + '|(?!\\*)[punct](\\*+)(?!\\*)(?=[punct])' // (5) #***# can be either Left or Right Delimiter
1136
- + '|[^punct\\s](\\*+)(?=[^punct\\s])', 'gu') // (6) a***a can be either Left or Right Delimiter
1137
- .replace(/punct/g, _punctuation)
1138
- .getRegex();
1139
- // (6) Not allowed for _
1140
- const emStrongRDelimUnd = edit('^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)' // Skip orphan inside strong
1141
- + '|[^_]+(?=[^_])' // Consume to delim
1142
- + '|(?!_)[punct](_+)(?=[\\s]|$)' // (1) #___ can only be a Right Delimiter
1143
- + '|[^punct\\s](_+)(?!_)(?=[punct\\s]|$)' // (2) a___#, a___ can only be a Right Delimiter
1144
- + '|(?!_)[punct\\s](_+)(?=[^punct\\s])' // (3) #___a, ___a can only be Left Delimiter
1145
- + '|[\\s](_+)(?!_)(?=[punct])' // (4) ___# can only be Left Delimiter
1146
- + '|(?!_)[punct](_+)(?!_)(?=[punct])', 'gu') // (5) #___# can be either Left or Right Delimiter
1147
- .replace(/punct/g, _punctuation)
1148
- .getRegex();
1149
- const anyPunctuation = edit(/\\([punct])/, 'gu')
1150
- .replace(/punct/g, _punctuation)
1151
- .getRegex();
1152
- const autolink = edit(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/)
1153
- .replace('scheme', /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/)
1154
- .replace('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])?)+(?![-_])/)
1155
- .getRegex();
1156
- const _inlineComment = edit(_comment).replace('(?:-->|$)', '-->').getRegex();
1157
- const tag = edit('^comment'
1158
- + '|^</[a-zA-Z][\\w:-]*\\s*>' // self-closing tag
1159
- + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag
1160
- + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?>
1161
- + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html>
1162
- + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>') // CDATA section
1163
- .replace('comment', _inlineComment)
1164
- .replace('attribute', /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/)
1165
- .getRegex();
1166
- const _inlineLabel = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/;
1167
- const link = edit(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/)
1168
- .replace('label', _inlineLabel)
1169
- .replace('href', /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/)
1170
- .replace('title', /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/)
1171
- .getRegex();
1172
- const reflink = edit(/^!?\[(label)\]\[(ref)\]/)
1173
- .replace('label', _inlineLabel)
1174
- .replace('ref', _blockLabel)
1175
- .getRegex();
1176
- const nolink = edit(/^!?\[(ref)\](?:\[\])?/)
1177
- .replace('ref', _blockLabel)
1178
- .getRegex();
1179
- const reflinkSearch = edit('reflink|nolink(?!\\()', 'g')
1180
- .replace('reflink', reflink)
1181
- .replace('nolink', nolink)
1182
- .getRegex();
1183
- /**
1184
- * Normal Inline Grammar
1185
- */
1186
- const inlineNormal = {
1187
- _backpedal: noopTest, // only used for GFM url
1188
- anyPunctuation,
1189
- autolink,
1190
- blockSkip,
1191
- br,
1192
- code: inlineCode,
1193
- del: noopTest,
1194
- emStrongLDelim,
1195
- emStrongRDelimAst,
1196
- emStrongRDelimUnd,
1197
- escape,
1198
- link,
1199
- nolink,
1200
- punctuation,
1201
- reflink,
1202
- reflinkSearch,
1203
- tag,
1204
- text: inlineText,
1205
- url: noopTest,
1206
- };
1207
- /**
1208
- * Pedantic Inline Grammar
1209
- */
1210
- const inlinePedantic = {
1211
- ...inlineNormal,
1212
- link: edit(/^!?\[(label)\]\((.*?)\)/)
1213
- .replace('label', _inlineLabel)
1214
- .getRegex(),
1215
- reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/)
1216
- .replace('label', _inlineLabel)
1217
- .getRegex(),
1218
- };
1219
- /**
1220
- * GFM Inline Grammar
1221
- */
1222
- const inlineGfm = {
1223
- ...inlineNormal,
1224
- escape: edit(escape).replace('])', '~|])').getRegex(),
1225
- url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, 'i')
1226
- .replace('email', /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/)
1227
- .getRegex(),
1228
- _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,
1229
- del: /^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/,
1230
- text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/,
1231
- };
1232
- /**
1233
- * GFM + Line Breaks Inline Grammar
1234
- */
1235
- const inlineBreaks = {
1236
- ...inlineGfm,
1237
- br: edit(br).replace('{2,}', '*').getRegex(),
1238
- text: edit(inlineGfm.text)
1239
- .replace('\\b_', '\\b_| {2,}\\n')
1240
- .replace(/\{2,\}/g, '*')
1241
- .getRegex(),
1242
- };
1243
- /**
1244
- * exports
1245
- */
1246
- const block = {
1247
- normal: blockNormal,
1248
- gfm: blockGfm,
1249
- pedantic: blockPedantic,
1250
- };
1251
- const inline = {
1252
- normal: inlineNormal,
1253
- gfm: inlineGfm,
1254
- breaks: inlineBreaks,
1255
- pedantic: inlinePedantic,
1256
- };
1047
+ else if (this.lexer.state.inRawBlock && this.rules.other.endPreScriptTag.test(cap[0])) {
1048
+ this.lexer.state.inRawBlock = false;
1049
+ }
1050
+ return {
1051
+ type: 'html',
1052
+ raw: cap[0],
1053
+ inLink: this.lexer.state.inLink,
1054
+ inRawBlock: this.lexer.state.inRawBlock,
1055
+ block: false,
1056
+ text: cap[0],
1057
+ };
1058
+ }
1059
+ }
1060
+ link(src) {
1061
+ const cap = this.rules.inline.link.exec(src);
1062
+ if (cap) {
1063
+ const trimmedUrl = cap[2].trim();
1064
+ if (!this.options.pedantic && this.rules.other.startAngleBracket.test(trimmedUrl)) {
1065
+ // commonmark requires matching angle brackets
1066
+ if (!(this.rules.other.endAngleBracket.test(trimmedUrl))) {
1067
+ return;
1068
+ }
1069
+ // ending angle bracket cannot be escaped
1070
+ const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\');
1071
+ if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {
1072
+ return;
1073
+ }
1074
+ }
1075
+ else {
1076
+ // find closing parenthesis
1077
+ const lastParenIndex = findClosingBracket(cap[2], '()');
1078
+ if (lastParenIndex > -1) {
1079
+ const start = cap[0].indexOf('!') === 0 ? 5 : 4;
1080
+ const linkLen = start + cap[1].length + lastParenIndex;
1081
+ cap[2] = cap[2].substring(0, lastParenIndex);
1082
+ cap[0] = cap[0].substring(0, linkLen).trim();
1083
+ cap[3] = '';
1084
+ }
1085
+ }
1086
+ let href = cap[2];
1087
+ let title = '';
1088
+ if (this.options.pedantic) {
1089
+ // split pedantic href and title
1090
+ const link = this.rules.other.pedanticHrefTitle.exec(href);
1091
+ if (link) {
1092
+ href = link[1];
1093
+ title = link[3];
1094
+ }
1095
+ }
1096
+ else {
1097
+ title = cap[3] ? cap[3].slice(1, -1) : '';
1098
+ }
1099
+ href = href.trim();
1100
+ if (this.rules.other.startAngleBracket.test(href)) {
1101
+ if (this.options.pedantic && !(this.rules.other.endAngleBracket.test(trimmedUrl))) {
1102
+ // pedantic allows starting angle bracket without ending angle bracket
1103
+ href = href.slice(1);
1104
+ }
1105
+ else {
1106
+ href = href.slice(1, -1);
1107
+ }
1108
+ }
1109
+ return outputLink(cap, {
1110
+ href: href ? href.replace(this.rules.inline.anyPunctuation, '$1') : href,
1111
+ title: title ? title.replace(this.rules.inline.anyPunctuation, '$1') : title,
1112
+ }, cap[0], this.lexer, this.rules);
1113
+ }
1114
+ }
1115
+ reflink(src, links) {
1116
+ let cap;
1117
+ if ((cap = this.rules.inline.reflink.exec(src))
1118
+ || (cap = this.rules.inline.nolink.exec(src))) {
1119
+ const linkString = (cap[2] || cap[1]).replace(this.rules.other.multipleSpaceGlobal, ' ');
1120
+ const link = links[linkString.toLowerCase()];
1121
+ if (!link) {
1122
+ const text = cap[0].charAt(0);
1123
+ return {
1124
+ type: 'text',
1125
+ raw: text,
1126
+ text,
1127
+ };
1128
+ }
1129
+ return outputLink(cap, link, cap[0], this.lexer, this.rules);
1130
+ }
1131
+ }
1132
+ emStrong(src, maskedSrc, prevChar = '') {
1133
+ let match = this.rules.inline.emStrongLDelim.exec(src);
1134
+ if (!match)
1135
+ return;
1136
+ // _ can't be between two alphanumerics. \p{L}\p{N} includes non-english alphabet/numbers as well
1137
+ if (match[3] && prevChar.match(this.rules.other.unicodeAlphaNumeric))
1138
+ return;
1139
+ const nextChar = match[1] || match[2] || '';
1140
+ if (!nextChar || !prevChar || this.rules.inline.punctuation.exec(prevChar)) {
1141
+ // unicode Regex counts emoji as 1 char; spread into array for proper count (used multiple times below)
1142
+ const lLength = [...match[0]].length - 1;
1143
+ let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0;
1144
+ const endReg = match[0][0] === '*' ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd;
1145
+ endReg.lastIndex = 0;
1146
+ // Clip maskedSrc to same section of string as src (move to lexer?)
1147
+ maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
1148
+ while ((match = endReg.exec(maskedSrc)) != null) {
1149
+ rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
1150
+ if (!rDelim)
1151
+ continue; // skip single * in __abc*abc__
1152
+ rLength = [...rDelim].length;
1153
+ if (match[3] || match[4]) { // found another Left Delim
1154
+ delimTotal += rLength;
1155
+ continue;
1156
+ }
1157
+ else if (match[5] || match[6]) { // either Left or Right Delim
1158
+ if (lLength % 3 && !((lLength + rLength) % 3)) {
1159
+ midDelimTotal += rLength;
1160
+ continue; // CommonMark Emphasis Rules 9-10
1161
+ }
1162
+ }
1163
+ delimTotal -= rLength;
1164
+ if (delimTotal > 0)
1165
+ continue; // Haven't found enough closing delimiters
1166
+ // Remove extra characters. *a*** -> *a*
1167
+ rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal);
1168
+ // char length can be >1 for unicode characters;
1169
+ const lastCharLength = [...match[0]][0].length;
1170
+ const raw = src.slice(0, lLength + match.index + lastCharLength + rLength);
1171
+ // Create `em` if smallest delimiter has odd char count. *a***
1172
+ if (Math.min(lLength, rLength) % 2) {
1173
+ const text = raw.slice(1, -1);
1174
+ return {
1175
+ type: 'em',
1176
+ raw,
1177
+ text,
1178
+ tokens: this.lexer.inlineTokens(text),
1179
+ };
1180
+ }
1181
+ // Create 'strong' if smallest delimiter has even char count. **a***
1182
+ const text = raw.slice(2, -2);
1183
+ return {
1184
+ type: 'strong',
1185
+ raw,
1186
+ text,
1187
+ tokens: this.lexer.inlineTokens(text),
1188
+ };
1189
+ }
1190
+ }
1191
+ }
1192
+ codespan(src) {
1193
+ const cap = this.rules.inline.code.exec(src);
1194
+ if (cap) {
1195
+ let text = cap[2].replace(this.rules.other.newLineCharGlobal, ' ');
1196
+ const hasNonSpaceChars = this.rules.other.nonSpaceChar.test(text);
1197
+ const hasSpaceCharsOnBothEnds = this.rules.other.startingSpaceChar.test(text) && this.rules.other.endingSpaceChar.test(text);
1198
+ if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
1199
+ text = text.substring(1, text.length - 1);
1200
+ }
1201
+ return {
1202
+ type: 'codespan',
1203
+ raw: cap[0],
1204
+ text,
1205
+ };
1206
+ }
1207
+ }
1208
+ br(src) {
1209
+ const cap = this.rules.inline.br.exec(src);
1210
+ if (cap) {
1211
+ return {
1212
+ type: 'br',
1213
+ raw: cap[0],
1214
+ };
1215
+ }
1216
+ }
1217
+ del(src) {
1218
+ const cap = this.rules.inline.del.exec(src);
1219
+ if (cap) {
1220
+ return {
1221
+ type: 'del',
1222
+ raw: cap[0],
1223
+ text: cap[2],
1224
+ tokens: this.lexer.inlineTokens(cap[2]),
1225
+ };
1226
+ }
1227
+ }
1228
+ autolink(src) {
1229
+ const cap = this.rules.inline.autolink.exec(src);
1230
+ if (cap) {
1231
+ let text, href;
1232
+ if (cap[2] === '@') {
1233
+ text = cap[1];
1234
+ href = 'mailto:' + text;
1235
+ }
1236
+ else {
1237
+ text = cap[1];
1238
+ href = text;
1239
+ }
1240
+ return {
1241
+ type: 'link',
1242
+ raw: cap[0],
1243
+ text,
1244
+ href,
1245
+ tokens: [
1246
+ {
1247
+ type: 'text',
1248
+ raw: text,
1249
+ text,
1250
+ },
1251
+ ],
1252
+ };
1253
+ }
1254
+ }
1255
+ url(src) {
1256
+ let cap;
1257
+ if (cap = this.rules.inline.url.exec(src)) {
1258
+ let text, href;
1259
+ if (cap[2] === '@') {
1260
+ text = cap[0];
1261
+ href = 'mailto:' + text;
1262
+ }
1263
+ else {
1264
+ // do extended autolink path validation
1265
+ let prevCapZero;
1266
+ do {
1267
+ prevCapZero = cap[0];
1268
+ cap[0] = this.rules.inline._backpedal.exec(cap[0])?.[0] ?? '';
1269
+ } while (prevCapZero !== cap[0]);
1270
+ text = cap[0];
1271
+ if (cap[1] === 'www.') {
1272
+ href = 'http://' + cap[0];
1273
+ }
1274
+ else {
1275
+ href = cap[0];
1276
+ }
1277
+ }
1278
+ return {
1279
+ type: 'link',
1280
+ raw: cap[0],
1281
+ text,
1282
+ href,
1283
+ tokens: [
1284
+ {
1285
+ type: 'text',
1286
+ raw: text,
1287
+ text,
1288
+ },
1289
+ ],
1290
+ };
1291
+ }
1292
+ }
1293
+ inlineText(src) {
1294
+ const cap = this.rules.inline.text.exec(src);
1295
+ if (cap) {
1296
+ const escaped = this.lexer.state.inRawBlock;
1297
+ return {
1298
+ type: 'text',
1299
+ raw: cap[0],
1300
+ text: cap[0],
1301
+ escaped,
1302
+ };
1303
+ }
1304
+ }
1305
+ }
1257
1306
 
1258
1307
  /**
1259
1308
  * Block Lexer
@@ -1280,6 +1329,7 @@ class _Lexer {
1280
1329
  top: true,
1281
1330
  };
1282
1331
  const rules = {
1332
+ other,
1283
1333
  block: block.normal,
1284
1334
  inline: inline.normal,
1285
1335
  };
@@ -1325,8 +1375,7 @@ class _Lexer {
1325
1375
  * Preprocessing
1326
1376
  */
1327
1377
  lex(src) {
1328
- src = src
1329
- .replace(/\r\n|\r/g, '\n');
1378
+ src = src.replace(other.carriageReturn, '\n');
1330
1379
  this.blockTokens(src, this.tokens);
1331
1380
  for (let i = 0; i < this.inlineQueue.length; i++) {
1332
1381
  const next = this.inlineQueue[i];
@@ -1337,31 +1386,28 @@ class _Lexer {
1337
1386
  }
1338
1387
  blockTokens(src, tokens = [], lastParagraphClipped = false) {
1339
1388
  if (this.options.pedantic) {
1340
- src = src.replace(/\t/g, ' ').replace(/^ +$/gm, '');
1389
+ src = src.replace(other.tabCharGlobal, ' ').replace(other.spaceLine, '');
1341
1390
  }
1342
- let token;
1343
- let lastToken;
1344
- let cutSrc;
1345
1391
  while (src) {
1346
- if (this.options.extensions
1347
- && this.options.extensions.block
1348
- && this.options.extensions.block.some((extTokenizer) => {
1349
- if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
1350
- src = src.substring(token.raw.length);
1351
- tokens.push(token);
1352
- return true;
1353
- }
1354
- return false;
1355
- })) {
1392
+ let token;
1393
+ if (this.options.extensions?.block?.some((extTokenizer) => {
1394
+ if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
1395
+ src = src.substring(token.raw.length);
1396
+ tokens.push(token);
1397
+ return true;
1398
+ }
1399
+ return false;
1400
+ })) {
1356
1401
  continue;
1357
1402
  }
1358
1403
  // newline
1359
1404
  if (token = this.tokenizer.space(src)) {
1360
1405
  src = src.substring(token.raw.length);
1361
- if (token.raw.length === 1 && tokens.length > 0) {
1406
+ const lastToken = tokens.at(-1);
1407
+ if (token.raw.length === 1 && lastToken !== undefined) {
1362
1408
  // if there's a single \n as a spacer, it's terminating the last line,
1363
1409
  // so move it there so that we don't get unnecessary paragraph tags
1364
- tokens[tokens.length - 1].raw += '\n';
1410
+ lastToken.raw += '\n';
1365
1411
  }
1366
1412
  else {
1367
1413
  tokens.push(token);
@@ -1371,12 +1417,12 @@ class _Lexer {
1371
1417
  // code
1372
1418
  if (token = this.tokenizer.code(src)) {
1373
1419
  src = src.substring(token.raw.length);
1374
- lastToken = tokens[tokens.length - 1];
1420
+ const lastToken = tokens.at(-1);
1375
1421
  // An indented code block cannot interrupt a paragraph.
1376
- if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) {
1422
+ if (lastToken?.type === 'paragraph' || lastToken?.type === 'text') {
1377
1423
  lastToken.raw += '\n' + token.raw;
1378
1424
  lastToken.text += '\n' + token.text;
1379
- this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
1425
+ this.inlineQueue.at(-1).src = lastToken.text;
1380
1426
  }
1381
1427
  else {
1382
1428
  tokens.push(token);
@@ -1422,11 +1468,11 @@ class _Lexer {
1422
1468
  // def
1423
1469
  if (token = this.tokenizer.def(src)) {
1424
1470
  src = src.substring(token.raw.length);
1425
- lastToken = tokens[tokens.length - 1];
1426
- if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) {
1471
+ const lastToken = tokens.at(-1);
1472
+ if (lastToken?.type === 'paragraph' || lastToken?.type === 'text') {
1427
1473
  lastToken.raw += '\n' + token.raw;
1428
1474
  lastToken.text += '\n' + token.raw;
1429
- this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
1475
+ this.inlineQueue.at(-1).src = lastToken.text;
1430
1476
  }
1431
1477
  else if (!this.tokens.links[token.tag]) {
1432
1478
  this.tokens.links[token.tag] = {
@@ -1450,8 +1496,8 @@ class _Lexer {
1450
1496
  }
1451
1497
  // top-level paragraph
1452
1498
  // prevent paragraph consuming extensions by clipping 'src' to extension start
1453
- cutSrc = src;
1454
- if (this.options.extensions && this.options.extensions.startBlock) {
1499
+ let cutSrc = src;
1500
+ if (this.options.extensions?.startBlock) {
1455
1501
  let startIndex = Infinity;
1456
1502
  const tempSrc = src.slice(1);
1457
1503
  let tempStart;
@@ -1466,29 +1512,29 @@ class _Lexer {
1466
1512
  }
1467
1513
  }
1468
1514
  if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) {
1469
- lastToken = tokens[tokens.length - 1];
1515
+ const lastToken = tokens.at(-1);
1470
1516
  if (lastParagraphClipped && lastToken?.type === 'paragraph') {
1471
1517
  lastToken.raw += '\n' + token.raw;
1472
1518
  lastToken.text += '\n' + token.text;
1473
1519
  this.inlineQueue.pop();
1474
- this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
1520
+ this.inlineQueue.at(-1).src = lastToken.text;
1475
1521
  }
1476
1522
  else {
1477
1523
  tokens.push(token);
1478
1524
  }
1479
- lastParagraphClipped = (cutSrc.length !== src.length);
1525
+ lastParagraphClipped = cutSrc.length !== src.length;
1480
1526
  src = src.substring(token.raw.length);
1481
1527
  continue;
1482
1528
  }
1483
1529
  // text
1484
1530
  if (token = this.tokenizer.text(src)) {
1485
1531
  src = src.substring(token.raw.length);
1486
- lastToken = tokens[tokens.length - 1];
1487
- if (lastToken && lastToken.type === 'text') {
1532
+ const lastToken = tokens.at(-1);
1533
+ if (lastToken?.type === 'text') {
1488
1534
  lastToken.raw += '\n' + token.raw;
1489
1535
  lastToken.text += '\n' + token.text;
1490
1536
  this.inlineQueue.pop();
1491
- this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
1537
+ this.inlineQueue.at(-1).src = lastToken.text;
1492
1538
  }
1493
1539
  else {
1494
1540
  tokens.push(token);
@@ -1517,18 +1563,18 @@ class _Lexer {
1517
1563
  * Lexing/Compiling
1518
1564
  */
1519
1565
  inlineTokens(src, tokens = []) {
1520
- let token, lastToken, cutSrc;
1521
1566
  // String with links masked to avoid interference with em and strong
1522
1567
  let maskedSrc = src;
1523
- let match;
1524
- let keepPrevChar, prevChar;
1568
+ let match = null;
1525
1569
  // Mask out reflinks
1526
1570
  if (this.tokens.links) {
1527
1571
  const links = Object.keys(this.tokens.links);
1528
1572
  if (links.length > 0) {
1529
1573
  while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) {
1530
1574
  if (links.includes(match[0].slice(match[0].lastIndexOf('[') + 1, -1))) {
1531
- maskedSrc = maskedSrc.slice(0, match.index) + '[' + 'a'.repeat(match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex);
1575
+ maskedSrc = maskedSrc.slice(0, match.index)
1576
+ + '[' + 'a'.repeat(match[0].length - 2) + ']'
1577
+ + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex);
1532
1578
  }
1533
1579
  }
1534
1580
  }
@@ -1541,22 +1587,23 @@ class _Lexer {
1541
1587
  while ((match = this.tokenizer.rules.inline.anyPunctuation.exec(maskedSrc)) != null) {
1542
1588
  maskedSrc = maskedSrc.slice(0, match.index) + '++' + maskedSrc.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);
1543
1589
  }
1590
+ let keepPrevChar = false;
1591
+ let prevChar = '';
1544
1592
  while (src) {
1545
1593
  if (!keepPrevChar) {
1546
1594
  prevChar = '';
1547
1595
  }
1548
1596
  keepPrevChar = false;
1597
+ let token;
1549
1598
  // extensions
1550
- if (this.options.extensions
1551
- && this.options.extensions.inline
1552
- && this.options.extensions.inline.some((extTokenizer) => {
1553
- if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
1554
- src = src.substring(token.raw.length);
1555
- tokens.push(token);
1556
- return true;
1557
- }
1558
- return false;
1559
- })) {
1599
+ if (this.options.extensions?.inline?.some((extTokenizer) => {
1600
+ if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
1601
+ src = src.substring(token.raw.length);
1602
+ tokens.push(token);
1603
+ return true;
1604
+ }
1605
+ return false;
1606
+ })) {
1560
1607
  continue;
1561
1608
  }
1562
1609
  // escape
@@ -1568,14 +1615,7 @@ class _Lexer {
1568
1615
  // tag
1569
1616
  if (token = this.tokenizer.tag(src)) {
1570
1617
  src = src.substring(token.raw.length);
1571
- lastToken = tokens[tokens.length - 1];
1572
- if (lastToken && token.type === 'text' && lastToken.type === 'text') {
1573
- lastToken.raw += token.raw;
1574
- lastToken.text += token.text;
1575
- }
1576
- else {
1577
- tokens.push(token);
1578
- }
1618
+ tokens.push(token);
1579
1619
  continue;
1580
1620
  }
1581
1621
  // link
@@ -1587,8 +1627,8 @@ class _Lexer {
1587
1627
  // reflink, nolink
1588
1628
  if (token = this.tokenizer.reflink(src, this.tokens.links)) {
1589
1629
  src = src.substring(token.raw.length);
1590
- lastToken = tokens[tokens.length - 1];
1591
- if (lastToken && token.type === 'text' && lastToken.type === 'text') {
1630
+ const lastToken = tokens.at(-1);
1631
+ if (token.type === 'text' && lastToken?.type === 'text') {
1592
1632
  lastToken.raw += token.raw;
1593
1633
  lastToken.text += token.text;
1594
1634
  }
@@ -1635,8 +1675,8 @@ class _Lexer {
1635
1675
  }
1636
1676
  // text
1637
1677
  // prevent inlineText consuming extensions by clipping 'src' to extension start
1638
- cutSrc = src;
1639
- if (this.options.extensions && this.options.extensions.startInline) {
1678
+ let cutSrc = src;
1679
+ if (this.options.extensions?.startInline) {
1640
1680
  let startIndex = Infinity;
1641
1681
  const tempSrc = src.slice(1);
1642
1682
  let tempStart;
@@ -1656,8 +1696,8 @@ class _Lexer {
1656
1696
  prevChar = token.raw.slice(-1);
1657
1697
  }
1658
1698
  keepPrevChar = true;
1659
- lastToken = tokens[tokens.length - 1];
1660
- if (lastToken && lastToken.type === 'text') {
1699
+ const lastToken = tokens.at(-1);
1700
+ if (lastToken?.type === 'text') {
1661
1701
  lastToken.raw += token.raw;
1662
1702
  lastToken.text += token.text;
1663
1703
  }
@@ -1694,17 +1734,17 @@ class _Renderer {
1694
1734
  return '';
1695
1735
  }
1696
1736
  code({ text, lang, escaped }) {
1697
- const langString = (lang || '').match(/^\S*/)?.[0];
1698
- const code = text.replace(/\n$/, '') + '\n';
1737
+ const langString = (lang || '').match(other.notSpaceStart)?.[0];
1738
+ const code = text.replace(other.endingNewline, '') + '\n';
1699
1739
  if (!langString) {
1700
1740
  return '<pre><code>'
1701
- + (escaped ? code : escape$1(code, true))
1741
+ + (escaped ? code : escape(code, true))
1702
1742
  + '</code></pre>\n';
1703
1743
  }
1704
1744
  return '<pre><code class="language-'
1705
- + escape$1(langString)
1745
+ + escape(langString)
1706
1746
  + '">'
1707
- + (escaped ? code : escape$1(code, true))
1747
+ + (escaped ? code : escape(code, true))
1708
1748
  + '</code></pre>\n';
1709
1749
  }
1710
1750
  blockquote({ tokens }) {
@@ -1737,10 +1777,11 @@ class _Renderer {
1737
1777
  if (item.task) {
1738
1778
  const checkbox = this.checkbox({ checked: !!item.checked });
1739
1779
  if (item.loose) {
1740
- if (item.tokens.length > 0 && item.tokens[0].type === 'paragraph') {
1780
+ if (item.tokens[0]?.type === 'paragraph') {
1741
1781
  item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
1742
1782
  if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
1743
- item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text;
1783
+ item.tokens[0].tokens[0].text = checkbox + ' ' + escape(item.tokens[0].tokens[0].text);
1784
+ item.tokens[0].tokens[0].escaped = true;
1744
1785
  }
1745
1786
  }
1746
1787
  else {
@@ -1748,6 +1789,7 @@ class _Renderer {
1748
1789
  type: 'text',
1749
1790
  raw: checkbox + ' ',
1750
1791
  text: checkbox + ' ',
1792
+ escaped: true,
1751
1793
  });
1752
1794
  }
1753
1795
  }
@@ -1813,7 +1855,7 @@ class _Renderer {
1813
1855
  return `<em>${this.parser.parseInline(tokens)}</em>`;
1814
1856
  }
1815
1857
  codespan({ text }) {
1816
- return `<code>${text}</code>`;
1858
+ return `<code>${escape(text, true)}</code>`;
1817
1859
  }
1818
1860
  br(token) {
1819
1861
  return '<br>';
@@ -1830,7 +1872,7 @@ class _Renderer {
1830
1872
  href = cleanHref;
1831
1873
  let out = '<a href="' + href + '"';
1832
1874
  if (title) {
1833
- out += ' title="' + title + '"';
1875
+ out += ' title="' + (escape(title)) + '"';
1834
1876
  }
1835
1877
  out += '>' + text + '</a>';
1836
1878
  return out;
@@ -1838,18 +1880,20 @@ class _Renderer {
1838
1880
  image({ href, title, text }) {
1839
1881
  const cleanHref = cleanUrl(href);
1840
1882
  if (cleanHref === null) {
1841
- return text;
1883
+ return escape(text);
1842
1884
  }
1843
1885
  href = cleanHref;
1844
1886
  let out = `<img src="${href}" alt="${text}"`;
1845
1887
  if (title) {
1846
- out += ` title="${title}"`;
1888
+ out += ` title="${escape(title)}"`;
1847
1889
  }
1848
1890
  out += '>';
1849
1891
  return out;
1850
1892
  }
1851
1893
  text(token) {
1852
- return 'tokens' in token && token.tokens ? this.parser.parseInline(token.tokens) : token.text;
1894
+ return 'tokens' in token && token.tokens
1895
+ ? this.parser.parseInline(token.tokens)
1896
+ : ('escaped' in token && token.escaped ? token.text : escape(token.text));
1853
1897
  }
1854
1898
  }
1855
1899
 
@@ -1925,7 +1969,7 @@ class _Parser {
1925
1969
  for (let i = 0; i < tokens.length; i++) {
1926
1970
  const anyToken = tokens[i];
1927
1971
  // Run any renderer extensions
1928
- if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[anyToken.type]) {
1972
+ if (this.options.extensions?.renderers?.[anyToken.type]) {
1929
1973
  const genericToken = anyToken;
1930
1974
  const ret = this.options.extensions.renderers[genericToken.type].call({ parser: this }, genericToken);
1931
1975
  if (ret !== false || !['space', 'hr', 'heading', 'code', 'table', 'blockquote', 'list', 'html', 'paragraph', 'text'].includes(genericToken.type)) {
@@ -1983,7 +2027,7 @@ class _Parser {
1983
2027
  type: 'paragraph',
1984
2028
  raw: body,
1985
2029
  text: body,
1986
- tokens: [{ type: 'text', raw: body, text: body }],
2030
+ tokens: [{ type: 'text', raw: body, text: body, escaped: true }],
1987
2031
  });
1988
2032
  }
1989
2033
  else {
@@ -2008,13 +2052,12 @@ class _Parser {
2008
2052
  /**
2009
2053
  * Parse Inline Tokens
2010
2054
  */
2011
- parseInline(tokens, renderer) {
2012
- renderer = renderer || this.renderer;
2055
+ parseInline(tokens, renderer = this.renderer) {
2013
2056
  let out = '';
2014
2057
  for (let i = 0; i < tokens.length; i++) {
2015
2058
  const anyToken = tokens[i];
2016
2059
  // Run any renderer extensions
2017
- if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[anyToken.type]) {
2060
+ if (this.options.extensions?.renderers?.[anyToken.type]) {
2018
2061
  const ret = this.options.extensions.renderers[anyToken.type].call({ parser: this }, anyToken);
2019
2062
  if (ret !== false || !['escape', 'html', 'link', 'image', 'strong', 'em', 'codespan', 'br', 'del', 'text'].includes(anyToken.type)) {
2020
2063
  out += ret || '';
@@ -2419,7 +2462,7 @@ class Marked {
2419
2462
  e.message += '\nPlease report this to https://github.com/markedjs/marked.';
2420
2463
  if (silent) {
2421
2464
  const msg = '<p>An error occurred:</p><pre>'
2422
- + escape$1(e.message + '', true)
2465
+ + escape(e.message + '', true)
2423
2466
  + '</pre>';
2424
2467
  if (async) {
2425
2468
  return Promise.resolve(msg);