简介
最近正在开发一个对docx文件进行公文自动排版的程序,将经验分享一下,让看到的人少走弯路,主要使用的是python 3.8.8,docxtpl 0.16.6,python-docx 0.8.11。
我遇到的具体需求是:用户在前端可以通过类似腾讯文档、金山文档等在线文档编辑的方式,对正文内容进行编辑,然后将正文以docx文件格式及其他相关参数提交给后端,后端再使用docxtpl和python-docx对文档进行格式化,生成统一格式的公文。
目前的效果是:从发送内容到生成文件只需要几分钟。
公文结构分析
公文主要包含几个部分:1、红头,2、正文,3、附件,4、版记
开发方案
通过对各类公文的分析,制定的方案为:
一、先对获取到的docx文件清除所有格式,然后使用统一的格式进行一遍设置。这一步比较简单。
二、对特定的格式进行设置,例如:正文标题、一级标题、二级标题、接收单位、附件清单、落款日期等。这一步的难点在于如何对内容进行判断,这里需要与用户进行约定和确认,以确保不同的内容使用不同的格式。
比如我遇到的情况是:
一级标题统一为“一、二、三、四、”,
二级标题统一为“(一)(二)(三)(四)”,
默认正文的第一行为正文标题,第二行为接收单位,第三行开始为正文及附件内容。
三、制作不同公文对应的模板,将对应的内容填写到模板对应的位置,然后将已经设置好格式的正文内容复制到模板中。这一步的问题是复制到模板中的正文格式会发生变化,还需要对发生变化的地方重新设置格式。
核心代码
对于填充模板内容相对简单,这里提供一个演示代码,实际使用需要根据具体需求进行调整
from docxtpl import DocxTemplate
class fdocx:
def format_docx(self):
# 创建文档对象,并加载模板文件
doc = DocxTemplate(r"e:\doctest\fgstzs1.docx")
# 定义模板上要替换的标记
title='这是标题'
towho='各部门'
ftype='工作通知书'
year= '2023'
month= '06'
day= '15'
who='拟稿人'
bumen='办公室'
params = {
'title': title,
'towho':towho,
'type1':ftype,
'year': year,
'month': month,
'day': day,
'who':who,
'bumen':bumen
}
doc.render(params)
# 将生成的 Word 文档保存到本地
doc.save(r"e:\doctest\output.docx")
清除所有内容的格式,演示代码如下:
import docx
class fdocx:
def clear_formatting(self, doc):
# 清除段落格式和字体格式
for paragraph in doc.paragraphs:
text = paragraph.text
paragraph.clear()
paragraph.text = text
定位正文的内容以设置不同的格式,思路是通过find查找对应的内容,然后进行格式设置。示例代码如下:
import docx
from docx.oxml.ns import qn
from docx.shared import Pt, RGBColor
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
class fdocx:
def __init__(self):
#用json统一定义不同内容对应的字体格式,段落格式。
self.formats = [
{'id': 1, 'font': '黑体', 'pt': 16, 'alignment': WD_PARAGRAPH_ALIGNMENT.LEFT, 'color': RGBColor(0, 0, 0),
'first_line_indent': 391160,'style':'Normal'}
, {'id': 2, 'font': '楷体_GB2312', 'pt': 16, 'alignment': WD_PARAGRAPH_ALIGNMENT.LEFT,
'color': RGBColor(0, 0, 0),
'first_line_indent': 391160,'style':'Normal'}
, {'id': 3, 'font': '仿宋_GB2312', 'pt': 16, 'alignment': WD_PARAGRAPH_ALIGNMENT.LEFT,
'color': RGBColor(0, 0, 0),
'first_line_indent': 391160,'style':'Normal'}
, {'id': 4, 'font': '仿宋_GB2312', 'pt': 16, 'alignment': WD_PARAGRAPH_ALIGNMENT.LEFT,
'color': RGBColor(0, 0, 0),
'first_line_indent': 0,'style':'Normal'}
, {'id': 5, 'font': '方正小标宋简体', 'pt': 22, 'alignment': WD_PARAGRAPH_ALIGNMENT.CENTER,
'color': RGBColor(0, 0, 0), 'first_line_indent': 0,'style':'Normal'}
, {'id': 6, 'font': '仿宋_GB2312', 'pt': 16, 'alignment': WD_PARAGRAPH_ALIGNMENT.LEFT,
'color': RGBColor(0, 0, 0),
'first_line_indent': 963930,'style':'Normal'}
, {'id': 7, 'font': '仿宋_GB2312', 'pt': 16, 'alignment': WD_PARAGRAPH_ALIGNMENT.RIGHT,
'color': RGBColor(0, 0, 0),
'first_line_indent': 0,'style':'Normal'}
]
self.fjformats = [
{'id': 1, 'font': '黑体', 'pt': 16, 'alignment': WD_PARAGRAPH_ALIGNMENT.LEFT, 'color': RGBColor(0, 0, 0),
'first_line_indent': 0,'style':'Normal'}
, {'id': 2, 'font': '仿宋_GB2312', 'pt': 16, 'alignment': WD_PARAGRAPH_ALIGNMENT.LEFT,
'color': RGBColor(0, 0, 0),
'first_line_indent': 391160,'style':'Normal'}
, {'id': 3, 'font': '方正小标宋简体', 'pt': 22, 'alignment': WD_PARAGRAPH_ALIGNMENT.CENTER,
'color': RGBColor(0, 0, 0), 'first_line_indent': 0,'style':'Normal'}]
#根据不同内容获取不同的格式设置的函数
def getformatjson(self, id, no):
# print('id',id)
ans = {}
if no == 0:
for w in self.formats:
if w['id'] == id:
ans = w
continue
else:
for w in self.fjformats:
if w['id'] == id:
ans = w
continue
return ans
#对格式进行设置的函数
def formatit(self, paragraph, run, id, no):
jsondata = self.getformatjson(id, no)
#设置段落格式
paragraph.style = jsondata['style']
paragraph.paragraph_format.alignment = jsondata['alignment']
paragraph.paragraph_format.first_line_indent = jsondata['first_line_indent']
#设置字体格式
run.font.name = jsondata['font']
run.font.size = docx.shared.Pt(jsondata['pt'])
r = run._element.rPr.rFonts
r.set(qn("w:eastAsia"), u''+jsondata['font'])
run.font.color.rgb = jsondata['color']
#遍历docx文档,进行相应的格式设置
def get_paragraph_formats(self, file_path):
doc = docx.Document(file_path)
self.clear_formatting(doc)
doc.save(file_path)
doc = docx.Document(file_path)
# self.outprint(doc)
# 遍历每个段落,Pt与磅的转换值为12700
for pno, paragraph in enumerate(doc.paragraphs):
if not paragraph.text.isspace() and len(paragraph.text) > 0:
for run in paragraph.runs:
if not run.text.isspace() and len(run.text) > 0:
# 先按照普通正文格式设置一次
self.formatit(paragraph, run, 3, 0)
#此处自行添加对各种内容的判断,然后设置相应的格式
if __name__ == '__main__':
file_path = 'e:\\doctest\\111.docx'
doc = docx.Document(file_path)
总结
技术上难度不大,难点在于前期需要和用户反复沟通确认需求,然后在开发时,细节上需要厘清思路,对文档内容进行合理判断,确保不同的内容使用不同的格式。
最后,就是合理使用gpt等大模型,帮助判断分析代码中的问题。