/** * 支持合并单元格的Markdown表格解析器 * 语法说明: * ^n# - 向上合并n个单元格 * >n# - 向左合并n个单元格 * ^n>m# - 同时向上合并n个单元格,向左合并m个单元格 */ class MergeTableParser { constructor() { this.patterns = { mergeUp: /\^(\d+)#/, // ^2# mergeLeft: />(\d+)#/, // >3# mergeBoth: /\^(\d+)>(\d+)#/, // ^2>3# }; } /** * 解析Markdown表格并转换为HTML表格 * @param {string} markdown - Markdown表格文本 * @returns {string} - HTML表格 */ parseMarkdownTable(markdown) { const lines = markdown.trim().split('\n'); const tableData = this.parseTableLines(lines); return this.generateHTMLTable(tableData); } /** * 解析表格行数据 * @param {string[]} lines - 表格行数组 * @returns {Object} - 表格数据对象 */ parseTableLines(lines) { const rows = []; let headers = []; let separatorIndex = -1; // 解析表头和分隔符 for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith('|') && line.endsWith('|')) { const cells = line.slice(1, -1).split('|').map(cell => cell.trim()); if (line.includes('---') || line.includes('===')) { separatorIndex = i; continue; } if (separatorIndex === -1) { headers = cells; } else { rows.push(cells); } } } return { headers, rows, separatorIndex }; } /** * 生成HTML表格 * @param {Object} tableData - 表格数据 * @returns {string} - HTML表格字符串 */ generateHTMLTable(tableData) { const { headers, rows } = tableData; const skipMap = new Map(); // 用于标记需要跳过的单元格 let html = '\n'; // 生成表头(支持合并单元格) if (headers.length > 0) { html += ' \n \n'; // 处理表头合并单元格 headers.forEach((header, colIndex) => { // 检查当前单元格是否应该被跳过 const key = `-1,${colIndex}`; // 表头行索引为-1 if (skipMap.has(key)) { return; } const mergeInfo = this.parseMergeSyntax(header); let cellHtml = ''; if (mergeInfo.rowspan > 1 || mergeInfo.colspan > 1) { cellHtml = ` \n`; // 标记被合并的表头单元格 this.markMergedCells(-1, colIndex, mergeInfo, skipMap); } else { cellHtml = ` \n`; } html += cellHtml; }); html += ' \n \n'; } // 生成表格主体 html += ' \n'; // 第一遍:标记所有需要合并的单元格 rows.forEach((row, rowIndex) => { row.forEach((cell, colIndex) => { const mergeInfo = this.parseMergeSyntax(cell); if (mergeInfo.rowspan > 1 || mergeInfo.colspan > 1) { this.markMergedCells(rowIndex, colIndex, mergeInfo, skipMap); } }); }); // 第二遍:生成HTML,跳过被合并的单元格 rows.forEach((row, rowIndex) => { html += ' \n'; let colOffset = 0; // 列偏移量,用于处理被跳过的单元格 row.forEach((cell, colIndex) => { // 检查当前单元格是否应该被跳过 const key = `${rowIndex},${colIndex}`; if (skipMap.has(key)) { colOffset++; return; } const mergeInfo = this.parseMergeSyntax(cell); let cellHtml = ''; if (mergeInfo.rowspan > 1 || mergeInfo.colspan > 1) { cellHtml = ` \n`; } else { cellHtml = ` \n`; } html += cellHtml; }); html += ' \n'; }); html += ' \n
${this.escapeHTML(mergeInfo.content)}${this.escapeHTML(header)}
${this.escapeHTML(mergeInfo.content)}${this.escapeHTML(cell)}
'; return html; } /** * 解析合并语法 * @param {string} cell - 单元格内容 * @returns {Object} - 合并信息 */ parseMergeSyntax(cell) { const result = { content: cell, rowspan: 1, colspan: 1, shouldSkip: false }; // 检查合并语法 const bothMatch = cell.match(this.patterns.mergeBoth); const upMatch = cell.match(this.patterns.mergeUp); const leftMatch = cell.match(this.patterns.mergeLeft); if (bothMatch) { result.rowspan = parseInt(bothMatch[1]) + 1; result.colspan = parseInt(bothMatch[2]) + 1; result.content = cell.replace(this.patterns.mergeBoth, '').trim(); } else if (upMatch) { result.rowspan = parseInt(upMatch[1]) + 1; result.content = cell.replace(this.patterns.mergeUp, '').trim(); } else if (leftMatch) { result.colspan = parseInt(leftMatch[1]) + 1; result.content = cell.replace(this.patterns.mergeLeft, '').trim(); } return result; } /** * 标记被合并的单元格 * @param {number} rowIndex - 行索引 * @param {number} colIndex - 列索引 * @param {Object} mergeInfo - 合并信息 * @param {Map} skipMap - 跳过映射 */ markMergedCells(rowIndex, colIndex, mergeInfo, skipMap) { for (let r = 0; r < mergeInfo.rowspan; r++) { for (let c = 0; c < mergeInfo.colspan; c++) { if (r === 0 && c === 0) continue; // 跳过主单元格 // 处理表头合并(行索引为-1) const targetRow = rowIndex === -1 ? -1 : rowIndex + r; const key = `${targetRow},${colIndex + c}`; skipMap.set(key, true); } } } /** * HTML转义,支持保留
标签 * @param {string} text - 原始文本 * @returns {string} - 转义后的文本 */ escapeHTML(text) { // 首先转义所有HTML特殊字符 const escapedText = text .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); // 然后恢复
标签的功能 return escapedText .replace(/<br\s*\/?>/gi, '
') .replace(/<br\s*\/?>/gi, '
') .replace(/\\n/g, '
'); // 也支持\n换行符 } /** * 将HTML表格转换回Markdown格式 * @param {string} html - HTML表格 * @returns {string} - Markdown表格 */ htmlToMarkdown(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const table = doc.querySelector('table'); if (!table) return ''; let markdown = ''; const rows = table.querySelectorAll('tr'); rows.forEach((row, rowIndex) => { const cells = row.querySelectorAll('th, td'); let rowMarkdown = '|'; cells.forEach(cell => { const rowspan = parseInt(cell.getAttribute('rowspan') || '1'); const colspan = parseInt(cell.getAttribute('colspan') || '1'); let content = cell.textContent.trim(); // 添加合并语法 if (rowspan > 1) { content += ` ^${rowspan - 1}`; } if (colspan > 1) { content += ` >${colspan - 1}`; } rowMarkdown += ` ${content} |`; }); markdown += rowMarkdown + '\n'; // 添加分隔符行(仅在第一行后) if (rowIndex === 0) { markdown += '|' + cells.map(() => '---').join('|') + '|\n'; } }); return markdown; } } // 导出为全局变量 if (typeof window !== 'undefined') { window.MergeTableParser = MergeTableParser; } // 使用示例 if (typeof module !== 'undefined' && module.exports) { module.exports = MergeTableParser; }