表格提取

This commit is contained in:
cxs 2025-05-20 19:21:58 +08:00
parent 44050b2391
commit f9ab2ffce0
5 changed files with 1905 additions and 25 deletions

View File

@ -48,6 +48,87 @@ pip install -r requirements.txt
## 最近更新
### 2024年6月11日
- **同时支持HTML标签显示和HTML文件生成**
- 优化表格处理功能,兼顾多种输出需求
- 移除Word文档中自动添加的表格标题保持文档结构简洁
- Word文档中直接以HTML标签形式显示所有表格方便查看表格结构
- 同时生成独立的HTML文件提供完整的表格视图支持交互和打印
- 在Word文档中添加蓝色超链接提示指引用户查看对应的HTML文件
- 改进HTML样式增强响应式布局和打印支持
- 优化表格HTML标签生成过程确保标签规范性和一致性
- 增强错误处理,即使某些表格转换失败也能保持系统稳定
- 提升整体文档处理流程的健壮性和用户体验
### 2024年6月10日
- **采用HTML标签形式输出表格**
- 改进表格处理机制直接输出HTML标签形式的表格而非创建Word表格
- 精确保留所有表格结构信息,包括表头、主体和合并单元格属性
- 自动为表格生成符合HTML规范的标签包括class和id属性
- 正确处理表格中的垂直和水平合并单元格添加rowspan和colspan属性
- 将表格标签以等宽字体显示,提高可读性和直观性
- 优化标签生成过程严格遵循HTML表格标准
- 自动区分表头和数据行使用正确的thead和tbody标签
- 简化表格处理流程,提高效率和准确性
### 2024年6月9日
- **改进Word文档表格显示方式**
- 修改表格处理机制直接在Word文档中显示表格不再需要外部HTML文件
- 准确复制原始表格的结构、内容和合并单元格信息
- 保留表格样式并自动设置表头格式
- 正确处理垂直和水平合并的单元格
- 改进表格位置控制,保持与原始文档的一致性
- 优化表格边框和样式,提供更专业的外观
- 简化处理流程,提高文档生成效率
- 修复合并单元格时的潜在错误
### 2024年6月8日
- **修复Word文档打开问题并改进表格处理**
- 解决了清洗后Word文档无法打开的关键问题
- 优化HTML表格生成方式确保文档处理的稳定性
- 在Word文档中添加醒目的HTML表格文件引用提示
- 保留表格的文本格式作为备用显示方式
- 改进错误处理,提供更详细的诊断信息
- 简化文档处理流程,提高代码可维护性
- 增强HTML表格文件的样式提供更好的打印支持
- 改进文档处理日志,便于追踪处理过程
### 2024年6月7日
- **表格直接HTML输出功能增强**
- 修改表格处理机制现在所有表格都将以HTML格式输出而非文本格式
- 彻底解决复杂表格的显示问题,包括多层表头和合并单元格
- 自动为每个表格生成独特的HTML标识符确保正确引用
- 提供更美观的表格样式,包括悬停效果和自适应宽度
- 改进表格边框和单元格间距,提升阅读体验
- 保留单元格格式化内容如换行符并在HTML中正确显示
- 针对打印场景优化表格样式,确保打印输出质量
- 技术说明由于Word文档格式限制HTML表格将保存在独立的HTML文件中
### 2024年6月6日
- **增强复杂表格识别与处理能力**
- 优化表格类型自动识别算法,通过多维度特征分析提高复杂表格的识别精度
- 增强表格结构分析能力,支持更精确地识别垂直和水平合并单元格
- 改进多级表头处理,提高复杂表头的识别和解析能力
- 引入表格宽高比分析,自动识别宽表格和复杂结构表格
- 新增单元格数一致性检查机制,提高对不规则表格的处理能力
- 优化垂直合并单元格的内容填充算法,改进空单元格的值传播机制
- 添加更详细的表格处理日志,便于诊断和调试复杂表格处理问题
- 完善异常处理,提高处理复杂表格时的稳定性和鲁棒性
### 2024年6月5日
- **模块化表格处理系统升级**
- 重构了表格处理架构,将不同类型的表格处理逻辑拆分为专门的处理器
- 实现了针对五种特殊表格类型的专用处理器:
- 多级表头表格处理器:处理具有复杂多级表头结构的表格
- 合并单元格密集型表格处理器:优化处理含有大量合并单元格的表格
- 带计算功能的表格处理器:识别并处理包含公式和计算的表格
- 嵌套表格处理器:处理表格内嵌套的子表格结构
- 跨页长表格处理器:正确识别和处理跨越多页的长表格
- 优化表格识别机制,智能匹配最适合的处理器
- 提高表格转文本的准确性和可读性
- 保持与原有系统的兼容性,同时提高处理复杂表格的能力
- 增强了系统对特殊表格结构的识别率和处理精度
### 2024年6月2日
- 改进Markdown表格转换功能
- 修复了合并单元格内容重复显示的问题

