""" Document creation and manipulation tools for Word Document Server. """ import os import json, re from typing import Dict, List, Optional, Any from docx import Document from core.tables import copy_table from utils.file_utils import check_file_writeable, ensure_docx_extension, create_document_copy from utils.document_utils import get_document_properties, extract_document_text, get_document_structure, clear_header from core.styles import ensure_heading_style, ensure_table_style from docx.oxml.shared import qn from docx.oxml import OxmlElement, parse_xml from tools.content_tools import search_and_replace,add_picture_to_table,add_picture from tools.Get_Json import get_full_picture_url from tools.get_pictures import resize_and_reduce_quality, get_template_pic from tools.defines import * from docx.enum.section import WD_SECTION from tools.json_to_docx import list_to_json_with_merges, json_to_docx async def create_document(filename: str, title: Optional[str] = None, author: Optional[str] = None, section_args: Optional[Dict[str, Any]] = None) -> str: """创建一个包含可选元数据的新Word文档。 参数: filename: 要创建的文档名称(带或不带.docx扩展名) title: 可选标题 author: 可选作者 """ filename = ensure_docx_extension(filename) # Check if file is writeable is_writeable, error_message = check_file_writeable(filename) if not is_writeable: return f"Cannot create document: {error_message}" try: doc = Document() # Set properties if provided if title: doc.core_properties.title = title if author: doc.core_properties.author = author # Ensure necessary styles exist ensure_heading_style(doc) ensure_table_style(doc) # 更改纸张大小为A4 from docx.shared import Mm, Inches sections = doc.sections if section_args: for section in sections: section.page_height = Mm(section_args.get('page_height', 297)) section.page_width = Mm(section_args.get('page_width', 210)) section.left_margin = Mm(section_args.get('left_margin', 20)) section.right_margin = Mm(section_args.get('right_margin', 20)) section.top_margin = Mm(section_args.get('top_margin', 10)) section.bottom_margin = Mm(section_args.get('bottom_margin', 10)) else: for section in sections: section.page_height = Mm(297) section.page_width = Mm(210) section.left_margin = Inches(0.94) section.right_margin = Inches(0.94) # Save the document doc.save(filename) return f"Document {filename} created successfully" except Exception as e: return f"Failed to create document: {str(e)}" async def get_document_info(filename: str) -> str: """获得文档信息 Args: filename: 目标文档 """ filename = ensure_docx_extension(filename) if not os.path.exists(filename): return f"Document {filename} does not exist" try: properties = get_document_properties(filename) return json.dumps(properties, indent=2) except Exception as e: return f"Failed to get document info: {str(e)}" async def get_document_text(filename: str) -> str: """获得文档的所有文本 Args: filename: 目标文档 """ filename = ensure_docx_extension(filename) return extract_document_text(filename) async def get_document_outline(filename: str) -> str: """获得文档的所有结构信息 Args: filename: 目标文档 """ filename = ensure_docx_extension(filename) structure = get_document_structure(filename) return json.dumps(structure, indent=2) async def list_available_documents(directory: str = ".") -> str: """列出目录下所有Word文档 Args: directory: 目录 """ try: if not os.path.exists(directory): return f"Directory {directory} does not exist" docx_files = [f for f in os.listdir(directory) if f.endswith('.docx')] if not docx_files: return f"No Word documents found in {directory}" result = f"Found {len(docx_files)} Word documents in {directory}:\n" for file in docx_files: file_path = os.path.join(directory, file) size = os.path.getsize(file_path) / 1024 # KB result += f"- {file} ({size:.2f} KB)\n" return result except Exception as e: return f"Failed to list documents: {str(e)}" async def copy_document(source_filename: str, destination_filename: Optional[str] = None) -> str: """创建文档的副本 Args: source_filename: 源文档路径 destination_filename: 目标文档路径,为空则为当前目录 """ source_filename = ensure_docx_extension(source_filename) if destination_filename: destination_filename = ensure_docx_extension(destination_filename) success, message, new_path = create_document_copy(source_filename, destination_filename) if success: return message else: return f"Failed to copy document: {message}" def add_documents(target_filename: str, source_filename: str) -> str: """将源文档(文本)添加到目标文档尾部 Args: target_doc: 目标文档 source_filename: 源文档路径 """ target_doc = Document(target_filename) source_filename = ensure_docx_extension(source_filename) source_doc = Document(source_filename) for source_paragraph in source_doc.paragraphs: new_paragraph = target_doc.add_paragraph(source_paragraph.text) new_paragraph.style = target_doc.styles['Normal'] # Default style #获取合并等样式2025427 new_paragraph.alignment = source_paragraph.alignment # Try to match the style if possible try: if source_paragraph.style and source_paragraph.style.name in target_doc.styles: new_paragraph.style = target_doc.styles[source_paragraph.style.name] except Exception as e: print(f"Failed to apply style: {e}") # Copy run formatting for i, run in enumerate(source_paragraph.runs): if i < len(new_paragraph.runs): new_run = new_paragraph.runs[i] # Copy basic formatting new_run.bold = run.bold new_run.italic = run.italic new_run.underline = run.underline #添加同时合并字体2025427 new_run.font.name = run.font.name rPr = new_run.element.get_or_add_rPr() rFonts = rPr.get_or_add_rFonts() # 检查 run.font.name 是否为 None if run.font.name is None: # 设置默认的中文字体名称 run.font.name = '宋体 (中文正文)' # 或者使用其他你喜欢的中文字体 rFonts.set(qn('w:eastAsia'), run.font.name) new_run.font.color.rgb = run.font.color.rgb # Font size if specified if run.font.size: new_run.font.size = run.font.size target_doc.save(target_filename) return f"{target_filename}添加{source_filename}成功" def write_table(target_filename: str, rows: int, cols: int, table_num: int, data: Optional[List[List[str]]] = None, ifadjustheight: Optional[bool] = True, height: Optional[float] = None, key_words: re.Pattern[str] = None, ALIGMENT: Optional[str] = 'CENTER', width: Optional[float] = None) -> Document: """填写word文档里的表格,返回填写后的文档 Args: target_filename: 目标文档路径 rows: 表格行数 cols: 表格列数 table_num: 表格序号 data: 表格数据,二维列表,每个单元格为字符串 ifadjustheight: bool,为真则表格行高自动调整 """ table_num = -1 target_filename = ensure_docx_extension(target_filename) # Check if target file is writeable is_writeable, error_message = check_file_writeable(target_filename) if not is_writeable: return f"Cannot create target document: {error_message}" try: target_filename = ensure_docx_extension(target_filename) target_doc = Document(target_filename) except Exception as e: print(f"获取{target_filename}失败:{str(e)}") # Try to set the table style try: target_doc.tables[table_num].style = 'Table Grid' except KeyError as k: pass except Exception as e: print(f"{target_doc}最后一个表格更改样式失败: {str(e)}") print("开始写入表格") from docx.enum.table import WD_TABLE_ALIGNMENT from docx.enum.table import WD_ALIGN_VERTICAL from docx.shared import Pt, Inches, Cm, RGBColor try: if data: for i, row_data in enumerate(data): if i >= rows + 1: break for j, cell_text in enumerate(row_data): if j >= cols + 1: break if cell_text is not None and cell_text.endswith((".png", ".jpg", ".jpeg")): target_doc.save(target_filename) target_doc.tables[table_num].cell(i,j).text = "" print(f"在[{i},{j}]处添加图片{cell_text}") if os.path.exists(cell_text): if height: target_doc.tables[table_num].cell(i,j).add_paragraph().add_run().add_picture(cell_text, height=Inches(height), width=Inches(width)) else: target_doc.tables[table_num].cell(i,j).add_paragraph().add_run().add_picture(cell_text,) continue else: print(f"本地没有图片{cell_text},从服务器下载图片") if height: add_picture_to_table(target_doc, target_filename, i, j, cell_text, height=Inches(height), width=Inches(width)) else: add_picture_to_table(target_doc, target_filename, i, j, get_full_picture_url(cell_text)) continue if str(cell_text) == "": continue print(f"在的[{i},{j}]处写入{str(cell_text)}") target_doc.tables[table_num].cell(i,j).text = str(cell_text) print(key_words, cell_text) if key_words and key_words.search(str(cell_text)): print(f'{cell_text}包含关键字,已置红') target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 0, 0) target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0].font.name = "Times New Roman" #设置英文字体 target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0].font.size = Pt(9) # 字体大小 target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0]._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') #设置中文字体 if ALIGMENT == 'CENTER': target_doc.tables[table_num].cell(i,j).paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.CENTER elif ALIGMENT == 'LEFT': target_doc.tables[table_num].cell(i,j).paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.LEFT target_doc.tables[table_num].cell(i,j).vertical_alignment = WD_ALIGN_VERTICAL.CENTER except Exception as e: print(f"写入{target_filename}tables.cell({i},{j})失败:{str(e)}") print("表格写入完成") return target_doc def add_table_to_document(target_filename: str, source_filename: str, rows: int, cols: int, table_num: int, data: Optional[List[List[str]]] = None, ifadjustheight: Optional[bool] = True, height: Optional[float] = None, key_words: re.Pattern[str] = None, ALIGMENT: Optional[str] = 'CENTER', if_merge : Optional[bool] = True, Report_Enum = 'DT', if_para : Optional[bool] = True, width : Optional[float] = None) -> str: """复制源文件中的文字与表格(先文字后表格格式)到目标文档 Args: target_filename: 目标文档路径 source_doc: 源文档路径 rows: 表格行数 cols: 表格列数 table_num: 表格序号 data: 表格数据,二维列表,每个单元格为字符串 ifadjustheight: bool,为真则表格行高自动调整 key_words: list, 关键字 """ target_filename = ensure_docx_extension(target_filename) source_filename = ensure_docx_extension(source_filename) source_doc = Document(source_filename) target_doc = Document(target_filename) if if_para: try: # Copy all paragraphs for paragraph in source_doc.paragraphs: # Create a new paragraph with the same text and style new_paragraph = target_doc.add_paragraph(paragraph.text) new_paragraph.style = target_doc.styles['Normal'] # Default style #获取合并等样式2025427 new_paragraph.alignment = paragraph.alignment # 复制段落分页属性 new_paragraph.paragraph_format.page_break_before = paragraph.paragraph_format.page_break_before # Try to match the style if possible try: if paragraph.style and paragraph.style.name in target_doc.styles: new_paragraph.style = target_doc.styles[paragraph.style.name] except: pass # Copy run formatting for i, run in enumerate(paragraph.runs): if i < len(new_paragraph.runs): new_run = new_paragraph.runs[i] # Copy basic formatting new_run.bold = run.bold new_run.italic = run.italic new_run.underline = run.underline #添加同时合并字体2025427 new_run.font.name = run.font.name rPr = new_run.element.get_or_add_rPr() rFonts = rPr.get_or_add_rFonts() # 检查 run.font.name 是否为 None if run.font.name is None: # 设置默认的中文字体名称 run.font.name = '宋体(中文正文)' rFonts.set(qn('w:eastAsia'), run.font.name) new_run.font.color.rgb = run.font.color.rgb # Font size if specified if run.font.size: new_run.font.size = run.font.size except Exception as e: print(f"添加表格前文章失败:{str(e)}") try:# Copy all tables copy_table(source_doc.tables[0], target_doc, ifadjustheight, height, if_merge, REPORT_ENUM=Report_Enum) except Exception as e: print(f"添加表格失败:{str(e)}") target_doc.save(target_filename) print(f"{target_doc}写入表格{source_doc.tables[0]}成功") if data: try: target_doc = write_table(target_filename, rows, cols, table_num, data, ifadjustheight, height, key_words, ALIGMENT, width) except Exception as e: print(f"{target_filename}写入{data}失败:{str(e)}") target_doc.save(target_filename) return target_doc,f"{target_filename}添加表格{source_doc}成功" from docx.document import Document as Document_ def add_table(target_filename: str, source_filename : str | Document_, if_merge = True, REPORT_ENUM = 'DT'): if isinstance(source_filename, str): output_doc = copy_table(Document(source_filename).tables[0], Document(target_filename), True, if_merge=if_merge, REPORT_ENUM = REPORT_ENUM) output_doc.save(target_filename) else: try: output_doc = copy_table(source_filename.tables[0], Document(target_filename), True, if_merge=if_merge, REPORT_ENUM = REPORT_ENUM) output_doc.save(target_filename) except Exception as e: print(f"添加表格失败:{str(e)}") return f"{target_filename}添加表格{source_filename}成功" def add_table_and_replace( target_filename: str, source_filename: str, ifadjustheight: Optional[bool] = True, list_to_replace: dict = {}, height: Optional[float] = 1, no_para : Optional[bool] = False, REPORT_ENUM = 'DT'): """复制源文件中的文字与表格(先文字后表格格式)到目标文档 Args: target_filename: 目标文档路径 source_doc: 源文档路径 ifadjustheight: bool,为真则表格行高自动调整 list_to_replace: dict, 待替换内容和替换内容 height: float, 表格行高 no_para: bool, 无段落 """ target_filename = ensure_docx_extension(target_filename) source_filename = ensure_docx_extension(source_filename) source_doc = Document(source_filename) target_doc = Document(target_filename) if not no_para: try: # Copy all paragraphs for paragraph in source_doc.paragraphs: # Create a new paragraph with the same text and style new_paragraph = target_doc.add_paragraph(paragraph.text) new_paragraph.style = target_doc.styles['Normal'] # Default style #获取合并等样式2025427 new_paragraph.alignment = paragraph.alignment # 复制段落分页属性 new_paragraph.paragraph_format.page_break_before = paragraph.paragraph_format.page_break_before # Try to match the style if possible try: if paragraph.style and paragraph.style.name in target_doc.styles: new_paragraph.style = target_doc.styles[paragraph.style.name] except: pass # Copy run formatting for i, run in enumerate(paragraph.runs): if i < len(new_paragraph.runs): new_run = new_paragraph.runs[i] # Copy basic formatting new_run.bold = run.bold new_run.italic = run.italic new_run.underline = run.underline #添加同时合并字体2025427 new_run.font.name = run.font.name rPr = new_run.element.get_or_add_rPr() rFonts = rPr.get_or_add_rFonts() # 检查 run.font.name 是否为 None if run.font.name is None: # 设置默认的中文字体名称 run.font.name = '宋体(中文正文)' # 或者使用其他你喜欢的中文字体 rFonts.set(qn('w:eastAsia'), run.font.name) new_run.font.color.rgb = run.font.color.rgb # Font size if specified if run.font.size: new_run.font.size = run.font.size except Exception as e: print(f"添加表格前文章失败:{str(e)}") try:# Copy all tables from core.tables import copy_table copy_table(source_doc.tables[0], target_doc, ifadjustheight, height, REPORT_ENUM=REPORT_ENUM) target_doc.save(target_filename) except Exception as e: print(f"添加表格失败:{str(e)}") for find_text, replace_text in list_to_replace.items(): print(search_and_replace(target_filename, find_text, replace_text)) async def merge_documents(target_filename: str, source_filenames: List[str], add_page_breaks: bool = True) -> str: """合并文档(文本) 表格会添加到最后 Args: target_filename: 合并后文档路径 source_filenames: 源文档路径(列表) add_page_breaks: bool,为真则每个源文档中间加入分页符 """ from core.tables import copy_table target_filename = ensure_docx_extension(target_filename) # Check if target file is writeable is_writeable, error_message = check_file_writeable(target_filename) if not is_writeable: return f"Cannot create target document: {error_message}" # Validate all source documents exist missing_files = [] for filename in source_filenames: doc_filename = ensure_docx_extension(filename) if not os.path.exists(doc_filename): missing_files.append(doc_filename) if missing_files: return f"Cannot merge documents. The following source files do not exist: {', '.join(missing_files)}" try: # Create a new document for the merged result target_doc = Document() # Process each source document for i, filename in enumerate(source_filenames): doc_filename = ensure_docx_extension(filename) source_doc = Document(doc_filename) # Add page break between documents (except before the first one) if add_page_breaks and i > 0: target_doc.add_page_break() # Copy all paragraphs for paragraph in source_doc.paragraphs: # Create a new paragraph with the same text and style new_paragraph = target_doc.add_paragraph(paragraph.text) new_paragraph.style = target_doc.styles['Normal'] # Default style #获取合并等样式2025427 new_paragraph.alignment = paragraph.alignment # Try to match the style if possible try: if paragraph.style and paragraph.style.name in target_doc.styles: new_paragraph.style = target_doc.styles[paragraph.style.name] except: pass # Copy run formatting for i, run in enumerate(paragraph.runs): if i < len(new_paragraph.runs): new_run = new_paragraph.runs[i] # Copy basic formatting new_run.bold = run.bold new_run.italic = run.italic new_run.underline = run.underline #添加同时合并字体2025427 new_run.font.name = run.font.name rPr = new_run.element.get_or_add_rPr() rFonts = rPr.get_or_add_rFonts() # 检查 run.font.name 是否为 None if run.font.name is None: # 设置默认的中文字体名称 run.font.name = '宋体(中文正文)' # 或者使用其他你喜欢的中文字体 rFonts.set(qn('w:eastAsia'), run.font.name) new_run.font.color.rgb = run.font.color.rgb # Font size if specified if run.font.size: new_run.font.size = run.font.size # Copy all tables for table in source_doc.tables: copy_table(table, target_doc) # Save the merged document target_doc.save(target_filename) return f"Successfully merged {len(source_filenames)} documents into {target_filename}" except Exception as e: return f"Failed to merge documents: {str(e)}" async def right_align_last_three_para(target_filename: str) -> str: """右对齐最后三个段落 Args: target_filename: 目标文档路径 """ target_filename = ensure_docx_extension(target_filename) # Check if target file is writeable is_writeable, error_message = check_file_writeable(target_filename) if not is_writeable: return f"Cannot right align paragraphs: {error_message}" try: # Open the target document target_doc = Document(target_filename) # Get the last three paragraphs paragraphs = target_doc.paragraphs[-3:] # Set the alignment of each paragraph to right from docx.enum.text import WD_ALIGN_PARAGRAPH for paragraph in paragraphs: paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT # Save the modified document target_doc.save(target_filename) return f"Successfully right aligned the last three paragraphs in {target_filename}" except Exception as e: return f"Failed to right align paragraphs: {str(e)}" async def add_dynamic_table(output_doc, output_dir, table_num, TABLES, JIANCHA_XIANGQING_DIR, PICTURES, row, col, i, FLAG, xuhao): """创建动态表 Args: output_doc (Document): 文档对象 output_dir (str): 输出目录 table_num (int): 表格序号 TABLES (list): 表格数据 JIANCHA_XIANGQING_DIR (str): 检查详情表目录 PICTURES (dict): 图片数据字典,键为表索引,值为图片路径列表 row (int): 行数 col (int): 列数 i (int): 表格序号 FLAG: 其他标志 Returns: tuple: (i, table_num) 更新后的表格序号和表格数量 """ for table_idx, Table in enumerate(TABLES): print(Table) output_doc, message = add_table_to_document(output_dir, JIANCHA_XIANGQING_DIR, row, col, i, Table, FLAG, if_para=False) print(message) # 获取当前表格对应的图片 current_table_pictures = PICTURES.get(table_idx, []) print(f"开始处理图片列表: {current_table_pictures}") for picturedir in current_table_pictures: if picturedir is None: print(f"图片路径为空,跳过") continue try: print(f"添加 {picturedir} {type(picturedir)}到表格{table_idx}") resize_and_reduce_quality(picturedir, picturedir) add_picture_to_table(output_doc, output_dir, 4, 0, picturedir, i, 4.7232) except Exception as e: print(f"添加图片失败:{e}") try: print(search_and_replace(output_dir, 'tupian_xuhao', f'{xuhao}')) except Exception as e: print(f"替换图片序号失败:{e}") table_num += 1 i += 1 xuhao += 1 return i, table_num, xuhao async def process_images_table(data_dict, output_dir, start_i, JIANCHA_NEIRONG_PICTURES_TABLE, key_words = None): """添加对应表格且填写图片名与插入图片 Args: data_dict (dict): dict内容,图片:图片路径 output_dir (str): 输出路径 start_i (int): 总表格数量 JIANCHA_NEIRONG_PICTURES_TABLE (str): 二维表模板路径 Returns: int: 最后使用的表格序号 """ items = list(data_dict.items()) picture_num = len(items) line_index = 0 picture_index = 0 i = start_i for content_row in range(((picture_num + 2) // 3) * 2): if content_row % 2 == 1: # 文字行(从 items 取图片名) JIANCHA_NEIRONG_TEXT = [["" for _ in range(3)] for _ in range(1)] # 1行3列 for k in range(1): # 只有1行 for l in range(3): if line_index >= picture_num: break JIANCHA_NEIRONG_TEXT[k][l] = items[line_index][0] # 图片名 print(f'当前为文字表格,在({k},{l})位置插入文字: {items[line_index][0]}') line_index += 1 print(f"当前待插入表格: {JIANCHA_NEIRONG_TEXT}") print(f"当前表格序号为 {i}") output_doc, message = add_table_to_document( output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, JIANCHA_NEIRONG_TEXT, False, None, key_words ) i += 1 else: # 图片行(从 items 取图片路径) print(f"当前表格序号为 {i}") output_doc, message = add_table_to_document( output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, None, False ) for k in range(3): if picture_index < picture_num: pic_path = items[picture_index][1] # 图片路径 print(f"当前为图片表格,在(0,{k})位置插入图片: {pic_path}") print(add_picture_to_table(output_doc, output_dir, 0, k, pic_path, i, 1.8898)) picture_index += 1 i += 1 print(message) return i # 返回最后使用的表格序号 async def process_server_images_table(data_list, image_source_list, output_dir, start_i, JIANCHA_NEIRONG_PICTURES_TABLE, key_words=None): """从服务器获取的图片数据添加对应表格且填写图片名与插入图片 逻辑:按来源排序,添加 Args: data_list (list): 图片数据列表,每个元素为图片路径 [ {'imageId': '41543f531be24522b7bec741b9a483a2', 'imageName': 'F02_机组10060_叶片,叶片PS面距叶根15.5米处轴向裂纹,轴向:260mm,弦向:20mm。DJI_20250526090227_0052_Z.png', 'imagePath': '/static/image/temp/out-work/12bc30fb209f3af3bf530541c5b062bd/F02_机组10060_叶片,叶片PS面距叶根15.5米处轴向裂纹,轴向:260mm,弦向:20mm。DJI_20250526090227_0052_Z.png', 'partId': '12bc30fb209f3af3bf530541c5b062bd', 'partName': '叶片1', 'imageResolution': '8000x6000', 'focalDistance': None, 'shootingTime': None, 'cameraManufacturer': None, 'cameraModel': None, 'weather': None, 'weatherLabel': None, 'humidness': None, 'temperature': None, 'windLevel': None, 'shootingMethod': 'UAV', 'shootingMethodLabel': '无人机航拍', 'shootingDistance': None, 'collectorName': '程启谦', 'imageSource': 'out-work', 'imageSourceLabel': '外部工作', 'imageType': 'DEFECT', 'imageTypeLabel': '缺陷影像', 'audioList': None, 'preImagePath': None, 'preTreatment': False, 'projectId': None, 'gps': None}, ... ] image_source_list (list): 来源列表,每个元素为来源名称 ['out-work', 'in-work', 'in-field'] (来源可能会少于3个,pop出没有给出对应来源的图片) output_dir (str): 输出路径 start_i (int): 总表格数量 JIANCHA_NEIRONG_PICTURES_TABLE (str): 二维表模板路径 key_words (re) Returns: int: 最后使用的表格序号 """ # 按来源对图片数据进行分组和排序 sorted_data = {source: [] for source in image_source_list} # 将图片数据按来源分组 for item in data_list: source = item['imageSource'] if source in sorted_data: # 存储图片名和图片路径的元组,与非server版本保持一致 sorted_data[source].append((item['imageTypeLabel'], item['imagePath'])) i = start_i # 按照image_source_list的顺序处理每个来源的图片 for source in image_source_list: items = sorted_data[source] if not items: continue # 如果该来源没有图片则跳过 print(f"处理来源: {source}") picture_num = len(items) line_index = 0 picture_index = 0 for content_row in range(((picture_num + 2) // 3) * 2): if content_row % 2 == 1: # 文字行(从 items 取图片名) JIANCHA_NEIRONG_TEXT = [["" for _ in range(3)] for _ in range(1)] # 1行3列 for k in range(1): # 只有1行 for l in range(3): if line_index >= picture_num: break JIANCHA_NEIRONG_TEXT[k][l] = items[line_index][0] # 图片名 print(f'当前为文字表格,在({k},{l})位置插入文字: {items[line_index][0]}') line_index += 1 print(f"当前待插入表格: {JIANCHA_NEIRONG_TEXT}") print(f"当前表格序号为 {i}") output_doc, message = add_table_to_document( output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, JIANCHA_NEIRONG_TEXT, False, None, key_words ) i += 1 else: # 图片行(从 items 取图片路径) print(f"当前表格序号为 {i}") output_doc, message = add_table_to_document( output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, None, False ) for k in range(3): if picture_index < picture_num: pic_path = items[picture_index][1] # 图片路径 print(f"当前为图片表格,在(0,{k})位置插入图片: {pic_path}") print(add_picture_to_table(output_doc, output_dir, 0, k, get_full_picture_url(pic_path), i, 1.8898)) picture_index += 1 i += 1 print(message) return i # 返回最后使用的表格序号 def add_header(target_dir : str, report_enum : str, if_clear_header : Optional[bool] = True, if_section : Optional[bool] = True, text : str = None): """添加页眉,添加封面后调用此函数,会分离页面和后续页面的节。 Args: target_dir (str): 目标目录 start_section (int): 开始节 end_section (int): 结束节 report_enum (str): 报告类型(DT或JF) """ document = Document(target_dir) # 打开文档 if if_clear_header: for section in document.sections: # 遍历所有节的页眉 clear_header(section) # 清除页眉的段落 if if_section: print(f"文档节数:{len(document.sections)},开始往当前节后添加页眉") document.sections[0].header.is_linked_to_previous = False # 取消页眉与上一页关联 document.add_section(WD_SECTION.NEW_PAGE) header = document.sections[1].header header.is_linked_to_previous = False # 取消页眉与上一页关联 else: header = document.sections[-1].header header.is_linked_to_previous = False # 取消页眉与上一页关联 paragraph = header.paragraphs[0] # 获取页眉的第一个段落 run = paragraph.add_run() if report_enum == 'JF': print("添加金风模板的页眉") pic = run.add_picture(get_template_pic(TEMPLATE_HEADER.JINFENG_HEADER.PIC_DIR)) if text: run = paragraph.add_run(text) else: run = paragraph.add_run(TEMPLATE_HEADER.JINFENG_HEADER.PARA) run.font.name = TEMPLATE_HEADER.JINFENG_HEADER.FONT run.font.size = TEMPLATE_HEADER.JINFENG_HEADER.PT elif report_enum == 'DT': print("添加迪特模板的页眉") pic = run.add_picture(get_template_pic(TEMPLATE_HEADER.DT_HEADER.PIC_DIR)) if text: run = paragraph.add_run(text) else: run = paragraph.add_run(TEMPLATE_HEADER.DT_HEADER.PARA) run.font.name = TEMPLATE_HEADER.DT_HEADER.FONT run.font.size = TEMPLATE_HEADER.DT_HEADER.PT else: print("未知模板,不添加页眉") # 定义边框的 XML 字符串 border_xml = """ """ # 解析 XML 并添加到段落属性 pPr = paragraph._element.get_or_add_pPr() pBdr = parse_xml(border_xml) pPr.append(pBdr) print(f"文档节数:{len(document.sections)}") document.save(target_dir) # 保存文档 from docx.enum.section import WD_ORIENT from docx.enum.text import WD_BREAK from docx.document import Document as Document_ def add_landscape_section(target_dir : str): # 添加横向节 doc = Document(target_dir) section = doc.add_section() section.orientation = WD_ORIENT.LANDSCAPE section.page_width, section.page_height = section.page_height, section.page_width print(f"文档:{target_dir},添加了新横向节,页宽:{section.page_width}, 页高:{section.page_height}") return doc def add_section(target_dir : str) -> Document_: doc = Document(target_dir) section = doc.add_section() return doc def add_title(target_dir : str, title : str, style_config : dict = {}): doc = Document(target_dir) para = doc.add_paragraph() run = para.add_run(title) font_config = style_config.get('font', {}) run.font.name = font_config.get('name', "宋体") run.element.get_or_add_rPr().get_or_add_rFonts().set(qn('w:eastAsia'), font_config.get('name', "宋体")) run.font.size = font_config.get('size', Pt(14)) run.bold = font_config.get('bold', False) para.alignment = style_config.get('alignment', WD_ALIGN_PARAGRAPH.CENTER) print(f"添加新标题:{title},字体:{run.font.name},大小:{run.font.size},是否加粗:{run.bold}") return doc def merge_documents(target_dir : str, source_dirs : List[str]): """合并多个文档、图片 Args: target_dir (str): 目标目录 source_dirs (List[str]): 源目录列表 """ # 打开目标文档 document = Document(target_dir) for dir in source_dirs: if dir.endswith('.docx'): print(add_documents(target_dir, dir)) if dir.endswith('.jpg') or dir.endswith('.png'): print(add_picture(target_dir, dir, is_center=True)) def add_defect_info_table(output_dir, defect_info, MUBAN_DIR, total_table_num, REPORT_ENUM): """将defectinfo写入word文档 Args: output_dir: 输出目录 defect_info: 缺陷信息 { '叶片1': { #按叶片分记录 '表面裂纹': { 'SLIGHT': #按类型分等级,等级从'defectLevel'获取 [ #按等级分记录 { record:{...} }, #一张图片一个记录 ... ] ... } } total_table_num: 总表格数量 Returns: table_num: 表格数量 功能:表头分别为:序号,缺陷类型,等级,损伤数量,处理建议,备注(是否完成处理) (默认否)。 总体就按等级分类,但损伤数量需要说明为:n支m处。n为有这个缺陷和等级的叶片数,m为有这个缺陷和等级的所有叶片加起来的缺陷数。 处理建议从record[0]的值中获取 """ table_data = [] # 存储所有表格数据的列表,每个元素是一个字典 # 添加新记录(使用字典格式) table_data.append({ "xuhao" : "序号", # 序号 "defecttype" : "损伤名称", # 缺陷类型 "level" : "损伤等级", # 等级 "defectnuminfo" : "损伤数量", # 损伤数量 "suggestion" : "处理建议", # 处理建议 "remark" : "备注(是否处理完成)" # 备注(是否完成处理) }) # 遍历缺陷信息,整理数据 for blade_name, defects in defect_info.items(): for defect_type, levels in defects.items(): for level, records in levels.items(): if records: # 如果有记录 # 计算损伤数量:n支m处 blade_count = 1 # 当前叶片就是1支 defect_count = len(records) # 当前叶片的缺陷数量 # 查找是否已有同类型同等级的数据 found = False for item in table_data: if item["defecttype"] == defect_type and item["level"] == level: # 更新已有记录 current_n = item["defectnuminfo"] parts = current_n.split("支") existing_blades = int(parts[0]) existing_defects = int(parts[1].split("处")[0]) item["defectnuminfo"] = f"{existing_blades + blade_count}支{existing_defects + defect_count}处" found = True break if not found: # 获取处理建议(假设第一条记录中有处理建议) suggestion = records[0].get('record', {}).get('处理建议', '') # 添加新记录(使用字典格式) table_data.append({ "xuhao" : str(len(table_data)), # 序号 "defecttype" : defect_type, # 缺陷类型 "level" : level, # 等级 "defectnuminfo" : f"{blade_count}支{defect_count}处", # 损伤数量 "suggestion" : suggestion, # 处理建议 "remark" : "否" # 备注(是否完成处理) }) # 添加空行 for _ in range(2): table_data.append({ "xuhao" : "", # 序号 "defecttype" : "", # 缺陷类型 "level" : "", # 等级 "defectnuminfo" : "", # 损伤数量 "suggestion" : "", # 处理建议 "remark" : "" # 备注(是否完成处理) }) for row in table_data: add_table_and_replace(output_dir, MUBAN_DIR, list_to_replace=row, no_para = True, ifadjustheight=True, REPORT_ENUM=REPORT_ENUM) total_table_num += 1 return total_table_num from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.shared import RGBColor def add_table_title(output_dir, TITLE): doc = Document(output_dir) table = doc.add_table(rows=1, cols=6, style='Table Grid') table.cell(0,0).merge(table.cell(0,5)) para = table.cell(0,0).paragraphs[0] run = para.add_run(TITLE) run.font.name = "宋体" run.font.size = Pt(9) para.alignment = WD_ALIGN_PARAGRAPH.CENTER print(f"添加了标题表格:{TITLE}") doc.save(output_dir) def change_heading(output_dir, style_name, style_config = {}): doc = Document(output_dir) heading_style = doc.styles[style_name] font_config = style_config.get('font', {}) heading_style.font.name = font_config.get('name', "宋体") heading_style.element.get_or_add_rPr().get_or_add_rFonts().set(qn('w:eastAsia'), font_config.get('name', "宋体")) heading_style.font.size = font_config.get('size', Pt(11)) heading_style.bold = font_config.get('bold', True) heading_style.font.color.rgb = font_config.get('color', DocxColors.BLACK.rgb) print(f"修改了标题样式:{style_name},字体:{heading_style.font.name},大小:{heading_style.font.size},是否加粗:{heading_style.bold}") doc.save(output_dir) from docxtpl import DocxTemplate def add_auto_toc_at_end(doc_path): """使用 python-docx-template 在末尾自动生成目录""" doc = DocxTemplate(doc_path) # 渲染模板(r.toc=True 会触发目录生成) context = { 'r': { 'toc': True # 自动生成目录 } } doc.render(context) doc.save(doc_path) def add_jf_picture_table( typical_picture_list: list, defect_picture_list: list, TYPICAL_MUBAN_DIR: str, DEFECT_MUBAN_DIR: str, output_dir: str, total_table_num: int, ): """添加金风版本的图片展示表格 Args: typical_picture_list: 每列有两种情况 [ [str(图片描述), str(图片描述), ...], [str(图片地址), int(n), ...], [str(""), str(缺陷类型), ...] ] defect_picture_list: 只有一种情况 [ [str(图片描述), str(图片描述), ...], [str(图片地址), str(图片地址), ...] ] TYPICAL_MUBAN_DIR: 典型图模板路径 DEFECT_MUBAN_DIR: 缺陷图模板路径 """ def add_table(table_type, data, rows, cols): nonlocal total_table_num template = TYPICAL_MUBAN_DIR if table_type == "typical" else DEFECT_MUBAN_DIR key_words = re.compile(r'损伤|处') if table_type == "typical" else re.compile(r'损伤') print(f"添加{table_type}表格:{data}") add_table_to_document( output_dir, template, rows, cols, total_table_num, data, Report_Enum='JF', if_merge=False, height=1.4173, width=1.8898, if_para=False, key_words=key_words ) total_table_num += 1 return list(list("" for _ in range(5)) for _ in range(rows)) # 返回新表格 print(f"获得典型图列表:{typical_picture_list}") print(f"获得缺陷图列表:{defect_picture_list}") # 初始化表格 typical_table = [[""] * 5 for _ in range(3)] # 三行五列 defect_table = [[""] * 5 for _ in range(2)] # 二行五列 typical_col_idx = 0 defect_count = 0 # 需要添加的缺陷图数量 pending_defects = False # 处理典型图 while all(typical_picture_list) and all(len(lst) > 0 for lst in typical_picture_list): desc = typical_picture_list[0].pop(0) content = typical_picture_list[1].pop(0) defect_type = typical_picture_list[2].pop(0) # 检查是否需要新建表格 if typical_col_idx >= 5: typical_table = add_table("typical", typical_table, 3, 5) typical_col_idx = 0 # 如果有待处理的缺陷图,先处理 if pending_defects: defect_col_idx = 0 while defect_picture_list[0] and defect_picture_list[1] and defect_count > 0: if defect_col_idx >= 5: defect_table = add_table("defect", defect_table, 2, 5) defect_col_idx = 0 defect_table[0][defect_col_idx] = defect_picture_list[0].pop(0) defect_table[1][defect_col_idx] = defect_picture_list[1].pop(0) defect_col_idx += 1 defect_count -= 1 if defect_col_idx > 0: defect_table = add_table("defect", defect_table, 2, 5) pending_defects = False # 填充典型图表 if isinstance(content, int): typical_table[0][typical_col_idx] = desc typical_table[1][typical_col_idx] = f"损伤有{content}处,详见下表" typical_table[2][typical_col_idx] = f"{defect_type}{content}处" defect_count += content pending_defects = True else: typical_table[0][typical_col_idx] = desc typical_table[1][typical_col_idx] = content typical_col_idx += 1 # 添加剩余的典型图表 if typical_col_idx > 0: add_table("typical", typical_table, 3, 5) # 处理剩余的缺陷图 if pending_defects and defect_picture_list[0] and defect_picture_list[1]: defect_col_idx = 0 while defect_picture_list[0] and defect_picture_list[1] and defect_count > 0: if defect_col_idx >= 5: defect_table = add_table("defect", defect_table, 2, 5) defect_col_idx = 0 defect_table[0][defect_col_idx] = defect_picture_list[0].pop(0) defect_table[1][defect_col_idx] = defect_picture_list[1].pop(0) defect_col_idx += 1 defect_count -= 1 if defect_col_idx > 0: add_table("defect", defect_table, 2, 5) return total_table_num