commit fb4514e91deffb9955d01b6d7d2688b9f555e993 Author: 还不如一只猪威武 <30920712+lanink@users.noreply.github.com> Date: Sat Dec 13 10:48:51 2025 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d017762 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/build/ +/dist/ +.idea +*.spec \ No newline at end of file diff --git a/data.json b/data.json new file mode 100644 index 0000000..725e0db --- /dev/null +++ b/data.json @@ -0,0 +1,100 @@ +[ + { + "key": "{Title}", + "value": "测试标题", + "type": "text" + }, + { + "key": "{文件内容}", + "value": "测试内容内容内容", + "type": "text" + }, + { + "key": "{TableTitle}", + "value": "这是标题", + "type": "text" + }, + { + "key": "{dateTime}", + "value": "2025年11月26日", + "type": "text" + }, + { + "key": "{remark}", + "value": "这个是备注备注备注备注", + "type": "text" + }, + { + "key": "{Test1}", + "value": "2025年11月26日", + "type": "text" + }, + { + "key": "{Test2}", + "value": "2025年11月26日", + "type": "text" + }, + { + "key": "{Test3}", + "value": "2025年", + "type": "text" + }, + { + "key": "{Picture}", + "value": "rust.jpg", + "type": "picture", + "width": 1.25 + }, + { + "key": "{Table}", + "type": "table", + "value": [ + { + "No": "序号", + "Name": "名称", + "Col2": "第一列", + "Col3": "第二列" + }, + { + "No": "1", + "Name": "名称测试1", + "Col2": "列2", + "Col3": "列3" + }, + { + "No": "2", + "Name": "名称测试2", + "Col2": "列2", + "Col3": "列3" + }, + { + "No": "3", + "Name": "名称测试3", + "Col2": "列2", + "Col3": "列3" + }, + { + "No": "4", + "Name": "名称测试4", + "Col2": "列2", + "Col3": "列3" + }, + { + "No": "5", + "Name": "名称测试5", + "Col2": "列2", + "Col3": "列3" + } + ] + }, + { + "key": "{List}", + "type": "list", + "value": [ + "A1队列3的生存性最高,机动时间0s,机动距离0m,综合生存能力为0,隐蔽等级为1,道路性质为0;", + "A2队列3的生存性最高,机动时间0s,机动距离0m,综合生存能力为0,隐蔽等级为1,道路性质为0;", + "A3队列3的生存性最高,机动时间0s,机动距离0m,综合生存能力为0,隐蔽等级为1,道路性质为0;", + "A4队列3的生存性最高,机动时间0s,机动距离0m,综合生存能力为0,隐蔽等级为1,道路性质为0;" + ] + } +] \ No newline at end of file diff --git a/demo.docx b/demo.docx new file mode 100644 index 0000000..6c2d1cb Binary files /dev/null and b/demo.docx differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..94075d9 --- /dev/null +++ b/main.py @@ -0,0 +1,253 @@ +import json +import sys +from docx import Document +from docx.shared import Inches + +def get_command_argv_by_sys(): + # 默认值-用于测试 + template_path = "template.docx" + filename_path = "demo.docx" + datafile_path = "data.json" + + # 读取参数 + number = len(sys.argv) + if 2 == number: + template_path = sys.argv[1] + if 3 == number: + template_path = sys.argv[1] + filename_path = sys.argv[2] + if 4 == number: + template_path = sys.argv[1] + filename_path = sys.argv[2] + datafile_path = sys.argv[3] + + return template_path, filename_path, datafile_path + + +def read_data(filepath): + content = [] + if filepath != "": + try: + with open(filepath, "r", encoding="utf-8") as file: + content = json.load(file) + finally: + return content + + +def replace(template_path, filename_path, data_json): + try: + document_file = Document(template_path) + except Exception: + return 2 + + # 段落替换 + for paragraph in document_file.paragraphs: + paragraph.text = paragraph.text.replace(" ", "") + find = False + for datum in data_json: + key = datum["key"] + if key not in paragraph.text: + continue + find = True + if datum["type"] == "text": + runs = paragraph.runs + for i in range(len(runs)): + if key in runs[i].text: + runs[i].text = runs[i].text.replace(key, str(datum["value"])) + + elif datum["type"] == "number": + paragraph.text = paragraph.text.replace(key, str(datum["value"])) + elif datum["type"] == "picture": + # 清空整个段落并插入图片(假设占位符独占段落) + paragraph.clear() + width = float(datum.get("width", 2.0)) + paragraph.add_run().add_picture(datum["value"], width=Inches(width)) + elif datum["type"] == "list": + p_elem = paragraph._p + parent = p_elem.getparent() + index = parent.index(p_elem) + values = datum["value"] + # 删除原段落 + parent.remove(p_elem) + # 为每个值插入新段落 + for i, val in enumerate(values): + new_para = document_file.add_paragraph() + new_para.text = str(val) + parent.insert(index + i, new_para._p) + elif datum["type"] == "object_list": + p_elem = paragraph._p + parent = p_elem.getparent() + index = parent.index(p_elem) + values = datum["value"] + # 删除原段落(占位符) + parent.remove(p_elem) + # 为每个对象插入新段落 + for i, obj_item in enumerate(values): + new_para = document_file.add_paragraph() + item_type = obj_item.get("type") + if item_type == "text": + new_para.add_run(str(obj_item["value"])) + elif item_type == "number": + new_para.add_run(str(obj_item["value"])) + elif item_type == "picture": + width = float(obj_item.get("width", 2.0)) + new_para.add_run().add_picture(obj_item["value"], width=Inches(width)) + else: + new_para.add_run(str(obj_item.get("value", ""))) + # 插入到原位置 + parent.insert(index + i, new_para._p) + if not find: + paragraph.text = "" + + + # 表格替换 + for table in document_file.tables: + # 字段 + group = None + fields = [] + repeat = False + + for row in table.rows: + if repeat: + break + for cell in row.cells: + if "." in cell.text: + split = cell.text.replace("}","").split(".") + group = split[0] + "}" + field = split[1] + fields.append(field.strip()) + repeat = True + else: + for paragraph in cell.paragraphs: + for run in paragraph.runs: + find = False + for datum in data_json: + if datum["key"] in cell.text: + find = True + if "text" == datum["type"]: + run.text = run.text.replace(datum["key"], datum["value"]) + elif "picture" == datum["type"]: + paragraph = cell.paragraphs[0] + paragraph.clear() + paragraph.add_run().add_picture(datum["value"], width=Inches(datum["width"])) + elif "number" == datum["type"]: + run.text = run.text.replace(datum["key"], str(datum["value"])) + if not find and run.text.startswith("{") and run.text.endswith("}"): + run.text = "" + + # 替换 + if repeat: + for datum in data_json: + rows = len(table.rows) + if datum["key"] in group: + values = datum["value"] + # 读取表格格式模板(保存每列的格式信息) + column_formats = [] + if rows > 0: + template_row = table.rows[0] + for i, cell in enumerate(template_row.cells): + if i < len(fields): # 只需要处理与数据字段对应的列 + if cell.paragraphs: + para = cell.paragraphs[0] + # 保存列格式信息 + column_format = { + 'font_name': '', + 'font_size': None, + 'alignment': para.alignment, + 'bold': False, + 'italic': False, + 'underline': False, + } + + # 优先使用run的格式(如果有) + if para.runs: + run = para.runs[0] + if run.font.name: + column_format['font_name'] = run.font.name + if run.font.size: + column_format['font_size'] = run.font.size + if run.font.bold is not None: + column_format['bold'] = run.font.bold + if run.font.italic is not None: + column_format['italic'] = run.font.italic + if run.font.underline is not None: + column_format['underline'] = run.font.underline + # 保存字体颜色 + if run.font.color.rgb: + column_format['font_color'] = run.font.color.rgb + # 如果没有run,使用样式的格式 + else: + if para.style.font.name: + column_format['font_name'] = para.style.font.name + if para.style.font.size: + column_format['font_size'] = para.style.font.size + if para.style.font.bold is not None: + column_format['bold'] = para.style.font.bold + if para.style.font.italic is not None: + column_format['italic'] = para.style.font.italic + if para.style.font.underline is not None: + column_format['underline'] = para.style.font.underline + + column_formats.append(column_format) + + # 处理每一行数据 + for (i, item) in enumerate(values): + if i < rows: + row = table.rows[i] + else: + row = table.add_row() + # 复制行高 + if i == 0 and rows > 0: + row.height = template_row.height + row.height_rule = template_row.height_rule + + # 处理每一列数据和格式 + for z in range(0, len(fields)): + field = fields[z] + cell = row.cells[z] + + # 设置单元格内容 + if item.get(field) is not None: + cell.text = str(item.get(field)) + else: + cell.text = "" + + # 应用表格格式 + if z < len(column_formats) and cell.paragraphs: + format_info = column_formats[z] + para = cell.paragraphs[0] + + # 设置段落对齐方式 + para.alignment = format_info['alignment'] + + # 设置字体格式 + if para.runs: + run = para.runs[0] + if format_info['font_name']: + run.font.name = format_info['font_name'] + if format_info['font_size']: + run.font.size = format_info['font_size'] + run.font.bold = format_info['bold'] + run.font.italic = format_info['italic'] + run.font.underline = format_info['underline'] + # 设置字体颜色 + if 'font_color' in format_info: + run.font.color.rgb = format_info['font_color'] + + document_file.save(filename_path) + return 0 + + +def main(): + params = get_command_argv_by_sys() + [template, filename, datafile] = params + data = read_data(datafile) + if len(data) == 0: + return 1 + else: + return replace(template, filename, data) + + +if __name__ == '__main__': + print(main()) + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..0ba8c46 --- /dev/null +++ b/readme.md @@ -0,0 +1,43 @@ +# 读取模板生成报表 + +## 打包 + +1. 打包 + > ```powershell + > pyinstaller -F main.py -n doc_generate.exe + > ``` +1. 使用 + > 在 dist目录下找到可执行文件, 使用命令行调用 + > ```python + > ./doc_generate.exe [模板文件] [生成文件] [数据文件] + > ``` +1. 使用示例 + - Qt + > ```c++ + > QString templatefile = "template/template.docx"; // 模板文件 + > QString generatefile = "Test.docx"; // 生成文件 + > QString datafile = "data/data.json"; // 数据文件 + > QString cmd = QString("doc_generate %1 %2 %3").arg(templatefile, generatefile, datafile); + > QProcess process; + > process.start(cmd); + > process.waitForFinished(); + > QString output = process.readAllStandardOutput(); + > qDebug() << output; + > ``` + +## 注意 + +1. 模板文件 使用相对位置,即相对**doc_generate**可执行文件的位置 +1. 生成文件 生成的文件都在**doc_generate**所在位置 +1. 数据文件 使用相对位置,即相对**doc_generate**可执行文件的位置 +2. 数据文件中若有图片路径,则图片路径也应是相对位置,即图片相对**doc_generate**可执行文件的位置 + +## 返回值说明 + +| 值 | 说明 | +|:-:|:-------:| +| 0 | 正常 | +| 1 | 数据内容不正确 | +| 2 | 模板文件不正确 | + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..473eac5 Binary files /dev/null and b/requirements.txt differ diff --git a/rust.jpg b/rust.jpg new file mode 100644 index 0000000..97d9ec1 Binary files /dev/null and b/rust.jpg differ diff --git a/template.docx b/template.docx new file mode 100644 index 0000000..333017d Binary files /dev/null and b/template.docx differ