View File

@ -1,24 +0,0 @@
1111
【图片识别文本】
“ 完 善 了 异 常 处 理 , 防 止 惑 时 目 录 券 除 失 败 导 致 程 序 崖
4. 更 新 README.md
* 在 暨 近 更 新 部 分 记 录 了 临 时 文 件 处 理 机 制 的 改 进
。 添 加 了 Excel 文 件 句 柄 管 理
使 用 说 明
这 东 改 进 不 需 要 您 做 任 何 额 外 操 作 , 系 统 会 自 动 -
1. 在 处 理 Excel 文 件 时 正 球 关 闭 文 件 句 柄
2 当 尝 试 删 除 文 件 通 刨 “ 文 件 被 占 用 “ 错 误 时 , 自 动 等 待
并 重 试
3 即 使 无 法 券 除 临 时 文 件 , 也 不 影 响 处 #
如 果 仍 然 通 到 惧 时 文 件 问 题 , 系 统 会 在 下 次 启 动 时 自 动
清 理 所 有 临 时 文 件 , 不 会 影 响 系 统 功 能 。
以 上 优 化 星 觞 失 了 临 时 文 伟 删 除 问 题 , 又 保 持 了 系 统 的
稳 定 性 , 让 您 能 雪 顺 畅 地 处 理 Bxcel 文 件 。

View File

@ -1 +0,0 @@
1111 【图片识别文本】 “ 完 善 了 异 常 处 理 , 防 止 惑 时 目 录 券 除 失 败 导 致 程 序 崖 澎 澎 4. 更 新 README.md * 在 暨 近 更 新 部 分 记 录 了 临 时 文 件 处 理 机 制 的 改 进 。 添 加 了 Excel 文 件 句 柄 管 理 使 用 说 明 这 东 改 进 不 需 要 您 做 任 何 额 外 操 作 , 系 统 会 自 动 - 1. 在 处 理 Excel 文 件 时 正 球 关 闭 文 件 句 柄 2 当 尝 试 删 除 文 件 通 刨 “ 文 件 被 占 用 “ 错 误 时 , 自 动 等 待 并 重 试 3 即 使 无 法 券 除 临 时 文 件 , 也 不 影 响 处 # 如 果 仍 然 通 到 惧 时 文 件 问 题 , 系 统 会 在 下 次 启 动 时 自 动 清 理 所 有 临 时 文 件 , 不 会 影 响 系 统 功 能 。 以 上 优 化 星 觞 失 了 临 时 文 伟 删 除 问 题 , 又 保 持 了 系 统 的 稳 定 性 , 让 您 能 雪 顺 畅 地 处 理 Bxcel 文 件 。

1380
table/table_cleaner.py Normal file

File diff suppressed because it is too large Load Diff

444
table/table_to_html.py Normal file
View File

