@peaceroad/markdown-it-table-ex 0.2.0 → 0.3.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/index.js CHANGED
@@ -1,4 +1,60 @@
1
- const checkedTdReg = /^\*\*[\s\S]*?\*\*$/;
1
+ const getLeadingStrongCloseIndex = (inline) => {
2
+ if (!inline || !Array.isArray(inline.children) || inline.children.length < 3) {
3
+ return -1;
4
+ }
5
+ const children = inline.children;
6
+ let start = 0;
7
+ while (start < children.length &&
8
+ children[start].type === 'text' &&
9
+ children[start].content === '') {
10
+ start++;
11
+ }
12
+ if (start >= children.length || children[start].type !== 'strong_open') {
13
+ return -1;
14
+ }
15
+ let depth = 0;
16
+ for (let i = start; i < children.length; i++) {
17
+ const type = children[i].type;
18
+ if (type === 'strong_open') {
19
+ depth++;
20
+ } else if (type === 'strong_close') {
21
+ depth--;
22
+ if (depth === 0) {
23
+ return i;
24
+ }
25
+ if (depth < 0) {
26
+ return -1;
27
+ }
28
+ }
29
+ }
30
+ return -1;
31
+ };
32
+
33
+ const isStrongWrappedInline = (inline, allowFallback) => {
34
+ if (!inline || typeof inline.content !== 'string') return false;
35
+ const content = inline.content;
36
+ if (!content.startsWith('**') || !content.endsWith('**')) return false;
37
+ if (allowFallback) return true;
38
+ if (!Array.isArray(inline.children)) return false;
39
+ return getLeadingStrongCloseIndex(inline) !== -1;
40
+ };
41
+
42
+ const hasLeadingStrongMarker = (inline, allowFallback) => {
43
+ if (!inline || typeof inline.content !== 'string') return false;
44
+ if (!inline.content.startsWith('**')) return false;
45
+ if (allowFallback) return true;
46
+ if (!Array.isArray(inline.children)) return false;
47
+ return getLeadingStrongCloseIndex(inline) !== -1;
48
+ };
49
+
50
+ const hasInlineRule = (md, name) => {
51
+ const rules = md && md.inline && md.inline.ruler && md.inline.ruler.__rules__;
52
+ if (!Array.isArray(rules)) return false;
53
+ for (let i = 0; i < rules.length; i++) {
54
+ if (rules[i].name === name) return true;
55
+ }
56
+ return false;
57
+ };
2
58
 
