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