303 lines
11 KiB
Python
303 lines
11 KiB
Python
"""
|
||
Table-related operations for Word Document Server.
|
||
"""
|
||
from docx.oxml.shared import OxmlElement, qn
|
||
from docx.oxml.ns import nsdecls
|
||
from docx.oxml import parse_xml
|
||
|
||
|
||
def set_cell_border(cell, **kwargs):
|
||
"""
|
||
Set cell border properties.
|
||
|
||
Args:
|
||
cell: The cell to modify
|
||
**kwargs: Border properties (top, bottom, left, right, val, color)
|
||
"""
|
||
tc = cell._tc
|
||
tcPr = tc.get_or_add_tcPr()
|
||
|
||
# Create border elements
|
||
for key, value in kwargs.items():
|
||
if key in ['top', 'left', 'bottom', 'right']:
|
||
tag = 'w:{}'.format(key)
|
||
|
||
element = OxmlElement(tag)
|
||
element.set(qn('w:val'), kwargs.get('val', 'single'))
|
||
element.set(qn('w:sz'), kwargs.get('sz', '4'))
|
||
element.set(qn('w:space'), kwargs.get('space', '0'))
|
||
element.set(qn('w:color'), kwargs.get('color', 'auto'))
|
||
|
||
tcBorders = tcPr.first_child_found_in("w:tcBorders")
|
||
if tcBorders is None:
|
||
tcBorders = OxmlElement('w:tcBorders')
|
||
tcPr.append(tcBorders)
|
||
|
||
tcBorders.append(element)
|
||
|
||
|
||
def apply_table_style(table, has_header_row=False, border_style=None, shading=None):
|
||
"""
|
||
Apply formatting to a table.
|
||
|
||
Args:
|
||
table: The table to format
|
||
has_header_row: If True, formats the first row as a header
|
||
border_style: Style for borders ('none', 'single', 'double', 'thick')
|
||
shading: 2D list of cell background colors (by row and column)
|
||
|
||
Returns:
|
||
True if successful, False otherwise
|
||
"""
|
||
try:
|
||
# Format header row if requested
|
||
if has_header_row and table.rows:
|
||
header_row = table.rows[0]
|
||
for cell in header_row.cells:
|
||
for paragraph in cell.paragraphs:
|
||
if paragraph.runs:
|
||
for run in paragraph.runs:
|
||
run.bold = True
|
||
|
||
# Apply border style if specified
|
||
if border_style:
|
||
val_map = {
|
||
'none': 'nil',
|
||
'single': 'single',
|
||
'double': 'double',
|
||
'thick': 'thick'
|
||
}
|
||
val = val_map.get(border_style.lower(), 'single')
|
||
|
||
# Apply to all cells
|
||
for row in table.rows:
|
||
for cell in row.cells:
|
||
set_cell_border(
|
||
cell,
|
||
top=True,
|
||
bottom=True,
|
||
left=True,
|
||
right=True,
|
||
val=val,
|
||
color="000000"
|
||
)
|
||
|
||
# Apply cell shading if specified
|
||
if shading:
|
||
for i, row_colors in enumerate(shading):
|
||
if i >= len(table.rows):
|
||
break
|
||
for j, color in enumerate(row_colors):
|
||
if j >= len(table.rows[i].cells):
|
||
break
|
||
try:
|
||
# Apply shading to cell
|
||
cell = table.rows[i].cells[j]
|
||
shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="{color}"/>')
|
||
cell._tc.get_or_add_tcPr().append(shading_elm)
|
||
except:
|
||
# Skip if color format is invalid
|
||
pass
|
||
|
||
return True
|
||
except Exception:
|
||
return False
|
||
|
||
def copy_table(source_table, target_doc, ifadjustheight=True, height = 1, if_merge = True, REPORT_ENUM = 'DT'):
|
||
"""
|
||
Copy a table from one document to another.
|
||
|
||
Args:
|
||
source_table: The table to copy
|
||
target_doc: The document to copy the table to
|
||
|
||
Returns:
|
||
The new table in the target document
|
||
"""
|
||
# Create a new table with the same dimensions
|
||
new_table = target_doc.add_table(rows=len(source_table.rows), cols=len(source_table.columns))
|
||
# 获取表格属性
|
||
tbl_pr = new_table._tbl.tblPr
|
||
|
||
# 设置表格边框
|
||
tbl_borders = OxmlElement('w:tblBorders')
|
||
for border_name in ('top', 'left', 'bottom', 'right', 'insideH', 'insideV'):
|
||
border = OxmlElement(f'w:{border_name}')
|
||
border.set(qn('w:val'), 'single') # 边框类型
|
||
border.set(qn('w:sz'), '4') # 边框粗细(1-96,8代表1磅)
|
||
border.set(qn('w:space'), '0') # 边框间距
|
||
border.set(qn('w:color'), 'auto') # 边框颜色
|
||
tbl_borders.append(border)
|
||
|
||
tbl_pr.append(tbl_borders)
|
||
|
||
# Try to apply the same style
|
||
try:
|
||
if source_table.style:
|
||
new_table.style = 'Table Grid'
|
||
except:
|
||
# Fall back to default grid style
|
||
try:
|
||
new_table.style = 'Table Grid'
|
||
except:
|
||
pass
|
||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||
from docx.enum.table import WD_ALIGN_VERTICAL, WD_ROW_HEIGHT_RULE
|
||
from docx.shared import Pt, Inches, Cm, RGBColor
|
||
|
||
# Copy cell contents
|
||
for i, row in enumerate(source_table.rows):
|
||
for j, cell in enumerate(row.cells):
|
||
for paragraph in cell.paragraphs:
|
||
average_char_width_in_points = 6
|
||
if paragraph.text:
|
||
new_table.cell(i,j).text = paragraph.text
|
||
new_table.cell(i,j).paragraphs[0].runs[0].font.name = "Times New Roman" #设置英文字体
|
||
new_table.cell(i,j).paragraphs[0].runs[0].font.size = Pt(10.5) # 字体大小
|
||
new_table.cell(i,j).paragraphs[0].runs[0]._element.rPr.rFonts.set(qn('w:eastAsia'), '仿宋') #设置中文字体
|
||
new_table.cell(i,j).paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||
new_table.cell(i,j).vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||
#new_table.cell(i,j).width = cell.width
|
||
if REPORT_ENUM == 'JF':
|
||
new_table.cell(i,j).paragraphs[0].runs[0]._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') #设置中文字体
|
||
new_table.cell(i,j).paragraphs[0].runs[0].font.size = Pt(8) # 字体大小
|
||
"""
|
||
待添加:如何让表格自适应大小(autofit目前不知为何没有作用)
|
||
"""
|
||
#new_table.rows[i].height = row.height
|
||
#new_table.rows[i].height_rule = WD_ROW_HEIGHT_RULE.EXACTLY
|
||
|
||
if not ifadjustheight:
|
||
new_table.auto_fit = True
|
||
if if_merge:
|
||
try:
|
||
new_table = merge_tables(new_table)
|
||
except Exception as e:
|
||
print(f"合并表格失败:{e}")
|
||
return target_doc
|
||
|
||
|
||
from collections import deque
|
||
|
||
def merge_tables(table):
|
||
"""BFS遍历,将相邻且相同单元格合并
|
||
|
||
Args:
|
||
table: 表格,docx库的Table类型
|
||
Returns:
|
||
合并后的表格
|
||
"""
|
||
if not table or len(table.rows) == 0:
|
||
return table
|
||
|
||
rows = len(table.rows)
|
||
cols = len(table.columns)
|
||
|
||
# 创建访问标记矩阵
|
||
visited = [[False for _ in range(cols)] for _ in range(rows)]
|
||
|
||
# 定义四个方向的移动:上、右、下、左
|
||
directions = [(0, 0), (0, 1), (1, 0), (0, 0)]
|
||
|
||
for i in range(rows):
|
||
for j in range(cols):
|
||
if not visited[i][j]:
|
||
current_cell = table.cell(i, j)
|
||
current_text = current_cell.text.strip()
|
||
|
||
if not current_text: # 跳过空单元格
|
||
visited[i][j] = True
|
||
continue
|
||
|
||
# BFS队列
|
||
queue = deque()
|
||
queue.append((i, j))
|
||
visited[i][j] = True
|
||
|
||
# 记录需要合并的单元格
|
||
cells_to_merge = []
|
||
|
||
while queue:
|
||
x, y = queue.popleft()
|
||
cells_to_merge.append((x, y))
|
||
|
||
for dx, dy in directions:
|
||
nx, ny = x + dx, y + dy
|
||
|
||
# 检查边界和访问状态
|
||
if 0 <= nx < rows and 0 <= ny < cols and not visited[nx][ny]:
|
||
neighbor_cell = table.cell(nx, ny)
|
||
neighbor_text = neighbor_cell.text.strip()
|
||
|
||
if neighbor_text == current_text:
|
||
visited[nx][ny] = True
|
||
queue.append((nx, ny))
|
||
|
||
# 如果有需要合并的单元格
|
||
if len(cells_to_merge) > 1:
|
||
# 按行和列排序,确保左上角是第一个单元格
|
||
cells_to_merge.sort()
|
||
min_row, min_col = cells_to_merge[0]
|
||
max_row, max_col = cells_to_merge[-1]
|
||
|
||
# 清空所有待合并单元格(包括换行符)
|
||
for x, y in cells_to_merge[1:]:
|
||
cell = table.cell(x, y)
|
||
# 删除所有段落(彻底清空)
|
||
for paragraph in list(cell.paragraphs):
|
||
p = paragraph._element
|
||
p.getparent().remove(p)
|
||
# 可选:添加一个空段落防止格式问题
|
||
cell.add_paragraph()
|
||
|
||
# 执行合并
|
||
if max_row > min_row or max_col > min_col:
|
||
table.cell(min_row, min_col).merge(table.cell(max_row, max_col))
|
||
|
||
return table
|
||
|
||
def fill_tables(Y_table_list, row, col, Y_Table_num, Y):
|
||
"""根据前端返回json块填写表格list,并实时跟进已填写表格数量
|
||
目前只支持固定的缺陷图的填写
|
||
|
||
Args:
|
||
Y_table_list (list): 前端返回的json块
|
||
row (int): 表格行数
|
||
col (int): 表格列数
|
||
Y_Table_num: json块中有几个表格
|
||
Xu_Hao: 是第几个json块
|
||
Y: 其他参数
|
||
|
||
Return:
|
||
Y1_TABLES: 三维,表和对应元素
|
||
table_index_to_images: 字典,表索引到图片路径列表的映射
|
||
Xu_Hao:到达第几个表了
|
||
"""
|
||
table_index_to_images = {}
|
||
Y_TABLES = [[["" for _ in range(row)] for _ in range(col)] for _ in range(Y_Table_num)]
|
||
|
||
# 处理前端返回数据
|
||
for l, table_dict in enumerate(Y_table_list):
|
||
if table_dict:
|
||
Y_TABLES[l][1][0] = Y
|
||
Y_TABLES[l][1][1] = table_dict["QueXianLeiXing"]
|
||
Y_TABLES[l][1][2] = table_dict["QueXianWeiZhi"]
|
||
Y_TABLES[l][1][3] = table_dict["QueXianChiCun"]
|
||
Y_TABLES[l][3][0] = table_dict["WeiZongDengJi"]
|
||
Y_TABLES[l][3][1] = table_dict["visibility"]
|
||
Y_TABLES[l][3][2] = table_dict["urgency"]
|
||
Y_TABLES[l][3][3] = table_dict["repair_suggestion"]
|
||
|
||
# 获取图片路径
|
||
image_path = table_dict['Tupian_Dir']
|
||
if image_path:
|
||
# 确保路径是字符串形式
|
||
if isinstance(image_path, list):
|
||
table_index_to_images[l] = image_path.copy()
|
||
else:
|
||
table_index_to_images[l] = [str(image_path)]
|
||
|
||
return Y_TABLES, table_index_to_images
|
||
|
||
|