3
59
  const createTokenTemplate = (tokenType, tag, nesting, level) => ({
4
60
  type: tokenType,
@@ -17,117 +73,123 @@ const createTokenTemplate = (tokenType, tag, nesting, level) => ({
17
73
  });
18
74
 
19
75
  // Helper function to remove strong tags that wrap the entire content
20
- const removeStrongWrappers = (inline) => {
76
+ const removeStrongWrappers = (inline, allowFallback) => {
21
77
  if (!Array.isArray(inline.children) || inline.children.length === 0) {
22
78
  return;
23
79
  }
24
80
 
25
81
  const children = inline.children;
26
- const content = inline.content;
27
-
28
- // Quick check to avoid regex if obviously not matching
29
- if (!content.startsWith('**') || !content.endsWith('**')) {
82
+ if (!isStrongWrappedInline(inline, allowFallback)) {
30
83
  return;
31
84
  }
32
-
33
- // Check if the content matches the pattern **...** where entire content is wrapped
34
- if (!checkedTdReg.test(content)) {
85
+
86
+ if (children.length === 3 &&
87
+ children[0].type === 'strong_open' &&
88
+ children[2].type === 'strong_close' &&
89
+ children[1].type === 'text') {
90
+ inline.children = [children[1]];
91
+ inline.content = children[1].content;
35
92
  return;
36
93
  }
37
94
 
38
- // Find all strong open/close positions in one pass
39
- const strongPositions = [];
95
+ // Find strong open/close positions in one pass
96
+ let openCount = 0;
97
+ let closeCount = 0;
98
+ let firstOpen = -1;
99
+ let firstClose = -1;
100
+ let firstPair = null;
101
+ let lastPair = null;
102
+ const stack = [];
103
+
40
104
  for (let i = 0; i < children.length; i++) {
41
105
  const type = children[i].type;
42
106
  if (type === 'strong_open') {
43
- strongPositions.push({ type: 'open', index: i });
107
+ if (firstOpen === -1) {
108
+ firstOpen = i;
109
+ }
110
+ openCount++;
111
+ stack.push(i);
44
112
  } else if (type === 'strong_close') {
45
- strongPositions.push({ type: 'close', index: i });
113
+ if (firstClose === -1) {
114
+ firstClose = i;
115
+ }
116
+ closeCount++;
117
+ if (stack.length > 0) {
118
+ const openIdx = stack.pop();
119
+ if (!firstPair) {
120
+ firstPair = { open: openIdx, close: i };
121
+ }
122
+ lastPair = { open: openIdx, close: i };
123
+ }
46
124
  }
47
125
  }
48
126
 
49
- const openCount = strongPositions.filter(p => p.type === 'open').length;
50
- const closeCount = strongPositions.filter(p => p.type === 'close').length;
51
-
52
127
  if (openCount !== closeCount || openCount === 0) {
53
128
  return;
54
129
  }
55
130
 
56
- if (openCount >= 2) {
131
+ if (openCount >= 2 && firstPair && lastPair) {
57
132
  // Multiple strong pairs case - find pairs by stack matching
58
- const pairs = [];
59
- const stack = [];
133
+ const newChildren = [];
134
+ const contentParts = [];
60
135
 
61
- for (const pos of strongPositions) {
62
- if (pos.type === 'open') {
63
- stack.push(pos.index);
64
- } else if (stack.length > 0) {
65
- const openIdx = stack.pop();
66
- pairs.push({ open: openIdx, close: pos.index });
136
+ // Helper function to add children and build content
137
+ const addChildrenRange = (start, end) => {
138
+ for (let i = start; i < end; i++) {
139
+ const child = children[i];
140
+ newChildren.push(child);
141
+ if (child.type === 'text') {
142
+ contentParts.push(child.content);
143
+ } else if (child.type === 'em_open' || child.type === 'em_close') {
144
+ contentParts.push('*');
145
+ }
67
146
  }
68
- }
147
+ };
69
148
 
70
- if (pairs.length >= 2) {
71
- const firstPair = pairs[0];
72
- const lastPair = pairs[pairs.length - 1];
73
-
74
- const newChildren = [];
75
- const contentParts = [];
76
-
77
- // Helper function to add children and build content
78
- const addChildrenRange = (start, end) => {
79
- for (let i = start; i < end; i++) {
80
- newChildren.push(children[i]);
81
- if (children[i].type === 'text') {
82
- contentParts.push(children[i].content);
83
- } else if (children[i].type === 'em_open' || children[i].type === 'em_close') {
84
- contentParts.push('*');
85
- }
86
- }
87
- };
88
-
89
- // Add content before first pair
90
- addChildrenRange(0, firstPair.open);
91
-
92
- // Add content from first pair (without the strong tags)
93
- addChildrenRange(firstPair.open + 1, firstPair.close);
94
-
95
- // Add content between first and last pairs with strong wrapping
96
- for (let i = firstPair.close + 1; i < lastPair.open; i++) {
97
- if (children[i].type === 'text' && children[i].content.trim() !== '') {
98
- // Create strong tokens more efficiently
99
- const strongOpen = Object.create(children[i].constructor.prototype);
100
- Object.assign(strongOpen, createTokenTemplate('strong_open', 'strong', 1, children[i].level));
101
-
102
- const strongClose = Object.create(children[i].constructor.prototype);
103
- Object.assign(strongClose, createTokenTemplate('strong_close', 'strong', -1, children[i].level));
104
-
105
- newChildren.push(strongOpen, children[i], strongClose);
106
- contentParts.push('**', children[i].content, '**');
107
- } else {
108
- newChildren.push(children[i]);
109
- if (children[i].type === 'text') {
110
- contentParts.push(children[i].content);
111
- } else if (children[i].type === 'em_open' || children[i].type === 'em_close') {
112
- contentParts.push('*');
113
- }
149
+ // Add content before first pair
150
+ addChildrenRange(0, firstPair.open);
151
+
152
+ // Add content from first pair (without the strong tags)
153
+ addChildrenRange(firstPair.open + 1, firstPair.close);
154
+
155
+ // Add content between first and last pairs with strong wrapping
156
+ for (let i = firstPair.close + 1; i < lastPair.open; i++) {
157
+ const child = children[i];
158
+ if (child.type === 'text' && child.content.trim() !== '') {
159
+ // Create strong tokens more efficiently
160
+ const strongOpen = Object.create(child.constructor.prototype);
161
+ Object.assign(strongOpen, createTokenTemplate('strong_open', 'strong', 1, child.level));
162
+
163
+ const strongClose = Object.create(child.constructor.prototype);
164
+ Object.assign(strongClose, createTokenTemplate('strong_close', 'strong', -1, child.level));
165
+
166
+ newChildren.push(strongOpen, child, strongClose);
167
+ contentParts.push('**', child.content, '**');
168
+ } else {
169
+ newChildren.push(child);
170
+ if (child.type === 'text') {
171
+ contentParts.push(child.content);
172
+ } else if (child.type === 'em_open' || child.type === 'em_close') {
173
+ contentParts.push('*');
114
174
  }
115
175
  }
116
-
117
- // Add content from last pair (without the strong tags)
118
- addChildrenRange(lastPair.open + 1, lastPair.close);
119
-
120
- // Add content after last pair
121
- addChildrenRange(lastPair.close + 1, children.length);
122
-
123
- inline.content = contentParts.join('');
124
- inline.children = newChildren;
125
176
  }
126
177
 
178
+ // Add content from last pair (without the strong tags)
179
+ addChildrenRange(lastPair.open + 1, lastPair.close);
180
+
181
+ // Add content after last pair
182
+ addChildrenRange(lastPair.close + 1, children.length);
183
+
184
+ inline.content = contentParts.join('');
185
+ inline.children = newChildren;
127
186
  } else if (openCount === 1) {
128
187
  // Single strong pair: remove completely
129
- const strongOpen = strongPositions.find(p => p.type === 'open').index;
130
- const strongClose = strongPositions.find(p => p.type === 'close').index;
188
+ const strongOpen = firstOpen;
189
+ const strongClose = firstClose;
190
+ if (strongOpen < 0 || strongClose < 0) {
191
+ return;
192
+ }
131
193
 
132
194
  const newChildren = [];
133
195
  const contentParts = [];
@@ -149,74 +211,74 @@ const removeStrongWrappers = (inline) => {
149
211
  }
150
212
  }
151
213
 
152
- const addTheadThScope = (state, theadVar) => {
153
- let isEmpty = false;
214
+ const addTheadThScope = (state, theadVar, allowFallback) => {
215
+ const tokens = state.tokens;
154
216
  let firstThPos = theadVar.pos;
155
217
  let j = theadVar.i + 2;
156
218
  //if (state.tokens[theadVar.i + 1].type !== 'tr_open') return threadVar
157
- while (j < state.tokens.length) {
158
- if (state.tokens[j].type === 'th_open') {
159
- state.tokens[j].attrPush(['scope', 'col']);
219
+ while (j < tokens.length) {
220
+ if (tokens[j].type === 'th_open') {
221
+ tokens[j].attrPush(['scope', 'col']);
160
222
  if (j === theadVar.i + 2) {
161
- isEmpty = state.tokens[j+1].content === ''
162
- let isStrong = checkedTdReg.test(state.tokens[j+1].content)
163
- if (isStrong) {
223
+ const inline = tokens[j + 1];
224
+ const content = inline.content;
225
+ if (content === '' || isStrongWrappedInline(inline, allowFallback)) {
164
226
  firstThPos = j
165
227
  // Only remove strong tags from the first th content for matrix processing
166
228
  // The actual removal will be done in changeTdToTh if matrix conditions are met
167
- } else if (isEmpty) {
168
- firstThPos = j
169
229
  }
170
230
  }
171
231
  }
172
- if (state.tokens[j].type === 'tr_close') break
232
+ if (tokens[j].type === 'tr_close') break
173
233
  j++
174
234
  }
175
- return {i: j, firstThPos: firstThPos, isEmpty: isEmpty}
235
+ return {i: j, firstThPos: firstThPos}
176
236
  }
177
237
 
178
- const changeTdToTh = (state, tdPoses, hasThead) => {
238
+ const changeTdToTh = (state, tdPoses, hasThead, allowFallback) => {
179
239
  //console.log('hasThead: ' + hasThead + ', tdPoses: ' + tdPoses)
240
+ const tokens = state.tokens;
180
241
  let j = 0
181
242
  while(j < tdPoses.length) {
182
243
  //console.log('state.tokens[' + j + '].type: ' + state.tokens[tdPoses[j]].type)
183
244
  const pos = tdPoses[j]
184
245
  if (j > 0 || (!hasThead && j === 0)) {
185
- state.tokens[pos].type = 'th_open';
186
- state.tokens[pos].tag = 'th';
187
- state.tokens[pos].attrPush(['scope', 'row']);
188
- state.tokens[pos + 2].type = 'th_close';
189
- state.tokens[pos + 2].tag = 'th';
246
+ tokens[pos].type = 'th_open';
247
+ tokens[pos].tag = 'th';
248
+ tokens[pos].attrPush(['scope', 'row']);
249
+ tokens[pos + 2].type = 'th_close';
250
+ tokens[pos + 2].tag = 'th';
190
251
  }
191
252
 
192
253
  // Remove strong tags that wrap the entire content
193
- const inline = state.tokens[pos + 1];
194
- removeStrongWrappers(inline);
254
+ const inline = tokens[pos + 1];
255
+ removeStrongWrappers(inline, allowFallback);
195
256
  j++
196
257
  }
197
258
  }
198
259
 
199
- const checkTbody = (state, tbodyVar) => {
260
+ const checkTbody = (state, tbodyVar, allowFallback) => {
261
+ const tokens = state.tokens;
200
262
  let isAllFirstTh = true
201
263
  let tbodyFirstThPoses = []
202
264
  let j = tbodyVar.i + 1
203
- while (j < state.tokens.length) {
204
- if (state.tokens[j].type === 'tr_open') {
265
+ while (j < tokens.length) {
266
+ if (tokens[j].type === 'tr_open') {
205
267
  j++
206
- if (state.tokens[j].type === 'td_open' && state.tokens[j + 1].content.match(checkedTdReg)) {
268
+ if (tokens[j].type === 'td_open' && isStrongWrappedInline(tokens[j + 1], allowFallback)) {
207
269
  tbodyFirstThPoses.push(j)
208
270
  } else {
209
271
  isAllFirstTh = false
210
272
  break
211
273
  }
212
274
  }
213
- if (state.tokens[j].type === 'tbody_close') break
275
+ if (tokens[j].type === 'tbody_close') break
214
276
  j++
215
277
  }
216
278
  return { i: j, isAllFirstTh: isAllFirstTh, tbodyFirstThPoses: tbodyFirstThPoses}
217
279
  }
218
280
 
219
- const setColgroup = (state, tableOpenIdx, opt) => {
281
+ const setColgroup = (state, tableOpenIdx, opt, allowFallback) => {
220
282
  const tokens = state.tokens;
221
283
  const Token = state.Token;
222
284
 
@@ -244,23 +306,42 @@ const setColgroup = (state, tableOpenIdx, opt) => {
244
306
  // Calculate group names and colspan for the first row of th
245
307
  const groupData = {
246
308
  spans: [],
247
- names: [],
248
309
  rawNames: [],
249
310
  headerCount: 0
250
311
  };
251
312
 
313
+ const colgroupMatchReg = opt.colgroupWithNoAsterisk
314
+ ? /^([^::]+)(?::|:)\s*/
315
+ : /^\*\*([^*::]+)[::]\*\*\s*/;
316
+ const origColgroupMatchReg = opt.colgroupWithNoAsterisk
317
+ ? /^[^::]+(?::|:)\s*(.*)$/
318
+ : /^\*\*[^*::]+[::]\*\*\s*(.*)$/;
319
+
252
320
  let thIdx = tr1 + 1;
253
- const thTokens = [];
321
+ const origThInfos = [];
322
+ let trCloseIdx = -1;
254
323
 
255
- while (thIdx < tokens.length && tokens[thIdx].type !== 'tr_close') {
256
- if (tokens[thIdx].type === 'th_open') {
257
- thTokens.push(thIdx);
324
+ while (thIdx < tokens.length) {
325
+ const tokenType = tokens[thIdx].type;
326
+ if (tokenType === 'th_open') {
258
327
  const inline = tokens[thIdx + 1];
328
+ let map = null;
329
+ if (Array.isArray(tokens[thIdx].map)) {
330
+ map = tokens[thIdx].map.slice();
331
+ } else if (inline && Array.isArray(inline.map)) {
332
+ map = inline.map.slice();
333
+ }
334
+ origThInfos.push({ inline, map });
259
335
  const content = inline.content;
260
- const colgroupMatchReg = opt.colgroupWithNoAsterisk
261
- ? /^([^::]+)(?::|:)\s*/
262
- : /^\*\*([^*::]+)[::]\*\*\s*/;
263
- const match = content.match(colgroupMatchReg);
336
+ const hasLeadingStrong = hasLeadingStrongMarker(inline, allowFallback);
337
+ let match = null;
338
+ if (opt.colgroupWithNoAsterisk) {
339
+ if (content.indexOf(':') !== -1 || content.indexOf(':') !== -1) {
340
+ match = content.match(colgroupMatchReg);
341
+ }
342
+ } else if (hasLeadingStrong) {
343
+ match = content.match(colgroupMatchReg);
344
+ }
264
345
 
265
346
  if (match) {
266
347
  const group = match[1].trim();
@@ -277,6 +358,9 @@ const setColgroup = (state, tableOpenIdx, opt) => {
277
358
  groupData.rawNames.push(null);
278
359
  groupData.spans.push(1);
279
360
  }
361
+ } else if (tokenType === 'tr_close') {
362
+ trCloseIdx = thIdx;
363
+ break;
280
364
  }
281
365
  thIdx++;
282
366
  }
@@ -284,15 +368,7 @@ const setColgroup = (state, tableOpenIdx, opt) => {
284
368
  // If there are not two or more grouped header cells, do not auto-generate
285
369
  if (groupData.headerCount < 2) return;
286
370
 
287
- // Determine group names
288
- const nonNullGroups = groupData.rawNames.filter(name => name !== null);
289
- const allSame = nonNullGroups.length > 0 && nonNullGroups.every(name => name === nonNullGroups[0]);
290
-
291
- if (allSame) {
292
- groupData.names = [...groupData.rawNames];
293
- } else {
294
- groupData.names = [...groupData.rawNames];
295
- }
371
+ const groupNames = groupData.rawNames;
296
372
 
297
373
  // Generate colgroup if needed
298
374
  const hasSpan = groupData.spans.some(span => span > 1);
@@ -322,13 +398,12 @@ const setColgroup = (state, tableOpenIdx, opt) => {
322
398
 
323
399
  // Update indices
324
400
  const offset = insertTokens.length;
325
- theadOpen += offset;
326
401
  tr1 += offset;
327
402
  theadClose += offset;
403
+ trCloseIdx += offset;
328
404
  }
329
405
 
330
406
  // Generate two-row header
331
- const trCloseIdx = tokens.findIndex((t, idx) => idx > tr1 && t.type === 'tr_close');
332
407
 
333
408
  // Create new rows more efficiently
334
409
  const newTr1 = [
@@ -342,37 +417,25 @@ const setColgroup = (state, tableOpenIdx, opt) => {
342
417
  Object.assign(new Token('text', '', 0), { content: '\n' })
343
418
  ];
344
419
 
345
- // Enumerate th_open/inline between tr1 and trCloseIdx
346
- const origThs = [];
347
- for (let i = tr1 + 1; i < trCloseIdx; i++) {
348
- if (tokens[i].type === 'th_open') {
349
- origThs.push({
350
- th_open: tokens[i],
351
- inline: tokens[i + 1]
352
- });
353
- }
354
- }
355
-
356
420
  let thPtr = 0;
357
421
  for (let i = 0; i < groupData.spans.length; i++) {
358
- if (groupData.names[i] === null) {
422
+ const origInfo = origThInfos[thPtr];
423
+ const cellMap = origInfo && origInfo.map ? origInfo.map : null;
424
+ if (groupNames[i] === null) {
359
425
  // Leftmost cell: rowspan=2
360
426
  const thOpen = new Token('th_open', 'th', 1);
361
427
  thOpen.attrSet('rowspan', '2');
362
428
  thOpen.attrSet('scope', 'col');
429
+ if (cellMap) thOpen.map = cellMap;
363
430
 
364
431
  const thInline = new Token('inline', '', 0);
365
- const origInline = origThs[thPtr]?.inline;
432
+ const origInline = origInfo ? origInfo.inline : null;
366
433
 
367
434
  if (origInline) {
368
- // Create a copy for processing
369
- const tempInline = {
370
- content: origInline.content,
371
- children: origInline.children ? [...origInline.children] : []
372
- };
373
- removeStrongWrappers(tempInline);
374
- thInline.content = tempInline.content;
375
- thInline.children = tempInline.children;
435
+ removeStrongWrappers(origInline, allowFallback);
436
+ thInline.content = origInline.content;
437
+ thInline.children = Array.isArray(origInline.children) ? origInline.children : [];
438
+ if (cellMap) thInline.map = cellMap;
376
439
  } else {
377
440
  thInline.content = '';
378
441
  thInline.children = [];
@@ -392,10 +455,12 @@ const setColgroup = (state, tableOpenIdx, opt) => {
392
455
  thOpen.attrSet('colspan', groupData.spans[i].toString());
393
456
  }
394
457
  thOpen.attrSet('scope', 'col');
458
+ if (cellMap) thOpen.map = cellMap;
395
459
 
396
460
  const thInline = new Token('inline', '', 0);
397
- thInline.content = groupData.names[i];
398
- thInline.children = [{ type: 'text', content: groupData.names[i], level: 0 }];
461
+ thInline.content = groupNames[i];
462
+ thInline.children = [{ type: 'text', content: groupNames[i], level: 0 }];
463
+ if (cellMap) thInline.map = cellMap;
399
464
 
400
465
  newTr1.push(
401
466
  thOpen,
@@ -406,17 +471,26 @@ const setColgroup = (state, tableOpenIdx, opt) => {
406
471
 
407
472
  // Second row: each item in the group
408
473
  for (let j = 0; j < groupData.spans[i]; j++) {
474
+ const subInfo = origThInfos[thPtr];
475
+ const subMap = subInfo && subInfo.map ? subInfo.map : null;
409
476
  const th2Open = new Token('th_open', 'th', 1);
410
477
  th2Open.attrSet('scope', 'col');
478
+ if (subMap) th2Open.map = subMap;
411
479
 
412
480
  const th2Inline = new Token('inline', '', 0);
413
- const orig = origThs[thPtr]?.inline?.content || '';
414
- const origColgroupMatchReg = opt.colgroupWithNoAsterisk
415
- ? /^[^::]+(?::|:)\s*(.*)$/
416
- : /^\*\*[^*::]+[::]\*\*\s*(.*)$/;
417
- const match = orig.match(origColgroupMatchReg);
481
+ const origInline = subInfo ? subInfo.inline : null;
482
+ const orig = origInline?.content || '';
483
+ let match = null;
484
+ if (opt.colgroupWithNoAsterisk) {
485
+ if (orig.indexOf(':') !== -1 || orig.indexOf(':') !== -1) {
486
+ match = orig.match(origColgroupMatchReg);
487
+ }
488
+ } else if (hasLeadingStrongMarker(origInline, allowFallback)) {
489
+ match = orig.match(origColgroupMatchReg);
490
+ }
418
491
  th2Inline.content = match ? match[1] : orig;
419
492
  th2Inline.children = [{ type: 'text', content: th2Inline.content, level: 0 }];
493
+ if (subMap) th2Inline.map = subMap;
420
494
 
421
495
  newTr2.push(
422
496
  th2Open,
@@ -469,11 +543,12 @@ const setColgroup = (state, tableOpenIdx, opt) => {
469
543
  const tdIdx = j + 1;
470
544
  if (tokens[tdIdx].type === 'td_open') {
471
545
  const inline = tokens[tdIdx + 1];
472
- if (inline && typeof inline.content === 'string' && checkedTdReg.test(inline.content)) {
546
+ if (inline && typeof inline.content === 'string' &&
547
+ isStrongWrappedInline(inline, allowFallback)) {
473
548
  tokens[tdIdx].type = 'th_open';
474
549
  tokens[tdIdx].tag = 'th';
475
550
  tokens[tdIdx].attrSet('scope', 'row');
476
- removeStrongWrappers(inline);
551
+ removeStrongWrappers(inline, allowFallback);
477
552
  if (tokens[tdIdx + 2].type === 'td_close') {
478
553
  tokens[tdIdx + 2].type = 'th_close';
479
554
  tokens[tdIdx + 2].tag = 'th';
@@ -492,16 +567,20 @@ const setColgroup = (state, tableOpenIdx, opt) => {
492
567
  // Calculate group names and colspan for multi-row case
493
568
  const groupData = {
494
569
  spans: [],
495
- names: [],
496
570
  rawNames: []
497
571
  };
572
+ const groupMatchReg = /^\*\*([^*:]+):\*\*/;
498
573
 
499
574
  let thIdx = tr1 + 1;
500
575
  while (thIdx < tokens.length && tokens[thIdx].type !== 'tr_close') {
501
576
  if (tokens[thIdx].type === 'th_open') {
502
577
  const inline = tokens[thIdx + 1];
503
578
  const content = inline.content;
504
- const match = content.match(/^\*\*([^*:]+):\*\*/);
579
+ const hasLeadingStrong = hasLeadingStrongMarker(inline, allowFallback);
580
+ let match = null;
581
+ if (hasLeadingStrong) {
582
+ match = content.match(groupMatchReg);
583
+ }
505
584
 
506
585
  if (match) {
507
586
  const group = match[1].trim();
@@ -521,7 +600,8 @@ const setColgroup = (state, tableOpenIdx, opt) => {
521
600
  thIdx++;
522
601
  }
523
602
 
524
- groupData.names = [...groupData.rawNames];
603
+ const groupNames = groupData.rawNames;
604
+ const groupStripReg = /^\*\*[^*:]+:\*\*\s*(.*)$/;
525
605
 
526
606
  // Generate colgroup if needed
527
607
  const hasSpan = groupData.spans.some(span => span > 1);
@@ -544,11 +624,11 @@ const setColgroup = (state, tableOpenIdx, opt) => {
544
624
  let th1 = tr1 + 1, th2 = tr2 + 1, groupIdx = 0;
545
625
  while (th1 < tokens.length && tokens[th1].type !== 'tr_close') {
546
626
  if (tokens[th1].type === 'th_open') {
547
- if (groupData.names[groupIdx] === null) {
627
+ if (groupNames[groupIdx] === null) {
548
628
  // Leftmost cell: rowspan=2
549
629
  tokens[th1].attrSet('rowspan', '2');
550
630
  tokens[th1].attrSet('scope', 'col');
551
- removeStrongWrappers(tokens[th1 + 1]);
631
+ removeStrongWrappers(tokens[th1 + 1], allowFallback);
552
632
 
553
633
  // Find corresponding th in second row
554
634
  let t2 = th2;
@@ -567,7 +647,7 @@ const setColgroup = (state, tableOpenIdx, opt) => {
567
647
  tokens[th1].attrSet('colspan', groupData.spans[groupIdx].toString());
568
648
  }
569
649
  tokens[th1].attrSet('scope', 'col');
570
- tokens[th1 + 1].content = groupData.names[groupIdx];
650
+ tokens[th1 + 1].content = groupNames[groupIdx];
571
651
 
572
652
  // Process corresponding ths in second row
573
653
  let count = 0, t2 = th2;
@@ -575,8 +655,12 @@ const setColgroup = (state, tableOpenIdx, opt) => {
575
655
  if (tokens[t2].type === 'th_open') {
576
656
  tokens[t2].attrSet('scope', 'col');
577
657
  // Remove group name part from content
578
- const t2content = tokens[t2 + 1].content;
579
- const t2match = t2content.match(/^\*\*[^*:]+:\*\*\s*(.*)$/);
658
+ const t2Inline = tokens[t2 + 1];
659
+ const t2content = t2Inline.content;
660
+ let t2match = null;
661
+ if (hasLeadingStrongMarker(t2Inline, allowFallback)) {
662
+ t2match = t2content.match(groupStripReg);
663
+ }
580
664
  if (t2match) {
581
665
  tokens[t2 + 1].content = t2match[1];
582
666
  }
@@ -594,7 +678,8 @@ const setColgroup = (state, tableOpenIdx, opt) => {
594
678
 
595
679
  const tableEx = (state, opt) => {
596
680
  const tokens = state.tokens;
597
- const tokenLength = tokens.length;
681
+ let tokenLength = tokens.length;
682
+ const allowStrongFallback = !hasInlineRule(state.md, 'strong_ja');
598
683
 
599
684
  let idx = 0;
600
685
  while (idx < tokenLength) {
@@ -608,57 +693,63 @@ const tableEx = (state, opt) => {
608
693
  if (opt.wrapper) {
609
694
  const wrapperStartToken = new state.Token('div_open', 'div', 1);
610
695
  wrapperStartToken.attrPush(['class', 'table-wrapper']);
696
+ if (Array.isArray(tokens[idx].map)) {
697
+ wrapperStartToken.map = tokens[idx].map.slice();
698
+ }
611
699
  const linebreakToken = new state.Token('text', '', 0);
612
700
  linebreakToken.content = '\n';
613
701
  tokens.splice(idx, 0, wrapperStartToken, linebreakToken);
702
+ tokenLength += 2;
614
703
  idx += 2;
615
704
  }
616
705
 
617
706
  let theadVar = {
618
707
  i: idx + 1,
619
708
  firstThPos: -1,
620
- isEmpty: false,
621
709
  };
622
710
 
623
711
  const hasThead = tokens[theadVar.i] && tokens[theadVar.i].type === 'thead_open';
624
712
  if (hasThead) {
625
- theadVar = addTheadThScope(state, theadVar);
713
+ theadVar = addTheadThScope(state, theadVar, allowStrongFallback);
626
714
  idx = theadVar.i + 1;
627
715
  if (opt.colgroup) {
628
- setColgroup(state, tableOpenIdx, opt);
716
+ const beforeLength = tokens.length;
717
+ setColgroup(state, tableOpenIdx, opt, allowStrongFallback);
718
+ tokenLength += tokens.length - beforeLength;
629
719
  }
630
720
  }
631
721
 
632
- let tbodyVar = {
633
- i: idx + 1,
634
- isAllFirstTh: false,
635
- tbodyFirstThPoses: [],
636
- };
637
-
638
- const hasTbody = tokens[tbodyVar.i] && tokens[tbodyVar.i].type === 'tbody_open';
639
- if (hasTbody) {
640
- tbodyVar = checkTbody(state, tbodyVar);
641
- idx = tbodyVar.i + 1;
642
- }
643
-
644
- if (theadVar.firstThPos && tbodyVar.isAllFirstTh) {
645
- const firstTdPoses = [...tbodyVar.tbodyFirstThPoses];
646
- if (hasThead) {
647
- firstTdPoses.unshift(theadVar.firstThPos);
722
+ if (opt.matrix) {
723
+ let tbodyVar = {
724
+ i: idx + 1,
725
+ isAllFirstTh: false,
726
+ tbodyFirstThPoses: [],
727
+ };
728
+
729
+ const hasTbody = tokens[tbodyVar.i] && tokens[tbodyVar.i].type === 'tbody_open';
730
+ if (hasTbody) {
731
+ tbodyVar = checkTbody(state, tbodyVar, allowStrongFallback);
732
+ idx = tbodyVar.i + 1;
648
733
  }
649
- if (opt.matrix) {
650
- changeTdToTh(state, firstTdPoses, hasThead, theadVar);
734
+
735
+ if (theadVar.firstThPos && tbodyVar.isAllFirstTh) {
736
+ const firstTdPoses = [...tbodyVar.tbodyFirstThPoses];
737
+ if (hasThead) {
738
+ firstTdPoses.unshift(theadVar.firstThPos);
739
+ }
740
+ changeTdToTh(state, firstTdPoses, hasThead, allowStrongFallback);
651
741
  }
652
742
  }
653
743
 
654
744
  // Find table_close more efficiently
655
- while (idx < tokens.length) {
745
+ while (idx < tokenLength) {
656
746
  if (tokens[idx].type === 'table_close') {
657
747
  if (opt.wrapper) {
658
748
  const wrapperEndToken = new state.Token('div_close', 'div', -1);
659
749
  const linebreakToken = new state.Token('text', '', 0);
660
750
  linebreakToken.content = '\n';
661
751
  tokens.splice(idx + 1, 0, wrapperEndToken, linebreakToken);
752
+ tokenLength += 2;
662
753
  idx += 2;
663
754
  }
664
755
  break;