1 | 'use strict';
|
2 | var DFA = require('./lib/dfa.js');
|
3 |
|
4 | module.exports = function multimd_table_plugin(md, options) {
|
5 | var defaults = {
|
6 | multiline: false,
|
7 | rowspan: false,
|
8 | headerless: false,
|
9 | multibody: true
|
10 | };
|
11 | options = md.utils.assign({}, defaults, options || {});
|
12 |
|
13 | function scan_bound_indices(state, line) {
|
14 | |
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | var start = state.bMarks[line] + state.sCount[line],
|
21 | head = state.bMarks[line] + state.blkIndent,
|
22 | end = state.skipSpacesBack(state.eMarks[line], head),
|
23 | bounds = [], pos, posjump,
|
24 | escape = false, code = false;
|
25 |
|
26 |
|
27 | for (pos = start; pos < end; pos++) {
|
28 | switch (state.src.charCodeAt(pos)) {
|
29 | case 0x5c :
|
30 | escape = true; break;
|
31 | case 0x60 :
|
32 | posjump = state.skipChars(pos, 0x60) - 1;
|
33 | |
34 |
|
35 |
|
36 | if (posjump > pos) { pos = posjump; }
|
37 | else if (code || !escape) { code = !code; }
|
38 | escape = false; break;
|
39 | case 0x7c :
|
40 | if (!code && !escape) { bounds.push(pos); }
|
41 | escape = false; break;
|
42 | default:
|
43 | escape = false; break;
|
44 | }
|
45 | }
|
46 | if (bounds.length === 0) return bounds;
|
47 |
|
48 |
|
49 | if (bounds[0] > head) { bounds.unshift(head - 1); }
|
50 | if (bounds[bounds.length - 1] < end - 1) { bounds.push(end); }
|
51 |
|
52 | return bounds;
|
53 | }
|
54 |
|
55 | function table_caption(state, silent, line) {
|
56 | var meta = { text: null, label: null },
|
57 | start = state.bMarks[line] + state.sCount[line],
|
58 | max = state.eMarks[line],
|
59 | capRE = /^\[([^\[\]]+)\](\[([^\[\]]+)\])?\s*$/,
|
60 | matches = state.src.slice(start, max).match(capRE);
|
61 |
|
62 | if (!matches) { return false; }
|
63 | if (silent) { return true; }
|
64 |
|
65 |
|
66 | meta.text = matches[1];
|
67 | meta.label = matches[2] || matches[1];
|
68 | meta.label = meta.label.toLowerCase().replace(/\W+/g, '');
|
69 |
|
70 | return meta;
|
71 | }
|
72 |
|
73 | function table_row(state, silent, line) {
|
74 | var meta = { bounds: null, multiline: null },
|
75 | bounds = scan_bound_indices(state, line),
|
76 | start, pos, oldMax;
|
77 |
|
78 | if (bounds.length < 2) { return false; }
|
79 | if (silent) { return true; }
|
80 |
|
81 | meta.bounds = bounds;
|
82 |
|
83 |
|
84 | if (options.multiline) {
|
85 | start = state.bMarks[line] + state.sCount[line];
|
86 | pos = state.eMarks[line] - 1;
|
87 | meta.multiline = (state.src.charCodeAt(pos) === 0x5C);
|
88 | if (meta.multiline) {
|
89 | oldMax = state.eMarks[line];
|
90 | state.eMarks[line] = state.skipSpacesBack(pos, start);
|
91 | meta.bounds = scan_bound_indices(state, line);
|
92 | state.eMarks[line] = oldMax;
|
93 | }
|
94 | }
|
95 |
|
96 | return meta;
|
97 | }
|
98 |
|
99 | function table_separator(state, silent, line) {
|
100 | var meta = { aligns: [], wraps: [] },
|
101 | bounds = scan_bound_indices(state, line),
|
102 | sepRE = /^:?(-+|=+):?\+?$/,
|
103 | c, text, align;
|
104 |
|
105 |
|
106 | if (state.sCount[line] - state.blkIndent >= 4) { return false; }
|
107 | if (bounds.length === 0) { return false; }
|
108 |
|
109 | for (c = 0; c < bounds.length - 1; c++) {
|
110 | text = state.src.slice(bounds[c] + 1, bounds[c + 1]).trim();
|
111 | if (!sepRE.test(text)) { return false; }
|
112 |
|
113 | meta.wraps.push(text.charCodeAt(text.length - 1) === 0x2B);
|
114 | align = ((text.charCodeAt(0) === 0x3A) << 4) |
|
115 | (text.charCodeAt(text.length - 1 - meta.wraps[c]) === 0x3A);
|
116 | switch (align) {
|
117 | case 0x00: meta.aligns.push(''); break;
|
118 | case 0x01: meta.aligns.push('right'); break;
|
119 | case 0x10: meta.aligns.push('left'); break;
|
120 | case 0x11: meta.aligns.push('center'); break;
|
121 | }
|
122 | }
|
123 | if (silent) { return true; }
|
124 | return meta;
|
125 | }
|
126 |
|
127 | function table_empty(state, silent, line) {
|
128 | return state.isEmpty(line);
|
129 | }
|
130 |
|
131 | function table(state, startLine, endLine, silent) {
|
132 | |
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 | var tableDFA = new DFA(),
|
143 | grp = 0x10, mtr = -1,
|
144 | token, tableToken, trToken,
|
145 | colspan, leftToken,
|
146 | rowspan, upTokens = [],
|
147 | tableLines, tgroupLines,
|
148 | tag, text, range, r, c, b;
|
149 |
|
150 | if (startLine + 2 > endLine) { return false; }
|
151 |
|
152 | |
153 |
|
154 |
|
155 |
|
156 |
|
157 | tableToken = new state.Token('table_open', 'table', 1);
|
158 | tableToken.meta = { sep: null, cap: null, tr: [] };
|
159 |
|
160 | tableDFA.set_highest_alphabet(0x10000);
|
161 | tableDFA.set_initial_state(0x10100);
|
162 | tableDFA.set_accept_states([ 0x10010, 0x10011, 0x00000 ]);
|
163 | tableDFA.set_match_alphabets({
|
164 | 0x10000: table_caption.bind(this, state, true),
|
165 | 0x01000: table_separator.bind(this, state, true),
|
166 | 0x00100: table_row.bind(this, state, true),
|
167 | 0x00010: table_row.bind(this, state, true),
|
168 | 0x00001: table_empty.bind(this, state, true)
|
169 | });
|
170 | tableDFA.set_transitions({
|
171 | 0x10100: { 0x10000: 0x00100, 0x00100: 0x01100 },
|
172 | 0x00100: { 0x00100: 0x01100 },
|
173 | 0x01100: { 0x01000: 0x10010, 0x00100: 0x01100 },
|
174 | 0x10010: { 0x10000: 0x00000, 0x00010: 0x10011 },
|
175 | 0x10011: { 0x10000: 0x00000, 0x00010: 0x10011, 0x00001: 0x10010 }
|
176 | });
|
177 | if (options.headerless) {
|
178 | tableDFA.set_initial_state(0x11100);
|
179 | tableDFA.update_transition(0x11100,
|
180 | { 0x10000: 0x01100, 0x01000: 0x10010, 0x00100: 0x01100 }
|
181 | );
|
182 | trToken = new state.Token('table_fake_header_row', 'tr', 1);
|
183 | trToken.meta = Object();
|
184 | }
|
185 | if (!options.multibody) {
|
186 | tableDFA.update_transition(0x10010,
|
187 | { 0x10000: 0x00000, 0x00010: 0x10010 }
|
188 | );
|
189 | }
|
190 |
|
191 | tableDFA.set_actions(function (_line, _state, _type) {
|
192 |
|
193 | switch (_type) {
|
194 | case 0x10000:
|
195 | if (tableToken.meta.cap) { break; }
|
196 | tableToken.meta.cap = table_caption(state, false, _line);
|
197 | tableToken.meta.cap.map = [ _line, _line + 1 ];
|
198 | tableToken.meta.cap.first = (_line === startLine);
|
199 | break;
|
200 | case 0x01000:
|
201 | tableToken.meta.sep = table_separator(state, false, _line);
|
202 | tableToken.meta.sep.map = [ _line, _line + 1 ];
|
203 | trToken.meta.grp |= 0x01;
|
204 | grp = 0x10;
|
205 | break;
|
206 | case 0x00100:
|
207 | case 0x00010:
|
208 | trToken = new state.Token('tr_open', 'tr', 1);
|
209 | trToken.map = [ _line, _line + 1 ];
|
210 | trToken.meta = table_row(state, false, _line);
|
211 | trToken.meta.type = _type;
|
212 | trToken.meta.grp = grp;
|
213 | grp = 0x00;
|
214 | tableToken.meta.tr.push(trToken);
|
215 |
|
216 | if (options.multiline) {
|
217 | if (trToken.meta.multiline && mtr < 0) {
|
218 |
|
219 | mtr = tableToken.meta.tr.length - 1;
|
220 | } else if (!trToken.meta.multiline && mtr >= 0) {
|
221 |
|
222 | token = tableToken.meta.tr[mtr];
|
223 | token.meta.mbounds = tableToken.meta.tr
|
224 | .slice(mtr).map(function (tk) { return tk.meta.bounds; });
|
225 | token.map[1] = trToken.map[1];
|
226 | tableToken.meta.tr = tableToken.meta.tr.slice(0, mtr + 1);
|
227 | mtr = -1;
|
228 | }
|
229 | }
|
230 | break;
|
231 | case 0x00001:
|
232 | trToken.meta.grp |= 0x01;
|
233 | grp = 0x10;
|
234 | break;
|
235 | }
|
236 | });
|
237 |
|
238 | if (tableDFA.execute(startLine, endLine) === false) { return false; }
|
239 |
|
240 | if (!tableToken.meta.tr.length) { return false; }
|
241 | if (silent) { return true; }
|
242 |
|
243 |
|
244 | tableToken.meta.tr[tableToken.meta.tr.length - 1].meta.grp |= 0x01;
|
245 |
|
246 |
|
247 | |
248 |
|
249 |
|
250 |
|
251 |
|
252 | tableToken.map = tableLines = [ startLine, 0 ];
|
253 | tableToken.block = true;
|
254 | tableToken.level = state.level++;
|
255 | state.tokens.push(tableToken);
|
256 |
|
257 | if (tableToken.meta.cap) {
|
258 | token = state.push('caption_open', 'caption', 1);
|
259 | token.map = tableToken.meta.cap.map;
|
260 | token.attrs = [ [ 'id', tableToken.meta.cap.label ] ];
|
261 |
|
262 | token = state.push('inline', '', 0);
|
263 | token.content = tableToken.meta.cap.text;
|
264 | token.map = tableToken.meta.cap.map;
|
265 | token.children = [];
|
266 |
|
267 | token = state.push('caption_close', 'caption', -1);
|
268 | }
|
269 |
|
270 | for (r = 0; r < tableToken.meta.tr.length; r++) {
|
271 | leftToken = new state.Token('table_fake_tcol_open', '', 1);
|
272 |
|
273 |
|
274 | trToken = tableToken.meta.tr[r];
|
275 |
|
276 | if (trToken.meta.grp & 0x10) {
|
277 | tag = (trToken.meta.type === 0x00100) ? 'thead' : 'tbody';
|
278 | token = state.push(tag + '_open', tag, 1);
|
279 | token.map = tgroupLines = [ trToken.map[0], 0 ];
|
280 | upTokens = [];
|
281 | }
|
282 | trToken.block = true;
|
283 | trToken.level = state.level++;
|
284 | state.tokens.push(trToken);
|
285 |
|
286 |
|
287 | for (c = 0; c < trToken.meta.bounds.length - 1; c++) {
|
288 | range = [ trToken.meta.bounds[c] + 1, trToken.meta.bounds[c + 1] ];
|
289 | text = state.src.slice.apply(state.src, range);
|
290 |
|
291 | if (text === '') {
|
292 | colspan = leftToken.attrGet('colspan');
|
293 | leftToken.attrSet('colspan', colspan === null ? 2 : colspan + 1);
|
294 | continue;
|
295 | }
|
296 | if (options.rowspan && upTokens[c] && text.trim() === '^^') {
|
297 | rowspan = upTokens[c].attrGet('rowspan');
|
298 | upTokens[c].attrSet('rowspan', rowspan === null ? 2 : rowspan + 1);
|
299 | continue;
|
300 | }
|
301 |
|
302 | tag = (trToken.meta.type === 0x00100) ? 'th' : 'td';
|
303 | token = state.push(tag + '_open', tag, 1);
|
304 | token.map = trToken.map;
|
305 | token.attrs = [];
|
306 | if (tableToken.meta.sep.aligns[c]) {
|
307 | token.attrs.push([ 'style', 'text-align:' + tableToken.meta.sep.aligns[c] ]);
|
308 | }
|
309 | if (tableToken.meta.sep.wraps[c]) {
|
310 | token.attrs.push([ 'class', 'extend' ]);
|
311 | }
|
312 | leftToken = upTokens[c] = token;
|
313 |
|
314 |
|
315 | if (options.multiline && trToken.meta.multiline && trToken.meta.mbounds) {
|
316 | text = [ text.trimRight() ];
|
317 | for (b = 1; b < trToken.meta.mbounds.length; b++) {
|
318 |
|
319 | if (c > trToken.meta.mbounds[b].length - 2) { continue; }
|
320 | range = [ trToken.meta.mbounds[b][c] + 1, trToken.meta.mbounds[b][c + 1] ];
|
321 | text.push(state.src.slice.apply(state.src, range).trimRight());
|
322 | }
|
323 | state.md.block.parse(text.join('\n'), state.md, state.env, state.tokens);
|
324 | } else {
|
325 | token = state.push('inline', '', 0);
|
326 | token.content = text.trim();
|
327 | token.map = trToken.map;
|
328 | token.children = [];
|
329 | }
|
330 |
|
331 | token = state.push(tag + '_close', tag, -1);
|
332 | }
|
333 |
|
334 |
|
335 | state.push('tr_close', 'tr', -1);
|
336 | if (trToken.meta.grp & 0x01) {
|
337 | tag = (trToken.meta.type === 0x00100) ? 'thead' : 'tbody';
|
338 | token = state.push(tag + '_close', tag, -1);
|
339 | tgroupLines[1] = trToken.map[1];
|
340 | }
|
341 | }
|
342 |
|
343 | tableLines[1] = Math.max(
|
344 | tgroupLines[1],
|
345 | tableToken.meta.sep.map[1],
|
346 | tableToken.meta.cap ? tableToken.meta.cap.map[1] : -1
|
347 | );
|
348 | token = state.push('table_close', 'table', -1);
|
349 |
|
350 | state.line = tableLines[1];
|
351 | return true;
|
352 | }
|
353 |
|
354 | md.block.ruler.at('table', table, { alt: [ 'paragraph', 'reference' ] });
|
355 | };
|
356 |
|
357 |
|