在markdown中,有如下一个表格,如何实现合并单元格?
| 表头| 表头 | 表头 | 表头 | 表头 | 表头|
|-----|:-----|-----:|:-----:|:-----:|:---:|
| a | e | *** | *** | *** | *** |
| b | ↑ | *** | *** | *** | *** |
| ↓ | g | *** | *** | *** | *** |
| c | ↓ | *** | *** | *** | *** |
| ↓ | *** | *** | *** | *** | *** |
| d | → | h | i | → | *** |
思路:
- 以
↑
↓
为边界,合并行 - 以
←
→
为边界,合并列 - 识别单元格对其样式:
--- 默认
:---: 居中
:--- 左对齐
---: 右对齐
核心代码实现:
function mergeCells(list) {
// 第一行作为表头
let thead = list.shift();
let tbody = [];
let aligns = [];
let rowspan = [];
// 兼容markdown格式的表格的第二行:对其方式
if (/^:?-{3,}:?$/.test(list?.[0]?.[0])) {
let alignStyle = list.shift();
alignStyle.forEach(col => {
let result = ''
if (/^:-{3,}$/.test(col)) {
result = 'left'
} else if (/^-{3,}:$/.test(col)) {
result = 'right'
} else if (/^:-{3,}:$/.test(col)) {
result = 'center'
}
aligns.push(result)
})
thead = thead.map((m, i) => {
let align = aligns[i]
let th = {
text: m,
}
if (align === 'left' || align === 'right') {
th.align = align;
}
return th
})
}
// 处理tbody
tbody = list.map((row, index) => {
// 合并行信息
rowspan.push([])
// 合并列信息
let colspan = []
row = row.map((col, i) => {
let colObj = {
text: col,
}
// 记录合并行信息
rowspan[index].push({
col: i,
})
// 处理md中对其问题
let align = aligns[i]
if (align && align !== 'left') {
colObj.align = align;
}
// 根据单元格内容进行合并行、合并列的判断
switch (colObj.text) {
case '↑':
// 合并行开始
rowspan[index][i].start = index
break;
case '↓':
// 合并行结束
rowspan[index][i].end = index
break;
case '←':
// 合并列开始
colspan.push({
start: i,
})
break;
case '→':
// 合并列结束
if (colspan[colspan.length - 1]?.end === undefined) {
if (colspan[colspan.length - 1]) {
colspan[colspan.length - 1].end = i
} else {
// 处理
colspan.push({
start: 0,
end: i,
})
}
} else {
// 处理一行中有多个合并列的情况
colspan.push({
start: colspan[colspan.length - 1].end + 1,
end: i,
})
}
break;
default:
break;
}
return colObj
})
if (colspan.length) {
colspan.forEach(({end, start}) => {
let text = []
row = row.map((td, colIndex) => {
if (end > start && colIndex >= start && colIndex <= end) {
let span = end - start + 1;
if (colIndex === start) {
td.colspan = span
if (td.text !== '←' && td.text !== '→') {
text.push(td.text)
}
} else if (colIndex < end) {
text.push(td.text)
td = undefined
} else if (colIndex === end) {
td = undefined
row[start].text = text
row[start].align = 'center'
}
}
return td
})
})
}
row = row.filter(item => !!item)
return row
})
let rspan = [...rowspan[0]].map(item => ({col: item.col}));
rowspan.forEach(item => {
item.forEach(sub => {
if (sub.start !== undefined || sub.end !== undefined) {
if (rspan[sub.col].span === undefined) {
rspan[sub.col].span = [];
}
}
if (sub.start !== undefined) {
rspan[sub.col].span.push({
start: sub.start,
})
} else if (sub.end) {
let spanList = rspan[sub.col].span
let last = spanList[spanList.length - 1]
if (spanList.length === 0) {
last = {
end: sub.end,
}
spanList.push(last)
} else {
if (last.start === undefined) {
last = {
start: last.end + 1,
end: sub.end,
};
spanList.push(last)
} else {
last = {
start: last.start,
end: sub.end,
};
spanList[spanList.length - 1] = last;
}
}
}
})
})
rspan.forEach(sub => {
if (!sub.span) {
return;
}
sub.span.forEach(last => {
if (last.start === undefined) {
last.start = 0;
}
let text = [];
tbody = tbody.map((tr, index) => {
let td = tr[sub.col]
if (index === last.start) {
td.rowspan = last.end - last.start + 1;
if (td.text !== '↑' && td.text !== '↓') {
text.push(td.text)
}
} else if (index > last.start && index <= last.end) {
td.delete = 1;
if (td.text !== '↑' && td.text !== '↓') {
text.push(td.text)
}
}
if (index === last.end) {
tbody[last.start][sub.col].text = text
}
return tr
})
})
})
tbody = tbody.map(tr => {
return tr.filter(td => td.delete === undefined)
})
return {
thead,
tbody,
};
}
以上这个函数,就可以实现合并了,下面对合并后的数据,进行html的转换:
function tableToHtml(table) {
return `<table><thead><tr>${
table.thead.map((
{
align = '',
text = ''
}
) => {
return `<th ${align ? `align="${align}"` : ''}>${text}</th>`
}).join('')
}</tr></thead><tbody>${
table.tbody.map(tr => {
return `<tr> ${
tr.map((
{
rowspan = '',
colspan = '',
text = '',
align = ''
}
) => {
const a = align ? ` align="${align}"` : '';
const r = rowspan ? ` rowspan="${rowspan}"` : ''
const c = colspan ? ` colspan="${colspan}"` : ''
const attr = [a, r, c].join('')
return `<td${attr}>${typeof text === 'string' ? text : text.join('<br/>')}</td>`
}).join('')
}</tr>`
}).join('')
}</tbody></table>`
}
demo1: 原始markdown转换
let t = `
| 表头| 表头 | 表头 | 表头 | 表头 | 表头|
|-----|:-----|-----:|:-----:|:-----:|:---:|
| a | e | *** | *** | *** | *** |
| b | ↑ | *** | *** | *** | *** |
| ↓ | g | *** | *** | *** | *** |
| c | ↓ | *** | *** | *** | *** |
| ↓ | *** | *** | *** | *** | *** |
| d | → | h | i | → | *** |
`
t = t.trim()
let list = t.split(/\s*\n\s*/).map(row => row.replace(/^\s*\||\|\s*$/g, '').trim().split(/\s*\|\s*/))
let table = mergeCells(list)
let html = tableToHtml(table)
demo2: markdown转换后的table再次转换:
let trList = document.querySelector('.t1 table tr')
let list = Array.from(trList).map(tr => Array.from(tr.children).map(td => td.innerText))
let table = mergeCells(list)
let html = tableToHtml(table)