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