Report_Generate_Server/core/tables.py

303 lines
11 KiB
Python
Raw Normal View History

"""
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-968代表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