HTML表格简化和提取功能、合并单元格拆分功能、表格格式转换Markdown功能
代码主要包含三个核心功能模块:
1. **HTML表格简化和提取功能**
- 函数名:`simplifyHTMLAndExtractTables()`
- 功能:清理HTML表格,移除不必要的样式和属性,只保留基本表格结构
- 输出:生成简化后的HTML和纯表格内容
2. **合并单元格拆分功能**
- 主函数:`splitMergedCells()`
- 功能:将表格中的合并单元格(rowspan/colspan)拆分成独立单元格
- 包含表格分析和处理的详细逻辑
3. **表格格式转换功能**
- 主函数:`convertTablesToFormats()`
- 功能:将HTML表格转换为:
- Markdown格式
- 制表符分隔格式(TSV)
- 提供复制到剪贴板等辅助功能
这些功能主要用于表格数据的清理、处理和格式转换,适用于需要处理网页表格数据的场景。
class HTMLTableProcessor { constructor() { this.results = { simplified: null, processed: null, converted: null }; } // ===== HTML简化和表格提取功能 ===== /** * 精简HTML并提取table内容 * @param {Element} container - 目标容器元素,默认为document * @returns {Object} 包含完整HTML、纯表格内容和表格数量的对象 */ simplifyAndExtractTables(container = document) { // 创建当前页面的副本以避免破坏原页面 const originalHTML = container.documentElement ? container.documentElement.cloneNode(true) : container.cloneNode(true); // 移除不必要的元素 this._removeElements(originalHTML, [ 'link[rel="stylesheet"]', 'style', 'script', 'svg', 'link' ]); // 获取所有table元素 const tables = originalHTML.querySelectorAll('table'); if (tables.length === 0) { console.log('页面中未找到任何table元素'); return { fullHTML: '', tablesOnly: [], tableCount: 0 }; } // 清理table属性 tables.forEach(table => this._cleanTableAttributes(table)); // 创建简化的HTML let simplifiedHTML = this._buildSimplifiedHTML(tables); const result = { fullHTML: simplifiedHTML, tablesOnly: Array.from(tables).map(table => table.outerHTML), tableCount: tables.length }; this.results.simplified = result; return result; } /** * 移除指定的HTML元素 */ _removeElements(container, selectors) { selectors.forEach(selector => { const elements = container.querySelectorAll(selector); elements.forEach(element => element.remove()); }); } /** * 清理表格属性 */ _cleanTableAttributes(table) { const cleanAttributes = (element) => { // 移除不必要的属性 ['class', 'data-testid', 'style', 'id'].forEach(attr => { element.removeAttribute(attr); }); // 移除所有data-开头的属性 Array.from(element.attributes).forEach(attribute => { if (attribute.name.startsWith('data-')) { element.removeAttribute(attribute.name); } }); // 递归处理子元素 Array.from(element.children).forEach(child => { cleanAttributes(child); }); }; cleanAttributes(table); } /** * 构建简化的HTML结构 */ _buildSimplifiedHTML(tables) { let html = `<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Simplified Tables</title> <style> table { border-collapse: collapse; } table, th, td { border: 1px solid black; } </style> </head> <body> `; tables.forEach((table, index) => { html += `<!-- Table ${index + 1} -->\n`; html += table.outerHTML + '\n'; }); html += '</body>\n</html>'; return html; } // ===== 合并单元格拆分功能 ===== /** * 拆分表格中的合并单元格 * @param {Element} container - 目标容器,默认为document * @returns {Object} 处理结果统计 */ splitMergedCells(container = document) { const tables = container.querySelectorAll('table'); if (tables.length === 0) { console.log('页面中未找到任何table元素'); return { processedTables: 0, processedCells: 0 }; } let totalProcessedCells = 0; let processedTables = 0; tables.forEach((table, tableIndex) => { const result = this._processTable(table, tableIndex); if (result.processed) { processedTables++; totalProcessedCells += result.cellsProcessed; } }); const result = { processedTables, processedCells: totalProcessedCells, totalTables: tables.length }; this.results.processed = result; return result; } /** * 处理单个表格 */ _processTable(table, tableIndex) { // 检查是否有真正的合并单元格 const mergedCells = table.querySelectorAll('[rowspan]:not([rowspan="1"]), [colspan]:not([colspan="1"])'); if (mergedCells.length === 0) { return { processed: false, cellsProcessed: 0 }; } let cellsProcessed = 0; // 分别处理不同部分 const sections = [ { element: table.querySelector('thead'), type: 'thead' }, { element: table.querySelector('tbody'), type: 'tbody' }, { element: table.querySelector('tfoot'), type: 'tfoot' } ]; sections.forEach(section => { if (section.element) { const rows = section.element.querySelectorAll('tr'); if (rows.length > 0) { const newSection = this._processTableSection(rows, section.type); if (newSection) { section.element.replaceWith(newSection); cellsProcessed += mergedCells.length; } } } }); // 如果没有结构化部分,直接处理所有行 if (!sections.some(s => s.element)) { const allRows = table.querySelectorAll('tr'); if (allRows.length > 0) { const processedContent = this._processTableSection(allRows, 'direct'); if (processedContent) { table.innerHTML = ''; processedContent.querySelectorAll('tr').forEach(row => { table.appendChild(row); }); cellsProcessed += mergedCells.length; } } } return { processed: true, cellsProcessed }; } /** * 处理表格部分 */ _processTableSection(rows, sectionType) { const cellMatrix = []; const contentMatrix = []; let maxCols = 0; // 构建单元格矩阵 rows.forEach((row, rowIndex) => { const cells = row.querySelectorAll('td, th'); let colIndex = 0; if (!cellMatrix[rowIndex]) { cellMatrix[rowIndex] = []; contentMatrix[rowIndex] = []; } cells.forEach(cell => { // 找到正确的列位置 while (cellMatrix[rowIndex][colIndex] !== undefined) { colIndex++; } const rowspan = parseInt(cell.getAttribute('rowspan')) || 1; const colspan = parseInt(cell.getAttribute('colspan')) || 1; const cellContent = cell.innerHTML.trim(); // 在矩阵中标记位置 for (let r = rowIndex; r < rowIndex + rowspan; r++) { for (let c = colIndex; c < colIndex + colspan; c++) { if (!cellMatrix[r]) { cellMatrix[r] = []; contentMatrix[r] = []; } cellMatrix[r][c] = { originalCell: cell, isFirstCell: (r === rowIndex && c === colIndex), content: cellContent }; contentMatrix[r][c] = (r === rowIndex && c === colIndex) ? cellContent : ''; maxCols = Math.max(maxCols, c + 1); } } colIndex += colspan; }); }); // 重建结构 const newSection = document.createElement(sectionType === 'direct' ? 'div' : sectionType); cellMatrix.forEach((row, rowIndex) => { if (!row) return; const newRow = document.createElement('tr'); for (let colIndex = 0; colIndex < maxCols; colIndex++) { const cellInfo = row[colIndex]; if (!cellInfo) continue; const newCell = document.createElement(cellInfo.originalCell.tagName.toLowerCase()); newCell.innerHTML = contentMatrix[rowIndex][colIndex]; // 复制属性(排除rowspan和colspan) Array.from(cellInfo.originalCell.attributes).forEach(attr => { if (attr.name !== 'rowspan' && attr.name !== 'colspan') { newCell.setAttribute(attr.name, attr.value); } }); newRow.appendChild(newCell); } newSection.appendChild(newRow); }); return sectionType === 'direct' ? newSection : newSection; } // ===== 格式转换功能 ===== /** * 转换表格为Markdown和制表符分隔格式 * @param {Element} container - 目标容器,默认为document * @returns {Object} 转换结果 */ convertToFormats(container = document) { const tables = container.querySelectorAll('table'); if (tables.length === 0) { console.log('页面中未找到任何table元素'); return { markdown: [], tabSeparated: [], combined: { markdown: '', tabSeparated: '' } }; } // 清理table属性 tables.forEach(table => this._cleanTableAttributes(table)); const results = { markdown: [], tabSeparated: [], combined: { markdown: '', tabSeparated: '' } }; tables.forEach((table, index) => { const tableData = this._extractTableData(table); const markdown = this._convertToMarkdown(tableData, index + 1); const tabSeparated = this._convertToTabSeparated(tableData, index + 1); results.markdown.push(markdown); results.tabSeparated.push(tabSeparated); }); // 合并所有结果 results.combined.markdown = results.markdown.join('\n'); results.combined.tabSeparated = results.tabSeparated.join('\n'); this.results.converted = results; return results; } /** * 提取表格数据 */ _extractTableData(table) { const rows = table.querySelectorAll('tr'); const tableData = []; const hasHeader = table.querySelector('thead') || table.querySelector('th'); rows.forEach((row, rowIndex) => { const cells = row.querySelectorAll('td, th'); const rowData = []; cells.forEach(cell => { let cellText = cell.innerText || cell.textContent || ''; // 清理文本内容 cellText = cellText .replace(/\s+/g, ' ') .replace(/\n/g, ' ') .trim() .replace(/\|/g, '\|') .replace(/\*/g, '\*') .replace(/`/g, '\`'); if (cellText === '') { cellText = ' '; } rowData.push(cellText); }); if (rowData.length > 0) { tableData.push({ cells: rowData, isHeader: rowIndex === 0 && hasHeader }); } }); return tableData; } /** * 转换为Markdown格式 */ _convertToMarkdown(tableData, tableNumber) { if (tableData.length === 0) { return `<!-- 表格 ${tableNumber} 为空 -->\n`; } const maxCols = Math.max(...tableData.map(row => row.cells.length)); const colWidths = new Array(maxCols).fill(0); // 计算列宽 tableData.forEach(row => { row.cells.forEach((cell, colIndex) => { colWidths[colIndex] = Math.max(colWidths[colIndex], cell.length); }); }); let markdown = `<!-- 表格 ${tableNumber} -->\n`; // 补充部分开始 tableData.forEach((row, rowIndex) => { // 补齐行的列数到最大列数 const paddedCells = [...row.cells]; while (paddedCells.length < maxCols) { paddedCells.push(' '); } // 生成表格行 const rowText = '| ' + paddedCells.map((cell, colIndex) => { return cell.padEnd(colWidths[colIndex]); }).join(' | ') + ' |'; markdown += rowText + '\n'; // 如果是表头行,添加分隔行 if (row.isHeader && rowIndex === 0) { const separatorRow = '| ' + colWidths.map(width => { return '-'.repeat(width); }).join(' | ') + ' |'; markdown += separatorRow + '\n'; } }); // 补充部分结束 return markdown; } /** * 转换为制表符分隔格式 */ _convertToTabSeparated(tableData, tableNumber) { if (tableData.length === 0) { return `# 表格 ${tableNumber} 为空\n`; } let tabSeparated = `# 表格 ${tableNumber}\n`; // 补充部分开始 tableData.forEach(row => { const rowText = row.cells.join('\t'); tabSeparated += rowText + '\n'; }); // 补充部分结束 return tabSeparated; } // ===== 辅助功能 ===== /** * 获取表格统计信息 */ getTableStats(container = document) { const tables = container.querySelectorAll('table'); const stats = []; tables.forEach((table, index) => { const rows = table.querySelectorAll('tr'); const cells = table.querySelectorAll('td, th'); const hasHeader = table.querySelector('thead') || table.querySelector('th'); const mergedCells = table.querySelectorAll('[rowspan]:not([rowspan="1"]), [colspan]:not([colspan="1"])'); const tableStats = { index: index + 1, rows: rows.length, cells: cells.length, hasHeader: !!hasHeader, mergedCells: mergedCells.length, columns: rows.length > 0 ? rows[0].querySelectorAll('td, th').length : 0 }; stats.push(tableStats); }); return stats; } /** * 复制内容到剪贴板 */ async copyToClipboard(content, type = 'content') { try { await navigator.clipboard.writeText(content); console.log(`✅ ${type}已复制到剪贴板`); return true; } catch (err) { console.error(`❌ 复制${type}失败:`, err); return false; } } /** * 复制Markdown格式到剪贴板 */ async copyMarkdownToClipboard(container = document) { if (!this.results.converted) { this.convertToFormats(container); } return this.copyToClipboard(this.results.converted.combined.markdown, 'Markdown格式'); } /** * 复制制表符分隔格式到剪贴板 */ async copyTabSeparatedToClipboard(container = document) { if (!this.results.converted) { this.convertToFormats(container); } return this.copyToClipboard(this.results.converted.combined.tabSeparated, '制表符分隔格式'); } /** * 输出所有处理结果到控制台 */ /** * 输出所有处理结果到控制台 */ outputResults(container = document) { console.log('=== HTML表格处理器输出结果 ===\n'); // 输出统计信息 const stats = this.getTableStats(container); console.log('📊 表格统计信息:'); stats.forEach(stat => { console.log(`表格 ${stat.index}: ${stat.rows}行 ${stat.columns}列 ${stat.cells}个单元格 ${stat.mergedCells > 0 ? `(${stat.mergedCells}个合并单元格)` : ''}`); }); // 输出简化HTML if (this.results.simplified) { console.log('\n📄 ===== 简化HTML结果 ====='); console.log(`✅ 已处理 ${this.results.simplified.tableCount} 个表格`); console.log('\n简化HTML:完整HTML内容:'); console.log(this.results.simplified.fullHTML); console.log('\n纯表格内容:'); this.results.simplified.tablesOnly.forEach((tableHTML, index) => { console.log(`--- 表格 ${index + 1} ---`); console.log(tableHTML); }); } else { console.log('\n📄 简化HTML: 未执行'); } // 输出合并单元格处理结果 if (this.results.processed) { console.log('\n🔄 ===== 合并单元格处理结果 ====='); console.log(`✅ 处理统计: ${this.results.processed.processedTables}/${this.results.processed.totalTables} 个表格已处理`); console.log(`✅ 合并单元格拆分: ${this.results.processed.processedCells} 个`); if (this.results.processed.processedTables > 0) { console.log('\n处理后的表格HTML:'); // 输出处理后的表格 const tables = container.querySelectorAll('table'); tables.forEach((table, index) => { // 先清理属性 const cleanTable = table.cloneNode(true); this._cleanTableAttributes(cleanTable); console.log(`--- 处理后的表格 ${index + 1} ---`); console.log(cleanTable.outerHTML); }); // 输出带样式的完整HTML console.log('\n合并单元格处理结果:带样式的完整HTML:'); let processedHTML = `<style> table { border-collapse: collapse; } table, th, td { border: 1px solid black; } </style> `; tables.forEach((table, index) => { const cleanTable = table.cloneNode(true); this._cleanTableAttributes(cleanTable); processedHTML += `<!-- 处理后的表格 ${index + 1} -->\n`; processedHTML += cleanTable.outerHTML + '\n\n'; }); console.log(processedHTML); } } else { console.log('\n🔄 合并单元格处理: 未执行'); } // 输出格式转换结果 if (this.results.converted) { console.log('\n📝 ===== 格式转换结果 ====='); console.log(`✅ 已转换 ${this.results.converted.markdown.length} 个表格`); console.log('\n' + '='.repeat(50)); console.log('Markdown格式 (合并所有表格):'); console.log('='.repeat(50)); console.log(this.results.converted.combined.markdown); console.log('\n' + '='.repeat(50)); console.log('制表符分隔格式 (合并所有表格):'); console.log('='.repeat(50)); console.log(this.results.converted.combined.tabSeparated); // 单独输出每个表格的转换结果 if (this.results.converted.markdown.length > 1) { console.log('\n--- 各表格单独转换结果 ---'); this.results.converted.markdown.forEach((markdown, index) => { console.log(`\n表格 ${index + 1} - Markdown:`); console.log(markdown); }); this.results.converted.tabSeparated.forEach((tabSeparated, index) => { console.log(`\n表格 ${index + 1} - 制表符分隔:`); console.log(tabSeparated); }); } } else { console.log('\n📝 格式转换: 未执行'); } // 输出操作建议 console.log('\n💡 ===== 可用操作 ====='); console.log('📋 processor.copyMarkdownToClipboard() - 复制Markdown到剪贴板'); console.log('📋 processor.copyTabSeparatedToClipboard() - 复制制表符分隔格式到剪贴板'); console.log('🔄 processor.processAll() - 重新执行完整处理'); console.log('📊 processor.getTableStats() - 获取表格统计信息'); console.log('\n✨ 处理完成!'); } /** * 一键处理所有功能 */ processAll(container = document) { console.log('🚀 开始完整处理HTML表格...\n'); // 碰到一个问题,第二次使用的时候,简化就会直接是拆分合并单元格的结果,而拆分合并单元格就没有结果了。(因为container = document已经被改变) // 原本想用深度克隆容器的辅助方法。用了之后,那些属性无法去掉,好奇怪,算了,放弃。 // 创建原始容器的深度副本 let workingContainer = this.deepCloneContainer(container); // 1. 获取统计信息(使用原始容器获取初始统计) const stats = this.getTableStats(container); console.log('📊 发现表格:', stats.length, '个'); // 2. 简化和提取(使用副本并更新工作容器) //workingContainer = this.deepCloneContainer(workingContainer); //const simplified = this.simplifyAndExtractTables(workingContainer); const simplified = this.simplifyAndExtractTables(container); console.log('✅ HTML简化完成'); // 3. 拆分合并单元格(使用新副本并更新工作容器) //workingContainer = this.deepCloneContainer(workingContainer); const processed = this.splitMergedCells(container); console.log('✅ 合并单元格拆分完成'); // 4. 格式转换(使用新副本并更新工作容器) //workingContainer = this.deepCloneContainer(workingContainer); const converted = this.convertToFormats(container); console.log('✅ 格式转换完成'); // 5. 输出结果(使用最终的工作容器) this.outputResults(container); alert("处理完毕!请检查console。温馨提示:重复使用的话,会缺少第一次的简化HTML结果(不影响后面的数据,不算很重要),如果实在很需要第一次的结果,请刷新重试。"); return { stats, simplified, processed, converted, finalContainer: workingContainer // 可选:返回最终处理后的容器 }; } /** * 深度克隆容器的辅助方法。用了之后,那些属性无法去掉,好奇怪,算了,放弃。 */ deepCloneContainer(container) { // 如果是document对象,创建新的文档片段 if (container === document) { const fragment = document.createDocumentFragment(); // 克隆document.body的内容到片段中 if (document.body) { fragment.appendChild(document.body.cloneNode(true)); } return fragment; } // 如果是普通元素,使用cloneNode进行深度克隆 if (container && container.cloneNode) { return container.cloneNode(true); } // 如果是DocumentFragment或其他类型,尝试克隆 if (container && container.nodeType) { return container.cloneNode(true); } // 兜底情况:返回原容器(这种情况下需要在各个方法内部处理) console.warn('⚠️ 无法克隆容器,将使用原始容器'); return container; } } // 使用示例 console.log('📚 HTML表格处理器类已加载'); console.log('💡 使用方法:'); console.log('const processor = new HTMLTableProcessor();'); console.log('processor.processAll(); // 一键处理所有功能'); console.log('或分别调用: simplifyAndExtractTables(), splitMergedCells(), convertToFormats()'); // 创建全局实例供快速使用 window.tableProcessor = new HTMLTableProcessor(); console.log('🎯 全局实例已创建: window.tableProcessor 或 tableProcessor'); window.tableProcessor.processAll(); // -------------------------------------------------------------------------------------------------------------------------------------------
// 单独处理 示例
// 单独处理 示例 // 创建实例 const processor = new HTMLTableProcessor(); // 一键处理所有功能 processor.processAll(); // 或者分别调用各个功能 processor.simplifyAndExtractTables(); //包含完整HTML、纯表格内容和表格数量的对象 processor.splitMergedCells(); processor.convertToFormats(); // 获取统计信息 const stats = processor.getTableStats(); // 复制结果到剪贴板 processor.copyMarkdownToClipboard(); processor.copyTabSeparatedToClipboard(); // 处理特定容器中的表格 const container = document.getElementById('specific-container'); processor.processAll(container);