@ -0,0 +1,444 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import docx
import re
from docx.table import Table, _Cell
from docx.oxml import parse_xml
from docx.oxml.ns import nsdecls
from typing import List, Dict, Tuple, Optional, Union
import uuid
from bs4 import BeautifulSoup
import html
class TableToHtml:
def __init__(self, debug: bool = False):
"""
初始化表格到HTML转换器
Args:
debug: 是否启用调试模式输出更多日志信息
"""
self.debug = debug
# 为每个表格生成唯一ID
self.table_id = f"table_{uuid.uuid4().hex[:8]}"
def _log(self, message: str):
"""
输出调试日志
Args:
message: 日志消息
"""
if self.debug:
print(f"[TableToHtml] {message}")
def _get_vmerge_value(self, cell_element) -> Optional[str]:
"""
获取单元格的垂直合并属性
Args:
cell_element: 单元格元素
Returns:
str: 垂直合并属性值
"""
vmerge = cell_element.xpath('.//w:vMerge')
if vmerge:
return vmerge[0].get('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}val', 'continue')
return None
def _get_gridspan_value(self, cell_element) -> int:
"""
获取单元格的水平合并数量
Args:
cell_element: 单元格元素
Returns:
int: 水平合并的列数
"""
try:
gridspan = cell_element.xpath('.//w:gridSpan')
if gridspan and gridspan[0].get('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}val'):
return int(gridspan[0].get('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}val'))
except (ValueError, TypeError, AttributeError) as e:
self._log(f"警告获取gridspan值时出错: {str(e)}")
return 1 # 默认返回1表示没有合并
def _get_cell_content(self, cell: _Cell) -> str:
"""
获取单元格的文本内容并处理HTML特殊字符
Args:
cell: docx表格单元格对象
Returns:
str: 处理后的HTML内容
"""
content = cell.text.strip()
# 转义HTML特殊字符
content = html.escape(content)
# 处理换行
content = content.replace('\n', '<br>')
return content
def _analyze_table_structure(self, table: Table) -> Dict:
"""
分析表格结构包括合并单元格信息
Args:
table: docx表格对象
Returns:
Dict: 表格结构信息
"""
rows = len(table.rows)
cols = len(table.columns)
# 存储合并单元格信息
merged_cells = {}
# 存储垂直合并的源单元格
vmerge_sources = {}
# 分析合并单元格
for i in range(rows):
for j in range(cols):
try:
cell = table.cell(i, j)
# 检查垂直合并
if cell._element.tcPr is not None:
vmerge = cell._element.tcPr.xpath('.//w:vMerge')
if vmerge:
val = self._get_vmerge_value(cell._element)
if val == 'restart':
# 这是垂直合并的起始单元格
# 计算合并的行数
rowspan = 1
for k in range(i+1, rows):
next_cell = table.cell(k, j)
if self._get_vmerge_value(next_cell._element) == 'continue':
rowspan += 1
# 标记此单元格为被合并
merged_cells[(k, j)] = {'merged': True, 'source': (i, j)}
else:
break
# 记录合并信息
vmerge_sources[(i, j)] = {'rowspan': rowspan}
elif val == 'continue':
# 这是被合并的单元格,稍后处理
pass
# 检查水平合并
if cell._element.tcPr is not None:
gridspan = self._get_gridspan_value(cell._element)
if gridspan > 1:
# 记录colspan
merged_cells[(i, j)] = {'colspan': gridspan}
# 标记被合并的单元格
for k in range(1, gridspan):
if j + k < cols:
merged_cells[(i, j+k)] = {'merged': True, 'source': (i, j)}
except Exception as e:
self._log(f"警告:分析单元格 [{i},{j}] 时出错: {str(e)}")
continue
# 将垂直合并信息合并到主合并字典
for pos, info in vmerge_sources.items():
if pos in merged_cells:
merged_cells[pos].update(info)
else:
merged_cells[pos] = info
return {
'rows': rows,
'cols': cols,
'merged_cells': merged_cells
}
def _is_header_row(self, row_idx: int, table: Table, structure: Dict) -> bool:
"""
判断是否为表头行
Args:
row_idx: 行索引
table: 表格对象
structure: 表格结构信息
Returns:
bool: 是否为表头行
"""
# 简单策略:第一行通常是表头
if row_idx == 0:
return True
# 检查是否有垂直合并从第一行开始的单元格
for j in range(structure['cols']):
cell_pos = (row_idx, j)
if cell_pos in structure['merged_cells'] and 'merged' in structure['merged_cells'][cell_pos]:
source = structure['merged_cells'][cell_pos]['source']
if source[0] == 0: # 合并源是第一行
return True
return False
def _detect_table_headers(self, table: Table, structure: Dict) -> List[int]:
"""
检测表格表头行
Args:
table: 表格对象
structure: 表格结构信息
Returns:
List[int]: 表头行索引列表
"""
header_rows = []
rows = structure['rows']
# 检查前3行或所有行如果行数少于3
for i in range(min(3, rows)):
if self._is_header_row(i, table, structure):
header_rows.append(i)
# 如果没有检测到表头,默认第一行为表头
if not header_rows and rows > 0:
header_rows = [0]
self._log(f"检测到的表头行: {header_rows}")
return header_rows
def table_to_html(self, table: Table) -> str:
"""
将docx表格转换为HTML格式
Args:
table: docx表格对象
Returns:
str: HTML表格代码
"""
try:
# 分析表格结构
structure = self._analyze_table_structure(table)
rows = structure['rows']
cols = structure['cols']
merged_cells = structure['merged_cells']
self._log(f"表格结构: {rows}行 x {cols}列,合并单元格: {len(merged_cells)}")
# 检测表头
header_rows = self._detect_table_headers(table, structure)
# 构建HTML表格
soup = BeautifulSoup('<table></table>', 'html.parser')
table_tag = soup.table
table_tag['class'] = ['docx-table']
table_tag['id'] = self.table_id
# 添加表头部分thead
if header_rows:
thead = soup.new_tag('thead')
table_tag.append(thead)
for i in header_rows:
if i >= rows:
continue
tr = soup.new_tag('tr')
thead.append(tr)
j = 0
while j < cols:
cell_pos = (i, j)
# 检查是否被合并
if cell_pos in merged_cells and 'merged' in merged_cells[cell_pos]:
j += 1
continue
# 创建th元素
th = soup.new_tag('th')
# 处理合并
if cell_pos in merged_cells:
if 'rowspan' in merged_cells[cell_pos]:
th['rowspan'] = merged_cells[cell_pos]['rowspan']
if 'colspan' in merged_cells[cell_pos]:
th['colspan'] = merged_cells[cell_pos]['colspan']
j += merged_cells[cell_pos]['colspan'] - 1
# 设置单元格内容
cell = table.cell(i, j)
content = self._get_cell_content(cell)
th.string = content
tr.append(th)
j += 1
# 添加表格主体tbody
tbody = soup.new_tag('tbody')
table_tag.append(tbody)
# 计算数据行的起始索引
data_start = max(header_rows) + 1 if header_rows else 0
# 处理数据行
for i in range(data_start, rows):
tr = soup.new_tag('tr')
tbody.append(tr)
j = 0
while j < cols:
cell_pos = (i, j)
# 检查是否被合并
if cell_pos in merged_cells and 'merged' in merged_cells[cell_pos]:
j += 1
continue
# 创建td元素
td = soup.new_tag('td')
# 处理合并
if cell_pos in merged_cells:
if 'rowspan' in merged_cells[cell_pos]:
td['rowspan'] = merged_cells[cell_pos]['rowspan']
if 'colspan' in merged_cells[cell_pos]:
td['colspan'] = merged_cells[cell_pos]['colspan']
j += merged_cells[cell_pos]['colspan'] - 1
# 设置单元格内容
cell = table.cell(i, j)
content = self._get_cell_content(cell)
td.string = content
tr.append(td)
j += 1
# 添加基本的CSS样式
style = soup.new_tag('style')
style.string = f'''
#{self.table_id} {{
border-collapse: collapse;
width: 100%;
margin-bottom: 1em;
font-family: Arial, sans-serif;
}}
#{self.table_id} th, #{self.table_id} td {{
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}}
#{self.table_id} th {{
background-color: #f2f2f2;
font-weight: bold;
}}
#{self.table_id} tr:nth-child(even) {{
background-color: #f9f9f9;
}}
#{self.table_id} tr:hover {{
background-color: #f5f5f5;
}}
'''
# 返回完整的HTML代码
html_code = str(style) + str(table_tag)
return html_code
except Exception as e:
self._log(f"转换表格到HTML时出错: {str(e)}")
import traceback
traceback.print_exc()
return f"<div class='error'>表格处理失败: {str(e)}</div>"
def process_document_tables(self, doc_path: str) -> List[str]:
"""
处理文档中的所有表格并转换为HTML
Args:
doc_path: 文档文件路径
Returns:
List[str]: HTML表格代码列表
"""
try:
# 打开文档
doc = docx.Document(doc_path)
html_tables = []
# 处理所有表格
for i, table in enumerate(doc.tables):
self._log(f"处理第 {i+1} 个表格")
self.table_id = f"table_{uuid.uuid4().hex[:8]}" # 为每个表格生成唯一ID
html_code = self.table_to_html(table)
html_tables.append(html_code)
return html_tables
except Exception as e:
self._log(f"处理文档表格时出错: {str(e)}")
import traceback
traceback.print_exc()
return [f"<div class='error'>文档处理失败: {str(e)}</div>"]
def convert_tables_to_html(doc_path: str, output_path: str = None, debug: bool = False):
"""
将文档中的表格转换为HTML并保存
Args:
doc_path: 文档文件路径
output_path: 输出HTML文件路径如果为None则使用原文件名+.html
debug: 是否启用调试模式
Returns:
str: 输出文件路径
"""
if output_path is None:
# 创建默认输出路径
base_name = os.path.splitext(doc_path)[0]
output_path = f"{base_name}_tables.html"
converter = TableToHtml(debug=debug)
html_tables = converter.process_document_tables(doc_path)
# 创建完整HTML文档
html_content = f'''<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>表格预览</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1>文档中的表格</h1>
{' '.join(html_tables)}
</body>
</html>'''
# 保存HTML文件
with open(output_path, 'w', encoding='utf-8') as f:
f.write(html_content)
if debug:
print(f"HTML文件已保存到: {output_path}")
return output_path
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='将Word文档中的表格转换为HTML')
parser.add_argument('input_file', help='输入文档文件路径')
parser.add_argument('-o', '--output', help='输出HTML文件路径', default=None)
parser.add_argument('-d', '--debug', action='store_true', help='启用调试模式')
args = parser.parse_args()
result_path = convert_tables_to_html(args.input_file, args.output, args.debug)
print(f"表格已转换为HTML文件路径: {result_path}")