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