marked 2.1.0 → 3.0.0

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/src/Parser.js CHANGED
@@ -65,8 +65,8 @@ module.exports = class Parser {
65
65
  token = tokens[i];
66
66
 
67
67
  // Run any renderer extensions
68
- if (this.options.extensions?.renderers?.[token.type]) {
69
- ret = this.options.extensions.renderers[token.type].call(this, token);
68
+ if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
69
+ ret = this.options.extensions.renderers[token.type].call({ parser: this }, token);
70
70
  if (ret !== false || !['space', 'hr', 'heading', 'code', 'table', 'blockquote', 'list', 'html', 'paragraph', 'text'].includes(token.type)) {
71
71
  out += ret || '';
72
72
  continue;
@@ -103,22 +103,22 @@ module.exports = class Parser {
103
103
  l2 = token.header.length;
104
104
  for (j = 0; j < l2; j++) {
105
105
  cell += this.renderer.tablecell(
106
- this.parseInline(token.tokens.header[j]),
106
+ this.parseInline(token.header[j].tokens),
107
107
  { header: true, align: token.align[j] }
108
108
  );
109
109
  }
110
110
  header += this.renderer.tablerow(cell);
111
111
 
112
112
  body = '';
113
- l2 = token.cells.length;
113
+ l2 = token.rows.length;
114
114
  for (j = 0; j < l2; j++) {
115
- row = token.tokens.cells[j];
115
+ row = token.rows[j];
116
116
 
117
117
  cell = '';
118
118
  l3 = row.length;
119
119
  for (k = 0; k < l3; k++) {
120
120
  cell += this.renderer.tablecell(
121
- this.parseInline(row[k]),
121
+ this.parseInline(row[k].tokens),
122
122
  { header: false, align: token.align[k] }
123
123
  );
124
124
  }
@@ -149,7 +149,7 @@ module.exports = class Parser {
149
149
  if (item.task) {
150
150
  checkbox = this.renderer.checkbox(checked);
151
151
  if (loose) {
152
- if (item.tokens.length > 0 && item.tokens[0].type === 'text') {
152
+ if (item.tokens.length > 0 && item.tokens[0].type === 'paragraph') {
153
153
  item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
154
154
  if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
155
155
  item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text;
@@ -221,8 +221,8 @@ module.exports = class Parser {
221
221
  token = tokens[i];
222
222
 
223
223
  // Run any renderer extensions
224
- if (this.options.extensions?.renderers?.[token.type]) {
225
- ret = this.options.extensions.renderers[token.type].call(this, token);
224
+ if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
225
+ ret = this.options.extensions.renderers[token.type].call({ parser: this }, token);
226
226
  if (ret !== false || !['escape', 'html', 'link', 'image', 'strong', 'em', 'codespan', 'br', 'del', 'text'].includes(token.type)) {
227
227
  out += ret || '';
228
228
  continue;
package/src/Tokenizer.js CHANGED
@@ -6,18 +6,20 @@ const {
6
6
  findClosingBracket
7
7
  } = require('./helpers.js');
8
8
 
9
- function outputLink(cap, link, raw) {
9
+ function outputLink(cap, link, raw, lexer) {
10
10
  const href = link.href;
11
11
  const title = link.title ? escape(link.title) : null;
12
12
  const text = cap[1].replace(/\\([\[\]])/g, '$1');
13
13
 
14
14
  if (cap[0].charAt(0) !== '!') {
15
+ lexer.state.inLink = true;
15
16
  return {
16
17
  type: 'link',
17
18
  raw,
18
19
  href,
19
20
  title,
20
- text
21
+ text,
22
+ tokens: lexer.inlineTokens(text, [])
21
23
  };
22
24
  } else {
23
25
  return {
@@ -125,48 +127,15 @@ module.exports = class Tokenizer {
125
127
  }
126
128
  }
127
129
 
128
- return {
130
+ const token = {
129
131
  type: 'heading',
130
132
  raw: cap[0],
131
133
  depth: cap[1].length,
132
- text: text
133
- };
134
- }
135
- }
136
-
137
- nptable(src) {
138
- const cap = this.rules.block.nptable.exec(src);
139
- if (cap) {
140
- const item = {
141
- type: 'table',
142
- header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
143
- align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
144
- cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [],
145
- raw: cap[0]
134
+ text: text,
135
+ tokens: []
146
136
  };
147
-
148
- if (item.header.length === item.align.length) {
149
- let l = item.align.length;
150
- let i;
151
- for (i = 0; i < l; i++) {
152
- if (/^ *-+: *$/.test(item.align[i])) {
153
- item.align[i] = 'right';
154
- } else if (/^ *:-+: *$/.test(item.align[i])) {
155
- item.align[i] = 'center';
156
- } else if (/^ *:-+ *$/.test(item.align[i])) {
157
- item.align[i] = 'left';
158
- } else {
159
- item.align[i] = null;
160
- }
161
- }
162
-
163
- l = item.cells.length;
164
- for (i = 0; i < l; i++) {
165
- item.cells[i] = splitCells(item.cells[i], item.header.length);
166
- }
167
-
168
- return item;
169
- }
137
+ this.lexer.inline(token.text, token.tokens);
138
+ return token;
170
139
  }
171
140
  }
172
141
 
@@ -188,138 +157,155 @@ module.exports = class Tokenizer {
188
157
  return {
189
158
  type: 'blockquote',
190
159
  raw: cap[0],
160
+ tokens: this.lexer.blockTokens(text, []),
191
161
  text
192
162
  };
193
163
  }
194
164
  }
195
165
 
196
166
  list(src) {
197
- const cap = this.rules.block.list.exec(src);
167
+ let cap = this.rules.block.list.exec(src);
198
168
  if (cap) {
199
- let raw = cap[0];
200
- const bull = cap[2];
169
+ let raw, istask, ischecked, indent, i, blankLine, endsWithBlankLine,
170
+ line, lines, itemContents;
171
+
172
+ let bull = cap[1].trim();
201
173
  const isordered = bull.length > 1;
202
174
 
203
175
  const list = {
204
176
  type: 'list',
205
- raw,
177
+ raw: '',
206
178
  ordered: isordered,
207
179
  start: isordered ? +bull.slice(0, -1) : '',
208
180
  loose: false,
209
181
  items: []
210
182
  };
211
183
 
212
- // Get each top-level item.
213
- const itemMatch = cap[0].match(this.rules.block.item);
214
-
215
- let next = false,
216
- item,
217
- space,
218
- bcurr,
219
- bnext,
220
- addBack,
221
- loose,
222
- istask,
223
- ischecked,
224
- endMatch;
225
-
226
- let l = itemMatch.length;
227
- bcurr = this.rules.block.listItemStart.exec(itemMatch[0]);
228
- for (let i = 0; i < l; i++) {
229
- item = itemMatch[i];
230
- raw = item;
231
-
232
- if (!this.options.pedantic) {
233
- // Determine if current item contains the end of the list
234
- endMatch = item.match(new RegExp('\\n\\s*\\n {0,' + (bcurr[0].length - 1) + '}\\S'));
235
- if (endMatch) {
236
- addBack = item.length - endMatch.index + itemMatch.slice(i + 1).join('\n').length;
237
- list.raw = list.raw.substring(0, list.raw.length - addBack);
238
-
239
- item = item.substring(0, endMatch.index);
240
- raw = item;
241
- l = i + 1;
242
- }
184
+ bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`;
185
+
186
+ if (this.options.pedantic) {
187
+ bull = isordered ? bull : '[*+-]';
188
+ }
189
+
190
+ // Get next list item
191
+ const itemRegex = new RegExp(`^( {0,3}${bull})((?: [^\\n]*| *)(?:\\n[^\\n]*)*(?:\\n|$))`);
192
+
193
+ // Get each top-level item
194
+ while (src) {
195
+ if (this.rules.block.hr.test(src)) { // End list if we encounter an HR (possibly move into itemRegex?)
196
+ break;
243
197
  }
244
198
 
245
- // Determine whether the next list item belongs here.
246
- // Backpedal if it does not belong in this list.
247
- if (i !== l - 1) {
248
- bnext = this.rules.block.listItemStart.exec(itemMatch[i + 1]);
249
- if (
250
- !this.options.pedantic
251
- ? bnext[1].length >= bcurr[0].length || bnext[1].length > 3
252
- : bnext[1].length > bcurr[1].length
253
- ) {
254
- // nested list or continuation
255
- itemMatch.splice(i, 2, itemMatch[i] + (!this.options.pedantic && bnext[1].length < bcurr[0].length && !itemMatch[i].match(/\n$/) ? '' : '\n') + itemMatch[i + 1]);
256
- i--;
257
- l--;
258
- continue;
259
- } else if (
260
- // different bullet style
261
- !this.options.pedantic || this.options.smartLists
262
- ? bnext[2][bnext[2].length - 1] !== bull[bull.length - 1]
263
- : isordered === (bnext[2].length === 1)
264
- ) {
265
- addBack = itemMatch.slice(i + 1).join('\n').length;
266
- list.raw = list.raw.substring(0, list.raw.length - addBack);
267
- i = l - 1;
268
- }
269
- bcurr = bnext;
199
+ if (!(cap = itemRegex.exec(src))) {
200
+ break;
270
201
  }
271
202
 
272
- // Remove the list item's bullet
273
- // so it is seen as the next token.
274
- space = item.length;
275
- item = item.replace(/^ *([*+-]|\d+[.)]) ?/, '');
276
-
277
- // Outdent whatever the
278
- // list item contains. Hacky.
279
- if (~item.indexOf('\n ')) {
280
- space -= item.length;
281
- item = !this.options.pedantic
282
- ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
283
- : item.replace(/^ {1,4}/gm, '');
203
+ lines = cap[2].split('\n');
204
+
205
+ if (this.options.pedantic) {
206
+ indent = 2;
207
+ itemContents = lines[0].trimLeft();
208
+ } else {
209
+ indent = cap[2].search(/[^ ]/); // Find first non-space char
210
+ indent = cap[1].length + (indent > 4 ? 1 : indent); // intented code blocks after 4 spaces; indent is always 1
211
+ itemContents = lines[0].slice(indent - cap[1].length);
284
212
  }
285
213
 
286
- // trim item newlines at end
287
- item = rtrim(item, '\n');
288
- if (i !== l - 1) {
289
- raw = raw + '\n';
214
+ blankLine = false;
215
+ raw = cap[0];
216
+
217
+ if (!lines[0] && /^ *$/.test(lines[1])) { // items begin with at most one blank line
218
+ raw = cap[1] + lines.slice(0, 2).join('\n') + '\n';
219
+ list.loose = true;
220
+ lines = [];
290
221
  }
291
222
 
292
- // Determine whether item is loose or not.
293
- // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
294
- // for discount behavior.
295
- loose = next || /\n\n(?!\s*$)/.test(raw);
296
- if (i !== l - 1) {
297
- next = raw.slice(-2) === '\n\n';
298
- if (!loose) loose = next;
223
+ const nextBulletRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])`);
224
+
225
+ for (i = 1; i < lines.length; i++) {
226
+ line = lines[i];
227
+
228
+ if (this.options.pedantic) { // Re-align to follow commonmark nesting rules
229
+ line = line.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' ');
230
+ }
231
+
232
+ // End list item if found start of new bullet
233
+ if (nextBulletRegex.test(line)) {
234
+ raw = cap[1] + lines.slice(0, i).join('\n') + '\n';
235
+ break;
236
+ }
237
+
238
+ // Until we encounter a blank line, item contents do not need indentation
239
+ if (!blankLine) {
240
+ if (!line.trim()) { // Check if current line is empty
241
+ blankLine = true;
242
+ }
243
+
244
+ // Dedent if possible
245
+ if (line.search(/[^ ]/) >= indent) {
246
+ itemContents += '\n' + line.slice(indent);
247
+ } else {
248
+ itemContents += '\n' + line;
249
+ }
250
+ continue;
251
+ }
252
+
253
+ // Dedent this line
254
+ if (line.search(/[^ ]/) >= indent || !line.trim()) {
255
+ itemContents += '\n' + line.slice(indent);
256
+ continue;
257
+ } else { // Line was not properly indented; end of this item
258
+ raw = cap[1] + lines.slice(0, i).join('\n') + '\n';
259
+ break;
260
+ }
299
261
  }
300
262
 
301
- if (loose) {
302
- list.loose = true;
263
+ if (!list.loose) {
264
+ // If the previous item ended with a blank line, the list is loose
265
+ if (endsWithBlankLine) {
266
+ list.loose = true;
267
+ } else if (/\n *\n *$/.test(raw)) {
268
+ endsWithBlankLine = true;
269
+ }
303
270
  }
304
271
 
305
272
  // Check for task list items
306
273
  if (this.options.gfm) {
307
- istask = /^\[[ xX]\] /.test(item);
308
- ischecked = undefined;
274
+ istask = /^\[[ xX]\] /.exec(itemContents);
309
275
  if (istask) {
310
- ischecked = item[1] !== ' ';
311
- item = item.replace(/^\[[ xX]\] +/, '');
276
+ ischecked = istask[0] !== '[ ] ';
277
+ itemContents = itemContents.replace(/^\[[ xX]\] +/, '');
312
278
  }
313
279
  }
314
280
 
315
281
  list.items.push({
316
282
  type: 'list_item',
317
- raw,
318
- task: istask,
283
+ raw: raw,
284
+ task: !!istask,
319
285
  checked: ischecked,
320
- loose: loose,
321
- text: item
286
+ loose: false,
287
+ text: itemContents
322
288
  });
289
+
290
+ list.raw += raw;
291
+ src = src.slice(raw.length);
292
+ }
293
+
294
+ // Do not consume newlines at end of final item. Alternatively, make itemRegex *start* with any newlines to simplify/speed up endsWithBlankLine logic
295
+ list.items[list.items.length - 1].raw = raw.trimRight();
296
+ list.items[list.items.length - 1].text = itemContents.trimRight();
297
+ list.raw = list.raw.trimRight();
298
+
299
+ const l = list.items.length;
300
+
301
+ // Item child tokens handled here at end because we needed to have the final item to trim it first
302
+ for (i = 0; i < l; i++) {
303
+ this.lexer.state.top = false;
304
+ list.items[i].tokens = this.lexer.blockTokens(list.items[i].text, []);
305
+ if (list.items[i].tokens.some(t => t.type === 'space')) {
306
+ list.loose = true;
307
+ list.items[i].loose = true;
308
+ }
323
309
  }
324
310
 
325
311
  return list;
@@ -329,15 +315,20 @@ module.exports = class Tokenizer {
329
315
  html(src) {
330
316
  const cap = this.rules.block.html.exec(src);
331
317
  if (cap) {
332
- return {
333
- type: this.options.sanitize
334
- ? 'paragraph'
335
- : 'html',
318
+ const token = {
319
+ type: 'html',
336
320
  raw: cap[0],
337
321
  pre: !this.options.sanitizer
338
322
  && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
339
- text: this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0]
323
+ text: cap[0]
340
324
  };
325
+ if (this.options.sanitize) {
326
+ token.type = 'paragraph';
327
+ token.text = this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0]);
328
+ token.tokens = [];
329
+ this.lexer.inline(token.text, token.tokens);
330
+ }
331
+ return token;
341
332
  }
342
333
  }
343
334
 
@@ -361,16 +352,16 @@ module.exports = class Tokenizer {
361
352
  if (cap) {
362
353
  const item = {
363
354
  type: 'table',
364
- header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
355
+ header: splitCells(cap[1]).map(c => { return { text: c }; }),
365
356
  align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
366
- cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : []
357
+ rows: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : []
367
358
  };
368
359
 
369
360
  if (item.header.length === item.align.length) {
370
361
  item.raw = cap[0];
371
362
 
372
363
  let l = item.align.length;
373
- let i;
364
+ let i, j, k, row;
374
365
  for (i = 0; i < l; i++) {
375
366
  if (/^ *-+: *$/.test(item.align[i])) {
376
367
  item.align[i] = 'right';
@@ -383,11 +374,28 @@ module.exports = class Tokenizer {
383
374
  }
384
375
  }
385
376
 
386
- l = item.cells.length;
377
+ l = item.rows.length;
387
378
  for (i = 0; i < l; i++) {
388
- item.cells[i] = splitCells(
389
- item.cells[i].replace(/^ *\| *| *\| *$/g, ''),
390
- item.header.length);
379
+ item.rows[i] = splitCells(item.rows[i], item.header.length).map(c => { return { text: c }; });
380
+ }
381
+
382
+ // parse child tokens inside headers and cells
383
+
384
+ // header child tokens
385
+ l = item.header.length;
386
+ for (j = 0; j < l; j++) {
387
+ item.header[j].tokens = [];
388
+ this.lexer.inlineTokens(item.header[j].text, item.header[j].tokens);
389
+ }
390
+
391
+ // cell child tokens
392
+ l = item.rows.length;
393
+ for (j = 0; j < l; j++) {
394
+ row = item.rows[j];
395
+ for (k = 0; k < row.length; k++) {
396
+ row[k].tokens = [];
397
+ this.lexer.inlineTokens(row[k].text, row[k].tokens);
398
+ }
391
399
  }
392
400
 
393
401
  return item;
@@ -398,36 +406,45 @@ module.exports = class Tokenizer {
398
406
  lheading(src) {
399
407
  const cap = this.rules.block.lheading.exec(src);
400
408
  if (cap) {
401
- return {
409
+ const token = {
402
410
  type: 'heading',
403
411
  raw: cap[0],
404
412
  depth: cap[2].charAt(0) === '=' ? 1 : 2,
405
- text: cap[1]
413
+ text: cap[1],
414
+ tokens: []
406
415
  };
416
+ this.lexer.inline(token.text, token.tokens);
417
+ return token;
407
418
  }
408
419
  }
409
420
 
410
421
  paragraph(src) {
411
422
  const cap = this.rules.block.paragraph.exec(src);
412
423
  if (cap) {
413
- return {
424
+ const token = {
414
425
  type: 'paragraph',
415
426
  raw: cap[0],
416
427
  text: cap[1].charAt(cap[1].length - 1) === '\n'
417
428
  ? cap[1].slice(0, -1)
418
- : cap[1]
429
+ : cap[1],
430
+ tokens: []
419
431
  };
432
+ this.lexer.inline(token.text, token.tokens);
433
+ return token;
420
434
  }
421
435
  }
422
436
 
423
437
  text(src) {
424
438
  const cap = this.rules.block.text.exec(src);
425
439
  if (cap) {
426
- return {
440
+ const token = {
427
441
  type: 'text',
428
442
  raw: cap[0],
429
- text: cap[0]
443
+ text: cap[0],
444
+ tokens: []
430
445
  };
446
+ this.lexer.inline(token.text, token.tokens);
447
+ return token;
431
448
  }
432
449
  }
433
450
 
@@ -442,18 +459,18 @@ module.exports = class Tokenizer {
442
459
  }
443
460
  }
444
461
 
445
- tag(src, inLink, inRawBlock) {
462
+ tag(src) {
446
463
  const cap = this.rules.inline.tag.exec(src);
447
464
  if (cap) {
448
- if (!inLink && /^<a /i.test(cap[0])) {
449
- inLink = true;
450
- } else if (inLink && /^<\/a>/i.test(cap[0])) {
451
- inLink = false;
465
+ if (!this.lexer.state.inLink && /^<a /i.test(cap[0])) {
466
+ this.lexer.state.inLink = true;
467
+ } else if (this.lexer.state.inLink && /^<\/a>/i.test(cap[0])) {
468
+ this.lexer.state.inLink = false;
452
469
  }
453
- if (!inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
454
- inRawBlock = true;
455
- } else if (inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
456
- inRawBlock = false;
470
+ if (!this.lexer.state.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
471
+ this.lexer.state.inRawBlock = true;
472
+ } else if (this.lexer.state.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
473
+ this.lexer.state.inRawBlock = false;
457
474
  }
458
475
 
459
476
  return {
@@ -461,8 +478,8 @@ module.exports = class Tokenizer {
461
478
  ? 'text'
462
479
  : 'html',
463
480
  raw: cap[0],
464
- inLink,
465
- inRawBlock,
481
+ inLink: this.lexer.state.inLink,
482
+ inRawBlock: this.lexer.state.inRawBlock,
466
483
  text: this.options.sanitize
467
484
  ? (this.options.sanitizer
468
485
  ? this.options.sanitizer(cap[0])
@@ -524,7 +541,7 @@ module.exports = class Tokenizer {
524
541
  return outputLink(cap, {
525
542
  href: href ? href.replace(this.rules.inline._escapes, '$1') : href,
526
543
  title: title ? title.replace(this.rules.inline._escapes, '$1') : title
527
- }, cap[0]);
544
+ }, cap[0], this.lexer);
528
545
  }
529
546
  }
530
547
 
@@ -542,7 +559,7 @@ module.exports = class Tokenizer {
542
559
  text
543
560
  };
544
561
  }
545
- return outputLink(cap, link, cap[0]);
562
+ return outputLink(cap, link, cap[0], this.lexer);
546
563
  }
547
564
  }
548
565
 
@@ -591,18 +608,22 @@ module.exports = class Tokenizer {
591
608
 
592
609
  // Create `em` if smallest delimiter has odd char count. *a***
593
610
  if (Math.min(lLength, rLength) % 2) {
611
+ const text = src.slice(1, lLength + match.index + rLength);
594
612
  return {
595
613
  type: 'em',
596
614
  raw: src.slice(0, lLength + match.index + rLength + 1),
597
- text: src.slice(1, lLength + match.index + rLength)
615
+ text,
616
+ tokens: this.lexer.inlineTokens(text, [])
598
617
  };
599
618
  }
600
619
 
601
620
  // Create 'strong' if smallest delimiter has even char count. **a***
621
+ const text = src.slice(2, lLength + match.index + rLength - 1);
602
622
  return {
603
623
  type: 'strong',
604
624
  raw: src.slice(0, lLength + match.index + rLength + 1),
605
- text: src.slice(2, lLength + match.index + rLength - 1)
625
+ text,
626
+ tokens: this.lexer.inlineTokens(text, [])
606
627
  };
607
628
  }
608
629
  }
@@ -642,7 +663,8 @@ module.exports = class Tokenizer {
642
663
  return {
643
664
  type: 'del',
644
665
  raw: cap[0],
645
- text: cap[2]
666
+ text: cap[2],
667
+ tokens: this.lexer.inlineTokens(cap[2], [])
646
668
  };
647
669
  }
648
670
  }
@@ -712,11 +734,11 @@ module.exports = class Tokenizer {
712
734
  }
713
735
  }
714
736
 
715
- inlineText(src, inRawBlock, smartypants) {
737
+ inlineText(src, smartypants) {
716
738
  const cap = this.rules.inline.text.exec(src);
717
739
  if (cap) {
718
740
  let text;
719
- if (inRawBlock) {
741
+ if (this.lexer.state.inRawBlock) {
720
742
  text = this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0];
721
743
  } else {
722
744
  text = escape(this.options.smartypants ? smartypants(cap[0]) : cap[0]);
package/src/helpers.js CHANGED
@@ -160,6 +160,10 @@ function splitCells(tableRow, count) {
160
160
  cells = row.split(/ \|/);
161
161
  let i = 0;
162
162
 
163
+ // First/last cell in a row cannot be empty if it has no leading/trailing pipe
164
+ if (!cells[0].trim()) { cells.shift(); }
165
+ if (!cells[cells.length - 1].trim()) { cells.pop(); }
166
+
163
167
  if (cells.length > count) {
164
168
  cells.splice(count);
165
169
  } else {
package/src/marked.js CHANGED
@@ -155,7 +155,7 @@ marked.use = function(...args) {
155
155
  throw new Error('extension name required');
156
156
  }
157
157
  if (ext.renderer) { // Renderer extensions
158
- const prevRenderer = extensions.renderers?.[ext.name];
158
+ const prevRenderer = extensions.renderers ? extensions.renderers[ext.name] : null;
159
159
  if (prevRenderer) {
160
160
  // Replace extension with func to run new extension but fall back if false
161
161
  extensions.renderers[ext.name] = function(...args) {
@@ -260,12 +260,12 @@ marked.walkTokens = function(tokens, callback) {
260
260
  callback(token);
261
261
  switch (token.type) {
262
262
  case 'table': {
263
- for (const cell of token.tokens.header) {
264
- marked.walkTokens(cell, callback);
263
+ for (const cell of token.header) {
264
+ marked.walkTokens(cell.tokens, callback);
265
265
  }
266
- for (const row of token.tokens.cells) {
266
+ for (const row of token.rows) {
267
267
  for (const cell of row) {
268
- marked.walkTokens(cell, callback);
268
+ marked.walkTokens(cell.tokens, callback);
269
269
  }
270
270
  }
271
271
  break;
@@ -275,12 +275,11 @@ marked.walkTokens = function(tokens, callback) {
275
275
  break;
276
276
  }
277
277
  default: {
278
- if (marked.defaults?.extensions?.childTokens?.[token.type]) { // Walk any extensions
279
- marked.defaults?.extensions.childTokens[token.type].forEach(function(childTokens) {
278
+ if (marked.defaults.extensions && marked.defaults.extensions.childTokens && marked.defaults.extensions.childTokens[token.type]) { // Walk any extensions
279
+ marked.defaults.extensions.childTokens[token.type].forEach(function(childTokens) {
280
280
  marked.walkTokens(token[childTokens], callback);
281
281
  });
282
- }
283
- if (token.tokens && !marked.defaults?.extensions?.childTokens[token.type]) {
282
+ } else if (token.tokens) {
284
283
  marked.walkTokens(token.tokens, callback);
285
284
  }
286
285
  }