1. 程式人生 > >解決 openpyxl 垂直分頁符和水平分頁符同時新增的問題

解決 openpyxl 垂直分頁符和水平分頁符同時新增的問題

前言

十天前知乎上有人提問 python:openpyxl模組怎麼給表格新增分頁符?實現分頁列印功能?,看到問題之後,我很快的給他了一個如何新增垂直分頁符或水平分頁符的示例,你以為問題就結束了?我是這麼以為的,但是事實證明,我太天真了,就在我給出示例的幾分鐘後,他在我的回答下評論了,說是同時新增垂直分頁符和水平分頁符失敗了.
我當時的第一反應:

心裡想著,肯定是他的寫法有問題,毫不猶豫的回覆到," 沒有試過同時新增兩種分頁符的操作,預設是水平分頁符,如果你先添加了垂直分頁符的話,應該後面需要重新宣告:openpyxl.worksheet.pagebreak.PageBreak.tagname = "rowBreaks",聽著自己飛快擊打鍵盤的聲音,自己不經有點飄飄然.就在沉浸在自己的YY當中,又過去了幾分鐘,他用正確的程式碼錯誤的結果狠狠的摔在了我的臉上:

col_break = openpyxl.worksheet.pagebreak.Break(5) #建立分頁符,引數5:在第5/6中間分頁
sheet1.page_breaks.tagname = 'rowBreaks' #分頁符屬性設定為行分頁符
sheet1.page_breaks.append(col_break) #把分頁符物件新增到sheet物件裡

row_break = openpyxl.worksheet.pagebreak.Break(3) #建立分頁符
sheet1.page_breaks.tagname = 'colBreaks' #分頁符屬性設定為列分頁符
sheet1.page_breaks.append(row_break) #把分頁符物件新增到sheet物件裡

"結果是在第3和5列添加了兩個垂直分頁符,是哪裡有問題?大師",他問道. 我看了一眼程式碼好像沒有錯,我的心有點慌了,雙手開始微微顫抖起來,一遍又一遍地仔細地巡視著程式碼,檢視找出錯誤反駁他,但是並沒有,抱著最後的希望,我把他的程式碼複製到自己的檔案中,然後敲下回車符, excel 檔案靜靜的生成在目錄下,這可能是我最後的希望了.拿滑鼠的手不自覺的顫抖起來,那麼小的螢幕,那麼大的檔案,滑鼠怎麼半天都沒辦法移動上去,我深吸一口氣,控制住自己手,終於把滑鼠一上去了,雙擊excel,閉上眼睛,再睜開,我知道,我錯了.但是就這麼放棄了嗎?絕不!我要把這個問題打到!

亮劍

"是時候展示真正的技術了"

俗話說"解鈴還須繫鈴人",我們還得自己看一下問題程式碼:

# example.py

from openpyxl import Workbook
from openpyxl.compat import range
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.pagebreak import Break, PageBreak

wb = Workbook()
ws = wb.active

for row in range(1, 20):
    for col in range(1,30):
        _ = ws.cell(column=col, row=row, value="{0}".format(get_column_letter(col)))

col_break = Break(5) #建立分頁符,引數5:在第5/6中間分頁
ws.page_breaks.tagname = 'rowBreaks' #分頁符屬性設定為行分頁符
ws.page_breaks.append(col_break) #把分頁符物件新增到sheet物件裡

row_break = Break(3) #建立分頁符
ws.page_breaks.tagname = 'colBreaks' #分頁符屬性設定為列分頁符
ws.page_breaks.append(row_break) #把分頁符物件新增到sheet物件裡

wb.save(filename = dest_filename) 

從程式碼上應該是後面的 page_breaks 把前面的覆蓋了, 那讓我們看看 page_breaks 究竟是什麼東西.

class Worksheet(_WorkbookChild):
   # 省略部分程式碼
      def _setup(self):
        self.page_breaks = PageBreak() # 再看 PageBreak

class PageBreak(Serialisable):
    tagname = "rowBreaks"
    # 省略部分程式碼
    def append(self, brk=None):
        """
        Add a page break
        """
        vals = list(self.brk)
        if not isinstance(brk, Break):
            brk = Break(id=self.count+1)
        vals.append(brk)
        self.brk = vals

從 example 中我們不難發現,我們是通過修改 page_breaks 的 tag_name 去決定插入的分頁符是垂直分頁符還是水平分頁符的.但是 page_breaks 現在只有一個 PageBreak 這就難怪後宣告的會把前面的覆蓋了,那麼如果我們把 page_breaks 變成 PageBreak 的列表呢?

First Blood -- page_breaks

說改咱就改啊,首先嚐試修改 WorkSheet 類

class Worksheet(_WorkbookChild):
   # 省略部分程式碼
      def _setup(self):
        self.page_breaks = [PageBreak()]

然後再修改一下 example.py

from openpyxl import Workbook
from openpyxl.compat import range
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.pagebreak import Break, PageBreak

wb = Workbook()
dest_filename = 'empty_book.xlsx'

ws = wb.active
for row in range(1, 20):
    for col in range(1,30):
        _ = ws.cell(column=col, row=row, value="{0}".format(get_column_letter(col)))

rowPageBreak = PageBreak()
rowPageBreak.tagname = 'rowBreaks'

colPageBreak = PageBreak()
colPageBreak.tagname = 'colBreaks'

ws.page_breaks = [rowPageBreak, colPageBreak] 

ws.page_breaks[0].append(Break(id=5))  
ws.page_breaks[1].append(Break(id=3))
wb.save(filename = dest_filename)

敲下回車,心裡那個美滋滋,還沒高興幾秒鐘,就出問題了,果然做人還是得低調一點

Traceback (most recent call last):
  File "test.py", line 24, in <module>
    wb.save(filename = dest_filename)
  File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\workbook\workbook.py", line 391, in save
    save_workbook(self, filename)
  File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\writer\excel.py", line 284, in save_workbook
    writer.save(filename)
  File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\writer\excel.py", line 266, in save
    self.write_data()
  File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\writer\excel.py", line 83, in write_data
    self._write_worksheets()
  File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\writer\excel.py", line 203, in _write_worksheets
    xml = ws._write()
  File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\worksheet\worksheet.py", line 893, in _write
    return write_worksheet(self)
  File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\writer\worksheet.py", line 151, in write_worksheet
    xf.write(ws.page_breaks.to_tree())
AttributeError: 'list' object has no attribute 'to_tree'

看了一眼錯誤資訊,發現了從中作祟的傢伙再 worksheet.py 的 151 行, 讓我們悄悄地看一眼,打槍的不要.

# worksheet.py
# 省略部分程式碼
            if ws.page_breaks:
                    xf.write(ws.page_breaks.to_tree())

原來是我們修改了 page_breaks 之後, page_breaks 有時候不再是孤家寡人了,我們需要考慮它有另外的 PageBreak 的情況了.

Double Kill -- Worksheet

# worksheet.py
# 省略部分程式碼
            if ws.page_breaks:
                if isinstance(ws.page_breaks,list):
                    for page_break_item in ws.page_breaks:
                        xf.write(page_break_item.to_tree())
                else:
                    xf.write(ws.page_breaks.to_tree())

回車,毫無問題,人生啊就是這麼寂寞如雪~~~

後記

已經在 openpyxl 提了相應的issue,目前再寫測試用例,過段時間就提交 PR 了