python-docx模块是处理word的利器,希望通过调用模块生成预定格式的文件,word本身自带的模板使用不太方便,而工作中对文档格式要求很高(~~)
借助一个富文本编辑器,可以将文档内容输出为word,存在几类问题,
-
字体大小、字号、加粗等,这些直接调用styles即可实现,python-docx对字体支持还是很完善,
-
另外一个问题是段落间距问题,折腾了一天在抓狂的状态下终于解决,原因还是因为对xml原理不熟悉
以下介绍不改变原文件的方法,修改间距为“行”“自动”的方法:
1.如果段间距是以“磅”作为单位,那么对应的python中即是Pt单位,通过以下直接对读出的段落进行修改:
pars=doc1.paragraphs
for par in pars:
if par.style.name=="Heading 1":
par.paragraph_format.space_before = Pt(0)
par.paragraph_format.space_after =Pt(0)
也可以使用doc.styles["Heading 1"]进行类修改,网上资料较多,修改pt值即可实现段前断后间距;
2.如果段间距是以“行”作为单位,pydocx模块内置不能识别该格式,导致失效。
用xml查看会发现spacing设置“1行”的参数名称:
<w:spacing w:beforeLines="100" w:afterLines="100"/>
而源码文件parfmt.py中class CT_Spacing(BaseOxmlElement)只定义了四种类型:after,before,line,lineRule,虽然在word中看到的段前断后选1行和选1磅仍然在同一个框中,但是实际的数据类型已经是另外的了。所以要对pydocx源码进行修改。
2.1在oxml/text/parfmt.py的CT_Spacing新增两种类型:afterLines和beforeLines
class CT_Spacing(BaseOxmlElement):
"""
``<w:spacing>`` element, specifying paragraph spacing attributes such as
space before and line spacing.
"""
after = OptionalAttribute('w:after', ST_TwipsMeasure)
before = OptionalAttribute('w:before', ST_TwipsMeasure)
line = OptionalAttribute('w:line', ST_SignedTwipsMeasure)
lineRule = OptionalAttribute('w:lineRule', WD_LINE_SPACING)
afterLines = OptionalAttribute('w:afterLines', ST_TwipsMeasure)
beforeLines = OptionalAttribute('w:beforeLines', ST_TwipsMeasure)
2.2在parfmt.py修改spacing_after和spacing_before函数
def spacing_after(self):
"""
The value of `w:spacing/@w:after` or |None| if not present.
"""
spacing = self.spacing
if spacing is None:
return None
if spacing.afterLines is not None:
return spacing.afterLines
if spacing.after is not None:
return spacing.after
@spacing_after.setter
def spacing_after(self, value):
if value is None and self.spacing is None:
return
if self.spacing.afterLines is not None:
self.get_or_add_spacing().afterLines = value
return
if self.spacing.after is not None:
self.get_or_add_spacing().after = value
return
@property
一个是子函数,一个是设置参数,设置需要调用子函数,所以都要改。
- 首先判断spacing是不是空
- 接着判断是否有行参数,如果有则返回行参数或者修改行参数
- 如果行参数不存在,则按原来的pt等值进行修改
(到这猜测pydocx为什么没声明这种类型,国内用的word可能是微软针对国内word再加工,国外的统一用pt等度量值,可能没行单位,或者就是pydocx有需要优化的地方)
spacing_before也用同样道理修改:
def spacing_before(self):
"""
The value of `w:spacing/@w:before` or |None| if not present.
"""
spacing = self.spacing
if spacing is None:
return None
if spacing.beforeLines is not None:
return spacing.beforeLines
if spacing.before is not None:
return spacing.before
#return spacing.before
@spacing_before.setter
def spacing_before(self, value):
if value is None and self.spacing is None:
return
if self.spacing.beforeLines is not None:
self.get_or_add_spacing().beforeLines = value
return
if self.spacing.before is not None:
self.get_or_add_spacing().before = value
@property
2.3这样可以试一下读一个采用1行间距的值大小
读出的值应该是63500,而一个1磅的值应该是6350,差了10倍。
解决了“行”的问题,本来以为可以愉快玩耍了,但是遇到了下一个问题,“自动”
3.行间距为“自动”的间距设置
有些富文本编辑器导出的行间距沿用上一段或者沿用一种style的模板,所以行间距在word中显示自动,用上面改行的方法,对自动行虽然读出值是显示改了,但是实际效果并没有改,但是不怕,因为通过2已经大概了解ms的套路。
3.1同样用xml查看“自动”行
<w:spacing w:before="100" w:beforeAutospacing="1" w:after="100" w:afterAutospacing="1"/>
很明显,又多了一个beforeAutospacing类型,而且value值是1,所以推断它是一种布尔变量。
3.2在parfmt.py的CT_Spacing中再增加两个类型,最终:
class CT_Spacing(BaseOxmlElement):
"""
``<w:spacing>`` element, specifying paragraph spacing attributes such as
space before and line spacing.
"""
after = OptionalAttribute('w:after', ST_TwipsMeasure)
before = OptionalAttribute('w:before', ST_TwipsMeasure)
line = OptionalAttribute('w:line', ST_SignedTwipsMeasure)
lineRule = OptionalAttribute('w:lineRule', WD_LINE_SPACING)
afterLines = OptionalAttribute('w:afterLines', ST_TwipsMeasure)
beforeLines = OptionalAttribute('w:beforeLines', ST_TwipsMeasure)
beforeAutospacing=OptionalAttribute('w:beforeAutospacing', ST_TwipsMeasure)
afterAutospacing = OptionalAttribute('w:afterAutospacing', ST_TwipsMeasure)
3.3spacing_after和spacing_before的修改
当我们要调整格式的时候,显然不希望间距是自动,所以删除这种类型最好,但是找了几分钟get_or_add_spacing这个函数在pydocx中信息较少,只有个相关方法的注册,再继续研究又会陷入另外一个坑,所以转换一下思路。
既然修改的时候肯定不是自动,那么就保留它,而把布尔值设为0,另外再新增需要的间距值就行。
所以不用管子函数函数,只用改“设置”的那个函数,在设置间距之前先将“自动”赋0,所以after和before的修改如下:
def spacing_after(self, value):
if value is None and self.spacing is None:
return
self.spacing.beforeAutospacing=None
self.spacing.afterAutospacing = None
if self.spacing.afterLines is not None:
self.get_or_add_spacing().afterLines = value
return
if self.spacing.after is not None:
self.get_or_add_spacing().after = value
return
@property
def spacing_before(self, value):
if value is None and self.spacing is None:
return
self.spacing.beforeAutospacing=None
self.spacing.afterAutospacing = None
if self.spacing.beforeLines is not None:
self.get_or_add_spacing().beforeLines = value
return
if self.spacing.before is not None:
self.get_or_add_spacing().before = value
@property
将自动强制设为None.
3.4测试
将word找个行调整为自动或者X行,python程序设为Pt(0),运行一下,发现完美实现间距为0。
检查:将word导出为xml,结果发现,将beforeAutospacing,afterAutospacing两个变量设置为None后,xml实际删除了auto的变量类型,最终也实现了我想要的结果。
终于又可以愉快的玩耍了。
还有一个思路是打开原WORD,读出paragraph列表了之后,用循环将内容复制到另外一个word,用add_paragraph来设置间距、字体等。但是因为我有图有表,段落列表、图列表和表格列表一次出来后存在定位问题,又需要用xml去定位,坑可能更大,所以又返回来用修改原文件的方法。
介绍得有点啰嗦,主要说明思路,希望有所帮助。
TIPs:notepad等工具修改源码会使tab的空格发生变化而报错,所以还是用pycharm等工具修改源码较好。