import os import json import shutil import re import traceback from lxml import etree from datetime import datetime import sys import base64 from PIL import Image import io from docx.shared import Pt from docx.oxml.ns import qn from docx.shared import Emu, Inches from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.oxml import OxmlElement from docx import Document from docx.shared import RGBColor from Tool import getPfm, modCsv import modifyReport def getJsonData(jsonFilePath): """获取JSON数据""" if not os.path.exists(jsonFilePath): print(f"Error:JSON文件路径 '{jsonFilePath}' 不存在,请确认") return None with open(jsonFilePath, 'r', encoding='utf-8') as jsonFile: data = json.load(jsonFile) return data def getCsvData(csvFilePath): """获取CSV数据""" data_dict = {} if not os.path.exists(csvFilePath): print(f"Error:CSV文件路径 '{csvFilePath}' 不存在,请确认") return data_dict with open(csvFilePath, 'r', encoding='utf-8') as csvFile: lines = csvFile.readlines() for line in lines: if "," in line: values = line.strip().split(',') key = values[0] data_dict[key] = values[1] return data_dict def getTaskType(taskName, projectInfo_dict): """根据工况名称获取分析项类型""" taskType = projectInfo_dict.get("taskType", None) if taskType is not None: return taskType else: print( f"Warning:projectInfo.json中不存在'taskType'字段,将根据工况名称'{taskName}'进行判断") if "动力学" in taskName or taskName.startswith("dynamics"): return "dynamics" elif "静力学" in taskName or taskName.startswith("statics"): return "statics" elif "流体" in taskName or taskName.startswith("fluid"): return "fluid" elif "热分析" in taskName or taskName.startswith("thermal"): return "thermal" else: print(f"Warning:未知的分析项类型 '{taskName}',默认返回 'statics'") return "statics" def wirteValueToPerformance(keyName, value, highValue, csvPath): """把值写入到output_values.csv中 用于生成performance.json""" lines = [] if os.path.exists(csvPath): with open(csvPath, 'r+', encoding='utf-8') as fr: lines = fr.readlines() found = False for i in range(len(lines)): if lines[i].startswith(keyName + ","): lines[i] = f"{keyName},{value},{highValue}\n" found = True break if not found: lines.append(f"{keyName},{value},{highValue}\n") with open(csvPath, 'w+', encoding='utf-8') as fw: fw.writelines(lines) def replace_text_in_paragraph(paragraph, data_dict, csvPath=None): """替换段落中的文本""" text = paragraph.text # 居中显示 # paragraph.alignment = 1 ngFlag = False if "$" in text and "<" not in text and ">" not in text: keys_list = re.findall(r'\$([a-zA-Z0-9_]+)', text) for keyName in keys_list: value = data_dict.get(keyName, None) if value is not None: placeholder = f"${keyName}" text = text.replace(placeholder, str(value)) print(f"文本: {placeholder} -> {value}") paragraph.text = text elif "$" in text and "<" in text: # 处理带有<>的占位符 start_index = text.find("$") end_index = text.find("<", start_index) if end_index != -1: placeholder = text[start_index:end_index + 1] keyName = placeholder[1:-1] # 去掉$和<> value = data_dict.get(keyName, None) highValue = text.split("<")[1] if value is not None: # 把标准值写入到output_values.csv中 用于生成performance.json wirteValueToPerformance(keyName, value, highValue, csvPath) if float(value) > float(highValue): result = "NG" # 设置红色 ngFlag = True else: result = "OK" paragraph.text = result if ngFlag: for run in paragraph.runs: run.font.color.rgb = RGBColor(255, 0, 0) print(f"文本: {placeholder} -> {result}") elif "$" in text and ">" in text: # 处理带有<>的占位符 start_index = text.find("$") end_index = text.find(">", start_index) if end_index != -1: placeholder = text[start_index:end_index + 1] keyName = placeholder[1:-1] # 去掉$和<> value = data_dict.get(keyName, None) highValue = text.split(">")[1] if value is not None: if float(value) < float(highValue): result = "NG" # 设置红色 ngFlag = True else: result = "OK" paragraph.text = result if ngFlag: for run in paragraph.runs: run.font.color.rgb = RGBColor(255, 0, 0) print(f"文本: {placeholder} -> {result}") def replace_text_in_table(table, data_dict, csvPath): """替换表格中的文本""" for row in table.rows: for cell in row.cells: # 递归处理单元格中的嵌套表格 if cell.tables: for nested_table in cell.tables: replace_text_in_table(nested_table, data_dict, csvPath) # 处理当前单元格的段落文本 for paragraph in cell.paragraphs: replace_text_in_paragraph(paragraph, data_dict, csvPath) def replace_text_in_textbox(doc, data_dict): """替换文本框中的文本""" namespaces = { 'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main', 'wp': 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing', 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', 'wps': 'http://schemas.microsoft.com/office/word/2010/wordprocessingShape' } doc_modified = False # 整个文档是否被修改 for part_id, related_part in doc.part.related_parts.items(): if hasattr(related_part, 'blob'): xml_content = related_part.blob # 检查是否是 XML 内容(以 {value}") part_modified = True doc_modified = True # 关键步骤:将修改后的 XML 写回到部件中 if part_modified: # 同时更新 _element 和 _blob if hasattr(related_part, '_element'): related_part._element = root updated_xml = etree.tostring( root, encoding='UTF-8', xml_declaration=True) related_part._blob = updated_xml except etree.XMLSyntaxError as e: print(f"Warning:部件 {part_id} 不是有效的 XML: {e}") continue except Exception as e: print(f"Warning:处理部件 {part_id} 时出错: {e}") continue def replaceConclusion(doc, data_dict): """替换结论中的文本""" targeTable_list = doc.tables[0]._cells try: for targeTable in targeTable_list: for paragraph in targeTable.paragraphs: replace_text_in_paragraph(paragraph, data_dict) except Exception as e: pass def getImageNameList(projectPath): """获取项目根目录下所有图片名""" imageName_list = [] for file in os.listdir(projectPath): if file.endswith(".png") or file.endswith(".jpg") or file.endswith(".jpeg"): imageName_list.append(file) print(f"{projectPath} 图片列表: {imageName_list}") return imageName_list def modifyImageName(imageName_list, targetImage_list): """检查是否所有图片都存在 不存在则修改图片名""" existImage_list = [] for imageName in imageName_list: if imageName in targetImage_list: existImage_list.append(imageName) else: for targetImage in targetImage_list: if targetImage in existImage_list: continue newImageName = targetImage break # 修改图片名 newImage = f"{projectPath}/{newImageName}" oldImage = f"{projectPath}/{imageName}" print(f"Info:修改图片名: {imageName} -> {newImageName}") existImage_list.append(newImageName) if os.path.exists(oldImage): os.rename(oldImage, newImage) def getPefData(performance_dict, csvPath, templatePath, templateType): """获取性能指标数据 写入csv文件""" content_list = [] for key, value in performance_dict.items(): if key == "type": continue for k, item in value.items(): if k == "value": line = f"{key},{item}\n" content_list.append(line) break # 手动上传 根据ppt模板,修改key # mapPptKeyPath = f"{templatePath}/mapPptKeyToPerfor.json" # pptKeyData_dict = getJsonData(mapPptKeyPath)[templateType] # targetKey_list = list(pptKeyData_dict.keys()) # for index, line in enumerate(content_list): # keyName = line.split(",")[0] # if keyName not in targetKey_list and index < len(targetKey_list): # newKeyName = targetKey_list[index] # value = line.split(",")[1] # newLine = f"{newKeyName},{value}" # content_list[index] = newLine with open(csvPath, "w", encoding="utf-8") as f: f.writelines(content_list) print(f"写入csv文件: {csvPath}") def inspect_document_structure(doc_path): """ 检查文档结构,找出所有形状的名称 """ doc = Document(doc_path) print("文档中的形状信息:") for i, shape in enumerate(doc.inline_shapes): element = shape._inline doc_pr = element.find('.//wp:docPr', namespaces=element.nsmap) if doc_pr is not None: shape_name = doc_pr.get('name', '') shape_id = doc_pr.get('id', '') desc = doc_pr.get('descr', '') print(f"形状 {i+1}:") print(f" 名称: {shape_name}") print(f" ID: {shape_id}") print(f" 描述: {desc}") print("-" * 30) def generateReport(reportFilePath, dataPath, data_dict, csvPath, image_dict): """生成Word报告""" # inspect_document_structure(reportFilePath) doc = Document(reportFilePath) # 处理页眉 for section in doc.sections: if section.header: for paragraph in section.header.paragraphs: replace_text_in_paragraph(paragraph, data_dict, csvPath) # 处理页眉中的表格 for table in section.header.tables: replace_text_in_table(table, data_dict, csvPath) # 替换图片 for rel in doc.part.rels.values(): if "image" in rel.reltype: # print(rel.target_ref, rel.reltype) # media/image3.png imageItem = rel.target_ref.split("/")[-1] picName = image_dict.get(imageItem, None) if picName is not None and picName != ".png": replace_image_path = os.path.join(dataPath, picName) if os.path.exists(replace_image_path): print(f"图片: {imageItem} -> {picName}") rel.target_part._blob = open( replace_image_path, "rb").read() # 处理表格 for table in doc.tables: replace_text_in_table(table, data_dict, csvPath) # 处理结论 replaceConclusion(doc, data_dict) # 处理文本框 replace_text_in_textbox(doc, data_dict) # 保存文档 doc.save(reportFilePath) print(f"Info:Word文档已创建: {reportFilePath}") def generatePerformance(projectPath, csvPath, key_dict, templateType): """生成性能指标""" performancePath = f"{projectPath}/performance.json" if templateType in ["statics", "dynamics"]: performance_dict = getPfm.generalPerformance(csvPath, key_dict) elif templateType == "modal": performance_dict = getPfm.generalPerformanceModal(csvPath, key_dict) else: performance_dict = {} with open(performancePath, 'w', encoding='utf-8') as perfFile: json.dump(performance_dict, perfFile, indent=4, ensure_ascii=False) print(f"Info:性能指标已生成: {performancePath}") def exportWord(projectPath, templatePath, loacase=None): """主函数""" print(f"Info:开始生成工况: {loacase} 的Word报告...") # 根据工况名称获取docx模板类型和后处理图片和图片框对应关系 templateType = getTaskType(loacase) templateFilePath = f"{templatePath}/{templateType}_template.docx" mapPicNamePath = f"{templatePath}/mapPicNameToPpt.json" image_dict = getJsonData(mapPicNamePath).get(templateType, None) print(f"Info:模板类型: {templateType}, 图片对应关系: {image_dict}") if image_dict is None: print(f"Warning:模板类型: {templateType} 不存在图片替换,请确认") if not os.path.exists(templateFilePath): print(f"Error:模板文件路径 '{templateFilePath}' 不存在,请确认") return # 拷贝模板文件 reportPath = f"{projectPath}/report" if not os.path.exists(reportPath): os.makedirs(reportPath) reportFilePath = f"{reportPath}/report.docx" shutil.copy(templateFilePath, reportFilePath) # 获取项目信息数据和结果数据 projectInfoPath = f"{projectPath}/projectInfo.json" projectInfo_dict = getJsonData(projectInfoPath) dataPath = f"{projectPath}/result/process_data" csvPath = f"{dataPath}/output_values.csv" # 个别工况需要先对output_values.csv进行处理 if templateType == "thermal": modCsv.modifyCsvForThermal(csvPath) data_dict = getCsvData(csvPath) data_dict.update(projectInfo_dict) # 生成报告 generateReport(reportFilePath, dataPath, data_dict, csvPath, image_dict) # 生成性能指标 mapPptKeyPath = f"{templatePath}/mapPptKeyToPerfor.json" key_dict = getJsonData(mapPptKeyPath).get(templateType, None) print(f"\nInfo:PPT键值中英文: {key_dict}") generatePerformance(projectPath, csvPath, key_dict, templateType) def exportWordByUser(projectPath, templatePath, loacase=None): """用户手动导出Word报告 图片和性能指标都是手动上传到项目路径根目录下""" print(f"Info:开始生成工况: {loacase} 的Word报告...") projectInfoPath = f"{projectPath}/projectInfo.json" projectInfo_dict = getJsonData(projectInfoPath) templateType = getTaskType(loacase, projectInfo_dict) templateFilePath = f"{templatePath}/{templateType}_template.docx" mapPicNamePath = f"{templatePath}/mapPicNameToPpt.json" image_dict = getJsonData(mapPicNamePath).get(templateType, None) targetImage_list = list(image_dict.values()) print(f"Info:模板类型: {templateType}, 图片对应关系: {image_dict}") if image_dict is None: print(f"Warning:模板类型: {templateType} 不存在图片替换,请确认") if not os.path.exists(templateFilePath): print(f"Error:模板文件路径 '{templateFilePath}' 不存在,请确认") return # 获取上传的图片名 imageName_list = getImageNameList(projectPath) # 检查是否所有图片都存在 不存在则修改图片名 modifyImageName(imageName_list, targetImage_list) # 拷贝模板文件 reportPath = f"{projectPath}/report" if not os.path.exists(reportPath): os.makedirs(reportPath) reportFilePath = f"{reportPath}/report.docx" shutil.copy(templateFilePath, reportFilePath) # 获取项目信息数据 dataPath = projectPath # 获取指标数据 performancePath = f"{projectPath}/performance.json" performance_dict = getJsonData(performancePath) csvPath = f"{dataPath}/output_values.csv" getPefData(performance_dict, csvPath, templatePath, templateType) data_dict = getCsvData(csvPath) data_dict.update(projectInfo_dict) # 生成报告 generateReport(reportFilePath, dataPath, data_dict, csvPath, image_dict) class Tee: """自定义文件对象,同时写入文件和原控制台""" def __init__(self, *files): self.files = files def write(self, obj): for f in self.files: f.write(obj) f.flush() # 确保立即写入 def flush(self): for f in self.files: f.flush() def base64ToImg(projectPath, reportContent_list): """把base64字符串转成图片""" img_num = 0 imgName_dict = {} for item_dict in reportContent_list: if item_dict["type"] == "img": key_name = item_dict.get("key", "") if key_name not in imgName_dict: imgName_dict[key_name] = [] img_base64_list = item_dict.get("value", []) if img_base64_list: for img_base64_dict in img_base64_list: imgTemp_dict = {} img_base64_str = img_base64_dict.get("src", "") img_base64 = img_base64_str.split(",")[1] img_data = base64.b64decode(img_base64) img_num += 1 img_path = f"{projectPath}/pic{img_num}.png" picName = img_base64_dict.get("title", "") imgTemp_dict[f"pic{img_num}"] = picName imgName_dict[key_name].append(imgTemp_dict) with open(img_path, 'wb') as img_file: img_file.write(img_data) # 新增字典中的picPath为图片路径 # item_dict["picPath"] = img_path return img_num, imgName_dict def getDataDict(reportContent_list): """获取数据字典 用于替换文本""" data_dict = {} table_num = 0 for item_dict in reportContent_list: if item_dict["type"] == "text": data_dict[item_dict["key"]] = item_dict.get("value", "") elif item_dict["type"] == "table": tableContent = item_dict.get("value", []) # 获取表头 headers = item_dict.get("head", []) tableName = item_dict.get("key", "") if headers and tableName: header_str = ",".join(headers) # newKey = item_dict["key"] + "_" + header_str newKey = tableName + "_" + header_str data_dict[newKey] = tableContent table_num += 1 elif item_dict["type"] == "conclusion": data_dict[item_dict["key"]] = item_dict["value"] return data_dict, table_num def set_table_border(table): """ 通过底层 XML 强制给表格添加全框线(细黑实线) """ tbl = table._tbl tblPr = tbl.tblPr # 创建表格边框元素 borders = OxmlElement('w:tblBorders') # 定义需要添加边框的方向:上下左右、内部水平、内部垂直 for border_name in ['top', 'left', 'bottom', 'right', 'insideH', 'insideV']: edge = OxmlElement(f'w:{border_name}') edge.set(qn('w:val'), 'single') # 线型:单实线 edge.set(qn('w:sz'), '4') # 线宽:1/8 磅的倍数,4 表示 0.5 磅 edge.set(qn('w:space'), '0') # 间距 edge.set(qn('w:color'), 'auto') # 颜色:自动(黑色) borders.append(edge) tblPr.append(borders) def replaceParagraph(doc, paragraph, data_dict): """替换段落中的占位符:删除原段落并新增表格""" text = paragraph.text if "$table_" in text: judgeFalg = False targets = {"分析值", "达成状态", "目标值"} parts = text.split("_") if len(parts) < 2: return tableName = parts[1] for item_key, item_value in data_dict.items(): if tableName in item_key: head_str = item_key.split("_")[1] head_list = head_str.split(",") if all(item in head_list for item in targets): judgeFalg = True value_list = [list(item_dict.values()) for item_dict in item_value] table_content = [head_list] + value_list # 创建新表格 rows = len(table_content) cols = len(head_list) table = doc.add_table(rows=rows, cols=cols) set_table_border(table) for row_index, row_data in enumerate(table_content): valueIndex = None targetIndex = None judgeIndex = None if row_index == 0 and judgeFalg: # 记录目标值、达成状态、分析值的列索引 for col_index, cell_value in enumerate(row_data): if cell_value == "目标值": targetIndex = col_index elif cell_value == "达成状态": judgeIndex = col_index elif cell_value == "分析值": valueIndex = col_index for col_index, cell_value in enumerate(row_data): cell = table.cell(row_index, col_index) cell.text = "" p = cell.paragraphs[0] p.alignment = WD_ALIGN_PARAGRAPH.CENTER run = p.add_run(str(cell_value)) run.font.name = '微软雅黑' run._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑') run.font.size = Pt(10.5) if row_index == 0: run.bold = True # 如果是不合格变成红色字体 合格变成绿色 if cell_value == "不合格": run.font.color.rgb = RGBColor(0xFF, 0x00, 0x00) if cell_value == "合格": run.font.color.rgb = RGBColor(20, 139, 0) # 将表格的 XML 元素插入到当前段落元素的前面 paragraph._element.addprevious(table._tbl) # 删除原来的占位段落 p_element = paragraph._element p_element.getparent().remove(p_element) print(f"Info:新增表格{tableName}") break else: keys_list = re.findall(r'\$([a-zA-Z0-9_]+)', text) if not keys_list: return for keyName in keys_list: value = data_dict.get(keyName) if value is None: continue # placeholder = f"${keyName}" placeholder = keyName # 尝试在Run级别替换保留格式 for run in paragraph.runs: if "$" in run.text: textKey = run.text.split("$")[1] if textKey == "": run.text = "" elif keyName in textKey: run.text = run.text.replace(f"${keyName}", str(value)) print(f"Info: 文本替换: {placeholder} -> {str(value)}") elif run.text == keyName: run.text = run.text.replace(placeholder, str(value)) print(f"Info: 文本替换: {placeholder} -> {str(value)}") def clear_paragraph_indent(paragraph): """强制清除段落的所有缩进""" pPr = paragraph._element.get_or_add_pPr() ind = pPr.get_or_add_ind() ind.set(qn('w:firstLine'), '0') ind.set(qn('w:firstLineChars'), '0') ind.set(qn('w:left'), '0') ind.set(qn('w:leftChars'), '0') def crop_image_to_ratio(image_path, target_width_emu, target_height_emu): """ 根据目标宽高的比例,对图片进行【居中裁剪】 返回: BytesIO 对象 (可直接传给 add_picture) """ try: with Image.open(image_path) as img: # 1. 计算目标宽高比 target_ratio = target_width_emu / target_height_emu img_ratio = img.width / img.height # 2. 计算裁剪区域 (Box: left, upper, right, lower) if img_ratio > target_ratio: # 图片太宽,需要裁掉左右两边 new_width = int(img.height * target_ratio) offset = (img.width - new_width) // 2 box = (offset, 0, img.width - offset, img.height) else: # 图片太高,需要裁掉上下两边 new_height = int(img.width / target_ratio) offset = (img.height - new_height) // 2 box = (0, offset, img.width, img.height - offset) # 3. 执行裁剪 cropped_img = img.crop(box) # 4. 保存到内存流 img_byte_arr = io.BytesIO() # 保持原格式,如果是PNG保持RGBA,否则转RGB img_format = img.format if img.format else 'PNG' cropped_img.save(img_byte_arr, format=img_format) img_byte_arr.seek(0) return img_byte_arr except Exception as e: print(f"裁剪图片出错: {e}") return None def process_images_in_paragraphs(doc, image_dict, paragraphs, imgName_dict, projectPath): """ 将所有图片(包含换行部分)全部放入一个统一的大表格中 """ section = doc.sections[0] page_content_width = section.page_width - \ section.left_margin - section.right_margin gap_width_emu = 144000 for p in list(paragraphs): blips = p._element.xpath('.//a:blip') for blip in blips: rId = blip.get(qn('r:embed')) if rId in doc.part.rels: rel = doc.part.rels[rId] imageItem = rel.target_ref.split("/")[-1] picName = image_dict.get(imageItem, None) pic_value_list = imgName_dict.get(picName, []) if not pic_value_list: continue # 1. 获取基准尺寸 original_cx, original_cy = 2743200, 2743200 extents = p._element.xpath('.//wp:extent') if extents: try: original_cx = int(extents[0].get('cx')) original_cy = int(extents[0].get('cy')) except: pass # 2. 计算分行布局 (例如: [[图1,图2,图3], [图4]]) rows_data = [] current_row = [] current_w = 0 for item in pic_value_list: if not current_row: current_row.append(item) current_w = original_cx elif current_w + gap_width_emu + original_cx <= page_content_width: current_row.append(item) current_w += (gap_width_emu + original_cx) else: rows_data.append(current_row) current_row = [item] current_w = original_cx if current_row: rows_data.append(current_row) # 3. 创建统一的大表格 # 总行数 = 计算出的行数 * 2 (一行图,一行文) # 总列数 = 所有行中图片最多的那一行的数量 total_cols = max(len(r) for r in rows_data) big_table = doc.add_table( rows=len(rows_data) * 2, cols=total_cols) p._element.addnext(big_table._element) # 移除大表边框 tblPr = big_table._tbl.tblPr borders = OxmlElement('w:tblBorders') for b in ['top', 'left', 'bottom', 'right', 'insideH', 'insideV']: node = OxmlElement(f'w:{b}') node.set(qn('w:val'), 'nil') borders.append(node) tblPr.append(borders) # 4. 填充大表格内容 for row_idx, row_content in enumerate(rows_data): img_row_num = row_idx * 2 # 图片所在的行 txt_row_num = row_idx * 2 + 1 # 文字所在的行 for col_idx, img_item in enumerate(row_content): name = list(img_item.keys())[0] title = img_item[name] # path = os.path.join(projectPath, f"{name}.png") path = name # A. 插入图片 cell_img = big_table.cell(img_row_num, col_idx) p_img = cell_img.paragraphs[0] p_img.alignment = WD_ALIGN_PARAGRAPH.CENTER clear_paragraph_indent(p_img) stream = crop_image_to_ratio( path, original_cx, original_cy) p_img.add_run().add_picture(stream, width=Emu(original_cx), height=Emu(original_cy)) print(f"插入图片: {name} -> {title}") # B. 插入名称 cell_txt = big_table.cell(txt_row_num, col_idx) p_txt = cell_txt.paragraphs[0] p_txt.alignment = WD_ALIGN_PARAGRAPH.CENTER clear_paragraph_indent(p_txt) run_txt = p_txt.add_run(title) run_txt.font.size = Pt(10.5) run_txt.font.name = '宋体' run_txt._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 5. 删除占位段落并跳出 blip 循环 p._element.getparent().remove(p._element) break def getImgNameDict(reportContent_list): """获取图片名称字典""" imgName_dict = {} for item_dict in reportContent_list: if item_dict["type"] == "img": key_name = item_dict.get("key", "") if key_name not in imgName_dict: imgName_dict[key_name] = [] img_base64_list = item_dict.get("value", []) if img_base64_list: for img_base64_dict in img_base64_list: imgTemp_dict = {} picName = img_base64_dict.get("title", "") picPath = img_base64_dict.get("path", "") imgTemp_dict[picPath] = picName imgName_dict[key_name].append(imgTemp_dict) return imgName_dict def generateWord(projectPath, outputDocxPath, reportContent_list): """根据JSON数据生成报告文件""" print(f"Info:开始生成Word报告...") doc = Document(outputDocxPath) # 把base64转成图片 # img_num, imgName_dict = base64ToImg(projectPath, reportContent_list) # print(f"Info:共转换{img_num}张图片") # 获取图片地址 imgName_dict = getImgNameDict(reportContent_list) img_keys = list(imgName_dict.keys()) data_dict, table_num = getDataDict(reportContent_list) # image_dict = {"image1.png": "pic1", # "image2.png": "pic2" # } image_dict = { f"image{i+1}.png": img_keys[i] for i in range(len(img_keys))} print(f"Info:图片分类:{img_keys}, 与模版的对应关系:{image_dict}") # 处理段落和表格 for para in doc.paragraphs: if "$" in para.text: replaceParagraph(doc, para, data_dict) # 处理图片 process_images_in_paragraphs( doc, image_dict, doc.paragraphs, imgName_dict, projectPath) # 保存文档 doc.save(outputDocxPath) print(f"Info:Word文档已创建: {outputDocxPath}") if __name__ == "__main__": # currentPath = os.getcwd() # projectPath = f"{currentPath}/project/generateReport" # templatePath = f"{currentPath}/template/仿真报告标准模板_v1.0.docx" # pythonLogPath = os.path.dirname(projectPath) projectPath = sys.argv[1] templatePath = sys.argv[2] pythonLogPath = "/opt/script" # 输出log日志 # 日志文件路径 log_fileDir = f"{pythonLogPath}/pythonLog" if not os.path.exists(log_fileDir): os.makedirs(log_fileDir) # log文件用日期命名 log_file = f"{log_fileDir}/{datetime.now().strftime('%Y%m%d')}.log" # 使用上下文管理器重定向所有输出到文件 with open(log_file, 'a', encoding='utf-8') as log_file_obj: # 重定向标准输出和标准错误 original_stdout = sys.stdout original_stderr = sys.stderr # 创建Tee对象,同时输出到文件和原控制台 tee = Tee(log_file_obj, original_stdout) sys.stdout = tee sys.stderr = tee # 在日志开头添加时间戳 print("=" * 60) print(f"生成报告 程序开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"日志文件: {log_file}") print(f"项目路径: {projectPath}") print("=" * 60) try: # 获取项目信息数据 reportContentPath = f"{projectPath}/reportContent.json" reportContent_list = getJsonData(reportContentPath) # # 判断是否批量 # isBatch = False # if isBatch: # projectPath = f"{projectPath}/{loacase}" # # 是否自动出报告 # autoExport = projectInfo_dict.get("autoExport", False) # if autoExport: # exportWord(projectPath, templatePath, loacase=loacase) # else: # exportWordByUser(projectPath, templatePath, loacase=loacase) # 拷贝模板文件到项目目录下 outputDocxPath = f"{projectPath}/report_{datetime.now().strftime('%Y%m%d')}.docx" shutil.copy(templatePath, outputDocxPath) generateWord(projectPath, outputDocxPath, reportContent_list) print("\n" + "=" * 60) print(f"程序结束时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print("程序执行成功") print("=" * 60) except Exception as e: print(f"\n程序执行出错: {str(e)}", file=sys.stderr) traceback.print_exc() print("=" * 60) print(f"程序异常结束时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print("=" * 60) raise finally: # 恢复标准输出 sys.stdout = original_stdout sys.stderr = original_stderr