marked 7.0.1 → 7.0.2
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 +34 -24
- package/lib/marked.cjs.map +1 -1
- package/lib/marked.d.ts +111 -110
- package/lib/marked.esm.js +34 -24
- package/lib/marked.esm.js.map +1 -1
- package/lib/marked.umd.js +34 -24
- package/lib/marked.umd.js.map +1 -1
- package/marked.min.js +3 -3
- package/package.json +4 -5
- package/src/Hooks.ts +0 -29
- package/src/Instance.ts +0 -381
- package/src/Lexer.ts +0 -517
- package/src/MarkedOptions.ts +0 -212
- package/src/Parser.ts +0 -291
- package/src/Renderer.ts +0 -167
- package/src/Slugger.ts +0 -51
- package/src/TextRenderer.ts +0 -42
- package/src/Tokenizer.ts +0 -810
- package/src/Tokens.ts +0 -199
- package/src/defaults.ts +0 -35
- package/src/helpers.ts +0 -264
- package/src/marked.ts +0 -143
- package/src/rules.ts +0 -380
package/src/Tokenizer.ts
DELETED
|
@@ -1,810 +0,0 @@
|
|
|
1
|
-
import { _defaults } from './defaults.ts';
|
|
2
|
-
import {
|
|
3
|
-
rtrim,
|
|
4
|
-
splitCells,
|
|
5
|
-
escape,
|
|
6
|
-
findClosingBracket
|
|
7
|
-
} from './helpers.ts';
|
|
8
|
-
import { _Lexer } from './Lexer.ts';
|
|
9
|
-
import type { Links, Tokens } from './Tokens.ts';
|
|
10
|
-
import type { MarkedOptions } from './MarkedOptions.ts';
|
|
11
|
-
|
|
12
|
-
function outputLink(cap: string[], link: Pick<Tokens.Link, 'href' | 'title'>, raw: string, lexer: _Lexer): Tokens.Link | Tokens.Image {
|
|
13
|
-
const href = link.href;
|
|
14
|
-
const title = link.title ? escape(link.title) : null;
|
|
15
|
-
const text = cap[1].replace(/\\([\[\]])/g, '$1');
|
|
16
|
-
|
|
17
|
-
if (cap[0].charAt(0) !== '!') {
|
|
18
|
-
lexer.state.inLink = true;
|
|
19
|
-
const token: Tokens.Link = {
|
|
20
|
-
type: 'link',
|
|
21
|
-
raw,
|
|
22
|
-
href,
|
|
23
|
-
title,
|
|
24
|
-
text,
|
|
25
|
-
tokens: lexer.inlineTokens(text)
|
|
26
|
-
};
|
|
27
|
-
lexer.state.inLink = false;
|
|
28
|
-
return token;
|
|
29
|
-
}
|
|
30
|
-
return {
|
|
31
|
-
type: 'image',
|
|
32
|
-
raw,
|
|
33
|
-
href,
|
|
34
|
-
title,
|
|
35
|
-
text: escape(text)
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function indentCodeCompensation(raw: string, text: string) {
|
|
40
|
-
const matchIndentToCode = raw.match(/^(\s+)(?:```)/);
|
|
41
|
-
|
|
42
|
-
if (matchIndentToCode === null) {
|
|
43
|
-
return text;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const indentToCode = matchIndentToCode[1];
|
|
47
|
-
|
|
48
|
-
return text
|
|
49
|
-
.split('\n')
|
|
50
|
-
.map(node => {
|
|
51
|
-
const matchIndentInNode = node.match(/^\s+/);
|
|
52
|
-
if (matchIndentInNode === null) {
|
|
53
|
-
return node;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const [indentInNode] = matchIndentInNode;
|
|
57
|
-
|
|
58
|
-
if (indentInNode.length >= indentToCode.length) {
|
|
59
|
-
return node.slice(indentToCode.length);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return node;
|
|
63
|
-
})
|
|
64
|
-
.join('\n');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Tokenizer
|
|
69
|
-
*/
|
|
70
|
-
export class _Tokenizer {
|
|
71
|
-
options: MarkedOptions;
|
|
72
|
-
rules: any;
|
|
73
|
-
lexer!: _Lexer;
|
|
74
|
-
|
|
75
|
-
constructor(options?: MarkedOptions) {
|
|
76
|
-
this.options = options || _defaults;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
space(src: string): Tokens.Space | undefined {
|
|
80
|
-
const cap = this.rules.block.newline.exec(src);
|
|
81
|
-
if (cap && cap[0].length > 0) {
|
|
82
|
-
return {
|
|
83
|
-
type: 'space',
|
|
84
|
-
raw: cap[0]
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
code(src: string): Tokens.Code | undefined {
|
|
90
|
-
const cap = this.rules.block.code.exec(src);
|
|
91
|
-
if (cap) {
|
|
92
|
-
const text = cap[0].replace(/^ {1,4}/gm, '');
|
|
93
|
-
return {
|
|
94
|
-
type: 'code',
|
|
95
|
-
raw: cap[0],
|
|
96
|
-
codeBlockStyle: 'indented',
|
|
97
|
-
text: !this.options.pedantic
|
|
98
|
-
? rtrim(text, '\n')
|
|
99
|
-
: text
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
fences(src: string): Tokens.Code | undefined {
|
|
105
|
-
const cap = this.rules.block.fences.exec(src);
|
|
106
|
-
if (cap) {
|
|
107
|
-
const raw = cap[0];
|
|
108
|
-
const text = indentCodeCompensation(raw, cap[3] || '');
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
type: 'code',
|
|
112
|
-
raw,
|
|
113
|
-
lang: cap[2] ? cap[2].trim().replace(this.rules.inline._escapes, '$1') : cap[2],
|
|
114
|
-
text
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
heading(src: string): Tokens.Heading | undefined {
|
|
120
|
-
const cap = this.rules.block.heading.exec(src);
|
|
121
|
-
if (cap) {
|
|
122
|
-
let text = cap[2].trim();
|
|
123
|
-
|
|
124
|
-
// remove trailing #s
|
|
125
|
-
if (/#$/.test(text)) {
|
|
126
|
-
const trimmed = rtrim(text, '#');
|
|
127
|
-
if (this.options.pedantic) {
|
|
128
|
-
text = trimmed.trim();
|
|
129
|
-
} else if (!trimmed || / $/.test(trimmed)) {
|
|
130
|
-
// CommonMark requires space before trailing #s
|
|
131
|
-
text = trimmed.trim();
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
type: 'heading',
|
|
137
|
-
raw: cap[0],
|
|
138
|
-
depth: cap[1].length,
|
|
139
|
-
text,
|
|
140
|
-
tokens: this.lexer.inline(text)
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
hr(src: string): Tokens.Hr | undefined {
|
|
146
|
-
const cap = this.rules.block.hr.exec(src);
|
|
147
|
-
if (cap) {
|
|
148
|
-
return {
|
|
149
|
-
type: 'hr',
|
|
150
|
-
raw: cap[0]
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
blockquote(src: string): Tokens.Blockquote | undefined {
|
|
156
|
-
const cap = this.rules.block.blockquote.exec(src);
|
|
157
|
-
if (cap) {
|
|
158
|
-
const text = cap[0].replace(/^ *>[ \t]?/gm, '');
|
|
159
|
-
const top = this.lexer.state.top;
|
|
160
|
-
this.lexer.state.top = true;
|
|
161
|
-
const tokens = this.lexer.blockTokens(text);
|
|
162
|
-
this.lexer.state.top = top;
|
|
163
|
-
return {
|
|
164
|
-
type: 'blockquote',
|
|
165
|
-
raw: cap[0],
|
|
166
|
-
tokens,
|
|
167
|
-
text
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
list(src: string): Tokens.List | undefined {
|
|
173
|
-
let cap = this.rules.block.list.exec(src);
|
|
174
|
-
if (cap) {
|
|
175
|
-
let raw, istask, ischecked, indent, i, blankLine, endsWithBlankLine,
|
|
176
|
-
line, nextLine, rawLine, itemContents, endEarly;
|
|
177
|
-
|
|
178
|
-
let bull = cap[1].trim();
|
|
179
|
-
const isordered = bull.length > 1;
|
|
180
|
-
|
|
181
|
-
const list: Tokens.List = {
|
|
182
|
-
type: 'list',
|
|
183
|
-
raw: '',
|
|
184
|
-
ordered: isordered,
|
|
185
|
-
start: isordered ? +bull.slice(0, -1) : '',
|
|
186
|
-
loose: false,
|
|
187
|
-
items: [] as Tokens.ListItem[]
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`;
|
|
191
|
-
|
|
192
|
-
if (this.options.pedantic) {
|
|
193
|
-
bull = isordered ? bull : '[*+-]';
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Get next list item
|
|
197
|
-
const itemRegex = new RegExp(`^( {0,3}${bull})((?:[\t ][^\\n]*)?(?:\\n|$))`);
|
|
198
|
-
|
|
199
|
-
// Check if current bullet point can start a new List Item
|
|
200
|
-
while (src) {
|
|
201
|
-
endEarly = false;
|
|
202
|
-
if (!(cap = itemRegex.exec(src))) {
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (this.rules.block.hr.test(src)) { // End list if bullet was actually HR (possibly move into itemRegex?)
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
raw = cap[0];
|
|
211
|
-
src = src.substring(raw.length);
|
|
212
|
-
|
|
213
|
-
line = cap[2].split('\n', 1)[0].replace(/^\t+/, (t: string) => ' '.repeat(3 * t.length));
|
|
214
|
-
nextLine = src.split('\n', 1)[0];
|
|
215
|
-
|
|
216
|
-
if (this.options.pedantic) {
|
|
217
|
-
indent = 2;
|
|
218
|
-
itemContents = line.trimLeft();
|
|
219
|
-
} else {
|
|
220
|
-
indent = cap[2].search(/[^ ]/); // Find first non-space char
|
|
221
|
-
indent = indent > 4 ? 1 : indent; // Treat indented code blocks (> 4 spaces) as having only 1 indent
|
|
222
|
-
itemContents = line.slice(indent);
|
|
223
|
-
indent += cap[1].length;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
blankLine = false;
|
|
227
|
-
|
|
228
|
-
if (!line && /^ *$/.test(nextLine)) { // Items begin with at most one blank line
|
|
229
|
-
raw += nextLine + '\n';
|
|
230
|
-
src = src.substring(nextLine.length + 1);
|
|
231
|
-
endEarly = true;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (!endEarly) {
|
|
235
|
-
const nextBulletRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`);
|
|
236
|
-
const hrRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`);
|
|
237
|
-
const fencesBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`);
|
|
238
|
-
const headingBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`);
|
|
239
|
-
|
|
240
|
-
// Check if following lines should be included in List Item
|
|
241
|
-
while (src) {
|
|
242
|
-
rawLine = src.split('\n', 1)[0];
|
|
243
|
-
nextLine = rawLine;
|
|
244
|
-
|
|
245
|
-
// Re-align to follow commonmark nesting rules
|
|
246
|
-
if (this.options.pedantic) {
|
|
247
|
-
nextLine = nextLine.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' ');
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// End list item if found code fences
|
|
251
|
-
if (fencesBeginRegex.test(nextLine)) {
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// End list item if found start of new heading
|
|
256
|
-
if (headingBeginRegex.test(nextLine)) {
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// End list item if found start of new bullet
|
|
261
|
-
if (nextBulletRegex.test(nextLine)) {
|
|
262
|
-
break;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Horizontal rule found
|
|
266
|
-
if (hrRegex.test(src)) {
|
|
267
|
-
break;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (nextLine.search(/[^ ]/) >= indent || !nextLine.trim()) { // Dedent if possible
|
|
271
|
-
itemContents += '\n' + nextLine.slice(indent);
|
|
272
|
-
} else {
|
|
273
|
-
// not enough indentation
|
|
274
|
-
if (blankLine) {
|
|
275
|
-
break;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// paragraph continuation unless last line was a different block level element
|
|
279
|
-
if (line.search(/[^ ]/) >= 4) { // indented code block
|
|
280
|
-
break;
|
|
281
|
-
}
|
|
282
|
-
if (fencesBeginRegex.test(line)) {
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
285
|
-
if (headingBeginRegex.test(line)) {
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
288
|
-
if (hrRegex.test(line)) {
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
itemContents += '\n' + nextLine;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (!blankLine && !nextLine.trim()) { // Check if current line is blank
|
|
296
|
-
blankLine = true;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
raw += rawLine + '\n';
|
|
300
|
-
src = src.substring(rawLine.length + 1);
|
|
301
|
-
line = nextLine.slice(indent);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (!list.loose) {
|
|
306
|
-
// If the previous item ended with a blank line, the list is loose
|
|
307
|
-
if (endsWithBlankLine) {
|
|
308
|
-
list.loose = true;
|
|
309
|
-
} else if (/\n *\n *$/.test(raw)) {
|
|
310
|
-
endsWithBlankLine = true;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Check for task list items
|
|
315
|
-
if (this.options.gfm) {
|
|
316
|
-
istask = /^\[[ xX]\] /.exec(itemContents);
|
|
317
|
-
if (istask) {
|
|
318
|
-
ischecked = istask[0] !== '[ ] ';
|
|
319
|
-
itemContents = itemContents.replace(/^\[[ xX]\] +/, '');
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
list.items.push({
|
|
324
|
-
type: 'list_item',
|
|
325
|
-
raw,
|
|
326
|
-
task: !!istask,
|
|
327
|
-
checked: ischecked,
|
|
328
|
-
loose: false,
|
|
329
|
-
text: itemContents
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
list.raw += raw;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Do not consume newlines at end of final item. Alternatively, make itemRegex *start* with any newlines to simplify/speed up endsWithBlankLine logic
|
|
336
|
-
list.items[list.items.length - 1].raw = raw.trimRight();
|
|
337
|
-
(list.items[list.items.length - 1] as Tokens.ListItem).text = itemContents.trimRight();
|
|
338
|
-
list.raw = list.raw.trimRight();
|
|
339
|
-
|
|
340
|
-
const l = list.items.length;
|
|
341
|
-
|
|
342
|
-
// Item child tokens handled here at end because we needed to have the final item to trim it first
|
|
343
|
-
for (i = 0; i < l; i++) {
|
|
344
|
-
this.lexer.state.top = false;
|
|
345
|
-
list.items[i].tokens = this.lexer.blockTokens(list.items[i].text, []);
|
|
346
|
-
|
|
347
|
-
if (!list.loose) {
|
|
348
|
-
// Check if list should be loose
|
|
349
|
-
const spacers = list.items[i].tokens!.filter(t => t.type === 'space');
|
|
350
|
-
const hasMultipleLineBreaks = spacers.length > 0 && spacers.some(t => /\n.*\n/.test(t.raw!));
|
|
351
|
-
|
|
352
|
-
list.loose = hasMultipleLineBreaks;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Set all items to loose if list is loose
|
|
357
|
-
if (list.loose) {
|
|
358
|
-
for (i = 0; i < l; i++) {
|
|
359
|
-
list.items[i].loose = true;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return list;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
html(src: string): Tokens.HTML | Tokens.Paragraph | undefined {
|
|
368
|
-
const cap = this.rules.block.html.exec(src);
|
|
369
|
-
if (cap) {
|
|
370
|
-
const token: Tokens.HTML | Tokens.Paragraph = {
|
|
371
|
-
type: 'html',
|
|
372
|
-
block: true,
|
|
373
|
-
raw: cap[0],
|
|
374
|
-
pre: !this.options.sanitizer
|
|
375
|
-
&& (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
|
|
376
|
-
text: cap[0]
|
|
377
|
-
};
|
|
378
|
-
if (this.options.sanitize) {
|
|
379
|
-
const text = this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0]);
|
|
380
|
-
const paragraph = token as unknown as Tokens.Paragraph;
|
|
381
|
-
paragraph.type = 'paragraph';
|
|
382
|
-
paragraph.text = text;
|
|
383
|
-
paragraph.tokens = this.lexer.inline(text);
|
|
384
|
-
}
|
|
385
|
-
return token;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
def(src: string): Tokens.Def | undefined {
|
|
390
|
-
const cap = this.rules.block.def.exec(src);
|
|
391
|
-
if (cap) {
|
|
392
|
-
const tag = cap[1].toLowerCase().replace(/\s+/g, ' ');
|
|
393
|
-
const href = cap[2] ? cap[2].replace(/^<(.*)>$/, '$1').replace(this.rules.inline._escapes, '$1') : '';
|
|
394
|
-
const title = cap[3] ? cap[3].substring(1, cap[3].length - 1).replace(this.rules.inline._escapes, '$1') : cap[3];
|
|
395
|
-
return {
|
|
396
|
-
type: 'def',
|
|
397
|
-
tag,
|
|
398
|
-
raw: cap[0],
|
|
399
|
-
href,
|
|
400
|
-
title
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
table(src: string): Tokens.Table | undefined {
|
|
406
|
-
const cap = this.rules.block.table.exec(src);
|
|
407
|
-
if (cap) {
|
|
408
|
-
const item: Tokens.Table = {
|
|
409
|
-
type: 'table',
|
|
410
|
-
// splitCells expects a number as second argument
|
|
411
|
-
// @ts-expect-error
|
|
412
|
-
header: splitCells(cap[1]).map(c => {
|
|
413
|
-
return { text: c };
|
|
414
|
-
}),
|
|
415
|
-
align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
|
|
416
|
-
rows: cap[3] && cap[3].trim() ? cap[3].replace(/\n[ \t]*$/, '').split('\n') : []
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
if (item.header.length === item.align.length) {
|
|
420
|
-
item.raw = cap[0];
|
|
421
|
-
|
|
422
|
-
let l = item.align.length;
|
|
423
|
-
let i, j, k, row;
|
|
424
|
-
for (i = 0; i < l; i++) {
|
|
425
|
-
if (/^ *-+: *$/.test(item.align[i]!)) {
|
|
426
|
-
item.align[i] = 'right';
|
|
427
|
-
} else if (/^ *:-+: *$/.test(item.align[i]!)) {
|
|
428
|
-
item.align[i] = 'center';
|
|
429
|
-
} else if (/^ *:-+ *$/.test(item.align[i]!)) {
|
|
430
|
-
item.align[i] = 'left';
|
|
431
|
-
} else {
|
|
432
|
-
item.align[i] = null;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
l = item.rows.length;
|
|
437
|
-
for (i = 0; i < l; i++) {
|
|
438
|
-
item.rows[i] = splitCells(item.rows[i] as unknown as string, item.header.length).map(c => {
|
|
439
|
-
return { text: c };
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// parse child tokens inside headers and cells
|
|
444
|
-
|
|
445
|
-
// header child tokens
|
|
446
|
-
l = item.header.length;
|
|
447
|
-
for (j = 0; j < l; j++) {
|
|
448
|
-
item.header[j].tokens = this.lexer.inline(item.header[j].text);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// cell child tokens
|
|
452
|
-
l = item.rows.length;
|
|
453
|
-
for (j = 0; j < l; j++) {
|
|
454
|
-
row = item.rows[j];
|
|
455
|
-
for (k = 0; k < row.length; k++) {
|
|
456
|
-
row[k].tokens = this.lexer.inline(row[k].text);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
return item;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
lheading(src: string): Tokens.Heading | undefined {
|
|
466
|
-
const cap = this.rules.block.lheading.exec(src);
|
|
467
|
-
if (cap) {
|
|
468
|
-
return {
|
|
469
|
-
type: 'heading',
|
|
470
|
-
raw: cap[0],
|
|
471
|
-
depth: cap[2].charAt(0) === '=' ? 1 : 2,
|
|
472
|
-
text: cap[1],
|
|
473
|
-
tokens: this.lexer.inline(cap[1])
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
paragraph(src: string): Tokens.Paragraph | undefined {
|
|
479
|
-
const cap = this.rules.block.paragraph.exec(src);
|
|
480
|
-
if (cap) {
|
|
481
|
-
const text = cap[1].charAt(cap[1].length - 1) === '\n'
|
|
482
|
-
? cap[1].slice(0, -1)
|
|
483
|
-
: cap[1];
|
|
484
|
-
return {
|
|
485
|
-
type: 'paragraph',
|
|
486
|
-
raw: cap[0],
|
|
487
|
-
text,
|
|
488
|
-
tokens: this.lexer.inline(text)
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
text(src: string): Tokens.Text | undefined {
|
|
494
|
-
const cap = this.rules.block.text.exec(src);
|
|
495
|
-
if (cap) {
|
|
496
|
-
return {
|
|
497
|
-
type: 'text',
|
|
498
|
-
raw: cap[0],
|
|
499
|
-
text: cap[0],
|
|
500
|
-
tokens: this.lexer.inline(cap[0])
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
escape(src: string): Tokens.Escape | undefined {
|
|
506
|
-
const cap = this.rules.inline.escape.exec(src);
|
|
507
|
-
if (cap) {
|
|
508
|
-
return {
|
|
509
|
-
type: 'escape',
|
|
510
|
-
raw: cap[0],
|
|
511
|
-
text: escape(cap[1])
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
tag(src: string): Tokens.Tag | undefined {
|
|
517
|
-
const cap = this.rules.inline.tag.exec(src);
|
|
518
|
-
if (cap) {
|
|
519
|
-
if (!this.lexer.state.inLink && /^<a /i.test(cap[0])) {
|
|
520
|
-
this.lexer.state.inLink = true;
|
|
521
|
-
} else if (this.lexer.state.inLink && /^<\/a>/i.test(cap[0])) {
|
|
522
|
-
this.lexer.state.inLink = false;
|
|
523
|
-
}
|
|
524
|
-
if (!this.lexer.state.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
|
|
525
|
-
this.lexer.state.inRawBlock = true;
|
|
526
|
-
} else if (this.lexer.state.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
|
|
527
|
-
this.lexer.state.inRawBlock = false;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
return {
|
|
531
|
-
type: this.options.sanitize
|
|
532
|
-
? 'text'
|
|
533
|
-
: 'html',
|
|
534
|
-
raw: cap[0],
|
|
535
|
-
inLink: this.lexer.state.inLink,
|
|
536
|
-
inRawBlock: this.lexer.state.inRawBlock,
|
|
537
|
-
block: false,
|
|
538
|
-
text: this.options.sanitize
|
|
539
|
-
? (this.options.sanitizer
|
|
540
|
-
? this.options.sanitizer(cap[0])
|
|
541
|
-
: escape(cap[0]))
|
|
542
|
-
: cap[0]
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
link(src: string): Tokens.Link | Tokens.Image | undefined {
|
|
548
|
-
const cap = this.rules.inline.link.exec(src);
|
|
549
|
-
if (cap) {
|
|
550
|
-
const trimmedUrl = cap[2].trim();
|
|
551
|
-
if (!this.options.pedantic && /^</.test(trimmedUrl)) {
|
|
552
|
-
// commonmark requires matching angle brackets
|
|
553
|
-
if (!(/>$/.test(trimmedUrl))) {
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// ending angle bracket cannot be escaped
|
|
558
|
-
const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\');
|
|
559
|
-
if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
} else {
|
|
563
|
-
// find closing parenthesis
|
|
564
|
-
const lastParenIndex = findClosingBracket(cap[2], '()');
|
|
565
|
-
if (lastParenIndex > -1) {
|
|
566
|
-
const start = cap[0].indexOf('!') === 0 ? 5 : 4;
|
|
567
|
-
const linkLen = start + cap[1].length + lastParenIndex;
|
|
568
|
-
cap[2] = cap[2].substring(0, lastParenIndex);
|
|
569
|
-
cap[0] = cap[0].substring(0, linkLen).trim();
|
|
570
|
-
cap[3] = '';
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
let href = cap[2];
|
|
574
|
-
let title = '';
|
|
575
|
-
if (this.options.pedantic) {
|
|
576
|
-
// split pedantic href and title
|
|
577
|
-
const link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
|
|
578
|
-
|
|
579
|
-
if (link) {
|
|
580
|
-
href = link[1];
|
|
581
|
-
title = link[3];
|
|
582
|
-
}
|
|
583
|
-
} else {
|
|
584
|
-
title = cap[3] ? cap[3].slice(1, -1) : '';
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
href = href.trim();
|
|
588
|
-
if (/^</.test(href)) {
|
|
589
|
-
if (this.options.pedantic && !(/>$/.test(trimmedUrl))) {
|
|
590
|
-
// pedantic allows starting angle bracket without ending angle bracket
|
|
591
|
-
href = href.slice(1);
|
|
592
|
-
} else {
|
|
593
|
-
href = href.slice(1, -1);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
return outputLink(cap, {
|
|
597
|
-
href: href ? href.replace(this.rules.inline._escapes, '$1') : href,
|
|
598
|
-
title: title ? title.replace(this.rules.inline._escapes, '$1') : title
|
|
599
|
-
}, cap[0], this.lexer);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
reflink(src: string, links: Links): Tokens.Link | Tokens.Image | Tokens.Text | undefined {
|
|
604
|
-
let cap;
|
|
605
|
-
if ((cap = this.rules.inline.reflink.exec(src))
|
|
606
|
-
|| (cap = this.rules.inline.nolink.exec(src))) {
|
|
607
|
-
let link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
|
|
608
|
-
link = links[link.toLowerCase()];
|
|
609
|
-
if (!link) {
|
|
610
|
-
const text = cap[0].charAt(0);
|
|
611
|
-
return {
|
|
612
|
-
type: 'text',
|
|
613
|
-
raw: text,
|
|
614
|
-
text
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
return outputLink(cap, link, cap[0], this.lexer);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
emStrong(src: string, maskedSrc: string, prevChar = ''): Tokens.Em | Tokens.Strong | undefined {
|
|
622
|
-
let match = this.rules.inline.emStrong.lDelim.exec(src);
|
|
623
|
-
if (!match) return;
|
|
624
|
-
|
|
625
|
-
// _ can't be between two alphanumerics. \p{L}\p{N} includes non-english alphabet/numbers as well
|
|
626
|
-
if (match[3] && prevChar.match(/[\p{L}\p{N}]/u)) return;
|
|
627
|
-
|
|
628
|
-
const nextChar = match[1] || match[2] || '';
|
|
629
|
-
|
|
630
|
-
if (!nextChar || !prevChar || this.rules.inline.punctuation.exec(prevChar)) {
|
|
631
|
-
const lLength = match[0].length - 1;
|
|
632
|
-
let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0;
|
|
633
|
-
|
|
634
|
-
const endReg = match[0][0] === '*' ? this.rules.inline.emStrong.rDelimAst : this.rules.inline.emStrong.rDelimUnd;
|
|
635
|
-
endReg.lastIndex = 0;
|
|
636
|
-
|
|
637
|
-
// Clip maskedSrc to same section of string as src (move to lexer?)
|
|
638
|
-
maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
|
|
639
|
-
|
|
640
|
-
while ((match = endReg.exec(maskedSrc)) != null) {
|
|
641
|
-
rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
|
|
642
|
-
|
|
643
|
-
if (!rDelim) continue; // skip single * in __abc*abc__
|
|
644
|
-
|
|
645
|
-
rLength = rDelim.length;
|
|
646
|
-
|
|
647
|
-
if (match[3] || match[4]) { // found another Left Delim
|
|
648
|
-
delimTotal += rLength;
|
|
649
|
-
continue;
|
|
650
|
-
} else if (match[5] || match[6]) { // either Left or Right Delim
|
|
651
|
-
if (lLength % 3 && !((lLength + rLength) % 3)) {
|
|
652
|
-
midDelimTotal += rLength;
|
|
653
|
-
continue; // CommonMark Emphasis Rules 9-10
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
delimTotal -= rLength;
|
|
658
|
-
|
|
659
|
-
if (delimTotal > 0) continue; // Haven't found enough closing delimiters
|
|
660
|
-
|
|
661
|
-
// Remove extra characters. *a*** -> *a*
|
|
662
|
-
rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal);
|
|
663
|
-
|
|
664
|
-
const raw = src.slice(0, lLength + match.index + rLength + 1);
|
|
665
|
-
|
|
666
|
-
// Create `em` if smallest delimiter has odd char count. *a***
|
|
667
|
-
if (Math.min(lLength, rLength) % 2) {
|
|
668
|
-
const text = raw.slice(1, -1);
|
|
669
|
-
return {
|
|
670
|
-
type: 'em',
|
|
671
|
-
raw,
|
|
672
|
-
text,
|
|
673
|
-
tokens: this.lexer.inlineTokens(text)
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// Create 'strong' if smallest delimiter has even char count. **a***
|
|
678
|
-
const text = raw.slice(2, -2);
|
|
679
|
-
return {
|
|
680
|
-
type: 'strong',
|
|
681
|
-
raw,
|
|
682
|
-
text,
|
|
683
|
-
tokens: this.lexer.inlineTokens(text)
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
codespan(src: string): Tokens.Codespan | undefined {
|
|
690
|
-
const cap = this.rules.inline.code.exec(src);
|
|
691
|
-
if (cap) {
|
|
692
|
-
let text = cap[2].replace(/\n/g, ' ');
|
|
693
|
-
const hasNonSpaceChars = /[^ ]/.test(text);
|
|
694
|
-
const hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text);
|
|
695
|
-
if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
|
|
696
|
-
text = text.substring(1, text.length - 1);
|
|
697
|
-
}
|
|
698
|
-
text = escape(text, true);
|
|
699
|
-
return {
|
|
700
|
-
type: 'codespan',
|
|
701
|
-
raw: cap[0],
|
|
702
|
-
text
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
br(src: string): Tokens.Br | undefined {
|
|
708
|
-
const cap = this.rules.inline.br.exec(src);
|
|
709
|
-
if (cap) {
|
|
710
|
-
return {
|
|
711
|
-
type: 'br',
|
|
712
|
-
raw: cap[0]
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
del(src: string): Tokens.Del | undefined {
|
|
718
|
-
const cap = this.rules.inline.del.exec(src);
|
|
719
|
-
if (cap) {
|
|
720
|
-
return {
|
|
721
|
-
type: 'del',
|
|
722
|
-
raw: cap[0],
|
|
723
|
-
text: cap[2],
|
|
724
|
-
tokens: this.lexer.inlineTokens(cap[2])
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
autolink(src: string, mangle: (cap: string) => string): Tokens.Link | undefined {
|
|
730
|
-
const cap = this.rules.inline.autolink.exec(src);
|
|
731
|
-
if (cap) {
|
|
732
|
-
let text, href;
|
|
733
|
-
if (cap[2] === '@') {
|
|
734
|
-
text = escape(this.options.mangle ? mangle(cap[1]) : cap[1]);
|
|
735
|
-
href = 'mailto:' + text;
|
|
736
|
-
} else {
|
|
737
|
-
text = escape(cap[1]);
|
|
738
|
-
href = text;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
return {
|
|
742
|
-
type: 'link',
|
|
743
|
-
raw: cap[0],
|
|
744
|
-
text,
|
|
745
|
-
href,
|
|
746
|
-
tokens: [
|
|
747
|
-
{
|
|
748
|
-
type: 'text',
|
|
749
|
-
raw: text,
|
|
750
|
-
text
|
|
751
|
-
}
|
|
752
|
-
]
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
url(src: string, mangle: (cap: string) => string): Tokens.Link | undefined {
|
|
758
|
-
let cap;
|
|
759
|
-
if (cap = this.rules.inline.url.exec(src)) {
|
|
760
|
-
let text, href;
|
|
761
|
-
if (cap[2] === '@') {
|
|
762
|
-
text = escape(this.options.mangle ? mangle(cap[0]) : cap[0]);
|
|
763
|
-
href = 'mailto:' + text;
|
|
764
|
-
} else {
|
|
765
|
-
// do extended autolink path validation
|
|
766
|
-
let prevCapZero;
|
|
767
|
-
do {
|
|
768
|
-
prevCapZero = cap[0];
|
|
769
|
-
cap[0] = this.rules.inline._backpedal.exec(cap[0])[0];
|
|
770
|
-
} while (prevCapZero !== cap[0]);
|
|
771
|
-
text = escape(cap[0]);
|
|
772
|
-
if (cap[1] === 'www.') {
|
|
773
|
-
href = 'http://' + cap[0];
|
|
774
|
-
} else {
|
|
775
|
-
href = cap[0];
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
return {
|
|
779
|
-
type: 'link',
|
|
780
|
-
raw: cap[0],
|
|
781
|
-
text,
|
|
782
|
-
href,
|
|
783
|
-
tokens: [
|
|
784
|
-
{
|
|
785
|
-
type: 'text',
|
|
786
|
-
raw: text,
|
|
787
|
-
text
|
|
788
|
-
}
|
|
789
|
-
]
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
inlineText(src: string, smartypants: (cap: string) => string): Tokens.Text | undefined {
|
|
795
|
-
const cap = this.rules.inline.text.exec(src);
|
|
796
|
-
if (cap) {
|
|
797
|
-
let text;
|
|
798
|
-
if (this.lexer.state.inRawBlock) {
|
|
799
|
-
text = this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0];
|
|
800
|
-
} else {
|
|
801
|
-
text = escape(this.options.smartypants ? smartypants(cap[0]) : cap[0]);
|
|
802
|
-
}
|
|
803
|
-
return {
|
|
804
|
-
type: 'text',
|
|
805
|
-
raw: cap[0],
|
|
806
|
-
text
|
|
807
|
-
};
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
}
|