Skip to content

Commit 9664b65

Browse files
committed
Add support for colSpan, rowSpan, on <table>
Closes GH-62.
1 parent 0b521a1 commit 9664b65

File tree

6 files changed

+230
-20
lines changed

6 files changed

+230
-20
lines changed

lib/handlers/table-cell.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,15 @@ export function tableCell(h, node) {
1414
const wrap = h.wrapText
1515

1616
h.wrapText = false
17+
1718
const result = h(node, 'tableCell', all(h, node))
19+
20+
if (node.properties && (node.properties.rowSpan || node.properties.colSpan)) {
21+
const data = result.data || (result.data = {})
22+
if (node.properties.rowSpan) data.rowSpan = node.properties.rowSpan
23+
if (node.properties.colSpan) data.colSpan = node.properties.colSpan
24+
}
25+
1826
h.wrapText = wrap
1927

2028
return result

lib/handlers/table.js

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,74 @@ const cell = convertElement(['th', 'td'])
2424
* @param {Element} node
2525
*/
2626
export function table(h, node) {
27-
const info = inspect(node)
28-
return h(node, 'table', {align: info.align}, toRows(all(h, node), info))
27+
const {headless, align} = inspect(node)
28+
const rows = toRows(all(h, node), headless)
29+
let columns = 1
30+
let rowIndex = -1
31+
32+
while (++rowIndex < rows.length) {
33+
const cells = rows[rowIndex].children
34+
let cellIndex = -1
35+
36+
while (++cellIndex < cells.length) {
37+
const cell = cells[cellIndex]
38+
39+
if (cell.data) {
40+
const colSpan = Number.parseInt(String(cell.data.colSpan), 10) || 1
41+
const rowSpan = Number.parseInt(String(cell.data.rowSpan), 10) || 1
42+
43+
if (colSpan > 1 || rowSpan > 1) {
44+
let otherRowIndex = rowIndex - 1
45+
46+
while (++otherRowIndex < rowIndex + rowSpan) {
47+
let colIndex = cellIndex - 1
48+
49+
while (++colIndex < cellIndex + colSpan) {
50+
if (!rows[otherRowIndex]) {
51+
// Don’t add rows that don’t exist.
52+
// Browsers don’t render them either.
53+
break
54+
}
55+
56+
/** @type {Array.<MdastRowContent>} */
57+
const newCells = []
58+
59+
if (otherRowIndex !== rowIndex || colIndex !== cellIndex) {
60+
newCells.push({type: 'tableCell', children: []})
61+
}
62+
63+
rows[otherRowIndex].children.splice(colIndex, 0, ...newCells)
64+
}
65+
}
66+
}
67+
68+
// Clean the data fields.
69+
if ('colSpan' in cell.data) delete cell.data.colSpan
70+
if ('rowSpan' in cell.data) delete cell.data.rowSpan
71+
if (Object.keys(cell.data).length === 0) delete cell.data
72+
}
73+
}
74+
75+
if (cells.length > columns) columns = cells.length
76+
}
77+
78+
// Add extra empty cells.
79+
rowIndex = -1
80+
81+
while (++rowIndex < rows.length) {
82+
const cells = rows[rowIndex].children
83+
let cellIndex = cells.length - 1
84+
while (++cellIndex < columns) {
85+
cells.push({type: 'tableCell', children: []})
86+
}
87+
}
88+
89+
let alignIndex = align.length - 1
90+
while (++alignIndex < columns) {
91+
align.push(null)
92+
}
93+
94+
return h(node, 'table', {align}, rows)
2995
}
3096

3197
/**
@@ -69,25 +135,23 @@ function inspect(node) {
69135
* Ensure the rows are properly structured.
70136
*
71137
* @param {Array.<MdastNode>} children
72-
* @param {Info} info
138+
* @param {boolean} headless
73139
* @returns {Array.<MdastTableContent>}
74140
*/
75-
function toRows(children, info) {
141+
function toRows(children, headless) {
142+
let index = -1
76143
/** @type {Array.<MdastTableContent>} */
77144
const nodes = []
78-
let index = -1
79-
/** @type {MdastNode} */
80-
let node
81145
/** @type {Array.<MdastRowContent>|undefined} */
82146
let queue
83147

84148
// Add an empty header row.
85-
if (info.headless) {
149+
if (headless) {
86150
nodes.push({type: 'tableRow', children: []})
87151
}
88152

89153
while (++index < children.length) {
90-
node = children[index]
154+
const node = children[index]
91155

92156
if (node.type === 'tableRow') {
93157
if (queue) {
@@ -110,7 +174,7 @@ function toRows(children, info) {
110174
index = -1
111175

112176
while (++index < nodes.length) {
113-
nodes[index].children = toCells(nodes[index].children, info)
177+
nodes[index].children = toCells(nodes[index].children)
114178
}
115179

116180
return nodes
@@ -120,10 +184,9 @@ function toRows(children, info) {
120184
* Ensure the cells in a row are properly structured.
121185
*
122186
* @param {Array.<MdastNode>} children
123-
* @param {Info} info
124187
* @returns {Array.<MdastRowContent>}
125188
*/
126-
function toCells(children, info) {
189+
function toCells(children) {
127190
/** @type {Array.<MdastRowContent>} */
128191
const nodes = []
129192
let index = -1
@@ -160,11 +223,5 @@ function toCells(children, info) {
160223
node.children.push(...queue)
161224
}
162225

163-
index = nodes.length - 1
164-
165-
while (++index < info.align.length) {
166-
nodes.push({type: 'tableCell', children: []})
167-
}
168-
169226
return nodes
170227
}

readme.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ Say we have the following `example.html`:
3535

3636
```js
3737
import {unified} from 'unified'
38-
import remarkParse from 'rehype-parse'
38+
import rehypeParse from 'rehype-parse'
3939
import remarkStringify from 'remark-stringify'
4040
import {readSync} from 'to-vfile'
4141
import {toMdast} from 'hast-util-to-mdast'
4242

4343
const file = readSync('example.html')
4444

45-
const hast = unified().use(remarkParse).parse(file)
45+
const hast = unified().use(rehypeParse).parse(file)
4646

4747
const mdast = toMdast(hast)
4848

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<table>
2+
<tr>
3+
<th>a</th>
4+
</tr>
5+
<tr>
6+
<td rowspan="3" colspan="3">x</td>
7+
<td>a</td>
8+
<td>a</td>
9+
</tr>
10+
<tr>
11+
<td>a</td>
12+
</tr>
13+
<tr>
14+
<td>a</td>
15+
</tr>
16+
<tr>
17+
<td>a</td>
18+
</tr>
19+
<tr>
20+
<td>a</td>
21+
<td>a</td>
22+
<td>a</td>
23+
<td>a</td>
24+
<td>a</td>
25+
<td>a</td>
26+
</tr>
27+
</table>
28+
29+
<table>
30+
<tr>
31+
<th>a</th>
32+
<th>b</th>
33+
</tr>
34+
<tr>
35+
<td rowspan="2">Span 2</td>
36+
<td>c</td>
37+
</tr>
38+
<tr>
39+
<td>d</td>
40+
</tr>
41+
</table>
42+
43+
<table>
44+
<tr>
45+
<td>a</td>
46+
<td rowspan="2">Span 2</td>
47+
</tr>
48+
<tr>
49+
<td>b</td>
50+
</tr>
51+
</table>
52+
53+
<table>
54+
<tr>
55+
<td>a</td>
56+
<td rowspan="2">Span 2</td>
57+
<td>b</td>
58+
</tr>
59+
<tr>
60+
<td>c</td>
61+
<td>d</td>
62+
</tr>
63+
<tr>
64+
<td>e</td>
65+
<td>f</td>
66+
</tr>
67+
</table>
68+
69+
<table>
70+
<tr>
71+
<td rowspan="2">Span 2</td>
72+
</tr>
73+
<tr></tr>
74+
</table>
75+
76+
<table>
77+
<tr>
78+
<td>a</td>
79+
<td rowspan="2">Span 2</td>
80+
</tr>
81+
</table>
82+
83+
<table>
84+
<tr>
85+
<td rowspan="2">Span 2</td>
86+
</tr>
87+
</table>
88+
89+
<table>
90+
<tr>
91+
<th>a</th>
92+
<th>b</th>
93+
</tr>
94+
<tr>
95+
<td colspan="2">Span 2</td>
96+
<td>c</td>
97+
</tr>
98+
<tr>
99+
<td>d</td>
100+
</tr>
101+
</table>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"fragment": true
3+
}

test/fixtures/table-rowspan/index.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
| a | | | | | |
2+
| - | - | - | - | - | - |
3+
| x | | | a | a | |
4+
| | | | a | | |
5+
| | | | a | | |
6+
| a | | | | | |
7+
| a | a | a | a | a | a |
8+
9+
| a | b |
10+
| ------ | - |
11+
| Span 2 | c |
12+
| | d |
13+
14+
| | |
15+
| - | ------ |
16+
| a | Span 2 |
17+
| b | |
18+
19+
| | | |
20+
| - | ------ | - |
21+
| a | Span 2 | b |
22+
| c | | d |
23+
| e | f | |
24+
25+
| |
26+
| ------ |
27+
| Span 2 |
28+
| |
29+
30+
| | |
31+
| - | ------ |
32+
| a | Span 2 |
33+
34+
| |
35+
| ------ |
36+
| Span 2 |
37+
38+
| a | b | |
39+
| ------ | - | - |
40+
| Span 2 | | c |
41+
| d | | |

0 commit comments

Comments
 (0)