935 lines
36 KiB
Python
935 lines
36 KiB
Python
|
|
|
|||
|
|
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 内容(以 <?xml 或 < 开头)
|
|||
|
|
if not xml_content.strip().startswith(b'<?xml') and not xml_content.strip().startswith(b'<'):
|
|||
|
|
continue
|
|||
|
|
try:
|
|||
|
|
try:
|
|||
|
|
content_str = xml_content.decode('utf-8')
|
|||
|
|
except UnicodeDecodeError:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
# 检查是否包含 XML 声明或根元素
|
|||
|
|
if not content_str.strip().startswith('<?xml') and not content_str.strip().startswith('<'):
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
root = etree.fromstring(xml_content)
|
|||
|
|
# 查找文本框
|
|||
|
|
textboxes = root.xpath('//wps:txbx', namespaces=namespaces)
|
|||
|
|
part_modified = False # 当前部件是否被修改
|
|||
|
|
for textbox in textboxes:
|
|||
|
|
# 提取文本内容
|
|||
|
|
text_elements = textbox.xpath(
|
|||
|
|
'.//w:t', namespaces=namespaces)
|
|||
|
|
for elem in text_elements:
|
|||
|
|
# print(f"原始文本: {elem.text}")
|
|||
|
|
if elem.text and "$" in elem.text:
|
|||
|
|
keyName = elem.text.split("$")[1]
|
|||
|
|
value = data_dict.get(keyName, None)
|
|||
|
|
if value is not None:
|
|||
|
|
elem.text = elem.text.replace(
|
|||
|
|
f"${keyName}", str(value))
|
|||
|
|
print(f"文本框替换: ${keyName} -> {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
|