diff --git a/README.md b/README.md index ea451ef..77474af 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,46 @@ Output: ``` +Wellformed the table's _rowspan_ and/or _colspan_ attributes, usage sample below: +```md +| A | B | C | D | +| ----------------------- | --- | --- | ---------------- | +| 1 | 11 | 111 | 1111 {rowspan=3} | +| 2 {colspan=2 rowspan=2} | 22 | 222 | 2222 | +| 3 | 33 | 333 | 3333 | + +{border=1} +``` + +Output: +```html +
A | +B | +C | +D | +
---|---|---|---|
1 | +11 | +111 | +1111 | +
2 | +22 | +||
3 | +
{.c}
tokens.splice(i + 1, 3); } + }, { + /** + * | A | B | + * | -- | -- | + * | 1 | 2 | + * + * | C | D | + * | -- | -- | + * + * only `| A | B |` sets the colsnum metadata + */ + name: 'tables thead metadata', + tests: [ + { + shift: 0, + type: 'tr_close', + }, { + shift: 1, + type: 'thead_close' + }, { + shift: 2, + type: 'tbody_open' + } + ], + transform: (tokens, i) => { + const tr = utils.getMatchingOpeningToken(tokens, i); + const th = tokens[i - 1]; + let colsnum = 0; + let n = i; + while (--n) { + if (tokens[n] === tr) { + tokens[n - 1].meta = Object.assign({}, tokens[n + 2].meta, { colsnum }); + break; + } + colsnum += (tokens[n].level === th.level && tokens[n].type === th.type) >> 0; + } + tokens[i + 2].meta = Object.assign({}, tokens[i + 2].meta, { colsnum }); + } + }, { + /** + * | A | B | C | D | + * | -- | -- | -- | -- | + * | 1 | 11 | 111 | 1111 {rowspan=3} | + * | 2 {colspan=2 rowspan=2} | 22 | 222 | 2222 | + * | 3 | 33 | 333 | 3333 | + */ + name: 'tables tbody calculate', + tests: [ + { + shift: 0, + type: 'tbody_close', + hidden: false + } + ], + /** + * @param {number} i index of the tbody ending + */ + transform: (tokens, i) => { + /** index of the tbody beginning */ + let idx = i - 2; + while (idx > 0 && 'tbody_open' !== tokens[--idx].type); + + const calc = tokens[idx].meta ? tokens[idx].meta.colsnum >> 0 : 1; + if (calc < 2) { return; } + + const level = tokens[i].level + 2; + for (let n = idx; n < i; n++) { + if (tokens[n].level > level) { continue; } + + const token = tokens[n]; + const rows = token.hidden ? 0 : token.attrGet('rowspan') >> 0; + const cols = token.hidden ? 0 : token.attrGet('colspan') >> 0; + + if (rows > 1) { + let colsnum = calc - (cols > 0 ? cols : 1); + for (let k = n, num = rows; k < i, num > 1; k++) { + if ('tr_open' == tokens[k].type) { + tokens[k].meta = Object.assign({}, tokens[k].meta); + if (tokens[k].meta && tokens[k].meta.colsnum) { + colsnum -= 1; + } + tokens[k].meta.colsnum = colsnum; + num--; + } + } + } + + if ('tr_open' == token.type && token.meta && token.meta.colsnum) { + const max = token.meta.colsnum; + for (let k = n, num = 0; k < i; k++) { + if ('td_open' == tokens[k].type) { + num += 1; + } else if ('tr_close' == tokens[k].type) { + break; + } + num > max && (tokens[k].hidden || hidden(tokens[k])); + } + } + + if (cols > 1) { + /** @type {number[]} index of one row's children */ + const one = []; + /** last index of the row's children */ + let end = n + 3; + /** number of the row's children */ + let num = calc; + + for (let k = n; k > idx; k--) { + if ('tr_open' == tokens[k].type) { + num = tokens[k].meta && tokens[k].meta.colsnum || num; + break; + } else if ('td_open' === tokens[k].type) { + one.unshift(k); + } + } + + for (let k = n; k < i; k++) { + if ('tr_close' == tokens[k].type) { + end = k; + break; + } else if ('td_open' == tokens[k].type) { + k > idx && one.push(k); + } + } + + const off = one.indexOf(idx); + let real = num - off; + real = real > cols ? cols : real; + cols > real && token.attrSet('colspan', real + ''); + + for (let k = one.slice(num + 1 - calc - real)[0]; k < end; k++) { + tokens[k].hidden || hidden(tokens[k]); + } + } + } + } }, { /** * *emphasis*{.with attrs=1} @@ -361,3 +497,19 @@ module.exports = options => { function last(arr) { return arr.slice(-1)[0]; } + +/** + * Hidden table's cells and them inline children, + * specially cast inline's content as empty + * to prevent that escapes the table's box model + * @see https://github.com/markdown-it/markdown-it/issues/639 + * @param {import('.').Token} token + */ +function hidden(token) { + token.hidden = true; + token.children && token.children.forEach(t => ( + t.content = '', + hidden(t), + undefined + )); +} diff --git a/test.js b/test.js index d16c08f..309f93f 100644 --- a/test.js +++ b/test.js @@ -349,6 +349,42 @@ function describeTestsWithOptions(options, postText) { assert.equal(md.render(replaceDelimiters(src, options)), expected); }); + it(replaceDelimiters('should caculate table\'s colspan and/or rowspan', options), () => { + src = '| A | B | C | D |\n'; + src += '| -- | -- | -- | -- |\n'; + src += '| 1 | 11 | 111 | 1111 {rowspan=3} |\n'; + src += '| 2 {colspan=2 rowspan=2} | 22 | 222 | 2222 |\n'; + src += '| 3 | 33 | 333 | 3333 |\n'; + src += '\n'; + src += '{border=1}\n'; + expected = 'A | \n'; + expected += 'B | \n'; + expected += 'C | \n'; + expected += 'D | \n'; + expected += '
---|---|---|---|
1 | \n'; + expected += '11 | \n'; + expected += '111 | \n'; + expected += '1111 | \n'; + expected += '
2 | \n'; + expected += '22 | \n'; + expected += '||
3 | \n'; + expected += '