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