python-docx 不改变原文件调整段落行间距的问题

 

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行”的参数名称:

而源码文件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):
    """
    ```` 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查看“自动”行

很明显,又多了一个beforeAutospacing类型,而且value值是1,所以推断它是一种布尔变量。

3.2在parfmt.py的CT_Spacing中再增加两个类型,最终:

class CT_Spacing(BaseOxmlElement):
    """
    ```` 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等工具修改源码较好。

你可能感兴趣的:(python)