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