""" 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'') 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