You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

254 lines
11 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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())