@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/README.md +142 -11
- package/index.js +698 -91
- package/package.json +10 -4
- package/test/examples.txt +0 -411
- package/test/examples_wrapper.txt +0 -119
- package/test/examples_wrapper_with_caption.txt +0 -141
- package/test/test.js +0 -117
package/index.js
CHANGED
|
@@ -1,149 +1,756 @@
|
|
|
1
|
-
const
|
|
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
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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 <
|
|
9
|
-
if (
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
if (
|
|
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 (
|
|
230
|
+
if (tokens[j].type === 'tr_close') break
|
|
18
231
|
j++
|
|
19
232
|
}
|
|
20
|
-
return {i: j, firstThPos: firstThPos
|
|
233
|
+
return {i: j, firstThPos: firstThPos}
|
|
21
234
|
}
|
|
22
235
|
|
|
23
|
-
|
|
24
|
-
|
|
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('
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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 <
|
|
62
|
-
if (
|
|
263
|
+
while (j < tokens.length) {
|
|
264
|
+
if (tokens[j].type === 'tr_open') {
|
|
63
265
|
j++
|
|
64
|
-
if (
|
|
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 (
|
|
273
|
+
if (tokens[j].type === 'tbody_close') break
|
|
72
274
|
j++
|
|
73
275
|
}
|
|
74
|
-
return { i: j
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
|
686
|
+
i: idx + 1,
|
|
91
687
|
firstThPos: -1,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
113
|
-
if (
|
|
114
|
-
let
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|