实现md合并单元格

2021-12-29 14:50:33markdownmarkdown

在markdown中,有如下一个表格,如何实现合并单元格?

| 表头| 表头 | 表头 | 表头  | 表头  | 表头|
|-----|:-----|-----:|:-----:|:-----:|:---:|
| a   |  e   | ***  |  ***  |  ***  | *** |
| b   |  ↑  | ***  |  ***  |  ***  | *** |
| ↓  |  g   | ***  |  ***  |  ***  | *** |
| c   |  ↓  | ***  |  ***  |  ***  | *** |
| ↓  | ***  | ***  |  ***  |  ***  | *** |
| d   |  →  |  h   |   i   |   →  | *** |

思路:

  1. 为边界,合并行
  2. 为边界,合并列
  3. 识别单元格对其样式:
--- 默认
:---: 居中
:--- 左对齐
---: 右对齐

核心代码实现:

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)