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);

 

posted @ 2025-06-11 17:56  zzgreg  阅读(64)  评论(0)    收藏  举报