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行”的參數名稱:

<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等工具修改源碼較好。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章